Initial code commit
This commit is contained in:
Родитель
da9a2cbe8c
Коммит
df261d82c5
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
.idea
|
|
@ -0,0 +1 @@
|
|||
v10.16.0
|
|
@ -0,0 +1,25 @@
|
|||
## Unity Cache Server Diagnostic Tools
|
||||
|
||||
### Client Stream Player
|
||||
|
||||
|
||||
#### Usage
|
||||
|
||||
`stream_player.js [options] <filePath> [ServerAddress]`
|
||||
|
||||
Option | Description
|
||||
------------------------------- | -----------
|
||||
-i --iterations <n> | Number of times to send the recorded session to the server (default: 1)
|
||||
-c --max-concurrency <n> | Number of concurrent connections to make to the server (default: 1)
|
||||
-d --debug-protocol | Print protocol stream debugging data to the console.
|
||||
-q --no-verbose | Do not show progress and result statistics.
|
||||
-h, --help | Show usage information.
|
||||
|
||||
#### Description
|
||||
|
||||
The stream player can read one ore more recorded client session at `<filePath>`, optionally print the protocol stream to the console, and optionally send the protocol stream to a remote Cache Server at `[ServerAddress]` for e.g. performance load testing.
|
||||
|
||||
#### Notes
|
||||
|
||||
* If `<filePath>` is a directory, all files within the directory (recursively) will be read and played back. Some rudimentary validation is done on each file to detect whether or not it is a valid client session stream.
|
||||
* If `[ServerAddress]` is omitted, data will be sent to a temporary "no-op" TCP server. This is useful if you are only concerned with reading the debug protocol stream with the `-d` option.
|
|
@ -0,0 +1,83 @@
|
|||
const { Constants, Helpers } = require('unity-cache-server');
|
||||
const { Transform } = require('stream');
|
||||
const crypto = require('crypto');
|
||||
|
||||
class ClientStreamDebugger extends Transform {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this._writeHandlers = {
|
||||
putStream: this._handleWrite.bind(this),
|
||||
command: this._handleCommand.bind(this),
|
||||
version: this._handleVersion.bind(this)
|
||||
};
|
||||
|
||||
this._putHash = null;
|
||||
this._putSize = 0;
|
||||
this._putSent = 0;
|
||||
this._writeHandler = this._writeHandlers.version;
|
||||
}
|
||||
|
||||
_transform(chunk, encoding, callback) {
|
||||
this._writeHandler(chunk);
|
||||
this.push(chunk);
|
||||
callback();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Buffer} data
|
||||
* @private
|
||||
*/
|
||||
_handleVersion(data) {
|
||||
this.emit('debug', [Helpers.readUInt32(data)]);
|
||||
this._writeHandler = this._writeHandlers.command;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Buffer} data
|
||||
* @private
|
||||
*/
|
||||
_handleWrite(data) {
|
||||
this._putSent += data.length;
|
||||
this._putHash.update(data, 'ascii');
|
||||
if(this._putSent === this._putSize) {
|
||||
this.emit('debug', [`<BLOB ${this._putHash.digest().toString('hex')}>`]);
|
||||
this._writeHandler = this._writeHandlers.command;
|
||||
this._putSent = 0;
|
||||
this._putSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Buffer} data
|
||||
* @private
|
||||
*/
|
||||
_handleCommand(data) {
|
||||
const cmd = data.slice(0, Math.min(data.length, 2)).toString('ascii');
|
||||
const eventData = [cmd];
|
||||
let size, guid, hash = null;
|
||||
|
||||
if(data.length > 1) {
|
||||
if (data.length === 2 + Constants.ID_SIZE) {
|
||||
guid = Buffer.from(data.slice(2, 2 + Constants.GUID_SIZE));
|
||||
hash = Buffer.from(data.slice(2 + Constants.HASH_SIZE));
|
||||
eventData.push(Helpers.GUIDBufferToString(guid));
|
||||
eventData.push(hash.toString('hex'));
|
||||
}
|
||||
else if (data.length === 2 + Constants.SIZE_SIZE) {
|
||||
size = Helpers.readUInt64(data.slice(2));
|
||||
this._putSize = size;
|
||||
this._putHash = crypto.createHash('sha256');
|
||||
this._writeHandler = this._writeHandlers.putStream;
|
||||
eventData.push(size.toString());
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('debug', eventData);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientStreamDebugger;
|
|
@ -0,0 +1,288 @@
|
|||
{
|
||||
"name": "ucs-diag-utils",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"cli-cursor": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
|
||||
"integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
|
||||
"requires": {
|
||||
"restore-cursor": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"cli-spinners": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-1.3.1.tgz",
|
||||
"integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg=="
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
|
||||
"integrity": "sha1-1YuytcHuj4ew00ACfp6U4iLFpCI="
|
||||
},
|
||||
"config": {
|
||||
"version": "1.31.0",
|
||||
"resolved": "https://registry.npmjs.org/config/-/config-1.31.0.tgz",
|
||||
"integrity": "sha512-Ep/l9Rd1J9IPueztJfpbOqVzuKHQh4ZODMNt9xqTYdBBNRXbV4oTu34kCkkfdRVcDq0ohtpaeXGgb+c0LQxFRA==",
|
||||
"requires": {
|
||||
"json5": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
},
|
||||
"filesize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-4.1.2.tgz",
|
||||
"integrity": "sha1-/NVwrxNTzql4l75k9WGDrbmVmUs="
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.0.1.tgz",
|
||||
"integrity": "sha1-kClAgfl4sfGC80ekQKIJFUNEKFs=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"graceful-fs": {
|
||||
"version": "4.1.15",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
|
||||
"integrity": "sha1-/7cD4QZuig7qpMi4C6klPu77+wA="
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY="
|
||||
}
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.1.15",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
|
||||
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||
},
|
||||
"ip": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
||||
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
|
||||
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
||||
},
|
||||
"log-symbols": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
|
||||
"integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
|
||||
"requires": {
|
||||
"chalk": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"lokijs": {
|
||||
"version": "1.5.6",
|
||||
"resolved": "https://registry.npmjs.org/lokijs/-/lokijs-1.5.6.tgz",
|
||||
"integrity": "sha512-xJoDXy8TASTjmXMKr4F8vvNUCu4dqlwY5gmn0g5BajGt1GM3goDCafNiGAh/sfrWgkfWu1J4OfsxWm8yrWweJA=="
|
||||
},
|
||||
"mimic-fn": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
|
||||
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.24.0",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
|
||||
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
|
||||
},
|
||||
"onetime": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
|
||||
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
|
||||
"requires": {
|
||||
"mimic-fn": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"ora": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ora/-/ora-1.4.0.tgz",
|
||||
"integrity": "sha512-iMK1DOQxzzh2MBlVsU42G80mnrvUhqsMh74phHtDlrcTZPK0pH6o7l7DRshK+0YsxDyEuaOkziVdvM3T0QTzpw==",
|
||||
"requires": {
|
||||
"chalk": "^2.1.0",
|
||||
"cli-cursor": "^2.1.0",
|
||||
"cli-spinners": "^1.0.1",
|
||||
"log-symbols": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"progress": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
|
||||
},
|
||||
"restore-cursor": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
|
||||
"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
|
||||
"requires": {
|
||||
"onetime": "^2.0.0",
|
||||
"signal-exit": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"unity-cache-server": {
|
||||
"version": "6.4.0-beta3",
|
||||
"resolved": "https://registry.npmjs.org/unity-cache-server/-/unity-cache-server-6.4.0-beta3.tgz",
|
||||
"integrity": "sha512-/H7wNcCgzuLIjopjpIH/4jpV/v6gu4NLzFObAx9dU+4wqbv5uE9RZkb7zi+K5XGkNKalX4+leoB/2KW1U4AeLw==",
|
||||
"requires": {
|
||||
"commander": "^2.19.0",
|
||||
"config": "^1.31.0",
|
||||
"filesize": "^3.5.11",
|
||||
"fs-extra": "^5.0.0",
|
||||
"ip": "^1.1.5",
|
||||
"js-yaml": "^3.13.1",
|
||||
"lodash": "^4.17.11",
|
||||
"lokijs": "^1.5.5",
|
||||
"moment": "^2.23.0",
|
||||
"ora": "^1.4.0",
|
||||
"progress": "^2.0.3",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"filesize": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz",
|
||||
"integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg=="
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
|
||||
"integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "ucs-diag-utils",
|
||||
"version": "1.0.0",
|
||||
"description": "Diagnostic utilities for the Unity Cache Server (V1)",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Unity-Technologies/ucs-diag-utils.git"
|
||||
},
|
||||
"author": "Stephen Palmer",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Unity-Technologies/ucs-diag-utils/issues"
|
||||
},
|
||||
"homepage": "https://github.com/Unity-Technologies/ucs-diag-utils",
|
||||
"dependencies": {
|
||||
"commander": "^2.20.0",
|
||||
"filesize": "^4.1.2",
|
||||
"fs-extra": "^8.0.1",
|
||||
"unity-cache-server": "^6.4.0-beta3"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const program = require('commander');
|
||||
const net = require('net');
|
||||
const fs = require('fs-extra');
|
||||
const filesize = require('filesize');
|
||||
const crypto = require('crypto');
|
||||
const ClientStreamDebugger = require('./lib/client_stream_debugger');
|
||||
const { Constants, Helpers, ServerStreamProcessor, ClientStreamProcessor } = require('unity-cache-server');
|
||||
|
||||
program.arguments('<filePath> [ServerAddress]')
|
||||
.option('-i --iterations <n>', 'Number of times to send the recorded session to the server', 1)
|
||||
.option('-c --max-concurrency <n>', 'Number of concurrent connections to make to the server', 1)
|
||||
.option('-d --debug-protocol', 'Print protocol stream debugging data to the console', false)
|
||||
.option('-q --no-verbose', 'Do not show progress and result statistics')
|
||||
.action((filePath, serverAddress) => {
|
||||
const options = {
|
||||
numIterations: parseInt(program.iterations),
|
||||
numConcurrent: parseInt(program.maxConcurrency),
|
||||
verbose: program.verbose,
|
||||
debugProtocol: program.debugProtocol
|
||||
};
|
||||
|
||||
run(filePath, serverAddress, options)
|
||||
.then(stats => {
|
||||
if(options.verbose) {
|
||||
if(stats.bytesSent > 0) {
|
||||
const sendTime = stats.sendTime / 1000;
|
||||
const sendBps = stats.bytesSent / sendTime || 0;
|
||||
console.log(`Sent ${filesize(stats.bytesSent)} in ${sendTime} seconds (${filesize(sendBps)}/second)`);
|
||||
}
|
||||
|
||||
if(stats.bytesReceived > 0) {
|
||||
const receiveTime = stats.receiveTime / 1000;
|
||||
const receiveBps = stats.bytesReceived / receiveTime || 0;
|
||||
console.log(`Received ${filesize(stats.bytesReceived)} in ${receiveTime} seconds (${filesize(receiveBps)}/second)`);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
async function run(filePath, serverAddress, options) {
|
||||
let nullServer = null;
|
||||
|
||||
if(!serverAddress) {
|
||||
nullServer = net.createServer({}, socket => {
|
||||
socket.on('data', () => {});
|
||||
});
|
||||
|
||||
await new Promise(resolve => {
|
||||
nullServer.listen(0, "0.0.0.0", () => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
if(nullServer !== null) {
|
||||
options.nullServer = true;
|
||||
const a = nullServer.address();
|
||||
serverAddress = `${a.address}:${a.port}`;
|
||||
}
|
||||
|
||||
// Gather files
|
||||
const files = [];
|
||||
const stat = await fs.stat(filePath);
|
||||
if(stat.isDirectory()) {
|
||||
await Helpers.readDir(filePath, f => files.push(f.path));
|
||||
}
|
||||
else {
|
||||
files.push(filePath);
|
||||
}
|
||||
|
||||
// Validate files
|
||||
const verBuf = Buffer.alloc(Constants.VERSION_SIZE, 'ascii');
|
||||
for(let i = 0; i < files.length; i++) {
|
||||
const fd = await fs.open(files[i], "r");
|
||||
await fs.read(fd, verBuf, 0, Constants.VERSION_SIZE, 0);
|
||||
if(Helpers.readUInt32(verBuf) !== Constants.PROTOCOL_VERSION) {
|
||||
if(options.verbose) {
|
||||
console.log(`Skipping unrecognized file ${files[i]}`);
|
||||
}
|
||||
files[i] = null;
|
||||
}
|
||||
|
||||
await fs.close(fd);
|
||||
}
|
||||
|
||||
const jobs = [];
|
||||
const results = [];
|
||||
let i = 0;
|
||||
while(i < options.numIterations) {
|
||||
files.forEach(f => {
|
||||
if(f === null) return;
|
||||
jobs.push((n, t) => {
|
||||
if(options.verbose) console.log(`[${n}/${t}] Playing ${f}`);
|
||||
return playStream(f, serverAddress, options)
|
||||
.then(stats => results.push(stats))
|
||||
.catch(err => { throw(err); });
|
||||
});
|
||||
});
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
const totalJobs = jobs.length;
|
||||
let nextJobNum = 0;
|
||||
while(jobs.length > 0) {
|
||||
nextJobNum += Math.min(jobs.length, options.numConcurrent);
|
||||
const next = jobs.splice(0, options.numConcurrent);
|
||||
await Promise.all(next.map(t => t(nextJobNum, totalJobs)));
|
||||
}
|
||||
|
||||
if(nullServer !== null) nullServer.close();
|
||||
|
||||
return results.reduce((prev, cur) => {
|
||||
cur.bytesSent += prev.bytesSent;
|
||||
cur.bytesReceived += prev.bytesReceived;
|
||||
cur.sendTime += prev.sendTime;
|
||||
cur.receiveTime += prev.receiveTime;
|
||||
return cur;
|
||||
}, {
|
||||
receiveTime: 0,
|
||||
bytesReceived: 0,
|
||||
sendTime: 0,
|
||||
bytesSent: 0
|
||||
});
|
||||
}
|
||||
|
||||
async function playStream(filePath, serverAddress, options) {
|
||||
let bytesReceived = 0, fileOpen = false, receiveStartTime, receiveEndTime, sendStartTime, sendEndTime, dataHash;
|
||||
|
||||
if(!await fs.pathExists(filePath)) throw new Error(`Cannot find ${filePath}`);
|
||||
|
||||
const fileStats = await fs.stat(filePath);
|
||||
const address = await Helpers.parseAndValidateAddressString(serverAddress, Constants.DEFAULT_PORT);
|
||||
const client = new net.Socket();
|
||||
await new Promise(resolve => client.connect(address.port, address.host, () => resolve()));
|
||||
|
||||
client.on('error', (err) => {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
|
||||
const endClient = () => {
|
||||
if(options.nullServer || (reqCount === 0 && !fileOpen)) {
|
||||
process.nextTick(() => client.end(''));
|
||||
}
|
||||
};
|
||||
|
||||
fileStream.on('open', () => {
|
||||
fileOpen = true;
|
||||
sendStartTime = Date.now();
|
||||
}).on('close', () => {
|
||||
fileOpen = false;
|
||||
sendEndTime = Date.now();
|
||||
endClient();
|
||||
});
|
||||
|
||||
let reqCount = 0;
|
||||
|
||||
const ssp = new ServerStreamProcessor();
|
||||
|
||||
ssp.once('header', () => {
|
||||
receiveStartTime = Date.now();
|
||||
}).on('header', () => {
|
||||
reqCount--;
|
||||
if(reqCount === 0) endClient();
|
||||
}).on('data', (chunk) => {
|
||||
bytesReceived += chunk.length;
|
||||
}).on('dataEnd', () => {
|
||||
receiveEndTime = Date.now();
|
||||
});
|
||||
|
||||
if(options.debugProtocol) {
|
||||
ssp.on('header', header => {
|
||||
dataHash = crypto.createHash('sha256');
|
||||
|
||||
const debugData = [header.cmd];
|
||||
if(header.size) {
|
||||
debugData.push(header.size);
|
||||
}
|
||||
|
||||
debugData.push(Helpers.GUIDBufferToString(header.guid));
|
||||
debugData.push(header.hash.toString('hex'));
|
||||
|
||||
const txt = `<<< ${debugData.join(' ')}`;
|
||||
|
||||
if(header.size) {
|
||||
process.stdout.write(txt);
|
||||
} else {
|
||||
console.log(txt)
|
||||
}
|
||||
}).on('data', (chunk) => {
|
||||
dataHash.update(chunk, 'ascii');
|
||||
}).on('dataEnd', () => {
|
||||
console.log(` <BLOB ${dataHash.digest().toString('hex')}>`);
|
||||
});
|
||||
}
|
||||
|
||||
const csp = new ClientStreamProcessor({});
|
||||
|
||||
csp.on('cmd', cmd => {
|
||||
if(cmd[0] === 'g' || cmd === 'ts') reqCount++;
|
||||
if(cmd === 'te') reqCount --;
|
||||
});
|
||||
|
||||
let stream = fileStream.pipe(csp);
|
||||
|
||||
if(options.debugProtocol) {
|
||||
stream = stream.pipe(new ClientStreamDebugger({}))
|
||||
.on('debug', data => console.log(`>>> ${data.join(' ')}`));
|
||||
}
|
||||
|
||||
stream.pipe(client, {end: false}).pipe(ssp);
|
||||
|
||||
return new Promise(resolve => {
|
||||
client.on('close', () => {
|
||||
resolve({
|
||||
bytesSent: fileStats.size,
|
||||
bytesReceived: bytesReceived,
|
||||
sendTime: sendEndTime - sendStartTime,
|
||||
receiveTime: receiveEndTime - receiveStartTime,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Загрузка…
Ссылка в новой задаче