Skip to content

Instantly share code, notes, and snippets.

@jorangreef
Last active December 19, 2015 12:17
Show Gist options
  • Save jorangreef/d8b0bd5fb3d5b9ce6a94 to your computer and use it in GitHub Desktop.
Save jorangreef/d8b0bd5fb3d5b9ce6a94 to your computer and use it in GitHub Desktop.
Test the reliability and average and max latency of the underlying OS notification system used by Node's fs.watch. Uses fsync to flush the filesystem cache to make sure these don't delay notifications (this makes little to no difference).
var Node = {
child: require('child_process'),
fs: require('fs'),
path: require('path')
};
// This will be removed and then created and then removed:
var testdirectory = 'testfswatchmisses';
if (Node.fs.remove !== undefined) throw new Error('fs.remove exists');
Node.fs.remove = function(target, end) {
Node.fs.lstat(target,
function(error, stats) {
if (error && error.code == 'ENOENT') return end();
if (error) return end(error);
if (stats.isFile() || stats.isSymbolicLink()) {
Node.fs.unlink(target, end);
} else if (stats.isDirectory()) {
Node.fs.readdir(target,
function(error, paths) {
if (error) return end(error);
var index = 0;
function remove(error) {
if (error) return end(error);
if (index === paths.length) {
return Node.fs.rmdir(target, end);
}
var path = paths[index++];
Node.fs.remove(Node.path.join(target, path), remove);
}
remove();
}
);
} else {
end('Unsupported file type: ' + target);
}
}
);
};
Node.fs.remove(testdirectory,
function(error) {
if (error) throw error;
Node.fs.mkdirSync(testdirectory);
Node.fs.mkdirSync(Node.path.join(testdirectory, 'a'));
Node.fs.mkdirSync(Node.path.join(testdirectory, 'a', 'b'));
Node.fs.mkdirSync(Node.path.join(testdirectory, 'a', 'b', 'c'));
Node.fs.mkdirSync(Node.path.join(testdirectory, 'a', 'b', 'c', 'd'));
var relative = Node.path.join('a', 'b', 'c', 'd');
var produced = {};
var observed = {};
var watch = Node.fs.watch(testdirectory, { recursive: true });
watch.on('change',
function(change, binaryPath) {
if (!observed[binaryPath]) {
observed[binaryPath] = Date.now();
}
}
);
watch.on('error',
function(error) {
console.log('ERROR ' + error);
}
);
function makeBuffer() {
var size = Math.floor(Math.random() * 1000000);
var buffer = new Buffer(size);
return buffer;
}
var number = 0;
var length = 10000;
function report() {
watch.close();
var count = 0;
var missed = 0;
var sum = 0;
var max = 0;
for (var key in produced) {
var a = produced[key];
count++;
if (observed.hasOwnProperty(key)) {
var b = observed[key];
var latency = Math.max(0, b - a);
if (latency > max) max = latency;
sum += latency;
} else {
missed++;
}
}
console.log('Produced ' + count + ' event(s)');
console.log('Missed ' + missed + ' event(s)');
console.log(Math.round(sum / (count || 1)) + 'ms average latency');
console.log(max + 'ms max latency');
Node.fs.remove(testdirectory, function() {});
}
function fsyncDirectory(path, end) {
var flags = (process.platform === 'win32') ? 'r+' : 'r';
Node.fs.open(path, flags,
function(error, fd) {
if (error) throw error;
Node.fs.fsync(fd,
function(error) {
if (error) throw error;
Node.fs.close(fd, end);
}
);
}
);
}
function rename(source) {
setTimeout(
function() {
var target = 'renamed ' + source;
produced[Node.path.join(Node.path.dirname(relative), target)] = Date.now();
source = Node.path.join(testdirectory, relative, source);
target = Node.path.join(testdirectory, Node.path.dirname(relative), target);
Node.fs.rename(source, target,
function(error) {
if (error) throw error;
fsyncDirectory(Node.path.dirname(source), function() {});
fsyncDirectory(Node.path.dirname(target), function() {});
}
);
},
Math.floor(Math.random() * 10000)
);
}
function write() {
if (number === length) return setTimeout(report, 60000);
var name = 'update ' + (++number).toString();
var target = Node.path.join(testdirectory, relative, name);
Node.fs.open(target, 'wx',
function(error, fd) {
if (error) throw error;
writeFile(fd,
function(error) {
if (error) throw error;
Node.fs.fsync(fd,
function(error) {
if (error) throw error;
produced[Node.path.join(relative, name)] = Date.now();
Node.fs.close(fd,
function(error) {
if (error) throw error;
fsyncDirectory(Node.path.dirname(target), function() {});
write();
rename(name);
}
);
}
);
}
);
}
);
}
function writeFile(fd, end) {
if (Math.random() < 0.5) return end();
var buffer = makeBuffer();
Node.fs.writeFile(fd, buffer,
function(error) {
if (error) throw error;
setTimeout(
function() {
writeFile(fd, end);
},
0
);
}
);
}
console.log('Writing and renaming ' + length + ' file(s), this may take awhile...');
write();
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment