Expose control of the number of open files in libvips' cache.

Breaks API of existing cache method.
Disable libvips cache for I/O tests.
This commit is contained in:
Lovell Fuller 2016-01-06 20:37:37 +00:00
parent 8843211e12
commit 11329d5e09
29 changed files with 126 additions and 82 deletions

View File

@ -5,6 +5,7 @@
"maxcomplexity": 13,
"globals": {
"before": true,
"after": true,
"describe": true,
"it": true
}

View File

@ -41,8 +41,8 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch |
| ------: | :--------- |
| v0.12.0 | look |
| v0.13.0 | mind |
| v0.14.0 | needle |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.

View File

@ -590,19 +590,26 @@ An Object containing the version numbers of libvips and, on Linux, its dependenc
### Utilities
#### sharp.cache([memory], [items])
#### sharp.cache([options])
If `memory` or `items` are provided, set the limits of _libvips'_ operation cache.
If `options` is provided, sets 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
* `options.memory` is the maximum memory in MB to use for this cache, with a default value of 50
* `options.files` is the maximum number of files to hold open, with a default value of 20
* `options.items` is the maximum number of operations to cache, with a default value of 100
`options` can also be a boolean, where `true` enables the default cache settings and `false` disables all caching.
This method always returns cache statistics, useful for determining how much working memory is required for a particular task.
```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}
var stats = sharp.cache();
```
```javascript
sharp.cache( { items: 200 } );
sharp.cache( { files: 0 } );
sharp.cache(false);
```
#### sharp.concurrency([threads])

View File

@ -1,5 +1,10 @@
# Changelog
### v0.13 - "*mind*"
* Control number of open files in libvips' cache; breaks existing `cache` behaviour.
[#315](https://github.com/lovell/sharp/issues/315)
### v0.12 - "*look*"
#### v0.12.2 - 16<sup>th</sup> January 2016

View File

@ -834,18 +834,25 @@ Sharp.prototype.clone = function() {
return clone;
};
/*
Get and set cache memory and item limits
/**
Get and set cache memory, file and item limits
*/
module.exports.cache = function(memory, items) {
if (typeof memory !== 'number' || Number.isNaN(memory)) {
memory = null;
module.exports.cache = function(options) {
if (typeof options === 'boolean') {
if (options) {
// Default cache settings of 50MB, 20 files, 100 items
return sharp.cache(50, 20, 100);
} else {
return sharp.cache(0, 0, 0);
}
if (typeof items !== 'number' || Number.isNaN(items)) {
items = null;
} else if (typeof options === 'object') {
return sharp.cache(options.memory, options.files, options.items);
} else {
return sharp.cache();
}
return sharp.cache(memory, items);
};
// Ensure default cache settings are set
module.exports.cache(true);
/*
Get and set size of thread pool

View File

@ -1,6 +1,6 @@
{
"name": "sharp",
"version": "0.12.2",
"version": "0.13.0",
"author": "Lovell Fuller <npm@lovell.info>",
"contributors": [
"Pierre Inglebert <pierre.inglebert@gmail.com>",

View File

@ -11,10 +11,6 @@
NAN_MODULE_INIT(init) {
vips_init("sharp");
// Set libvips operation cache limits
vips_cache_set_max_mem(100 * 1024 * 1024); // 100 MB
vips_cache_set_max(500); // 500 operations
// Methods available to JavaScript
Nan::Set(target, Nan::New("metadata").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked());

View File

@ -24,33 +24,49 @@ using Nan::To;
using Nan::Utf8String;
/*
Get and set cache memory and item limits
Get and set cache limits
*/
NAN_METHOD(cache) {
HandleScope();
// Set cache memory limit
// Set memory limit
if (info[0]->IsInt32()) {
vips_cache_set_max_mem(To<int32_t>(info[0]).FromJust() * 1048576);
}
// Set cache items limit
// Set file limit
if (info[1]->IsInt32()) {
vips_cache_set_max(To<int32_t>(info[1]).FromJust());
vips_cache_set_max_files(To<int32_t>(info[1]).FromJust());
}
// Set items limit
if (info[2]->IsInt32()) {
vips_cache_set_max(To<int32_t>(info[2]).FromJust());
}
// Get cache statistics
Local<Object> cache = New<Object>();
Set(cache, New("current").ToLocalChecked(),
// Get memory stats
Local<Object> memory = New<Object>();
Set(memory, New("current").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem() / 1048576)))
);
Set(cache, New("high").ToLocalChecked(),
Set(memory, New("high").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem_highwater() / 1048576)))
);
Set(cache, New("memory").ToLocalChecked(),
Set(memory, New("max").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_cache_get_max_mem() / 1048576)))
);
Set(cache, New("items").ToLocalChecked(), New<Integer>(vips_cache_get_max()));
// Get file stats
Local<Object> files = New<Object>();
Set(files, New("current").ToLocalChecked(), New<Integer>(vips_tracked_get_files()));
Set(files, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max_files()));
// Get item stats
Local<Object> items = New<Object>();
Set(items, New("current").ToLocalChecked(), New<Integer>(vips_cache_get_size()));
Set(items, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max()));
Local<Object> cache = New<Object>();
Set(cache, New("memory").ToLocalChecked(), memory);
Set(cache, New("files").ToLocalChecked(), files);
Set(cache, New("items").ToLocalChecked(), items);
info.GetReturnValue().Set(cache);
}
@ -262,6 +278,7 @@ NAN_METHOD(_maxColourDistance) {
vips_object_local(hook, imagePremultipliedNoAlpha2);
image2 = imagePremultipliedNoAlpha2;
}
// Calculate colour distance
VipsImage *difference;
if (vips_dE00(image1, image2, &difference, nullptr)) {
@ -276,5 +293,10 @@ NAN_METHOD(_maxColourDistance) {
return ThrowError(vips_error_buffer());
}
g_object_unref(hook);
// Clean up libvips' per-request data and threads
vips_error_clear();
vips_thread_shutdown();
info.GetReturnValue().Set(New<Number>(maxColourDistance));
}

View File

@ -34,7 +34,7 @@ var magickFilterBilinear = 'Triangle';
var magickFilterBicubic = 'Lanczos';
// Disable libvips cache to ensure tests are as fair as they can be
sharp.cache(0);
sharp.cache(false);
// Enable use of SIMD
sharp.simd(true);

View File

@ -4,8 +4,6 @@ var assert = require('assert');
var fixtures = require('../fixtures');
var sharp = require('../../index');
sharp.cache(0);
describe('Alpha transparency', function() {
it('Flatten to black', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Blur', function() {
it('specific radius 1', function(done) {

View File

@ -6,9 +6,15 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Clone', function() {
before(function() {
sharp.cache(false);
});
after(function() {
sharp.cache(true);
});
it('Read from Stream and write to multiple Streams', function(done) {
var finishEventsExpected = 2;
// Output stream 1
@ -55,4 +61,5 @@ describe('Clone', function() {
// Go
fs.createReadStream(fixtures.inputJpg).pipe(rotator);
});
});

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Colour space conversion', function() {
it('To greyscale', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Crop gravities', function() {
var testSettings = [

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Embed', function() {
it('JPEG within PNG, no alpha channel', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Partial image extraction', function() {
describe('using the legacy extract(top,left,width,height) syntax', function () {
it('JPEG', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Gamma correction', function() {
it('value of 0.0 (disabled)', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Interpolation', function() {
it('nearest neighbour', function(done) {

View File

@ -6,10 +6,15 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Input/output', function() {
before(function() {
sharp.cache(false);
});
after(function() {
sharp.cache(true);
});
it('Read from File and write to Stream', function(done) {
var writable = fs.createWriteStream(fixtures.outputJpg);
writable.on('finish', function() {

View File

@ -8,8 +8,6 @@ var icc = require('icc');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Image metadata', function() {
it('JPEG', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Negate', function() {
it('negate (jpeg)', function(done) {
sharp(fixtures.inputJpg)

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Normalization', function () {
it('uses the same prototype for both spellings', function () {

View File

@ -4,8 +4,6 @@ var assert = require('assert');
var fixtures = require('../fixtures');
var sharp = require('../../index');
sharp.cache(0);
// Helpers
var getPaths = function(baseName, extension) {
if (typeof extension === 'undefined') {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Resize dimensions', function() {
it('Exact crop', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Rotation', function() {
['Landscape', 'Portrait'].forEach(function(orientation) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Sharpen', function() {
it('specific radius 10', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Threshold', function() {
it('threshold 1 jpeg', function(done) {
sharp(fixtures.inputJpg)

View File

@ -10,8 +10,6 @@ var rimraf = require('rimraf');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
// Verifies all tiles in a given dz output directory are <= size
var assertDeepZoomTiles = function(directory, expectedSize, expectedLevels, done) {
// Get levels

View File

@ -10,20 +10,48 @@ describe('Utilities', function() {
describe('Cache', function() {
it('Can be disabled', function() {
var cache = sharp.cache(0, 0);
assert.strictEqual(0, cache.memory);
assert.strictEqual(0, cache.items);
sharp.cache(false);
var cache = sharp.cache(false);
assert.strictEqual(cache.memory.current, 0);
assert.strictEqual(cache.memory.max, 0);
assert.strictEqual(typeof cache.memory.high, 'number');
assert.strictEqual(cache.files.current, 0);
assert.strictEqual(cache.files.max, 0);
assert.strictEqual(cache.items.current, 0);
assert.strictEqual(cache.items.max, 0);
});
it('Can be set to a maximum of 50MB and 500 items', function() {
var cache = sharp.cache(50, 500);
assert.strictEqual(50, cache.memory);
assert.strictEqual(500, cache.items);
it('Can be enabled with defaults', function() {
var cache = sharp.cache(true);
assert.strictEqual(cache.memory.max, 50);
assert.strictEqual(cache.files.max, 20);
assert.strictEqual(cache.items.max, 100);
});
it('Can be set to zero', function() {
var cache = sharp.cache({
memory: 0,
files: 0,
items: 0
});
assert.strictEqual(cache.memory.max, 0);
assert.strictEqual(cache.files.max, 0);
assert.strictEqual(cache.items.max, 0);
});
it('Can be set to a maximum of 10MB, 100 files and 1000 items', function() {
var cache = sharp.cache({
memory: 10,
files: 100,
items: 1000
});
assert.strictEqual(cache.memory.max, 10);
assert.strictEqual(cache.files.max, 100);
assert.strictEqual(cache.items.max, 1000);
});
it('Ignores invalid values', function() {
sharp.cache(50, 500);
sharp.cache(true);
var cache = sharp.cache('spoons');
assert.strictEqual(50, cache.memory);
assert.strictEqual(500, cache.items);
assert.strictEqual(cache.memory.max, 50);
assert.strictEqual(cache.files.max, 20);
assert.strictEqual(cache.items.max, 100);
});
});