Last active
December 19, 2015 12:17
-
-
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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