├── .gitignore ├── bin └── gnomon ├── meta ├── license.md ├── installation.md ├── make-docs.js ├── cli-template.js ├── intro.md └── usage.md ├── test ├── timer.js └── validator.js ├── LICENSE.txt ├── package.json ├── cli.js ├── README.md ├── doc └── gnomon.1 └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /bin/gnomon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../cli.js'); 3 | -------------------------------------------------------------------------------- /meta/license.md: -------------------------------------------------------------------------------- 1 | ## License 2 | 3 | gnomon uses the MIT license. -------------------------------------------------------------------------------- /meta/installation.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | with npm do: 4 | ``` 5 | npm install -g gnomon 6 | ``` -------------------------------------------------------------------------------- /test/timer.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | process.stdout.write( 3 | 'Writing to stdout at random intervals...\n' 4 | ); 5 | var nanoPow = Math.pow(10,9); 6 | var last = (new Date()).getTime(); 7 | var times = 20; 8 | function randomTime() { 9 | crypto.randomBytes(nanoPow/100, function(e, b) { 10 | if (e) throw e; 11 | last = (new Date()).getTime() - last; 12 | var seconds = last / 1000; 13 | process.stdout.write('above line took about ' + seconds.toFixed(4) + ' seconds\n'); 14 | last = (new Date()).getTime(); 15 | if (times--) setTimeout(randomTime, b.readUInt8(0) * 2); 16 | }); 17 | } 18 | randomTime(); 19 | process.stdout.on('error', process.exit); 20 | -------------------------------------------------------------------------------- /test/validator.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk'); 2 | var split = require('split'); 3 | var through = require('through'); 4 | var lastLine = ''; 5 | var precision = 0.001; 6 | console.log('Testing timer accuracy...') 7 | process.stdin.pipe(split()).pipe(through(function(line) { 8 | line = chalk.stripColor(line); 9 | var calculated, reported; 10 | var lm = lastLine.match(/(\d+\.\d+).+about/); 11 | var m = line.match(/about (\d+\.\d+)/); 12 | if (m && lm) { 13 | calculated = parseFloat(m[1]); 14 | reported = parseFloat(lm[1]); 15 | if (Math.abs(calculated - reported) > precision) { 16 | console.error(chalk.bold.red( 17 | reported + ' !~ ' + calculated + ' -- bad' 18 | )); 19 | process.exit(1); 20 | } 21 | this.queue(chalk.bold.green( 22 | reported + ' ~ ' + calculated + ' -- ok\n' 23 | )); 24 | } 25 | lastLine = line; 26 | })).pipe(process.stdout); 27 | -------------------------------------------------------------------------------- /meta/make-docs.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var local = path.join.bind(path, __dirname); 4 | var up = path.resolve.bind(path, __dirname, '..'); 5 | var docExt = '.md'; 6 | console.log('Reading docs from ./docs...'); 7 | var docs = fs.readdirSync(__dirname) 8 | .filter(function(file) { 9 | return fs.statSync(local(file)).isFile() && 10 | path.extname(file) === docExt; 11 | }) 12 | .reduce(function(out, name) { 13 | out[path.basename(name, docExt)] = fs.readFileSync(local(name), 'utf8') + '\n\n'; 14 | return out; 15 | }, {}); 16 | 17 | console.log('Assembling README and CLI help...'); 18 | var readme = docs.intro + docs.usage + docs.installation + docs.license; 19 | var printUsage = docs.usage + '\n' + docs.license; 20 | var cliSource = fs.readFileSync(local('cli-template.js'), 'utf8'); 21 | 22 | console.log('Writing README...'); 23 | fs.writeFileSync(up('README.md'), readme, 'utf8'); 24 | console.log('Writing CLI help...'); 25 | fs.writeFileSync( 26 | up('cli.js'), 27 | cliSource.replace(/__USAGE__/, JSON.stringify(printUsage)) 28 | ); 29 | console.log('Done.'); -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 PAYPAL, INC. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gnomon", 3 | "version": "1.5.0", 4 | "description": "Utility to annotate console logging statements with timestamps and find slow processes", 5 | "main": "index.js", 6 | "bin": { 7 | "gnomon": "./bin/gnomon" 8 | }, 9 | "man": "./doc/gnomon.1", 10 | "scripts": { 11 | "test": "node test/timer.js | ./bin/gnomon | node test/validator.js", 12 | "docs": "node meta/make-docs.js && marked-man README.md > ./doc/gnomon.1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/paypal/gnomon" 17 | }, 18 | "keywords": [ 19 | "time", 20 | "logger", 21 | "timestamp", 22 | "cli", 23 | "annotate", 24 | "stream" 25 | ], 26 | "author": { 27 | "name": "James Zetlen", 28 | "email": "jzetlen@paypal.com" 29 | }, 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/paypal/gnomon/issues" 33 | }, 34 | "homepage": "https://github.com/paypal/gnomon", 35 | "dependencies": { 36 | "chalk": "^1.1.1", 37 | "dateutil": "^0.1.0", 38 | "meow": "^3.7.0", 39 | "repeating": "^2.0.0", 40 | "split": "^1.0.0", 41 | "through": "^2.3.8", 42 | "window-size": "^0.2.0" 43 | }, 44 | "devDependencies": { 45 | "marked-man": "^0.1.6" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /meta/cli-template.js: -------------------------------------------------------------------------------- 1 | var meow = require('meow'); 2 | var usage = __USAGE__; 3 | var intro = [ 4 | '', 5 | '## Usage', 6 | '', 7 | 'Use UNIX (or DOS) pipes to pipe the stdout of any command through gnomon.', 8 | '', 9 | ' npm test | gnomon', 10 | '', 11 | 'Use command-line options to adjust gnomon\'s behavior.', 12 | '', 13 | ' any-command-to-be-timed | gnomon --type=elapsed-total --high=8.0', 14 | '', 15 | '' 16 | ]; 17 | var cli = meow(intro.join('\n') + usage, { 18 | string: ['format','type'], 19 | boolean: ['ignore-blank', 'ignoreBlank'], 20 | alias: { 21 | 'format': 'f', 22 | 'type': 't', 23 | 'ignoreBlank': ['ignore-blank','quiet','q','i'], 24 | 'high': 'h', 25 | 'medium': 'm', 26 | 'realTime': ['real-time', 'r'] 27 | } 28 | }); 29 | if (process.stdin.isTTY) { 30 | console.error('Error: You must pipe another command\'s output to gnomon with |.'); 31 | console.log(intro.join('\n ')); 32 | process.exit(1); 33 | } 34 | var flags = cli.flags; 35 | if (flags.realTime === 'false') flags.realTime = false; 36 | if (process.stdout.isTTY && !flags.hasOwnProperty('realTime')) { 37 | flags.realTime = true; 38 | } 39 | if (flags.realTime && typeof flags.realTime !== 'number') flags.realTime = 500; 40 | var split = require('split'); 41 | var gnomon = require('./'); 42 | process.stdin.pipe(split()) 43 | .pipe(gnomon(flags)) 44 | .pipe(process.stdout); 45 | process.stdout.on('error', process.exit); 46 | -------------------------------------------------------------------------------- /meta/intro.md: -------------------------------------------------------------------------------- 1 | # gnomon 2 | 3 | A command line utility, a bit like 4 | [moreutils's **ts**](https://joeyh.name/code/moreutils/), to prepend timestamp 5 | information to the standard output of another command. Useful for long-running 6 | processes where you'd like a historical record of what's taking so long. 7 | 8 | ## Example 9 | 10 | ![basic](https://cloud.githubusercontent.com/assets/1643758/13685018/17b4f76c-e6d4-11e5-8838-40fa52346ae8.gif) 11 | 12 | Piping anything to `gnomon` will prepend a timestamp to each line, indicating 13 | how long that line was the last line in the buffer--that is, how long it took 14 | the next line to appear. By default, `gnomon` will display the seconds elapsed 15 | between each line, but that is configurable. 16 | 17 | You can display total time elapsed since the process began: 18 | ![total](https://cloud.githubusercontent.com/assets/1643758/13685020/199b78b2-e6d4-11e5-9083-05de6c52cc60.gif) 19 | 20 | You can display an absolute timestamp: 21 | ![absolute](https://cloud.githubusercontent.com/assets/1643758/13685022/1ab3a5bc-e6d4-11e5-9ccf-3a5c68f9ea0c.gif) 22 | 23 | You can also use the `--high` and/or `--medium` options to specify a length 24 | threshold in seconds, over which `gnomon` will highlight the timestamp in red 25 | or yellow. And you can do a few other things, too. 26 | 27 | ![fancy](https://cloud.githubusercontent.com/assets/1643758/13685025/1bbf6ad6-e6d4-11e5-8806-e90a6e852bf7.gif) 28 | 29 | If the realtime timestamp updating is distracting or incompatible with your 30 | terminal, it can be disabled: 31 | 32 | ![norealtime](https://cloud.githubusercontent.com/assets/1643758/13685027/1cfd823e-e6d4-11e5-9f90-c047d67a35e0.gif) 33 | -------------------------------------------------------------------------------- /meta/usage.md: -------------------------------------------------------------------------------- 1 | ## Options 2 | 3 | --type= [default: elapsed-line] 4 | -t 5 | 6 | Type of timestamp to display. 7 | elapsed-line: Number of seconds that displayed line was the last line. 8 | elapsed-total: Number of seconds since the start of the process. 9 | absolute: An absolute timestamp in UTC. 10 | 11 | --format="format" [default: "H:i:s.u O"] 12 | -f "format" 13 | 14 | Format the absolute timestamp, using PHP date format strings. If the type 15 | is elapsed-line or elapsed-total, this option is ignored. 16 | 17 | --ignore-blank [default: false] 18 | --quiet 19 | -q 20 | -i 21 | 22 | Do not prepend a timestamp to blank lines; just pass them through. When 23 | this option is active, blank lines will not trigger an update of elapsed 24 | time. Therefore, if a lot of blank lines appear, the prior timestamp will 25 | display the total time between that line and the next non-blank line 26 | (if the type is elapsed-time was selected). 27 | 28 | --real-time= [default: 500] 29 | -r [non-tty default: false] 30 | 31 | Time increment to use when updating timestamp for the current line, in 32 | milliseconds. Pass `false` to this option to disable realtime entirely, 33 | if you need an extra performance boost or you find it distracting. When 34 | realtime is disabled, the log will always appear one line "behind" the 35 | original piped output, since it can't display the line until it's 36 | finished timing it. 37 | 38 | --high=seconds 39 | -h seconds 40 | 41 | High threshold. If the elapsed time for a line is equal to or higher than 42 | this value in seconds, then the timestamp will be colored bright red. 43 | This works for all timestamp types, including elapsed-total and absolute, 44 | where the elapsed line time is not actually displayed. 45 | 46 | --medium=seconds 47 | -m seconds 48 | 49 | Medium threshold. Works just like the high threshold described above, but 50 | colors the timestamp bright yellow instead. Can be used in conjunction 51 | with a high threshold for three levels. 52 | 53 | ### Notes 54 | - If a `high` and/or a `medium` threshold are specified, then all timestamps not 55 | meeting that threshold will be colored bright green. 56 | - If you pipe the output of `gnomon` into another command or a file (that is, 57 | not a tty) then the `real-time` option will be disabled by default and each line 58 | will appear only after it has been timed. You can force realtime by sending a 59 | `--real-time=` argument explicitly, but the ANSI codes would probably 60 | interfere with whatever you were trying to do. The sane default is to omit fancy 61 | stuff, like colors and escape sequences, when logging text directly to a file. -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | var meow = require('meow'); 2 | var usage = "## Options\n\n --type= [default: elapsed-line]\n -t \n\n Type of timestamp to display.\n elapsed-line: Number of seconds that displayed line was the last line.\n elapsed-total: Number of seconds since the start of the process.\n absolute: An absolute timestamp in UTC.\n\n --format=\"format\" [default: \"H:i:s.u O\"]\n -f \"format\"\n\n Format the absolute timestamp, using PHP date format strings. If the type\n is elapsed-line or elapsed-total, this option is ignored.\n\n --ignore-blank [default: false]\n --quiet\n -q\n -i\n\n Do not prepend a timestamp to blank lines; just pass them through. When\n this option is active, blank lines will not trigger an update of elapsed\n time. Therefore, if a lot of blank lines appear, the prior timestamp will\n display the total time between that line and the next non-blank line\n (if the type is elapsed-time was selected).\n\n\t--real-time= [default: 500]\n\t-r [non-tty default: false]\n\n\t Time increment to use when updating timestamp for the current line, in\n\t milliseconds. Pass `false` to this option to disable realtime entirely,\n\t if you need an extra performance boost or you find it distracting. When\n\t realtime is disabled, the log will always appear one line \"behind\" the\n\t original piped output, since it can't display the line until it's\n\t finished timing it.\n\n --high=seconds\n -h seconds\n\n High threshold. If the elapsed time for a line is equal to or higher than\n this value in seconds, then the timestamp will be colored bright red.\n This works for all timestamp types, including elapsed-total and absolute,\n where the elapsed line time is not actually displayed.\n\n --medium=seconds\n -m seconds\n\n Medium threshold. Works just like the high threshold described above, but\n colors the timestamp bright yellow instead. Can be used in conjunction\n with a high threshold for three levels.\n\n### Notes\n - If a `high` and/or a `medium` threshold are specified, then all timestamps not\nmeeting that threshold will be colored bright green.\n - If you pipe the output of `gnomon` into another command or a file (that is,\n not a tty) then the `real-time` option will be disabled by default and each line\n will appear only after it has been timed. You can force realtime by sending a\n `--real-time=` argument explicitly, but the ANSI codes would probably\n interfere with whatever you were trying to do. The sane default is to omit fancy\n stuff, like colors and escape sequences, when logging text directly to a file.\n\n\n## License\n\ngnomon uses the MIT license.\n\n"; 3 | var intro = [ 4 | '', 5 | '## Usage', 6 | '', 7 | 'Use UNIX (or DOS) pipes to pipe the stdout of any command through gnomon.', 8 | '', 9 | ' npm test | gnomon', 10 | '', 11 | 'Use command-line options to adjust gnomon\'s behavior.', 12 | '', 13 | ' any-command-to-be-timed | gnomon --type=elapsed-total --high=8.0', 14 | '', 15 | '' 16 | ]; 17 | var cli = meow(intro.join('\n') + usage, { 18 | string: ['format','type'], 19 | boolean: ['ignore-blank', 'ignoreBlank'], 20 | alias: { 21 | 'format': 'f', 22 | 'type': 't', 23 | 'ignoreBlank': ['ignore-blank','quiet','q','i'], 24 | 'high': 'h', 25 | 'medium': 'm', 26 | 'realTime': ['real-time', 'r'] 27 | } 28 | }); 29 | if (process.stdin.isTTY) { 30 | console.error('Error: You must pipe another command\'s output to gnomon with |.'); 31 | console.log(intro.join('\n ')); 32 | process.exit(1); 33 | } 34 | var flags = cli.flags; 35 | if (flags.realTime === 'false') flags.realTime = false; 36 | if (process.stdout.isTTY && !flags.hasOwnProperty('realTime')) { 37 | flags.realTime = true; 38 | } 39 | if (flags.realTime && typeof flags.realTime !== 'number') flags.realTime = 500; 40 | var split = require('split'); 41 | var gnomon = require('./'); 42 | process.stdin.pipe(split()) 43 | .pipe(gnomon(flags)) 44 | .pipe(process.stdout); 45 | process.stdout.on('error', process.exit); 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gnomon 2 | 3 | A command line utility, a bit like 4 | [moreutils's **ts**](https://joeyh.name/code/moreutils/), to prepend timestamp 5 | information to the standard output of another command. Useful for long-running 6 | processes where you'd like a historical record of what's taking so long. 7 | 8 | ## Example 9 | 10 | ![basic](https://cloud.githubusercontent.com/assets/1643758/13685018/17b4f76c-e6d4-11e5-8838-40fa52346ae8.gif) 11 | 12 | Piping anything to `gnomon` will prepend a timestamp to each line, indicating 13 | how long that line was the last line in the buffer--that is, how long it took 14 | the next line to appear. By default, `gnomon` will display the seconds elapsed 15 | between each line, but that is configurable. 16 | 17 | You can display total time elapsed since the process began: 18 | ![total](https://cloud.githubusercontent.com/assets/1643758/13685020/199b78b2-e6d4-11e5-9083-05de6c52cc60.gif) 19 | 20 | You can display an absolute timestamp: 21 | ![absolute](https://cloud.githubusercontent.com/assets/1643758/13685022/1ab3a5bc-e6d4-11e5-9ccf-3a5c68f9ea0c.gif) 22 | 23 | You can also use the `--high` and/or `--medium` options to specify a length 24 | threshold in seconds, over which `gnomon` will highlight the timestamp in red 25 | or yellow. And you can do a few other things, too. 26 | 27 | ![fancy](https://cloud.githubusercontent.com/assets/1643758/13685025/1bbf6ad6-e6d4-11e5-8806-e90a6e852bf7.gif) 28 | 29 | If the realtime timestamp updating is distracting or incompatible with your 30 | terminal, it can be disabled: 31 | 32 | ![norealtime](https://cloud.githubusercontent.com/assets/1643758/13685027/1cfd823e-e6d4-11e5-9f90-c047d67a35e0.gif) 33 | 34 | 35 | ## Options 36 | 37 | --type= [default: elapsed-line] 38 | -t 39 | 40 | Type of timestamp to display. 41 | elapsed-line: Number of seconds that displayed line was the last line. 42 | elapsed-total: Number of seconds since the start of the process. 43 | absolute: An absolute timestamp in UTC. 44 | 45 | --format="format" [default: "H:i:s.u O"] 46 | -f "format" 47 | 48 | Format the absolute timestamp, using PHP date format strings. If the type 49 | is elapsed-line or elapsed-total, this option is ignored. 50 | 51 | --ignore-blank [default: false] 52 | --quiet 53 | -q 54 | -i 55 | 56 | Do not prepend a timestamp to blank lines; just pass them through. When 57 | this option is active, blank lines will not trigger an update of elapsed 58 | time. Therefore, if a lot of blank lines appear, the prior timestamp will 59 | display the total time between that line and the next non-blank line 60 | (if the type is elapsed-time was selected). 61 | 62 | --real-time= [default: 500] 63 | -r [non-tty default: false] 64 | 65 | Time increment to use when updating timestamp for the current line, in 66 | milliseconds. Pass `false` to this option to disable realtime entirely, 67 | if you need an extra performance boost or you find it distracting. When 68 | realtime is disabled, the log will always appear one line "behind" the 69 | original piped output, since it can't display the line until it's 70 | finished timing it. 71 | 72 | --high=seconds 73 | -h seconds 74 | 75 | High threshold. If the elapsed time for a line is equal to or higher than 76 | this value in seconds, then the timestamp will be colored bright red. 77 | This works for all timestamp types, including elapsed-total and absolute, 78 | where the elapsed line time is not actually displayed. 79 | 80 | --medium=seconds 81 | -m seconds 82 | 83 | Medium threshold. Works just like the high threshold described above, but 84 | colors the timestamp bright yellow instead. Can be used in conjunction 85 | with a high threshold for three levels. 86 | 87 | ### Notes 88 | - If a `high` and/or a `medium` threshold are specified, then all timestamps not 89 | meeting that threshold will be colored bright green. 90 | - If you pipe the output of `gnomon` into another command or a file (that is, 91 | not a tty) then the `real-time` option will be disabled by default and each line 92 | will appear only after it has been timed. You can force realtime by sending a 93 | `--real-time=` argument explicitly, but the ANSI codes would probably 94 | interfere with whatever you were trying to do. The sane default is to omit fancy 95 | stuff, like colors and escape sequences, when logging text directly to a file. 96 | 97 | ## Installation 98 | 99 | with npm do: 100 | ``` 101 | npm install -g gnomon 102 | ``` 103 | 104 | ## License 105 | 106 | gnomon uses the MIT license. 107 | 108 | -------------------------------------------------------------------------------- /doc/gnomon.1: -------------------------------------------------------------------------------- 1 | .TH "GNOMON" "" "November 2016" "" "" 2 | .SH "NAME" 3 | \fBgnomon\fR 4 | .P 5 | A command line utility, a bit like 6 | moreutils's \fBts\fR \fIhttps://joeyh\.name/code/moreutils/\fR, to prepend timestamp 7 | information to the standard output of another command\. Useful for long\-running 8 | processes where you'd like a historical record of what's taking so long\. 9 | .SH Example 10 | .P 11 | basic \fIhttps://cloud\.githubusercontent\.com/assets/1643758/13685018/17b4f76c\-e6d4\-11e5\-8838\-40fa52346ae8\.gif\fR 12 | .P 13 | Piping anything to \fBgnomon\fP will prepend a timestamp to each line, indicating 14 | how long that line was the last line in the buffer\-\-that is, how long it took 15 | the next line to appear\. By default, \fBgnomon\fP will display the seconds elapsed 16 | between each line, but that is configurable\. 17 | .P 18 | You can display total time elapsed since the process began: 19 | total \fIhttps://cloud\.githubusercontent\.com/assets/1643758/13685020/199b78b2\-e6d4\-11e5\-9083\-05de6c52cc60\.gif\fR 20 | .P 21 | You can display an absolute timestamp: 22 | absolute \fIhttps://cloud\.githubusercontent\.com/assets/1643758/13685022/1ab3a5bc\-e6d4\-11e5\-9ccf\-3a5c68f9ea0c\.gif\fR 23 | .P 24 | You can also use the \fB\-\-high\fP and/or \fB\-\-medium\fP options to specify a length 25 | threshold in seconds, over which \fBgnomon\fP will highlight the timestamp in red 26 | or yellow\. And you can do a few other things, too\. 27 | .P 28 | fancy \fIhttps://cloud\.githubusercontent\.com/assets/1643758/13685025/1bbf6ad6\-e6d4\-11e5\-8806\-e90a6e852bf7\.gif\fR 29 | .P 30 | If the realtime timestamp updating is distracting or incompatible with your 31 | terminal, it can be disabled: 32 | .P 33 | norealtime \fIhttps://cloud\.githubusercontent\.com/assets/1643758/13685027/1cfd823e\-e6d4\-11e5\-9f90\-c047d67a35e0\.gif\fR 34 | .SH Options 35 | .P 36 | .RS 2 37 | .nf 38 | \-\-type= [default: elapsed\-line] 39 | \-t 40 | 41 | Type of timestamp to display\. 42 | elapsed\-line: Number of seconds that displayed line was the last line\. 43 | elapsed\-total: Number of seconds since the start of the process\. 44 | absolute: An absolute timestamp in UTC\. 45 | 46 | \-\-format="format" [default: "H:i:s\.u O"] 47 | \-f "format" 48 | 49 | Format the absolute timestamp, using PHP date format strings\. If the type 50 | is elapsed\-line or elapsed\-total, this option is ignored\. 51 | 52 | \-\-ignore\-blank [default: false] 53 | \-\-quiet 54 | \-q 55 | \-i 56 | 57 | Do not prepend a timestamp to blank lines; just pass them through\. When 58 | this option is active, blank lines will not trigger an update of elapsed 59 | time\. Therefore, if a lot of blank lines appear, the prior timestamp will 60 | display the total time between that line and the next non\-blank line 61 | (if the type is elapsed\-time was selected)\. 62 | 63 | \-\-real\-time= [default: 500] 64 | \-r [non\-tty default: false] 65 | 66 | Time increment to use when updating timestamp for the current line, in 67 | milliseconds\. Pass `false` to this option to disable realtime entirely, 68 | if you need an extra performance boost or you find it distracting\. When 69 | realtime is disabled, the log will always appear one line "behind" the 70 | original piped output, since it can't display the line until it's 71 | finished timing it\. 72 | 73 | \-\-high=seconds 74 | \-h seconds 75 | 76 | High threshold\. If the elapsed time for a line is equal to or higher than 77 | this value in seconds, then the timestamp will be colored bright red\. 78 | This works for all timestamp types, including elapsed\-total and absolute, 79 | where the elapsed line time is not actually displayed\. 80 | 81 | \-\-medium=seconds 82 | \-m seconds 83 | 84 | Medium threshold\. Works just like the high threshold described above, but 85 | colors the timestamp bright yellow instead\. Can be used in conjunction 86 | with a high threshold for three levels\. 87 | .fi 88 | .RE 89 | .SS Notes 90 | .RS 0 91 | .IP \(bu 2 92 | If a \fBhigh\fP and/or a \fBmedium\fP threshold are specified, then all timestamps not 93 | meeting that threshold will be colored bright green\. 94 | .IP \(bu 2 95 | If you pipe the output of \fBgnomon\fP into another command or a file (that is, 96 | not a tty) then the \fBreal\-time\fP option will be disabled by default and each line 97 | will appear only after it has been timed\. You can force realtime by sending a 98 | \fB\-\-real\-time=\fP argument explicitly, but the ANSI codes would probably 99 | interfere with whatever you were trying to do\. The sane default is to omit fancy 100 | stuff, like colors and escape sequences, when logging text directly to a file\. 101 | 102 | .RE 103 | .SH Installation 104 | .P 105 | with npm do: 106 | .P 107 | .RS 2 108 | .nf 109 | npm install \-g gnomon 110 | .fi 111 | .RE 112 | .SH License 113 | .P 114 | gnomon uses the MIT license\. 115 | 116 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var repeating = require('repeating'); 2 | var dateutil = require('dateutil'); 3 | var chalk = require('chalk'); 4 | var through = require('through'); 5 | 6 | var termwidth = require('window-size').width; 7 | var newline = require('os').EOL; 8 | var ansi = { 9 | prefix: '\x1b[', 10 | up: 'A', 11 | clearLine: '0G' 12 | }; 13 | 14 | var nanoPow = Math.pow(10,9); 15 | function durationToSeconds(dur) { 16 | return dur[0] + dur[1] / nanoPow; 17 | } 18 | 19 | var space = ' '; 20 | var sAbbr = 's'; 21 | var places = 4; 22 | var maxDisplaySecondsDigits = 4; 23 | function formatDuration(dur) { 24 | return durationToSeconds(dur).toFixed(places) + sAbbr; 25 | } 26 | 27 | var start = process.hrtime(); 28 | var elapsedLine = [0,0]; 29 | var elapsedTotal = [0,0]; 30 | var lap = start; 31 | var last = start; 32 | function tick(resetLine) { 33 | var now = process.hrtime(); 34 | if (resetLine) { 35 | lap = now; 36 | elapsedLine = process.hrtime(last); 37 | } else { 38 | elapsedLine = process.hrtime(lap); 39 | } 40 | elapsedTotal = process.hrtime(start); 41 | last = process.hrtime(); 42 | } 43 | 44 | var nullString = ''; 45 | function padFor(s, max) { 46 | var l = s.length; 47 | return l < max ? repeating(space, max - l) : nullString; 48 | } 49 | 50 | var bar = space + chalk.reset.inverse(' ') + space; 51 | var totalLabel = 'Total'; 52 | 53 | module.exports = function(opts) { 54 | opts = opts || {}; 55 | var fmt = opts.format || 'H:i:s.u O'; 56 | var type = opts.type || 'elapsed-line'; 57 | 58 | var stampers = { 59 | 'elapsed-line': function() { 60 | return formatDuration(elapsedLine); 61 | }, 62 | 'elapsed-total': function() { 63 | return formatDuration(elapsedTotal); 64 | }, 65 | 'absolute': function() { 66 | return dateutil.format(new Date(), fmt); 67 | } 68 | }; 69 | 70 | var maxDurLength, blank, maxLineLength; 71 | if (type === 'absolute') { 72 | maxDurLength = stampers.absolute().length; 73 | } else { 74 | maxDurLength = maxDisplaySecondsDigits + places + 2; // dot and 's' 75 | } 76 | maxDurLength = Math.max(maxDurLength, totalLabel.length); 77 | blank = repeating(space, maxDurLength) + bar; 78 | maxLineLength = termwidth - chalk.stripColor(blank).length; 79 | 80 | function stampLine(stamp, line) { 81 | var len = line ? chalk.stripColor(line).length : 0; 82 | if (len > maxLineLength) { 83 | return stamp + line.slice(0, maxLineLength) + newline + 84 | stampLine(blank, line.slice(maxLineLength)); 85 | } 86 | return stamp + line + newline; 87 | } 88 | 89 | var colorStamp; 90 | var high = opts.high; 91 | var medium = opts.medium; 92 | if (medium && high) { 93 | colorStamp = function(stamp) { 94 | var seconds = durationToSeconds(elapsedLine); 95 | if (seconds >= high) { 96 | return chalk.reset.red(stamp); 97 | } 98 | if (seconds >= medium) { 99 | return chalk.reset.yellow(stamp); 100 | } 101 | return chalk.reset.green(stamp); 102 | }; 103 | } else if (medium) { 104 | colorStamp = function(stamp) { 105 | if (durationToSeconds(elapsedLine) >= medium) { 106 | return chalk.reset.yellow(stamp); 107 | } 108 | return chalk.reset.green(stamp); 109 | } 110 | } else if (high) { 111 | colorStamp = function(stamp) { 112 | if (durationToSeconds(elapsedLine) >= high) { 113 | return chalk.reset.red(stamp); 114 | } 115 | return chalk.reset.green(stamp); 116 | } 117 | } else { 118 | colorStamp = function(stamp) { 119 | return chalk.reset(stamp); 120 | } 121 | } 122 | 123 | var createStamp = stampers[type]; 124 | 125 | function createFormattedStamp(text, value) { 126 | var text = createStamp(); 127 | return padFor(text, maxDurLength) + colorStamp(text, value) + bar; 128 | } 129 | 130 | var lastLine = false; 131 | var overwrite; 132 | var autoUpdate; 133 | function scheduleAutoUpdate(stream) { 134 | autoUpdate = setTimeout(function() { 135 | tick(false); 136 | stream.queue( 137 | overwrite + stampLine(createFormattedStamp(), lastLine) 138 | ); 139 | scheduleAutoUpdate(stream); 140 | }, opts.realTime); 141 | } 142 | function setLastLine(line) { 143 | lastLine = line; 144 | overwrite = ansi.prefix + (~~(lastLine.length / maxLineLength) + 1) + 145 | ansi.up + ansi.prefix + ansi.clearLine; 146 | } 147 | 148 | var feed; 149 | if (opts.realTime) { 150 | feed = function(stream, line, last) { 151 | feed = function(stream, line, last) { 152 | tick(false); 153 | stream.queue( 154 | overwrite + stampLine(createFormattedStamp(), lastLine) 155 | ); 156 | tick(true); 157 | if (autoUpdate) clearTimeout(autoUpdate); 158 | scheduleAutoUpdate(stream); 159 | setLastLine(line); 160 | if (!last) stream.queue(stampLine(blank, line)); 161 | }; 162 | stream.queue(stampLine(blank, line)); 163 | setLastLine(line); 164 | scheduleAutoUpdate(stream); 165 | } 166 | } else { 167 | feed = function(stream, line, last) { 168 | feed = function(stream, line, last) { 169 | tick(true); 170 | if (!last) 171 | stream.queue(stampLine(createFormattedStamp(), lastLine)); 172 | lastLine = line; 173 | }; 174 | lastLine = line; 175 | } 176 | } 177 | 178 | var onData; 179 | if (opts.ignoreBlank) { 180 | onData = function(line) { if (line) feed(this, line); } 181 | } else { 182 | onData = function(line) { feed(this, line); } 183 | } 184 | 185 | return through(onData, function end () { 186 | feed(this, '', true); 187 | this.queue( 188 | stampLine(blank, '') + 189 | padFor(totalLabel, maxDurLength) + totalLabel + bar + 190 | formatDuration(elapsedTotal) + newline 191 | ); 192 | if (autoUpdate) clearTimeout(autoUpdate); 193 | }); 194 | 195 | }; 196 | --------------------------------------------------------------------------------