diff --git a/README.md b/README.md index d43ef98f..949277b8 100755 --- a/README.md +++ b/README.md @@ -288,18 +288,30 @@ A Promises/A+ promise is returned when `callback` is not provided. ### Utility methods -#### sharp.cache([limit]) +#### sharp.cache([memory], [items]) -If `limit` is provided, set the (soft) limit of _libvips_ working/cache memory to this value in MB. The default value is 100. +If `memory` or `items` are provided, set the limits of _libvips'_ operation cache. + +* `memory` is the maximum memory in MB to use for this cache, with a default value of 100 +* `items` is the maximum number of operations to cache, with a default value of 500 This method always returns cache statistics, useful for determining how much working memory is required for a particular task. -Warnings such as _Application transferred too many scanlines_ are a good indicator you've set this value too low. +```javascript +var stats = sharp.cache(); // { current: 75, high: 99, memory: 100, items: 500 } +sharp.cache(200); // { current: 75, high: 99, memory: 200, items: 500 } +sharp.cache(50, 200); // { current: 49, high: 99, memory: 50, items: 200} +``` + +#### sharp.counters() + +Provides access to internal task counters. + +* `queue` is the number of tasks queuing for _libuv_ to provide a thread from its pool +* `process` is the number of tasks being processed ```javascript -var stats = sharp.cache(); // { current: 98, high: 115, limit: 100, queue: 0 } -sharp.cache(200); // { current: 98, high: 115, limit: 200, queue: 0 } -sharp.cache(50); // { current: 49, high: 115, limit: 50, queue: 0 } +var counters = sharp.counters(); // { queue: 2, process: 4 } ``` ## Testing diff --git a/index.js b/index.js index c60ca98c..a7766ded 100755 --- a/index.js +++ b/index.js @@ -335,9 +335,22 @@ Sharp.prototype._sharp = function(callback) { } }; -module.exports.cache = function(limit) { - if (Number.isNaN(limit)) { - limit = null; +/* + Get and set cache memory and item limits +*/ +module.exports.cache = function(memory, items) { + if (Number.isNaN(memory)) { + memory = null; } - return sharp.cache(limit); + if (Number.isNaN(items)) { + items = null; + } + return sharp.cache(memory, items); +}; + +/* + Get internal counters +*/ +module.exports.counters = function() { + return sharp.counters(); }; diff --git a/src/sharp.cc b/src/sharp.cc index f274007a..1c73cca5 100755 --- a/src/sharp.cc +++ b/src/sharp.cc @@ -55,7 +55,9 @@ unsigned char const MARKER_PNG[] = {0x89, 0x50}; unsigned char const MARKER_WEBP[] = {0x52, 0x49}; // How many tasks are in the queue? -volatile int queue_length = 0; +volatile int counter_queue = 0; +// How many tasks are being processed? +volatile int counter_process = 0; static bool ends_with(std::string const &str, std::string const &end) { return str.length() >= end.length() && 0 == str.compare(str.length() - end.length(), end.length(), end); @@ -124,6 +126,11 @@ class ResizeWorker : public NanAsyncWorker { ~ResizeWorker() {} void Execute () { + // Decrement queued task counter + g_atomic_int_dec_and_test(&counter_queue); + // Increment processing task counter + g_atomic_int_inc(&counter_process); + // Input ImageType inputImageType = JPEG; VipsImage *in = vips_image_new(); @@ -455,9 +462,12 @@ class ResizeWorker : public NanAsyncWorker { } } delete baton; + + // Decrement processing task counter + g_atomic_int_dec_and_test(&counter_process); + + // Return to JavaScript callback->Call(3, argv); - // Decrement queue length - g_atomic_int_dec_and_test(&queue_length); } private: @@ -512,29 +522,48 @@ NAN_METHOD(resize) { NanCallback *callback = new NanCallback(args[1].As()); NanAsyncQueueWorker(new ResizeWorker(callback, baton)); - // Increment queue length - g_atomic_int_inc(&queue_length); + // Increment queued task counter + g_atomic_int_inc(&counter_queue); NanReturnUndefined(); } +/* + Get and set cache memory and item limits +*/ NAN_METHOD(cache) { NanScope(); - // Set cache limit + // Set cache memory limit if (args[0]->IsInt32()) { vips_cache_set_max_mem(args[0]->Int32Value() * 1048576); } + // Set cache items limit + if (args[1]->IsInt32()) { + vips_cache_set_max(args[1]->Int32Value()); + } + // Get cache statistics Local cache = NanNew(); cache->Set(NanNew("current"), NanNew(vips_tracked_get_mem() / 1048576)); cache->Set(NanNew("high"), NanNew(vips_tracked_get_mem_highwater() / 1048576)); - cache->Set(NanNew("limit"), NanNew(vips_cache_get_max_mem() / 1048576)); - cache->Set(NanNew("queue"), NanNew(queue_length)); + cache->Set(NanNew("memory"), NanNew(vips_cache_get_max_mem() / 1048576)); + cache->Set(NanNew("items"), NanNew(vips_cache_get_max())); NanReturnValue(cache); } +/* + Get internal counters (queued tasks, processing tasks) +*/ +NAN_METHOD(counters) { + NanScope(); + Local counters = NanNew(); + counters->Set(NanNew("queue"), NanNew(counter_queue)); + counters->Set(NanNew("process"), NanNew(counter_process)); + NanReturnValue(counters); +} + static void at_exit(void* arg) { NanScope(); vips_shutdown(); @@ -544,8 +573,13 @@ extern "C" void init(Handle target) { NanScope(); vips_init(""); AtExit(at_exit); + // Set libvips operation cache limits + vips_cache_set_max_mem(100 * 1048576); // 100 MB + vips_cache_set_max(500); // 500 operations + // Methods available to JavaScript NODE_SET_METHOD(target, "resize", resize); NODE_SET_METHOD(target, "cache", cache); + NODE_SET_METHOD(target, "counters", counters); } NODE_MODULE(sharp, init) diff --git a/tests/unit.js b/tests/unit.js index 7bd99cbe..9ec7e2dd 100755 --- a/tests/unit.js +++ b/tests/unit.js @@ -18,6 +18,10 @@ var outputTiff = path.join(fixturesPath, "output.tiff"); var inputJpgWithExif = path.join(fixturesPath, "Landscape_8.jpg"); // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_8.jpg +// Ensure cache limits can be set +sharp.cache(0); // Disable +sharp.cache(50, 500); // 50MB, 500 items + async.series([ // Resize with exact crop function(done) { @@ -339,5 +343,12 @@ async.series([ }); var pipeline = sharp().resize(320, 240); readable.pipe(pipeline).pipe(writable) + }, + // Verify internal counters + function(done) { + var counters = sharp.counters(); + assert.strictEqual(0, counters.queue); + assert.strictEqual(0, counters.process); + done(); } ]);