├── test ├── fixtures │ └── null.js ├── test-express3.js ├── test-express4.js ├── test-agent-has-stop-function.js ├── test-agent-has-profile-function.js ├── test-agent-exits-on-server-shutdown.js ├── test-heap-profiler-start.js ├── strongloop.json ├── test-cpu-profiler-null-deref.js ├── license.js ├── test-null-module.js ├── test-config-appname.js ├── test-metrics-watchdog.js ├── dyninst-metrics-example.js ├── test-timer.js ├── test-linux-time.js ├── dyninst-metrics-example-patch.json ├── test-cpu-profiler-start.js ├── test-addon-missing.js ├── test-no-watchdog.js ├── test-cpu-metrics-in-range.js ├── test-cpu-profiler-stop.js ├── test-redis-non-array-arg.js ├── express3 │ └── server.js ├── express4 │ └── server.js ├── test-cpuinfo-when-no-fds ├── test-addon-heapdiff-eval.js ├── test-heap-profiler-stop.js ├── test-addon-heapdiff-long.js ├── test-metrics-poll.js ├── test-agent-metrics-cpu-errors.js ├── test-loop-statistics.js ├── test-agent-profile-reports-version.js ├── dyninst-target.js ├── test-config.js ├── test-config-logger.js ├── test-agent-use-reports-version.js ├── test-agent-configure.js ├── test-cpu-profiler-path.js ├── test-configurable-interval.js ├── test-agent-use-tiers.js ├── test-counts.js ├── test-use-custom-stats.js ├── test-top-functions.js ├── test-addon-counters.js ├── test-agent-intercepts-http-upgrade-event.js ├── test-metrics.js ├── test-probe-mysql.js ├── test-strong-express-metrics.js ├── test-agent-metrics-heapdiff.js ├── test-licenses.js ├── test-watchdog-activation-count.js ├── test-addon-heapdiff.js ├── test-probe-juggler-promise.js ├── test-custom-stats.js ├── test-dyninst-metrics.js ├── test-probe-memcache.js ├── test-agent-metrics-cpu.js ├── test-probe-memcached.js ├── test-probe-redis.js ├── helpers.js ├── test-graph-helper.js ├── test-probe-mongodb.js ├── test-agent-strong-trace.js ├── test-event-loop-watchdog.js ├── test-call-counts.js ├── test-agent-use-requires-license.js ├── test-probe-juggler.js ├── test-agent-dyninst-patch.js ├── test-agent-metrics.js ├── test-probe-postgresql.js ├── test-profile-start-stop.js └── test-leveldown.js ├── .gitignore ├── lib ├── main.js ├── probes │ ├── https.js │ ├── strong-oracle.js │ ├── sl-mq.js │ ├── strong-cluster-control.js │ ├── oracle.js │ ├── strong-express-metrics.js │ ├── strong-mq.js │ ├── express.js │ ├── axon.js │ ├── riak-js.js │ ├── loopback-datasource-juggler.js │ ├── mysql.js │ ├── memcached.js │ ├── oracledb.js │ ├── pg.js │ ├── redis.js │ ├── mongodb.js │ ├── memcache.js │ └── http.js ├── samples.js ├── license.js ├── addon.js ├── counts.js ├── metrics.js ├── timer.js ├── module-detector.js ├── loop.js ├── tiers.js ├── graph-helper.js ├── debug.js ├── profilers │ ├── memory.js │ └── cpu.js ├── custom-stats.js ├── top-functions.js ├── info.js ├── config.js ├── json.js ├── wrapping-probes │ └── leveldown.js ├── proc.js └── dyninst-metrics.js ├── .jshintignore ├── docs.json ├── .npmignore ├── LICENSE.md ├── Makefile ├── src ├── check-imports.sh ├── platform-posix.h ├── extras.h ├── platform-win32.h ├── Makefile ├── dyninst.h ├── uvmon.h ├── gcinfo.h ├── util-inl.h ├── util.h ├── queue.h └── strong-agent.cc ├── samples ├── app.js └── di.js ├── NOTICE ├── binding.gyp ├── .clang-format └── package.json /test/fixtures/null.js: -------------------------------------------------------------------------------- 1 | module.exports = null; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | node_modules 3 | coverage.html 4 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./agent'); 4 | -------------------------------------------------------------------------------- /test/test-express3.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./express3/server'); 4 | -------------------------------------------------------------------------------- /test/test-express4.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./express4/server'); 4 | -------------------------------------------------------------------------------- /lib/probes/https.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./http'); 4 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/express3/node_modules 3 | test/express4/node_modules 4 | -------------------------------------------------------------------------------- /lib/probes/strong-oracle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./oracle'); 4 | -------------------------------------------------------------------------------- /lib/probes/sl-mq.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports = module.exports = require('./strong-mq'); 4 | -------------------------------------------------------------------------------- /docs.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "strong-agent", 3 | "content": [ 4 | "README.md" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /test/test-agent-has-stop-function.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../'); 4 | var assert = require('assert'); 5 | assert.equal(typeof(agent.stop), 'function'); 6 | -------------------------------------------------------------------------------- /test/test-agent-has-profile-function.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../'); 4 | var assert = require('assert'); 5 | assert.equal(typeof(agent.profile), 'function'); 6 | -------------------------------------------------------------------------------- /lib/probes/strong-cluster-control.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../agent'); 4 | 5 | module.exports = function(control) { agent.prepareClusterControls(control); }; 6 | -------------------------------------------------------------------------------- /test/test-agent-exits-on-server-shutdown.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('../').profile('deadbeef', 'deadbeef', {quiet: true}); 4 | require('net').createServer().listen(0, function() { this.close(); }); 5 | -------------------------------------------------------------------------------- /lib/samples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Timer = require('./timer'); 4 | 5 | module.exports.timer = function(scope, command) { 6 | var t = new Timer(scope, command); 7 | t.start(); 8 | return t; 9 | } 10 | -------------------------------------------------------------------------------- /lib/license.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var License = require('strong-license'); 4 | 5 | module.exports = load; 6 | 7 | function load(key) { 8 | return new License(key, 'c374fa24098c7eb64a73dc05c428be40'); 9 | } 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.sublime-project 2 | *.sublime-workspace 3 | *.tgz 4 | .npmignore 5 | /build/ 6 | /node_modules/ 7 | bin/ 8 | coverage.html 9 | docs.json 10 | src/Makefile 11 | src/check-imports.sh 12 | src/cpplint.py 13 | tags 14 | test/ 15 | -------------------------------------------------------------------------------- /lib/probes/oracle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var oracleCore = require('./oracledb'); 4 | 5 | module.exports = oracle; 6 | 7 | function oracle(oracle) { 8 | oracleCore.oracleBase(oracle, 'connectSync', 'connect', 'Oracle'); 9 | } 10 | -------------------------------------------------------------------------------- /lib/probes/strong-express-metrics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../../'); 4 | module.exports = function(xstats) { 5 | xstats.onRecord(function(record) { 6 | agent.internal.emit('express:usage-record', record); 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /test/test-heap-profiler-start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../'); 4 | agent.profile('deadbeef', 'deadbeef', {quiet: true}); 5 | 6 | var profiler = require('../lib/profilers/memory'); 7 | profiler.init(agent, 100); 8 | profiler.start(); 9 | -------------------------------------------------------------------------------- /lib/addon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | try { 4 | module.exports = require('../build/Release/strong-agent'); 5 | } catch (e) { 6 | try { 7 | module.exports = require('../build/Debug/strong-agent'); 8 | } catch (e) { 9 | module.exports = null; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/strongloop.json: -------------------------------------------------------------------------------- 1 | { 2 | "": [ 3 | "Dummy strongloop.json for testing purposes. This file is require()'d by", 4 | "some of the tests and then monkey-patched with the actual configuration.", 5 | "See test-agent-https.js and test-agent-proxy.js for examples." 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/test-cpu-profiler-null-deref.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var profiler = require('../lib/profilers/cpu'); 5 | 6 | profiler.start(); 7 | assert.notEqual(profiler.stop(), undefined); 8 | assert.equal(profiler.stop(), undefined); // Should not segfault. 9 | -------------------------------------------------------------------------------- /test/license.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (process.argv[2]) { 4 | console.log('Decoded license contents: ', 5 | require('../lib/license')(process.argv[2])); 6 | } else { 7 | console.log('STRONGLOOP_LICENSE="%s"', 8 | require('./helpers').longTestLicense()); 9 | } 10 | -------------------------------------------------------------------------------- /lib/counts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var counts = null; 4 | 5 | exports.sample = function(code) { 6 | counts = counts || {}; 7 | counts[code] = 1 + (counts[code] | 0); 8 | }; 9 | 10 | exports.poll = function() { 11 | var snapshot = counts; 12 | counts = null; 13 | return snapshot; 14 | }; 15 | -------------------------------------------------------------------------------- /test/test-null-module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var agent = require('../').profile('key', 'name'); 5 | 6 | assert.doesNotThrow(function() { 7 | var nullModule = require('./fixtures/null'); 8 | assert.equal(nullModule, null); 9 | }, /.*/, 'Should not die when module.exports === null'); 10 | -------------------------------------------------------------------------------- /test/test-config-appname.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // test array form of profile()'s appName argument 4 | var assert = require('assert'); 5 | var defaults = require('../lib/config'); 6 | var config = defaults.configure(null, ['june', 'leonie'], {}, {}); 7 | assert.equal(config.appName, 'june'); 8 | assert.equal(config.hostname, 'leonie'); 9 | -------------------------------------------------------------------------------- /test/test-metrics-watchdog.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.STRONGLOOP_LICENSE = ''; 4 | process.env.HOME = '/no/such/home/dir'; 5 | var agent = require('../'); 6 | var assert = require('assert'); 7 | 8 | agent.profile('some app', 'some key'); 9 | assert.throws(function() { agent.metrics.startCpuProfiling(42) }, /license/i); 10 | -------------------------------------------------------------------------------- /test/dyninst-metrics-example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // save as `dyninst-metrics-example.js` 4 | var http = require('http'); 5 | 6 | http.createServer(request).listen(process.env.PORT || 3000); 7 | 8 | function request(req, res) { 9 | setTimeout(function() { // line 7 10 | res.end('OK\n'); // line 8 11 | }, Math.random() > 0.1 ? 10 : 100); 12 | } 13 | -------------------------------------------------------------------------------- /test/test-timer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Timer = require('../lib/timer'); 4 | var assert = require('assert'); 5 | 6 | // Timer module should be clobber-safe. 7 | process.hrtime = Math.round = function() { throw 'BAM' }; 8 | 9 | var timer = new Timer; 10 | 11 | timer.start(); 12 | setTimeout(function() { 13 | timer.end(); 14 | assert(timer.ms > 0); 15 | }); 16 | -------------------------------------------------------------------------------- /test/test-linux-time.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (process.platform !== 'linux') { 4 | console.log('1..0 # SKIP Linux-only feature'); 5 | return; 6 | } 7 | 8 | var agent = require('../'); 9 | var assert = require('assert'); 10 | 11 | var t = agent.internal.lrtime(); 12 | assert(Array.isArray(t)); 13 | assert(t[0] > 0); 14 | assert(t[1] >= 0); 15 | assert(t[1] <= 999999999); 16 | -------------------------------------------------------------------------------- /test/dyninst-metrics-example-patch.json: -------------------------------------------------------------------------------- 1 | { 2 | "Dyninst-metrics-example.js": [ 3 | { "type": "increment", "line": 7, "metric": "get.delay" }, 4 | { "type": "decrement", "line": 8, "metric": "get.delay" }, 5 | { "type": "timer-start", "line": 7, "metric": "get.delay", "context": "res" }, 6 | { "type": "timer-stop", "line": 8, "metric": "get.delay", "context": "res" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /test/test-cpu-profiler-start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var cpu = require('../lib/profilers/cpu'); 4 | cpu.start(); 5 | 6 | // Stop the profiler before exiting. It's implemented as a thread that sends 7 | // SIGPROF signals to the main thread. Node's program epilogue is not equipped 8 | // to deal with such signals and will randomly crash with a SIGILL or SIGSEGV. 9 | setImmediate(cpu.stop); 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Licensed Materials - Property of IBM 2 | IBM StrongLoop Software 3 | Copyright IBM Corp. 2016. All Rights Reserved. 4 | US Government Users Restricted Rights - Use, duplication or disclosure 5 | restricted by GSA ADP Schedule Contract with IBM Corp. 6 | 7 | See full text of IBM International Program License Agreement (IPLA) 8 | http://www-03.ibm.com/software/sla/sladb.nsf/pdf/ipla/$file/ipla.pdf 9 | -------------------------------------------------------------------------------- /test/test-addon-missing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var addon = require('../lib/addon'); 4 | require.cache[require.resolve('../lib/addon')].exports = null; 5 | 6 | var agent = require('../'); 7 | var assert = require('assert'); 8 | 9 | agent.start(); 10 | agent.on('poll::start', function() { numevents += 1; }); 11 | setImmediate(agent.poll.bind(agent)); 12 | 13 | var numevents = 0; 14 | process.on('exit', function() { assert.equal(numevents, 1); }); 15 | -------------------------------------------------------------------------------- /test/test-no-watchdog.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var addon = require('../lib/addon'); 4 | var agent = require('../'); 5 | var assert = require('assert'); 6 | var profiler = require('../lib/profilers/cpu'); 7 | 8 | if (process.platform === 'linux') { 9 | addon.startCpuProfiling = function(timeout) { if (timeout) return 'BAM'; }; 10 | } 11 | 12 | agent.profile('some app', 'some key'); 13 | profiler.start(); 14 | profiler.stop(); 15 | assert.throws(function() { profiler.start(1); }); 16 | -------------------------------------------------------------------------------- /lib/metrics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var metrics = null; 4 | 5 | exports.poll = function() { 6 | var snapshot = metrics; 7 | metrics = null; 8 | return snapshot; 9 | }; 10 | 11 | exports.add = function(name, value) { 12 | if (metrics == null) { 13 | // Users can add their own metrics; we need a prototype-less dictionary 14 | // to store them or a metric like '__proto__' would cause issues. 15 | metrics = Object.create(null); 16 | } 17 | metrics[name] = value; 18 | }; 19 | -------------------------------------------------------------------------------- /test/test-cpu-metrics-in-range.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var cpuinfo = require('../lib/cpuinfo'); 5 | 6 | // cpuutil() takes at least a second to generate data. 7 | setTimeout(check, 1000); 8 | setTimeout(check, 2000); 9 | 10 | function check() { 11 | cpuinfo.cpuutil(function(p, u, s) { 12 | console.log([p, u, s]); 13 | assert(p >= 0); 14 | assert(p <= 100); 15 | assert(u >= 0); 16 | assert(u <= 100); 17 | assert(s >= 0); 18 | assert(s <= 100); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /test/test-cpu-profiler-stop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var profiler = require('../lib/profilers/cpu'); 5 | 6 | profiler.start(); 7 | for (var i = 0, j = 1; i < 1e6; i++) { 8 | j = (j + i) * (j + i); 9 | } 10 | // Stop V8 from optimizing away the loop. 11 | global.ASSIGNMENT_FOR_SIDE_EFFECT = j; 12 | 13 | var data = profiler.stop(); 14 | assert.equal(typeof(data), 'object'); 15 | assert.equal(data === null, false); 16 | assert.equal(Array.isArray(data.children), true); 17 | assert.equal(typeof(data.functionName), 'string'); 18 | -------------------------------------------------------------------------------- /lib/timer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | // Store a reference in case the user clobbers the process or Math object. 5 | var hrtime = process.hrtime; 6 | var round = Math.round; 7 | 8 | function Timer(scope, command) { 9 | this.scope = scope; 10 | this.command = command; 11 | this._start = undefined; 12 | this.ms = 0; 13 | } 14 | 15 | Timer.prototype.start = function() { this._start = hrtime(); } 16 | 17 | Timer.prototype.end = function() { 18 | var t = hrtime(this._start); 19 | this.ms = t[0] * 1000 + t[1] / 1e6; 20 | }; 21 | 22 | module.exports = Timer; 23 | -------------------------------------------------------------------------------- /test/test-redis-non-array-arg.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('../').use(function() {}); 4 | 5 | var expectedError = Error('expected error'); 6 | 7 | function RedisClient() {} 8 | RedisClient.prototype.send_command = function(fst, snd) { 9 | if (!Array.isArray(snd)) throw expectedError; 10 | } 11 | 12 | var redis = { RedisClient: RedisClient }; 13 | require.cache['redis'] = { exports: redis }; 14 | require('../lib/probes/redis')(redis); 15 | 16 | try { 17 | (new RedisClient).send_command(); 18 | } catch (e) { 19 | if (e === expectedError) return; 20 | throw e; 21 | } 22 | 23 | throw Error('expected exception missing'); 24 | -------------------------------------------------------------------------------- /test/express3/server.js: -------------------------------------------------------------------------------- 1 | var agent = require('../../'); 2 | agent.profile('deadbeef', 'deadbeef', {quiet: true}); 3 | 4 | // Trap lib/proxy's call to agent.error() when the hook errors 5 | agent.error = function(err) { throw err; }; 6 | 7 | var app = require('express')(); 8 | var http = require('http'); 9 | 10 | app.get('/get', function(req, res) { 11 | console.log('at /get:', req.route); 12 | res.end('ok\n'); 13 | }); 14 | 15 | app.listen(0, function() { 16 | var server = this; 17 | http.get({port: server.address().port, path: '/get'}, function(res) { 18 | res.pipe(process.stdout); 19 | server.close(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/express4/server.js: -------------------------------------------------------------------------------- 1 | var agent = require('../../'); 2 | agent.profile('deadbeef', 'deadbeef', {quiet: true}); 3 | 4 | // Trap lib/proxy's call to agent.error() when the hook errors 5 | agent.error = function(err) { throw err; }; 6 | 7 | var app = require('express')(); 8 | var http = require('http'); 9 | 10 | app.get('/get', function(req, res) { 11 | console.log('at /get:', req.route); 12 | res.end('ok\n'); 13 | }); 14 | 15 | app.listen(0, function() { 16 | var server = this; 17 | http.get({port: server.address().port, path: '/get'}, function(res) { 18 | res.pipe(process.stdout); 19 | server.close(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /lib/module-detector.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | 5 | module.exports.detectModule = detectModule; 6 | function detectModule(modulename, hasRun) { 7 | var location; 8 | 9 | try { 10 | location = require.resolve(modulename); 11 | } catch (err) { 12 | } 13 | 14 | if (location) { 15 | location = path.dirname(location); 16 | var version = require(path.resolve(location, 'package.json')).version; 17 | return version; 18 | } else if (!hasRun) { 19 | return detectModule(path.resolve(process.cwd(), 'node_modules', modulename), 20 | true); 21 | } 22 | return null; 23 | } 24 | -------------------------------------------------------------------------------- /test/test-cpuinfo-when-no-fds: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.SL_ENV = 'dev'; 4 | 5 | var assert = require('assert'); 6 | var cpuinfo = require('../lib/cpuinfo'); 7 | var fs = require('fs'); 8 | 9 | if (process.platform === 'win32') { 10 | console.log('Skipping test, no RLIMIT_NOFILE on Windows.'); 11 | return; 12 | } 13 | 14 | for (;;) { 15 | try { 16 | fs.openSync(__filename, 'r'); 17 | } catch (e) { 18 | assert(e.code === 'EMFILE' || e.code === 'ENFILE'); 19 | break; 20 | } 21 | } 22 | 23 | // The callback may or may not get called but cpuutil() should not bring down 24 | // the process. 25 | cpuinfo.cpuutil(function() {}); 26 | -------------------------------------------------------------------------------- /test/test-addon-heapdiff-eval.js: -------------------------------------------------------------------------------- 1 | // The presence of eval() in a script upsets the heap snapshot generator 2 | // in v0.10. It's not that the nodes aren't there, it's that they get 3 | // represented in a different way. In particular, it's crucial that 4 | // v8::HeapGraphNode::kHidden nodes are followed. 5 | 6 | 'use strict'; 7 | 8 | var assert = require('assert'); 9 | var addon = require('../lib/addon'); 10 | assert(addon, 'Native add-on not found.'); 11 | 12 | addon.startHeapDiff(); 13 | var t = Array(1 << 16).join('.').split('').map(function() { return new Array }); 14 | var changes = addon.stopHeapDiff(true); 15 | assert(changes.length > 0); 16 | eval(''); 17 | -------------------------------------------------------------------------------- /test/test-heap-profiler-stop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../'); 4 | agent.profile('deadbeef', 'deadbeef', {quiet: true}); 5 | 6 | var assert = require('assert'); 7 | var profiler = require('../lib/profilers/memory'); 8 | 9 | profiler.init(agent); 10 | profiler.start(); 11 | for (var retain = [], i = 0; i < 1e3; i += 1) { 12 | retain.push([i]); 13 | } 14 | var state = profiler.poll(); 15 | profiler.stop(); 16 | assert(state != null); 17 | assert(state.length > 0); 18 | state.forEach(function(elm) { 19 | assert.equal(typeof(elm.type), 'string'); 20 | assert.equal(typeof(elm.size), 'number'); 21 | assert.equal(typeof(elm.total), 'number'); 22 | }); 23 | -------------------------------------------------------------------------------- /lib/loop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var addon = require('./addon'); 4 | 5 | exports.poll = function() { 6 | if (!addon) { 7 | return; 8 | } 9 | var statistics = addon.eventLoopStatistics; 10 | var min = statistics[0]; 11 | var max = statistics[1]; 12 | var num = statistics[2]; 13 | var sum = statistics[3]; 14 | 15 | statistics[0] = 0; 16 | statistics[1] = 0; 17 | statistics[2] = 0; 18 | statistics[3] = 0; 19 | 20 | if (num === 0) { 21 | return null; 22 | } 23 | 24 | // XXX(bnoordhuis) Backwards compatible field names. 25 | // XXX(bnoordhuis) fastest_ms is new though. 26 | return {count: num, fastest_ms: min, slowest_ms: max, sum_ms: sum}; 27 | }; 28 | -------------------------------------------------------------------------------- /test/test-addon-heapdiff-long.js: -------------------------------------------------------------------------------- 1 | // This test is mostly a reminder to self that while it's possible to create 2 | // variable or function names > 1024 characters, the V8 heap profiler will 3 | // only track the first 1024. 4 | 5 | 'use strict'; 6 | 7 | var assert = require('assert'); 8 | var addon = require('../lib/addon'); 9 | 10 | var s = Array(1 << 16).join('A'); 11 | var f = eval('function ' + s + '() {} ' + s); 12 | assert.equal(f.name, s); 13 | 14 | addon.startHeapDiff(); 15 | global.retain = new f; 16 | 17 | var changes = addon.stopHeapDiff(true).filter( 18 | function(e) { return e.type.slice(0, 7) === 'AAAAAAA'; }); 19 | assert.equal(changes.length, 1); 20 | assert.equal(changes[0].type, Array(1024 + 1).join('A')); 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014, StrongLoop Inc. 2 | # 3 | # This software is covered by the StrongLoop License. See StrongLoop-LICENSE 4 | # in the top-level directory or visit http://strongloop.com/license. 5 | 6 | PREFIX ?= $(dir $(lastword $(MAKEFILE_LIST))) 7 | 8 | CLANG_FORMAT ?= clang-format 9 | 10 | SOURCES := $(wildcard lib/*.js lib/*/*.js test/test-*.js test/*/*.js) 11 | SOURCES := $(SOURCES:%=$(PREFIX)%) 12 | 13 | .PHONY: all clang-format 14 | 15 | all: 16 | @echo "Not doing any destructive updates." 17 | @echo "Did you mean to run \`make clang-format\`?" 18 | 19 | # Note that clang-format needs to be r217311 or newer, clang 3.5 won't cut it. 20 | clang-format: 21 | $(CLANG_FORMAT) -i $(SOURCES) 22 | $(MAKE) -C src $@ 23 | -------------------------------------------------------------------------------- /lib/tiers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Tiers() { 4 | this.stats = null; 5 | } 6 | 7 | Tiers.prototype.sample = function(code, time) { 8 | var ms = time.ms; 9 | var stat = null; 10 | if (this.stats) { 11 | stat = this.stats[code]; 12 | } else { 13 | this.stats = {}; 14 | } 15 | if (stat == null) { 16 | this.stats[code] = {num: 1, mean: ms, min: ms, max: ms, sum: ms}; 17 | } else { 18 | if (ms < stat.min) stat.min = ms; 19 | if (ms > stat.max) stat.max = ms; 20 | stat.num += 1; 21 | stat.sum += ms; 22 | stat.mean = stat.sum / stat.num; 23 | } 24 | }; 25 | 26 | Tiers.prototype.poll = function() { 27 | var snapshot = this.stats; 28 | this.stats = null; 29 | return snapshot; 30 | }; 31 | 32 | module.exports = new Tiers; 33 | -------------------------------------------------------------------------------- /lib/graph-helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 5 | startNode: function startNode(name, q, agent) { 6 | if (agent.graph == undefined) return; 7 | 8 | var nodeId = agent.graph.nodes.length; 9 | var node = {name: name, q: q, start: Date.now(), value: 1}; 10 | agent.graph.nodes.push(node); 11 | 12 | var link = {source: agent.currentNode, target: nodeId, value: 1}; 13 | agent.graph.links.push(link); 14 | 15 | var prevNode = agent.currentNode; 16 | agent.currentNode = nodeId; 17 | 18 | return {node: node, link: link, prevNode: prevNode}; 19 | }, 20 | 21 | updateTimes: function updateTimes(graphNode, time) { 22 | if (graphNode == undefined) return; 23 | graphNode.node.value = time.ms || 1; 24 | graphNode.link.value = time.ms || 1; 25 | } 26 | 27 | }; 28 | -------------------------------------------------------------------------------- /lib/probes/strong-mq.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // FIXME(bnoordhuis) Unclear if require('../agent') is for side effects. 4 | var agent = require('../agent'); 5 | var proxy = require('../proxy'); 6 | var counts = require('../counts'); 7 | 8 | module.exports = function(strongmq) { 9 | proxy.after(strongmq, 'create', function(obj, args, connection) { 10 | proxy.after(connection, ['createPushQueue', 'createPubQueue'], 11 | function(obj, args, queue) { 12 | proxy.after(queue, 'publish', function(obj, args, queue) { 13 | counts.sample('strongmq_out'); 14 | }); 15 | }); 16 | proxy.after(connection, ['createPullQueue', 'createSubQueue'], 17 | function(obj, args, queue) { 18 | queue.on('message', function() { counts.sample('strongmq_in'); }); 19 | }); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /test/test-metrics-poll.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.STRONGLOOP_LICENSE = require('./helpers').shortTestLicense(); 4 | require('../lib/config').baseInterval = 25; 5 | 6 | var agent = require('../'); 7 | var assert = require('assert'); 8 | 9 | var polls = 0; 10 | var metrics = []; 11 | var started = false; 12 | 13 | agent.on('poll::start', function() { 14 | assert.equal(started, false); 15 | started = true; 16 | polls += 1; 17 | }); 18 | 19 | agent.on('poll::stop', function() { 20 | assert.equal(started, true); 21 | started = false; 22 | }); 23 | 24 | agent.use(function(name, value) { 25 | assert.equal(started, true); 26 | metrics.push(name, value); 27 | }); 28 | 29 | process.on('exit', function() { 30 | assert(metrics.length > 0); 31 | assert(polls > 0); 32 | }); 33 | 34 | setTimeout(function() {}, 100); 35 | -------------------------------------------------------------------------------- /src/check-imports.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright (c) 2014, StrongLoop Inc. 4 | # 5 | # This software is covered by the StrongLoop License. See StrongLoop-LICENSE 6 | # in the top-level directory or visit http://strongloop.com/license. 7 | 8 | SED=sed 9 | STATUS=0 10 | UNAME=`uname` 11 | 12 | if [ "$UNAME" = Darwin ] || [ "$UNAME" = FreeBSD ]; then 13 | SED=gsed 14 | fi 15 | 16 | for FILE in $*; do 17 | if ! $SED -rne 's/^using (\w+::\w+);$/\1/p' $FILE | sort -c; then 18 | echo "in $FILE" 19 | STATUS=1 20 | fi 21 | done 22 | 23 | for FILE in $*; do 24 | for IMPORT in `$SED -rne 's/^using (\w+)::(\w+);$/\2/p' $FILE`; do 25 | if ! $SED -re '/^using (\w+)::(\w+);$/d' $FILE | grep -q "$IMPORT"; then 26 | echo "$IMPORT unused in $FILE" 27 | STATUS=1 28 | fi 29 | done 30 | done 31 | 32 | exit $STATUS 33 | -------------------------------------------------------------------------------- /test/test-agent-metrics-cpu-errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var metrics = require('../').metrics; 4 | var tap = require('tap'); 5 | 6 | var check = metrics._checkCpuProfSupported; 7 | 8 | tap.test('with no addon', function(t) { 9 | try { 10 | check(null); 11 | t.fail(); 12 | } catch (er) { 13 | t.assert(/unavailable without compile/.test(er.message)); 14 | t.end(); 15 | } 16 | }); 17 | 18 | tap.test('with no profiling', function(t) { 19 | try { 20 | check({}); 21 | t.fail(); 22 | } catch (er) { 23 | t.assert(/unavailable on Node.js v/.test(er.message)); 24 | t.end(); 25 | } 26 | }); 27 | 28 | tap.test('with support', function(t) { 29 | t.equal(null, check({ 30 | stopCpuProfilingAndSerialize: true, 31 | startCpuProfiling: true, 32 | })); 33 | t.end(); 34 | }); 35 | -------------------------------------------------------------------------------- /samples/app.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, StrongLoop Inc. 2 | // 3 | // This software is covered by the StrongLoop License. See StrongLoop-LICENSE 4 | // in the top-level directory or visit http://strongloop.com/license. 5 | 6 | var agent = require('../'); 7 | var http = require('http'); 8 | var net = require('net'); 9 | var path = require('path'); 10 | 11 | net.createServer(agent.dyninst).listen(7000); 12 | http.createServer(onrequest).listen(8000, onlisten); 13 | 14 | function onlisten() { 15 | console.log('Listening on http://%s:%d/', 16 | this.address().address, 17 | this.address().port); 18 | console.log('Now run `%s %s/di`', 19 | process.argv[0], 20 | path.relative(process.cwd(), __dirname) || '.'); 21 | } 22 | 23 | function onrequest(req, res) { 24 | res.writeHead(200, { 'Content-Length': '2' }); 25 | res.end('OK'); 26 | } 27 | -------------------------------------------------------------------------------- /test/test-loop-statistics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('../lib/config').baseInterval = 25; 4 | 5 | var agent = require('../'); 6 | agent.profile('deadbeef', 'deadbeef', {quiet: true}); 7 | 8 | var assert = require('assert'); 9 | 10 | var nevents = 0; 11 | agent.internal.on('loop', function(metrics) { 12 | assert.equal(typeof(metrics.count), 'number'); 13 | assert.equal(typeof(metrics.slowest_ms), 'number'); 14 | assert.equal(typeof(metrics.sum_ms), 'number'); 15 | assert.equal(metrics.count, metrics.count | 0); 16 | assert.equal(metrics.slowest_ms, metrics.slowest_ms | 0); 17 | assert.equal(metrics.sum_ms, metrics.sum_ms | 0); 18 | assert(metrics.count >= 0); 19 | assert(metrics.slowest_ms >= 0); 20 | assert(metrics.sum_ms >= 0); 21 | nevents += 1; 22 | }); 23 | 24 | setTimeout(function() {}, 200); 25 | 26 | process.on('exit', function() { assert(nevents > 0); }); 27 | -------------------------------------------------------------------------------- /test/test-agent-profile-reports-version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var util = require('util'); 5 | 6 | var v = require('../package.json').version; 7 | var vMatch = new RegExp('v' + v.replace(/([.+])/g, '\\$1')); 8 | 9 | var logger = { 10 | log: expectVersion, 11 | info: noop, 12 | warn: noop, 13 | error: noop, 14 | }; 15 | 16 | var agent = require('../'); 17 | agent.profile('some key', 'some name', {logger: logger}); 18 | 19 | setTimeout(fail, 2000); 20 | 21 | function expectVersion(logmsg) { 22 | logmsg = util.format.apply(null, arguments); 23 | console.log(logmsg); 24 | if (vMatch.test(logmsg)) success(); 25 | } 26 | 27 | function noop() {} 28 | 29 | function success() { 30 | console.log('# version reported'); 31 | process.exit(0); 32 | } 33 | 34 | function fail() { 35 | console.log('# version not reported.'); 36 | process.exit(1); 37 | } 38 | -------------------------------------------------------------------------------- /test/dyninst-target.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function hello() { 4 | return 'hello'; 5 | } 6 | 7 | function bye() { 8 | return 'bye'; 9 | } 10 | 11 | function Runner(name) { 12 | this.name = name; 13 | } 14 | 15 | require('util').inherits(Runner, require('events').EventEmitter); 16 | 17 | Runner.prototype.start = function() { 18 | var self = this; 19 | self.start = process.hrtime(); 20 | setTimeout(function() { 21 | var diff = process.hrtime(self.start); // calculate diff, as control value 22 | self.diff = (diff[0] * 1e3 + diff[1] / 1e6); // [sec,ns], as ms 23 | self.emit('done'); 24 | }, self.interval); 25 | return self; 26 | } 27 | 28 | Runner.setInterval = function(interval) { // ms 29 | Runner.prototype.interval = Runner.interval = interval; 30 | } 31 | 32 | Runner.setInterval(10); 33 | 34 | exports.bye = bye; 35 | exports.hello = hello; 36 | exports.Runner = Runner; 37 | -------------------------------------------------------------------------------- /test/test-config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var defaults = require('../lib/config'); 5 | var config = defaults.configure(null, null, {}, {}); 6 | 7 | delete defaults.configure; 8 | delete config.configure; 9 | 10 | // assert and delete configuration properties that have no defaults 11 | function assertExists(name) { 12 | assert(name in config); 13 | delete config[name]; 14 | } 15 | assertExists('appName'); 16 | assertExists('logger'); 17 | assertExists('key'); 18 | assertExists('hostname'); 19 | assertExists('license'); 20 | 21 | // all other keys must exist in defaults, and in the config object (which has 22 | // had options and environment applied to configuration, based on defaults) 23 | var defaultKeys = Object.keys(defaults).sort(); 24 | var configKeys = Object.keys(config).sort(); 25 | console.log('defaults: %j', defaultKeys); 26 | console.log('configed: %j', configKeys); 27 | assert.deepEqual(configKeys, defaultKeys); 28 | -------------------------------------------------------------------------------- /lib/debug.js: -------------------------------------------------------------------------------- 1 | // Common debug wrapper. Its used by modules that do not have a reference to the 2 | // agent object, or want to debug under a particular section. 3 | // 4 | // Usage: 5 | // 6 | // // In greetings module 7 | // var debug = require('./debug')('greeter'); 8 | // 9 | // debug('yo, eh'); 10 | // 11 | // Enable friendly greeting with one of: 12 | // DEBUG='*' 13 | // DEBUG='strong-agent:*' 14 | // DEBUG='strong-agent:greeter' 15 | // 16 | // To avoid expensive argument preparation, you can do: 17 | // 18 | // var debug = require('./debug')('section'); 19 | // 20 | // if (debug.enabled) { 21 | // debug('%s', prepData()); // avoid prepData call if possible 22 | // } 23 | 24 | 'use strict'; 25 | 26 | var debug = require('debug'); 27 | 28 | module.exports = function(section) { 29 | var specification = 'strong-agent'; 30 | 31 | if (section) { 32 | specification += ':' + section; 33 | } 34 | 35 | return debug(specification); 36 | }; 37 | -------------------------------------------------------------------------------- /test/test-config-logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var util = require('util'); 5 | 6 | var logged = []; 7 | var log = function() { 8 | var msg = util.format.apply(util, arguments); 9 | logged.push(msg); 10 | }; 11 | var logger = { 12 | log: log, 13 | info: log, 14 | warn: log, 15 | error: log, 16 | }; 17 | 18 | var agent = require('../').profile('key', 'app', {logger: logger}); 19 | 20 | // The four log APIs supported by agent 21 | var levels = ['info', 'warn', 'notice', 'error']; 22 | 23 | levels.forEach(function(level) { 24 | agent[level].apply(agent, ['level is ' + level]); 25 | }); 26 | 27 | process.on('exit', function() { 28 | // We should have seen each of our messages 29 | var seen = logged.map(function(line) { 30 | var match = line.match('level is (.+)'); 31 | if (match) { 32 | return match[1]; 33 | } 34 | }).filter(function(line) { 35 | return !!line; 36 | }); 37 | assert.deepEqual(seen, levels); 38 | }); 39 | -------------------------------------------------------------------------------- /test/test-agent-use-reports-version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var helpers = require('./helpers'); 5 | var util = require('util'); 6 | 7 | var v = require('../package.json').version; 8 | var vMatch = new RegExp('v' + v.replace(/([.+])/g, '\\$1')); 9 | 10 | var logger = { 11 | log: expectVersion, 12 | info: noop, 13 | warn: noop, 14 | error: noop, 15 | }; 16 | 17 | process.env.STRONGLOOP_LICENSE = helpers.shortTestLicense(); 18 | var agent = require('../'); 19 | agent.config.logger = logger; 20 | agent.use(function noop() {}); 21 | 22 | setTimeout(fail, 2000); 23 | 24 | function expectVersion(logmsg) { 25 | logmsg = util.format.apply(null, arguments); 26 | console.log(logmsg); 27 | if (vMatch.test(logmsg)) success(); 28 | } 29 | 30 | function noop() {} 31 | 32 | function success() { 33 | console.log('# version reported'); 34 | process.exit(0); 35 | } 36 | 37 | function fail() { 38 | console.log('# version not reported.'); 39 | process.exit(1); 40 | } 41 | -------------------------------------------------------------------------------- /src/platform-posix.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, StrongLoop Inc. 2 | // 3 | // This software is covered by the StrongLoop License. See StrongLoop-LICENSE 4 | // in the top-level directory or visit http://strongloop.com/license. 5 | 6 | #ifndef AGENT_SRC_PLATFORM_POSIX_H_ 7 | #define AGENT_SRC_PLATFORM_POSIX_H_ 8 | 9 | #include "strong-agent.h" 10 | 11 | #include 12 | #include 13 | 14 | namespace strongloop { 15 | namespace agent { 16 | namespace platform { 17 | 18 | void CpuTime(double* total_system, double* total_user) { 19 | rusage usage; 20 | if (::getrusage(RUSAGE_SELF, &usage)) { 21 | // Error. Just return zeroes. 22 | *total_system = 0; 23 | *total_user = 0; 24 | } else { 25 | *total_system = usage.ru_stime.tv_sec + usage.ru_stime.tv_usec / 1e6; 26 | *total_user = usage.ru_utime.tv_sec + usage.ru_utime.tv_usec / 1e6; 27 | } 28 | } 29 | 30 | } // namespace platform 31 | } // namespace agent 32 | } // namespace strongloop 33 | 34 | #endif // AGENT_SRC_PLATFORM_POSIX_H_ 35 | -------------------------------------------------------------------------------- /test/test-agent-configure.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | process.env.STRONGLOOP_APPNAME = 'test-app-34'; 5 | var agent = require('../'); 6 | 7 | assert.strictEqual(agent, STRONGAGENT, 'exported agent same as global'); 8 | assert.strictEqual(agent.config.appName, 'test-app-34', 'app name from env'); 9 | assert(!agent.started, 'agent is not running'); 10 | 11 | process.env.STRONGLOOP_APPNAME = 'updated-env'; 12 | agent.configure(); 13 | assert.strictEqual(agent, STRONGAGENT, 'exported agent same as global'); 14 | assert.strictEqual(agent.config.appName, 'updated-env', 'app name from env'); 15 | assert(!agent.started, 'agent still not running'); 16 | 17 | agent.configure({ appName: 'override-name', apiKey: 'StrongOps key' }); 18 | assert.strictEqual(agent, STRONGAGENT, 'exported agent same as global'); 19 | assert.strictEqual(agent.config.appName, 'override-name', 'app name from env'); 20 | assert.strictEqual(agent.config.key, 'StrongOps key', 'app name from env'); 21 | assert(!agent.started, 'agent still not running'); 22 | -------------------------------------------------------------------------------- /lib/profilers/memory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var debug = require('../debug')('profilers:memory'); 4 | 5 | function Instances() { 6 | this.addon = require('../addon'); 7 | this.agent = null; 8 | this.enabled = false; 9 | } 10 | module.exports = new Instances; 11 | 12 | Instances.prototype.init = function(agent_) { 13 | this.agent = agent_; 14 | }; 15 | 16 | Instances.prototype.start = function() { 17 | if (!this.addon) { 18 | this.agent.info('could not start heap monitoring add-on'); 19 | return false; 20 | } 21 | debug('instance monitoring started'); 22 | this.addon.startHeapDiff(); 23 | this.enabled = true; 24 | return true; 25 | }; 26 | 27 | Instances.prototype.stop = function() { 28 | if (!this.enabled) return; 29 | debug('instance monitoring stopped'); 30 | this.addon.stopHeapDiff(false); 31 | this.enabled = false; 32 | }; 33 | 34 | Instances.prototype.poll = function() { 35 | if (!this.enabled) return null; 36 | var state = this.addon.stopHeapDiff(true); 37 | this.addon.startHeapDiff(); 38 | return state; 39 | }; 40 | -------------------------------------------------------------------------------- /test/test-cpu-profiler-path.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../'); 4 | var assert = require('assert'); 5 | var vm = require('vm'); 6 | 7 | var v8 = process.versions.v8.split('.').slice(0, 3).reduce(function(a, b) { 8 | return a = a << 8 | b; 9 | }); 10 | 11 | if (v8 < 0x31D00) { // V8 < 3.29 12 | return; // Known buggy version of V8, skip test. 13 | } 14 | 15 | function busy() { 16 | var start = Date.now(); 17 | while (Date.now() < start + 250); 18 | } 19 | 20 | try { 21 | agent.metrics.startCpuProfiling(); 22 | } catch (e) { 23 | return; // Skip, not supported for this node version. 24 | } 25 | 26 | var filename = 'C:\\Program Files\\node\\test.js'; 27 | vm.runInThisContext(busy + 'busy()', filename); 28 | var data = agent.metrics.stopCpuProfiling(); 29 | var root = JSON.parse(data); // Should not throw. 30 | 31 | function recurse(node) { 32 | // The .slice(2) is because V8 strips the "C:" prefix... 33 | if (node.url === filename.slice(2)) return true; 34 | return node.children.some(recurse); 35 | } 36 | assert(recurse(root.head)); 37 | -------------------------------------------------------------------------------- /lib/profilers/cpu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var licensed; 4 | 5 | // addon is exposed so it may be monkey-patched by unit tests 6 | exports.addon = require('../addon'); 7 | 8 | exports.init = function(agent) { 9 | licensed = agent.licensed.bind(agent); 10 | }; 11 | 12 | // A timeout > 0 starts the profiler in watchdog mode. Watchdog mode pauses 13 | // the profiler until at least |timeout| milliseconds pass without the event 14 | // loop going through epoll_wait(), a reliable indicator that the program is 15 | // stalled on something. Watchdog mode is currently implemented on i386 and 16 | // x86_64 Linux. 17 | exports.start = function(timeout) { 18 | if (timeout && !licensed('watchdog')) { 19 | throw Error('Watchdog CPU profiling mode requires license'); 20 | } 21 | if (exports.addon == null) { 22 | return false; 23 | } 24 | var errmsg = exports.addon.startCpuProfiling(timeout | 0); 25 | if (errmsg) { 26 | throw Error(errmsg); 27 | } 28 | exports.enabled = true; 29 | return true; 30 | }; 31 | 32 | exports.stop = function() { 33 | exports.enabled = false; 34 | return exports.addon && exports.addon.stopCpuProfiling(); 35 | }; 36 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Parts of this software are (c) 2012 Dmitri Melikyan. The original license 2 | follows: 3 | 4 | Copyright (c) 2012 Dmitri Melikyan 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a 7 | copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to permit 11 | persons to whom the Software is furnished to do so, subject to the 12 | following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 20 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 21 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 22 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 23 | THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /lib/custom-stats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.init = function init(agent) { 4 | function report(stat, value, type) { 5 | agent.internal.emit('stats', 'custom.' + stat, value, type); 6 | } 7 | 8 | var counters = Object.create(null); 9 | 10 | function count(stat, change) { 11 | var latest = counters[stat] = (counters[stat] | 0) + change; 12 | report(stat, latest, 'count'); 13 | } 14 | 15 | exports.increment = function increment(stat) { count(stat, +1); }; 16 | 17 | exports.decrement = function decrement(stat) { count(stat, -1); }; 18 | 19 | function StatTimer(stat) { 20 | this._stat = stat; 21 | this._start = process.hrtime(); 22 | }; 23 | 24 | StatTimer.prototype.stop = function stop() { 25 | if (!this._start) return; // Timer stopped multiple times! 26 | 27 | // Use full resolution ns internally, it will be converted to ms if reported 28 | // to statsd. 29 | var diff = process.hrtime(this._start); 30 | var delta = diff[0] * 1e9 + diff[1]; 31 | 32 | this._start = undefined; 33 | 34 | report(this._stat, delta, 'timer'); 35 | }; 36 | 37 | exports.createTimer = 38 | function createTimer(stat) { return new StatTimer(stat); }; 39 | 40 | return exports; 41 | } 42 | -------------------------------------------------------------------------------- /test/test-configurable-interval.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var helpers = require('./helpers'); 5 | var agent = require('../'); 6 | 7 | assert(!agent.started, 'agent is not running'); 8 | agent.configure({ interval: 500, license: helpers.shortTestLicense() }); 9 | 10 | var collected = 0; 11 | function tick(name, value) { 12 | console.log('use: %s = %s', name, value); 13 | // gc.heap.used is unpredictable, it depends on when v8 decides to gc 14 | if (name === 'gc.heap.used') 15 | return; 16 | collected += 1; 17 | } 18 | 19 | agent.use(tick); 20 | assert(agent.started, 'agent is running'); 21 | 22 | var counted = 0; 23 | setTimeout(function() { 24 | assert.strictEqual(collected, 0, 'nothing reported half way'); 25 | counted += 1; 26 | }, 250); 27 | 28 | setTimeout(function() { 29 | console.log('collected: ', collected); 30 | assert(collected > 0, 'something reported after a cycle'); 31 | // basics: 2 heap, 4 loop, 3 cpu, 1 watchdog 32 | assert(collected < 11, 'only one cycle worth reported in a cycle'); 33 | counted += 1; 34 | }, 750); 35 | 36 | 37 | process.on('exit', function(code) { 38 | assert(code === 0); 39 | assert(counted === 2, 'collection counts verified twice'); 40 | }); 41 | -------------------------------------------------------------------------------- /test/test-agent-use-tiers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.STRONGLOOP_LICENSE = require('./helpers').shortTestLicense(); 4 | 5 | var EventEmitter = require('events').EventEmitter; 6 | var agent = require('../'); 7 | var tap = require('tap'); 8 | 9 | var uses = new EventEmitter; 10 | 11 | agent.configure({ 12 | // these tests manually inject metrics, so we want a long 13 | // interval to prevent the _real_ metrics from interfering 14 | interval: 30000 15 | }); 16 | 17 | agent.use(uses.emit.bind(uses, 'use')); 18 | 19 | // XXX These tests assume the .average is the first value sent out 20 | var tests = { 21 | 'redis_out': 'tiers.redis.average', 22 | 'http': 'http.average', 23 | 'foo.bar.com:8080_out': 'tiers.foo.bar.com:8080.average', 24 | 'www.random.org_out': 'tiers.www.random.org.average', 25 | '127.0.0.1_out': 'tiers.127.0.0.1.average', 26 | }; 27 | 28 | for (var k in tests) { 29 | makeTest(k, tests[k]); 30 | } 31 | 32 | function makeTest(tier, metric) { 33 | tap.test(tier, function(t) { 34 | var data = {}; 35 | data[tier] = { 36 | min: 0, 37 | max: 2, 38 | avg: 1 39 | }; 40 | 41 | t.plan(1); 42 | uses.once('use', function(name, value) { t.equal(name, metric); }); 43 | agent.internal.emit('tiers', data); 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /test/test-counts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('../lib/config').baseInterval = 15; 4 | 5 | var agent = require('../'); 6 | var assert = require('assert'); 7 | var counts = require('../lib/counts'); 8 | 9 | function emit(data) { 10 | if (data.one) { 11 | emit.one += 1; 12 | assert.equal(data.one, 1); 13 | } 14 | if (data.two) { 15 | emit.two += 1; 16 | assert.equal(data.two, 2); 17 | } 18 | if (data.three) { 19 | emit.three += 1; 20 | assert.equal(data.three, 3); 21 | } 22 | } 23 | emit.one = 0; 24 | emit.two = 0; 25 | emit.three = 0; 26 | 27 | function spam() { 28 | counts.sample('one'); 29 | counts.sample('two'); 30 | counts.sample('two'); 31 | counts.sample('three'); 32 | counts.sample('three'); 33 | counts.sample('three'); 34 | if (--spam.ticks > 0) setTimeout(spam, 25); 35 | } 36 | spam.ticks = 3; 37 | 38 | process.on('exit', function() { 39 | // Timeouts are never too precise so let's not have too high or too exact 40 | // expectations, just expect that we've seen _some_ events. 41 | assert(emit.one > 1); 42 | assert(emit.two > 1); 43 | assert(emit.three > 1); 44 | }); 45 | 46 | agent.profile('deadbeef', 'deadbeef', {quiet: true}); 47 | agent.internal.on('counts', emit); 48 | 49 | setTimeout(function() {}, 100); 50 | spam(); 51 | -------------------------------------------------------------------------------- /test/test-use-custom-stats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.STRONGLOOP_LICENSE = require('./helpers').longTestLicense(); 4 | 5 | var EventEmitter = require('events').EventEmitter; 6 | var agent = require('../'); 7 | var assert = require('assert'); 8 | var tap = require('tap'); 9 | 10 | var metric = new EventEmitter; 11 | 12 | var ok = agent.use(function(name, value) { metric.emit('use', name, value); }); 13 | 14 | // success/fail cannot be determined, see strongloop/strongops#199, 15 | // assert(ok); 16 | 17 | function checkStat(t, name, value, test) { 18 | metric.once('use', function() { 19 | t.equal(arguments[0], name, 'stat name'); 20 | t.equal(arguments[1], value, 'stat value'); 21 | }); 22 | process.nextTick(test); 23 | return 2; 24 | } 25 | 26 | tap.test('count', function(t) { 27 | t.plan(checkStat(t, 'custom.a.count', 1, 28 | function() { agent.metrics.stats.increment('a'); })); 29 | }); 30 | 31 | tap.test('timer', function(t) { 32 | var timer = agent.metrics.stats.createTimer('b'); 33 | 34 | metric.once('use', function(name, value) { 35 | t.equal(name, 'custom.b.timer'); 36 | t.assert(value >= 180 /*millisec*/, 'time elapsed'); 37 | t.end(); 38 | }); 39 | 40 | setTimeout(function() { timer.stop(); }, 200 /*millisec*/); 41 | }); 42 | -------------------------------------------------------------------------------- /test/test-top-functions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.STRONGLOOP_LICENSE = require('./helpers').shortTestLicense(); 4 | require('../lib/config').baseInterval = 25; 5 | 6 | var agent = require('../'); 7 | agent.profile('deadbeef', 'deadbeef', {quiet: true}); 8 | 9 | var assert = require('assert'); 10 | var http = require('http'); 11 | 12 | var topFunctions = require('../lib/top-functions'); 13 | var updates = []; 14 | 15 | agent.on('topCalls', updates.push.bind(updates)); 16 | 17 | http.createServer(onrequest).listen(0, '127.0.0.1', onlisten); 18 | 19 | function onrequest(req, res) { 20 | res.writeHead(200, {'Content-Length': '32'}); 21 | res.end(Buffer(32)); 22 | } 23 | 24 | function onlisten() { 25 | var server = this; 26 | var address = server.address().address; 27 | var port = server.address().port; 28 | function done() { setTimeout(server.close.bind(server), 100) } 29 | function next() { pummel(address, port, --next.rounds > 0 ? next : done) } 30 | next.rounds = 32; 31 | next(); 32 | } 33 | 34 | function pummel(host, port, next) { 35 | http.get({host: host, port: port}, function(res) { 36 | res.on('end', next); 37 | res.resume(); 38 | }); 39 | } 40 | 41 | process.on('exit', function() { 42 | assert(updates.length > 0); 43 | updates.forEach(function(update) { assert('httpCalls' in update); }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/test-addon-counters.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, StrongLoop Inc. 2 | // 3 | // This software is covered by the StrongLoop License. See StrongLoop-LICENSE 4 | // in the top-level directory or visit http://strongloop.com/license. 5 | 6 | 'use strict'; 7 | 8 | var addon = require('../lib/addon'); 9 | var assert = require('assert'); 10 | 11 | assert.equal(typeof(gc), 'function', 'Run this test with --expose_gc'); 12 | 13 | var names = []; 14 | Object.keys(addon.counters).forEach(function(name) { 15 | var index = addon.counters[name]; 16 | names[index] = name; 17 | }); 18 | 19 | var metrics = []; 20 | addon[addon.kCountersCallback] = function(samples, n) { 21 | assert.equal(n % 2, 0); 22 | for (var i = 0; i < n; i += 1) metrics.push(samples[i]); 23 | }; 24 | 25 | addon.startCounters(); 26 | for (var i = 0; i < 1337; i += 1) JSON.parse(JSON.stringify(process.config)); 27 | gc(); 28 | 29 | // Wait for the counter aggregator's idle timer to run. 30 | setTimeout(function() { 31 | addon[addon.kCountersCallback] = assert.fail; 32 | addon.stopCounters(); 33 | assert(metrics.length > 0); 34 | assert.equal(metrics.length % 2, 0); 35 | for (var i = 0, n = metrics.length; i < n; i += 2) { 36 | var index = metrics[i + 0]; 37 | var value = metrics[i + 1]; 38 | assert.equal(typeof names[index], 'string'); 39 | assert.equal(typeof value, 'number'); 40 | } 41 | }, 20); 42 | -------------------------------------------------------------------------------- /test/test-agent-intercepts-http-upgrade-event.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('../lib/config').baseInterval = 50; 4 | require('../').profile('deadbeef', 'deadbeef', {quiet: true}); 5 | 6 | var assert = require('assert'); 7 | var http = require('http'); 8 | var net = require('net'); 9 | var agent = require('../'); 10 | 11 | assert.equal(http.Server.prototype.on, http.Server.prototype.addListener); 12 | 13 | http.createServer(assert.fail).listen(0, '127.0.0.1', function() { 14 | var kContextPropertyName = '__STRONGOPS_HTTP_CONTEXT__'; 15 | assert.equal(this, agent.httpServer); 16 | assert.equal(this.hasOwnProperty(kContextPropertyName), true); 17 | assert.equal(this[kContextPropertyName].connectionCounts[0], 0); 18 | this.on('upgrade', function(req, conn, head) { 19 | assert.equal(this[kContextPropertyName].connectionCounts[0], 1); 20 | seenUpgrade = true; 21 | conn.destroy(); 22 | this.close(); 23 | }); 24 | net.connect({ 25 | host: this.address().address, 26 | port: this.address().port, 27 | }, 28 | function() { 29 | this.write('GET / HTTP/1.1\r\n' + 30 | 'Connection: Upgrade\r\n' + 31 | 'Upgrade: Yes, please.\r\n' + 32 | '\r\n'); 33 | }); 34 | }); 35 | 36 | process.on('exit', function() { assert.equal(seenUpgrade, true); }); 37 | var seenUpgrade = false; 38 | -------------------------------------------------------------------------------- /src/extras.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, StrongLoop Inc. 2 | // 3 | // This software is covered by the StrongLoop License. See StrongLoop-LICENSE 4 | // in the top-level directory or visit http://strongloop.com/license. 5 | 6 | #ifndef AGENT_SRC_EXTRAS_H_ 7 | #define AGENT_SRC_EXTRAS_H_ 8 | 9 | #include "strong-agent.h" 10 | 11 | namespace strongloop { 12 | namespace agent { 13 | namespace extras { 14 | 15 | namespace C = ::compat; 16 | 17 | using v8::Array; 18 | using v8::FunctionTemplate; 19 | using v8::Isolate; 20 | using v8::Local; 21 | using v8::Object; 22 | 23 | C::ReturnType CpuTime(const C::ArgumentType& args) { 24 | double total_user = 0; 25 | double total_system = 0; 26 | platform::CpuTime(&total_user, &total_system); 27 | C::ReturnableHandleScope handle_scope(args); 28 | Isolate* isolate = args.GetIsolate(); 29 | Local result = C::Array::New(isolate, 2); 30 | result->Set(0, C::Number::New(isolate, total_user)); 31 | result->Set(1, C::Number::New(isolate, total_system)); 32 | return handle_scope.Return(result); 33 | } 34 | 35 | void Initialize(Isolate* isolate, Local binding) { 36 | binding->Set(C::String::NewFromUtf8(isolate, "cputime"), 37 | C::FunctionTemplate::New(isolate, CpuTime)->GetFunction()); 38 | } 39 | 40 | } // namespace extras 41 | } // namespace agent 42 | } // namespace strongloop 43 | 44 | #endif // AGENT_SRC_EXTRAS_H_ 45 | -------------------------------------------------------------------------------- /test/test-metrics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('../lib/config').baseInterval = 100; 4 | require('../').profile('deadbeef', 'deadbeef', {quiet: true}); 5 | 6 | var assert = require('assert'); 7 | var http = require('http'); 8 | var metrics = require('../lib/metrics'); 9 | var agent = require('../'); 10 | 11 | assert.equal(typeof(gc), 'function', 'Run this test with --expose_gc'); 12 | 13 | var callbacks = 0; 14 | process.on('exit', function() { assert(callbacks >= 5); }); 15 | 16 | agent.internal.on('metrics', function(data) { 17 | for (var name in data) { 18 | metric(name, data[name]); 19 | } 20 | }); 21 | 22 | function metric(name, value) { 23 | callbacks += 1; 24 | if (name === 'CPU util stime') { 25 | assert(value >= 0); 26 | assert(value <= 100); 27 | } 28 | if (name === 'CPU util utime') { 29 | assert(value >= 0); 30 | assert(value <= 100); 31 | } 32 | if (name === 'Heap Data') { 33 | assert(Array.isArray(value)); 34 | assert.equal(value.length, 3); 35 | } 36 | if (name === 'GC Full. V8 heap used') { 37 | assert(value > 0); 38 | } 39 | if (name === 'Connections') { 40 | assert(Array.isArray(value)); 41 | assert.equal(value.length, 2); 42 | } 43 | if (name === 'queue') { 44 | assert(Array.isArray(value)); 45 | assert.equal(value.length, 2); 46 | } 47 | } 48 | 49 | setInterval(gc, 100).unref(); 50 | setTimeout(function() { /* Keep process alive... */ }, 1000); 51 | -------------------------------------------------------------------------------- /lib/probes/express.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../agent'); 4 | var proxy = require('../proxy'); 5 | var samples = require('../samples'); 6 | var topFunctions = require('../top-functions'); 7 | 8 | module.exports = function(express) { 9 | 10 | // set up route on server init 11 | function routeHook(obj, args, router) { 12 | var method = args[0].toUpperCase(); 13 | var path = args[1]; 14 | var route; 15 | 16 | // start request 17 | proxy.callback(args, -1, function(obj, args) { 18 | if (agent.paused) return; 19 | 20 | var req = args[0]; 21 | var res = args[1]; 22 | var timer = samples.timer("Express Server", path, true); 23 | 24 | // finish request 25 | proxy.after(res, 'end', function(obj, args) { 26 | timer.end(); 27 | 28 | route = route || (method + ' ' + 29 | (res.app.route === '/' ? '' : res.app.route) + path); 30 | topFunctions.add('expressCalls', route, timer.ms, req.tiers, req.graph); 31 | }); 32 | }); 33 | } 34 | 35 | if (express.Router) { // express 3.x exposes the Router class directly 36 | proxy.before(express.Router.prototype, ['route'], routeHook); 37 | } else { // express 2.x exposes the router object once the server is created 38 | proxy.after(express, 'createServer', function(obj, args, app) { 39 | proxy.before(app.routes, ['_route'], routeHook); 40 | }); 41 | } 42 | 43 | }; 44 | -------------------------------------------------------------------------------- /test/test-probe-mysql.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (!('MYSQL_USER' in process.env) || !('MYSQL_PASSWORD' in process.env)) { 4 | console.log('1..0 # SKIP MYSQL_USER or MYSQL_PASSWORD not set in env'); 5 | return; 6 | } 7 | 8 | process.env.STRONGLOOP_LICENSE = require('./helpers').longTestLicense(); 9 | 10 | var agent = require('../'); 11 | var assert = require('assert'); 12 | 13 | var metrics = []; 14 | agent.use(metrics.push.bind(metrics)); 15 | 16 | agent.error = assert.fail; // Fail if mysql probe squeals. 17 | var mysql = require('mysql'); 18 | var conn = mysql.createConnection({ 19 | user: process.env.MYSQL_USER, 20 | password: process.env.MYSQL_PASSWORD, 21 | }); 22 | conn.connect(); 23 | 24 | var numcalls = 0; 25 | conn.query('SELECT 42 AS result', function(err, rows, fields) { 26 | assert.equal(err, null); 27 | assert.equal(rows.length, 1); 28 | assert.equal(fields.length, 1); 29 | assert.equal(rows[0].result, 42); 30 | conn.end(); 31 | numcalls += 1; 32 | }); 33 | 34 | process.on('exit', function() { 35 | assert.equal(numcalls, 1); 36 | agent.poll(); 37 | var avg = metrics.indexOf('tiers.mysql.average'); 38 | var min = metrics.indexOf('tiers.mysql.minimum'); 39 | var max = metrics.indexOf('tiers.mysql.maximum'); 40 | assert.notEqual(avg, -1); 41 | assert.notEqual(min, -1); 42 | assert.notEqual(max, -1); 43 | assert(metrics[avg + 1] > 0); 44 | assert(metrics[min + 1] > 0); 45 | assert(metrics[max + 1] > 0); 46 | }); 47 | -------------------------------------------------------------------------------- /lib/probes/axon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var proxy = require('../proxy'); 4 | var counts = require('../counts'); 5 | 6 | // Assumes following usage suggested in the README: 7 | // ``` 8 | // var pub = require('axon').socket('pub'); 9 | // var sub = require('axon').socket('sub'); 10 | // ``` 11 | 12 | // TODO(rmg): Support PubEmitter/SubEmitter, it doesn't use the same 13 | // send()/on('message') pattern as the others: 14 | // https://github.com/visionmedia/axon#pubemitter--subemitter 15 | 16 | module.exports = function(axon) { 17 | 18 | // TODO(rmg): Instrument axon.Socket.prototype instead? 19 | proxy.after(axon, 'socket', function(obj, args, socket) { 20 | 21 | proxy.after(socket, 'send', function(obj, args) { 22 | // When MQ metrics were first added to StrongOps it was for StrongMQ 23 | // specifically, so the metric names have been immortalized 24 | counts.sample('strongmq_out'); 25 | }); 26 | 27 | proxy.before(socket, ['on', 'addListener'], function(obj, args) { 28 | var event = args[0]; 29 | 30 | // TODO(rmg): PubEmitter/SubEmitter allows user-defined events 31 | if (event !== 'message') return; 32 | 33 | proxy.callback(args, -1, function(obj, args, extra) { 34 | // When MQ metrics were first added to StrongOps it was for StrongMQ 35 | // specifically, so the metric names have been immortalized 36 | counts.sample('strongmq_in'); 37 | }); 38 | }); 39 | 40 | }); 41 | 42 | }; 43 | -------------------------------------------------------------------------------- /src/platform-win32.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, StrongLoop Inc. 2 | // 3 | // This software is covered by the StrongLoop License. See StrongLoop-LICENSE 4 | // in the top-level directory or visit http://strongloop.com/license. 5 | 6 | #ifndef AGENT_SRC_PLATFORM_WIN32_H_ 7 | #define AGENT_SRC_PLATFORM_WIN32_H_ 8 | 9 | #include "strong-agent.h" 10 | 11 | #include 12 | #include 13 | 14 | namespace strongloop { 15 | namespace agent { 16 | namespace platform { 17 | 18 | namespace internal { 19 | 20 | inline double FileTimeToFractionalSeconds(const FILETIME* time) { 21 | uint64_t hi = static_cast(time->dwHighDateTime); 22 | uint64_t lo = static_cast(time->dwLowDateTime); 23 | return (lo | (hi << 32)) / 1e7; 24 | } 25 | 26 | } // namespace internal 27 | 28 | void CpuTime(double* total_system, double* total_user) { 29 | FILETIME system_time; 30 | FILETIME user_time; 31 | FILETIME unused0; 32 | FILETIME unused1; 33 | HANDLE self; 34 | 35 | self = ::GetCurrentProcess(); 36 | if (::GetProcessTimes(self, &unused0, &unused1, &system_time, &user_time)) { 37 | *total_system = internal::FileTimeToFractionalSeconds(&system_time); 38 | *total_user = internal::FileTimeToFractionalSeconds(&user_time); 39 | } else { 40 | // Error. Just return zeroes. 41 | *total_system = 0; 42 | *total_user = 0; 43 | } 44 | } 45 | 46 | } // namespace platform 47 | } // namespace agent 48 | } // namespace strongloop 49 | 50 | #endif // AGENT_SRC_PLATFORM_WIN32_H_ 51 | -------------------------------------------------------------------------------- /test/test-strong-express-metrics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../').profile('some app', 'some key');; 4 | 5 | var assert = require('assert'); 6 | var http = require('http'); 7 | var express = require('express'); 8 | 9 | var path = require('path'); 10 | var metricsPath = require.resolve('strong-express-metrics/package.json'); 11 | // loopback-boot loads middleware by using a full path to the module 12 | var metrics = require(path.dirname(metricsPath)); 13 | 14 | var collected = 0; 15 | process.on('exit', function() { 16 | assert.equal(collected, 1, 'expected: 1 record, actual: ' + collected); 17 | }); 18 | 19 | agent.on('express:usage-record', function(record) { 20 | collected++; 21 | // NOTE(bajtos) Move the assertion to a new tick (stack), because 22 | // express-metrics catches (and ignores) errors thrown by record observers 23 | // and the test never fails as a result. 24 | setImmediate(function() { 25 | assert.deepEqual( 26 | Object.keys(record).sort(), 27 | ['client', 'data', 'process', 'request', 'response', 'timestamp', 28 | 'version', 29 | ]); 30 | }); 31 | }); 32 | 33 | var app = express(); 34 | app.use(metrics()); 35 | 36 | var server = app.listen(0, '127.0.0.1', function() { 37 | http.get( 38 | { 39 | host: this.address().address, 40 | port: this.address().port, 41 | path: '/', 42 | }, 43 | function(res) { 44 | res.resume(); 45 | res.on('end', function() { 46 | server.close(); 47 | }); 48 | } 49 | ); 50 | }); 51 | -------------------------------------------------------------------------------- /test/test-agent-metrics-heapdiff.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.STRONGLOOP_LICENSE = require('./helpers').shortTestLicense(); 4 | require('../lib/config').baseInterval = 50; 5 | 6 | var agent = require('../'); 7 | var assert = require('assert'); 8 | var fmt = require('util').format; 9 | 10 | var metrics = []; 11 | agent.use(metrics.push.bind(metrics)); 12 | 13 | assert.equal(typeof(gc), 'function', 'Run this test with --expose_gc'); 14 | assert.equal(agent.metrics.startTrackingObjects(), true); 15 | for (var timeout = 0; timeout < 60; timeout += 12) setTimeout(gc, timeout); 16 | setTimeout(one, 60); 17 | 18 | function one() { 19 | agent.metrics.stopTrackingObjects(); 20 | assert(metrics.length > 0); 21 | 22 | // These are almost guaranteed to exist in the output. It's tricky of course 23 | // because garbage collection is fairly non-deterministic and the GC algorithm 24 | // may change over time. 25 | check('object.Array.count'); 26 | check('object.Array.size'); 27 | check('object.Timeout.count'); 28 | check('object.Timeout.size'); 29 | 30 | metrics = []; 31 | metrics.push = assert.fail; 32 | for (var timeout = 0; timeout < 60; timeout += 12) setTimeout(gc, timeout); 33 | setTimeout(two, 60); 34 | } 35 | 36 | function two() { 37 | assert.equal(metrics.filter(/ /.test.bind(/^object\./)).length, 0); 38 | } 39 | 40 | function check(key) { 41 | var index = metrics.indexOf(key); 42 | if (index === -1) throw Error(fmt('Key %j not found in %j', key, metrics)); 43 | assert.equal(typeof(metrics[index + 1]), 'number'); 44 | } 45 | -------------------------------------------------------------------------------- /test/test-licenses.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var tap = require('tap'); 4 | var helpers = require('./helpers'); 5 | process.env.STRONGLOOP_LICENSE = ''; 6 | var agent = require('../'); 7 | 8 | tap.test('adding licenses', function(t) { 9 | agent.licenses = []; 10 | t.ok(!agent.licensed('foo')); 11 | t.ok(!agent.licensed('bar')); 12 | t.ok(!agent.licensed('else')); 13 | 14 | agent.addLicense(helpers.shortTestLicense(['foo'])); 15 | t.ok(agent.licensed('foo')); 16 | t.ok(!agent.licensed('bar')); 17 | t.ok(!agent.licensed('else')); 18 | 19 | agent.addLicense(helpers.shortTestLicense(['bar'])); 20 | t.ok(agent.licensed('foo')); 21 | t.ok(agent.licensed('bar')); 22 | t.ok(!agent.licensed('else')); 23 | 24 | agent.addLicense(helpers.shortTestLicense(['*'])); 25 | t.ok(agent.licensed('foo')); 26 | t.ok(agent.licensed('bar')); 27 | t.ok(agent.licensed('else')); 28 | t.ok(agent.licensed('all the things')); 29 | 30 | t.end(); 31 | }); 32 | 33 | tap.test('multiple licenses', function(t) { 34 | var multi = [ 35 | helpers.shortTestLicense(['foo']), 36 | helpers.shortTestLicense(['bar']), 37 | helpers.shortTestLicense(['else']), 38 | ]; 39 | agent.licenses = []; 40 | 41 | t.ok(!agent.licensed('foo')); 42 | t.ok(!agent.licensed('bar')); 43 | t.ok(!agent.licensed('else')); 44 | 45 | agent.configure({ license: multi.join(':') }); 46 | t.ok(agent.licensed('foo')); 47 | t.ok(agent.licensed('bar')); 48 | t.ok(agent.licensed('else')); 49 | t.ok(!agent.licensed('all the things')); 50 | 51 | t.end(); 52 | }); 53 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014, StrongLoop Inc. 2 | # 3 | # This software is covered by the StrongLoop License. See StrongLoop-LICENSE 4 | # in the top-level directory or visit http://strongloop.com/license. 5 | 6 | { 7 | 'targets': [ 8 | { 9 | 'target_name': 'strong-agent', 10 | 'cflags': [ 11 | '-fvisibility=hidden', 12 | '-fno-exceptions', 13 | '-fno-rtti', 14 | '-fno-strict-aliasing', 15 | '-Wall', 16 | '-Wextra', 17 | ], 18 | # Need to repeat the compiler flags in xcode-specific lingo, 19 | # gyp on mac ignores the cflags field. 20 | 'xcode_settings': { 21 | 'GCC_ENABLE_CPP_EXCEPTIONS': 'NO', 22 | 'GCC_ENABLE_CPP_RTTI': 'NO', 23 | # -Wno-invalid-offsetof is only necessary for gcc 4.2, 24 | # it prints bogus warnings for POD types. 25 | 'GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO': 'NO', 26 | # -fvisibility=hidden 27 | 'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', 28 | 'WARNING_CFLAGS': ['-Wall', '-Wextra'], 29 | }, 30 | 'sources': [ 31 | 'src/compat-inl.h', 32 | 'src/compat.h', 33 | 'src/counters.h', 34 | 'src/dyninst.h', 35 | 'src/extras.h', 36 | 'src/features.h', 37 | 'src/gcinfo.h', 38 | 'src/heapdiff.h', 39 | 'src/platform-posix.h', 40 | 'src/platform-win32.h', 41 | 'src/profiler.h', 42 | 'src/strong-agent.cc', 43 | 'src/strong-agent.h', 44 | 'src/uvmon.h', 45 | 'src/watchdog.h', 46 | ], 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # BasedOnStyle: Google 3 | AccessModifierOffset: -1 4 | ConstructorInitializerIndentWidth: 4 5 | AlignEscapedNewlinesLeft: true 6 | AlignTrailingComments: true 7 | AllowAllParametersOfDeclarationOnNextLine: true 8 | AllowShortIfStatementsOnASingleLine: true 9 | AllowShortLoopsOnASingleLine: true 10 | AlwaysBreakTemplateDeclarations: true 11 | AlwaysBreakBeforeMultilineStrings: true 12 | BreakBeforeBinaryOperators: false 13 | BreakBeforeTernaryOperators: true 14 | BreakConstructorInitializersBeforeComma: false 15 | BinPackParameters: true 16 | ColumnLimit: 80 17 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 18 | DerivePointerBinding: true 19 | ExperimentalAutoDetectBinPacking: false 20 | IndentCaseLabels: true 21 | MaxEmptyLinesToKeep: 1 22 | NamespaceIndentation: None 23 | ObjCSpaceBeforeProtocolList: false 24 | PenaltyBreakBeforeFirstCallParameter: 1 25 | PenaltyBreakComment: 60 26 | PenaltyBreakString: 1000 27 | PenaltyBreakFirstLessLess: 120 28 | PenaltyExcessCharacter: 1000000 29 | PenaltyReturnTypeOnItsOwnLine: 200 30 | PointerBindsToType: true 31 | SpacesBeforeTrailingComments: 2 32 | Cpp11BracedListStyle: true 33 | Standard: Auto 34 | IndentWidth: 2 35 | TabWidth: 8 36 | UseTab: Never 37 | BreakBeforeBraces: Attach 38 | IndentFunctionDeclarationAfterType: true 39 | SpacesInParentheses: false 40 | SpacesInAngles: false 41 | SpacesInContainerLiterals: false 42 | SpaceInEmptyParentheses: false 43 | SpacesInCStyleCastParentheses: false 44 | SpaceAfterControlStatementKeyword: true 45 | SpaceBeforeAssignmentOperators: true 46 | ContinuationIndentWidth: 4 47 | ... 48 | 49 | -------------------------------------------------------------------------------- /test/test-watchdog-activation-count.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (process.platform !== 'linux') { 4 | console.log('1..0 # SKIP watchdog is Linux-only for now'); 5 | return; 6 | } 7 | 8 | if (process.versions.v8 >= '3.15' && process.versions.v8 < '3.29') { 9 | console.log('1..0 # SKIP watchdog is incompatible with this node version'); 10 | return; 11 | } 12 | 13 | process.env.STRONGLOOP_LICENSE = require('./helpers').longTestLicense(); 14 | 15 | var addon = require('../lib/addon'); 16 | var agent = require('../'); 17 | var assert = require('assert'); 18 | var profiler = require('../lib/profilers/cpu'); 19 | 20 | var watchdogActivationCountEvents = 0; 21 | var watchdogActivationCount = 0; 22 | 23 | var expectedBlocks = 10; 24 | 25 | assert(agent.internal.supports.watchdog); 26 | 27 | process.once('exit', function() { 28 | console.log('on exit: events reported %j cycles stalled %j', 29 | watchdogActivationCountEvents, watchdogActivationCount); 30 | assert(watchdogActivationCountEvents >= 1); 31 | assert(watchdogActivationCount >= expectedBlocks); 32 | }); 33 | 34 | agent.start(); 35 | agent.internal.once('watchdogActivationCount', function(count) { 36 | watchdogActivationCountEvents += 1; 37 | watchdogActivationCount += count; 38 | }); 39 | 40 | profiler.start(1); 41 | block(expectedBlocks); 42 | 43 | function block(count) { 44 | if (count <= 0) return done(); 45 | delay(25); 46 | setImmediate(block.bind(null, count - 1)); 47 | } 48 | 49 | function done() { 50 | profiler.stop(); 51 | agent.poll(); 52 | } 53 | 54 | function delay(ms) { 55 | var start = Date.now(); 56 | while (Date.now() < start + ms); 57 | } 58 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014, StrongLoop Inc. 2 | # 3 | # This software is covered by the StrongLoop License. See StrongLoop-LICENSE 4 | # in the top-level directory or visit http://strongloop.com/license. 5 | 6 | PREFIX ?= $(dir $(lastword $(MAKEFILE_LIST))) 7 | 8 | PYTHON ?= python 9 | 10 | CLANG_FORMAT ?= clang-format 11 | 12 | # Exclude queue.h, it's borrowed from libuv and doesn't adhere to the coding 13 | # style. 14 | SOURCES = \ 15 | compat-inl.h \ 16 | compat.h \ 17 | counters.h \ 18 | dyninst.h \ 19 | extras.h \ 20 | gcinfo.h \ 21 | heapdiff.h \ 22 | platform-posix.h \ 23 | platform-win32.h \ 24 | profiler.h \ 25 | strong-agent.cc \ 26 | strong-agent.h \ 27 | util-inl.h \ 28 | util.h \ 29 | uvmon.h \ 30 | watchdog.h \ 31 | 32 | SOURCES := $(SOURCES:%=$(PREFIX)%) 33 | 34 | # Disable build/include, it complains that "foo.h" includes should prefix the 35 | # header name with the directory name but that's incompatible with node-gyp. 36 | # 37 | # Disable build/include_order, it wants system headers before project headers 38 | # but we want to catch bugs where project headers have implicit dependencies 39 | # on system headers included in source files. 40 | # 41 | # Disable readability/function, it conflicts with -Wunused-parameter. 42 | # 43 | # Disable whitespace/indent, it sometimes conflicts with clang-format. 44 | FILTER = \ 45 | -build/include, \ 46 | -build/include_order, \ 47 | -readability/function, \ 48 | -whitespace/indent, \ 49 | 50 | .PHONY: check-imports lint clang-format 51 | 52 | lint: check-imports 53 | $(PYTHON) $(PREFIX)cpplint.py --filter="$(FILTER)" $(SOURCES) 54 | 55 | check-imports: 56 | $(SHELL) $(PREFIX)check-imports.sh $(SOURCES) 57 | 58 | clang-format: 59 | $(CLANG_FORMAT) -i $(SOURCES) 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strong-agent", 3 | "version": "2.0.1", 4 | "description": "StrongOps Application Performance Monitoring Agent", 5 | "author": "StrongLoop ", 6 | "homepage": "http://strongloop.com", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/strongloop/strong-agent" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/strongloop/strong-agent/issues" 13 | }, 14 | "license": "SEE LICENSE IN LICENSE.md", 15 | "contributors": [], 16 | "main": "lib/main", 17 | "directories": { 18 | "lib": "./lib" 19 | }, 20 | "dependencies": { 21 | "debug": "^2.0.0", 22 | "semver": "^5.4.1", 23 | "strong-license": "^1.2.0" 24 | }, 25 | "devDependencies": { 26 | "async": "~0.9.0", 27 | "bluebird": "^2.10.0", 28 | "express": "~3.3.5", 29 | "loopback": "^2.21.0", 30 | "loopback-connector-postgresql": "^2.3.0", 31 | "loopback-datasource-juggler": "^2.36.0", 32 | "memcache": "latest", 33 | "mongodb": "latest", 34 | "mysql": "^2.7.0", 35 | "pg": "latest", 36 | "redis": "latest", 37 | "strong-express-metrics": "^1.0.1", 38 | "tap": "^0.4.13" 39 | }, 40 | "scripts": { 41 | "clang-format": "make clang-format", 42 | "install": "node-gyp rebuild || exit 0", 43 | "test": "tap --tap --gc --stderr --timeout 30 test/test-*.js", 44 | "lint": "make -C src lint" 45 | }, 46 | "engines": { 47 | "node": ">=0.10.0" 48 | }, 49 | "keywords": [ 50 | "apm", 51 | "monitoring", 52 | "performance", 53 | "profiling", 54 | "heap", 55 | "event loop", 56 | "analytics", 57 | "metrics", 58 | "alerts", 59 | "profiler", 60 | "response", 61 | "time", 62 | "memory", 63 | "slowest functions" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /src/dyninst.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, StrongLoop Inc. 2 | // 3 | // This software is covered by the StrongLoop License. See StrongLoop-LICENSE 4 | // in the top-level directory or visit http://strongloop.com/license. 5 | 6 | #ifndef AGENT_SRC_DYNINST_H_ 7 | #define AGENT_SRC_DYNINST_H_ 8 | 9 | #include "node_version.h" 10 | #include "strong-agent.h" 11 | #include "v8-debug.h" 12 | 13 | namespace strongloop { 14 | namespace agent { 15 | namespace dyninst { 16 | 17 | namespace C = compat; 18 | 19 | using v8::Context; 20 | using v8::Debug; 21 | using v8::Isolate; 22 | using v8::Local; 23 | using v8::Object; 24 | using v8::Script; 25 | using v8::String; 26 | using v8::Value; 27 | 28 | C::ReturnType RunInDebugContext(const C::ArgumentType& args) { 29 | C::ReturnableHandleScope handle_scope(args); 30 | Local source = args[0]->ToString(); 31 | Local context = Debug::GetDebugContext(); 32 | #if NODE_MAJOR_VERSION > 0 33 | if (context.IsEmpty()) { 34 | // Force-load the debug context. 35 | Debug::GetMirror(args.GetIsolate()->GetCurrentContext(), source); 36 | context = Debug::GetDebugContext(); 37 | } 38 | #endif 39 | CHECK_EQ(false, context.IsEmpty()); 40 | Context::Scope context_scope(context); 41 | Local