├── .clang-format ├── .gitignore ├── .jshintignore ├── .npmignore ├── CHANGES.md ├── LICENSE.md ├── Makefile ├── NOTICE ├── README.md ├── binding.gyp ├── docs.json ├── lib ├── addon.js ├── agent.js ├── config.js ├── counts.js ├── cpuinfo.js ├── custom-stats.js ├── debug.js ├── dyninst-metrics.js ├── dyninst.js ├── graph-helper.js ├── info.js ├── json.js ├── license.js ├── loop.js ├── main.js ├── metrics.js ├── module-detector.js ├── probes │ ├── axon.js │ ├── express.js │ ├── http.js │ ├── https.js │ ├── loopback-datasource-juggler.js │ ├── memcache.js │ ├── memcached.js │ ├── mongodb.js │ ├── mysql.js │ ├── oracle.js │ ├── oracledb.js │ ├── pg.js │ ├── redis.js │ ├── riak-js.js │ ├── sl-mq.js │ ├── strong-cluster-control.js │ ├── strong-express-metrics.js │ ├── strong-mq.js │ └── strong-oracle.js ├── proc.js ├── profilers │ ├── cpu.js │ └── memory.js ├── proxy.js ├── samples.js ├── tiers.js ├── timer.js ├── top-functions.js └── wrapping-probes │ └── leveldown.js ├── package.json ├── samples ├── app.js └── di.js ├── src ├── Makefile ├── check-imports.sh ├── compat-inl.h ├── compat.h ├── counters.h ├── cpplint.py ├── dyninst.h ├── extras.h ├── gcinfo.h ├── heapdiff.h ├── platform-posix.h ├── platform-win32.h ├── profiler.h ├── queue.h ├── strong-agent.cc ├── strong-agent.h ├── util-inl.h ├── util.h ├── uvmon.h └── watchdog.h └── test ├── dyninst-metrics-example-patch.json ├── dyninst-metrics-example.js ├── dyninst-target.js ├── express3 └── server.js ├── express4 └── server.js ├── fixtures └── null.js ├── helpers.js ├── license.js ├── strongloop.json ├── test-addon-counters.js ├── test-addon-heapdiff-eval.js ├── test-addon-heapdiff-long.js ├── test-addon-heapdiff.js ├── test-addon-missing.js ├── test-agent-configure.js ├── test-agent-dyninst-patch.js ├── test-agent-exits-on-server-shutdown.js ├── test-agent-has-profile-function.js ├── test-agent-has-stop-function.js ├── test-agent-intercepts-http-upgrade-event.js ├── test-agent-metrics-cpu-errors.js ├── test-agent-metrics-cpu.js ├── test-agent-metrics-heapdiff.js ├── test-agent-metrics.js ├── test-agent-profile-reports-version.js ├── test-agent-strong-trace.js ├── test-agent-use-reports-version.js ├── test-agent-use-requires-license.js ├── test-agent-use-tiers.js ├── test-call-counts.js ├── test-config-appname.js ├── test-config-logger.js ├── test-config.js ├── test-configurable-interval.js ├── test-counts.js ├── test-cpu-metrics-in-range.js ├── test-cpu-profiler-null-deref.js ├── test-cpu-profiler-path.js ├── test-cpu-profiler-start.js ├── test-cpu-profiler-stop.js ├── test-cpuinfo-when-no-fds ├── test-custom-stats.js ├── test-dyninst-metrics.js ├── test-dyninst-patch.js ├── test-event-loop-watchdog.js ├── test-express3.js ├── test-express4.js ├── test-graph-helper.js ├── test-heap-profiler-start.js ├── test-heap-profiler-stop.js ├── test-json.js ├── test-leveldown.js ├── test-licenses.js ├── test-linux-time.js ├── test-loop-statistics.js ├── test-method-proxy.js ├── test-metrics-poll.js ├── test-metrics-watchdog.js ├── test-metrics.js ├── test-no-watchdog.js ├── test-null-module.js ├── test-probe-juggler-promise.js ├── test-probe-juggler.js ├── test-probe-memcache.js ├── test-probe-memcached.js ├── test-probe-mongodb.js ├── test-probe-mysql.js ├── test-probe-oracle.js ├── test-probe-postgresql.js ├── test-probe-redis.js ├── test-profile-start-stop.js ├── test-redis-non-array-arg.js ├── test-strong-express-metrics.js ├── test-timer.js ├── test-top-functions.js ├── test-use-custom-stats.js └── test-watchdog-activation-count.js /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | node_modules 3 | coverage.html 4 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/express3/node_modules 3 | test/express4/node_modules 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "strong-agent", 3 | "content": [ 4 | "README.md" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var extend = require('util')._extend; 4 | var os = require('os'); 5 | 6 | var defaults = { 7 | baseInterval: 15 * 1000, 8 | }; 9 | 10 | /** 11 | * Cascading config loader 12 | * 13 | * Search order: 14 | * arguments 15 | * process.env 16 | * ./strongloop.json 17 | * ./package.json 18 | * ~/strongloop.json 19 | * 20 | * @param {string} [key] [API Key] 21 | * @param {string} [appName] [Name to identify app with in dashboard] 22 | * @returns {object} [Returns config data] 23 | */ 24 | function configure(userKey, appName, options, env) { 25 | var home = 26 | process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE, 27 | cwd = process.cwd(), nfjson, pkgjson, userjson; 28 | var slLicenses; 29 | 30 | // Load configs from strongloop.json and package.json 31 | try { 32 | nfjson = require(cwd + '/strongloop.json'); 33 | } catch (e) { 34 | nfjson = {}; 35 | } 36 | try { 37 | pkgjson = require(cwd + '/package.json'); 38 | } catch (e) { 39 | pkgjson = {}; 40 | } 41 | try { 42 | userjson = require(home + '/strongloop.json'); 43 | } catch (e) { 44 | userjson = {}; 45 | } 46 | try { 47 | // this is normally done by strongloop-license, but its API 48 | // isn't quite flexible enough to use in strong-agent yet 49 | slLicenses = require(home + '/.strongloop/licenses.json') 50 | .licenses.map(function(l) { 51 | return l.licenseKey; 52 | }); 53 | } catch (e) { 54 | slLicenses = []; 55 | } 56 | 57 | var config = { 58 | key: userKey || env.STRONGLOOP_KEY || env.SL_KEY || 59 | env.NODEFLY_APPLICATION_KEY || nfjson.userKey || 60 | pkgjson.strongAgentKey || 61 | userjson.key || // Bug-for-bug backwards compatibility... 62 | userjson.userKey, 63 | 64 | appName: appName || env.STRONGLOOP_APPNAME || env.SL_APP_NAME || 65 | nfjson.appName || pkgjson.name || userjson.appName, 66 | 67 | license: options.license || env.STRONGLOOP_LICENSE || 68 | env.STRONG_AGENT_LICENSE || nfjson.agent_license || '', 69 | 70 | logger: options.logger || console, 71 | }; 72 | 73 | // add licenses found in ~/.strongloop/licenses.json 74 | // and make config.license an Array of licenses 75 | config.license = (config.license || '').split(':') 76 | .concat(slLicenses) 77 | .filter(function(l) { 78 | return !!l; 79 | }); 80 | 81 | appName = config.appName; 82 | 83 | if (appName instanceof Array) { 84 | config.appName = appName.shift(); 85 | config.hostname = appName.join(':'); 86 | } else { 87 | config.hostname = os.hostname(); 88 | } 89 | 90 | var result = extend(extend({}, defaults), config); 91 | if (options.interval) { 92 | result.baseInterval = options.interval; 93 | } 94 | return result; 95 | } 96 | 97 | module.exports = defaults; 98 | module.exports.configure = configure; 99 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/dyninst-metrics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var debug = require('./debug')('dyninst-metrics'); 5 | var util = require('util'); 6 | 7 | exports.init = function init(agent, dyninst) { 8 | exports.findScript = findScript; 9 | exports.patchLine = patchLine; 10 | exports.patch = patch; 11 | 12 | // name is a regex string long enough to uniquely match a script name. 13 | // 14 | // Script names are fully qualified when loaded using node require, 15 | // and short when builtin to node ('util.js'). 16 | // 17 | // Return is a debug script object if a unique match was found, null 18 | // otherwise. 19 | function findScript(name) { 20 | var re = RegExp(name); 21 | var scripts = dyninst.scripts().filter(function(script) { 22 | return re.test(script.name); 23 | }); 24 | // Mirrors what V8's Debug.findScript(re) does. 25 | return scripts.length === 1 ? scripts[0] : undefined; 26 | } 27 | 28 | // line is 1-base (first line is line 1), patch is literal code to insert 29 | // at beginning of line 30 | function patchLine(name, line, patch) { 31 | var script = findScript(name); 32 | 33 | if (!script) throw Error('noscript'); 34 | 35 | var position = dyninst.debug().findScriptSourcePosition(script, line - 1); 36 | 37 | if (position == null) throw Error('noline'); 38 | 39 | debug('patch script %s line %d pos %d: %j', script.name, line, position, 40 | patch); 41 | var changes = [position, patch]; 42 | 43 | return dyninst.patch(script, changes); 44 | } 45 | 46 | // patchset looks like: 47 | // 48 | // { 49 | // NAME: [ PATCH, ...], 50 | // ... 51 | // } 52 | // 53 | // NAME is a regular expression matching filename to patch 54 | // 55 | // PATCH looks like: 56 | // { 57 | // type: TYPE, 58 | // line: LINE, 59 | // [metric: METRIC] 60 | // [code: CODE,] 61 | // [context: CTX,] 62 | // } 63 | // 64 | // For type: 65 | // - code: code is literal code to insert 66 | // - increment, decrement, timer-start, timer-stop: 67 | // - metric is dot-separated metric name 68 | // 69 | // XXX(sam) I could separate format checking from application of patch, to 70 | // allow client to report verbose format errors without round-trip through 71 | // supervisor/agent. 72 | function patch(patchset) { 73 | var name; 74 | var patch; 75 | 76 | try { 77 | for (name in patchset) { 78 | var patches = patchset[name]; 79 | patches.forEach(function(_) { 80 | patch = _; 81 | debug('patching %s: %j', name, patch); 82 | assert(patch.type, 'patch for ' + name + ' has no type'); 83 | assert(patch.line, 'patch for ' + name + ' has no line'); 84 | switch (patch.type) { 85 | case 'code': 86 | assert(patch.code, 'nocode'); 87 | patchLine(name, patch.line, patch.code); 88 | break; 89 | 90 | case 'increment': 91 | case 'decrement': 92 | patchLine(name, patch.line, incdec(patch.type, patch.metric)); 93 | break; 94 | 95 | case 'timer-start': 96 | patchLine(name, patch.line, 97 | timerStart(patch.metric, patch.context)); 98 | break; 99 | 100 | case 'timer-stop': 101 | patchLine(name, patch.line, timerStop(patch.context)); 102 | break; 103 | 104 | default: 105 | throw Error('unsupported patch type: ' + patch.type); 106 | break; 107 | } 108 | }); 109 | } 110 | } catch (er) { 111 | patch.file = name; 112 | debug('patch failed: %s %j', er.message, patch); 113 | // 'illegal access' exceptions are simple strings, not Error objects. 114 | return {error: er.message || er, patch: patch}; 115 | } 116 | } 117 | 118 | function checkStatFormat(stat) { 119 | if (stat == null) { 120 | throw Error('stat is missing'); 121 | } 122 | if (typeof stat !== 'string') { 123 | throw Error('stat is not a string'); 124 | } 125 | if (!/^\w+(?:\.\w+)*$/.test(stat)) { 126 | throw Error('stat is not dot-separated words'); 127 | } 128 | return stat; 129 | } 130 | 131 | function checkContext(ctx) { 132 | if (ctx == null) { 133 | throw Error('context is missing'); 134 | } 135 | if (typeof ctx !== 'string') { 136 | throw Error('context is not a string'); 137 | } 138 | // XXX(sam) not so clear what else we can check, validity as an assignment 139 | // LHS depends on location of insertion 140 | return ctx; 141 | } 142 | 143 | function incdec(call, stat) { 144 | checkStatFormat(stat); 145 | return util.format('global.STRONGAGENT.metrics.stats.%s(\'%s\');', call, 146 | stat); 147 | } 148 | 149 | // XXX(sam) try/catch will cause v8 to de-optimize the function, I could 150 | // also pass all args to global.STRONGAGENT, and let it do the try/catch, 151 | // or even wrap in an anonymous function. I'm not sure if this is a problem 152 | // ATM, though. 153 | function timerStart(stat, ctx) { 154 | checkContext(ctx); 155 | return util.format('try{%s.___timer = %s}catch(_){};', ctx, 156 | incdec('createTimer', stat)); 157 | } 158 | 159 | function timerStop(ctx) { 160 | checkContext(ctx); 161 | return util.format('try{%s.___timer.stop()}catch(_){};', ctx); 162 | } 163 | 164 | return exports; 165 | }; 166 | -------------------------------------------------------------------------------- /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/info.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var addon = require('./addon'); 4 | var cpuinfo = require('./cpuinfo'); 5 | 6 | var agent; 7 | var gcstats = []; 8 | var timebase; 9 | 10 | exports.init = function(agent_) { 11 | agent = agent_; 12 | 13 | if (!addon) { 14 | return; 15 | } 16 | 17 | addon[addon.kGarbageCollectorStatisticsCallback] = function(samples) { 18 | for (var i = 0, n = samples.length; i < n; i += 1) { 19 | var len = gcstats.push(samples[i]); 20 | var total = gcstats.reduce(function(a, b) { return a + b }); 21 | var baseline = total / len / 1e6; 22 | agent.metric('GC Full. V8 heap used', baseline); 23 | collectHeap(baseline); 24 | if (len > 10) { 25 | gcstats.shift(); // Sliding window. 26 | } 27 | } 28 | }; 29 | addon.startGarbageCollectorStatistics(); 30 | 31 | timebase = Date.now(); 32 | collect(); 33 | }; 34 | 35 | exports.poll = function() { 36 | collect(); 37 | connectionInfo(); 38 | collectHeap(); 39 | }; 40 | 41 | function collectHeap(gcFull) { 42 | var mem = process.memoryUsage(); 43 | var rss = mem.rss / 1000000; 44 | var heapUsed = mem.heapUsed / 1000000; 45 | var heapData = [heapUsed, rss, gcFull]; 46 | agent.metric('Heap Data', heapData); 47 | } 48 | 49 | function connectionInfo() { 50 | // FIXME(bnoordhuis) Tracks only one HTTP server per process and it's not 51 | // very deterministic what server that is... 52 | var server = agent.httpServer; 53 | if (server == null) return; 54 | var kContextPropertyName = '__STRONGOPS_HTTP_CONTEXT__'; 55 | var context = server[kContextPropertyName]; 56 | var curr = context.connectionCounts[0]; 57 | var prev = context.connectionCounts[1]; 58 | context.connectionCounts[0] = 0; 59 | context.connectionCounts[1] = curr; 60 | if (server.getConnections) { 61 | server.getConnections(callback); 62 | } else { 63 | callback(null, server.connections || server._connections || 0); 64 | } 65 | function callback(err, conns) { 66 | if (err) return; 67 | var now = Date.now(); 68 | var tps = curr / (now - timebase) / 1000; 69 | timebase = now; 70 | var metrics = [conns, tps, curr, prev]; 71 | agent.metric('Connections', metrics); 72 | } 73 | } 74 | 75 | function collect() { 76 | cpuinfo.cpuutil(function(percent_proc, percent_user, percent_syst) { 77 | agent.metric('CPU util', percent_proc); 78 | agent.metric('CPU util stime', percent_syst); 79 | agent.metric('CPU util utime', percent_user); 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /lib/json.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 stream = require('stream'); 9 | var util = require('util'); 10 | 11 | exports.JsonDecoder = JsonDecoder; 12 | exports.JsonEncoder = JsonEncoder; 13 | 14 | // |maxlen| is the amount of unparsed JSON data that may be buffered. 15 | // |maxlen| < 1 means 'unlimited' 16 | // 17 | // When the |maxlen| threshold is exceeded, an 'error' event is emitted on 18 | // the decoder object and the decoder stops parsing. The decoder can then 19 | // be resumed by feeding it more data. 20 | function JsonDecoder(maxlen) { 21 | if (!(this instanceof JsonDecoder)) return new JsonDecoder(maxlen); 22 | this.constructor.call(this, {objectMode: true}); 23 | this.buffer_ = ''; 24 | this.index_ = 0; 25 | this.maxlen_ = (maxlen | 0) || -1; 26 | this.slice_ = null; 27 | } 28 | 29 | JsonDecoder.prototype = Object.create(stream.Transform.prototype); 30 | 31 | JsonDecoder.prototype._transform = function(chunk, encoding, done) { 32 | this.buffer_ += chunk.toString(); 33 | for (;;) { 34 | this.slice_ = null; 35 | var index = this.buffer_.indexOf('\n', this.index_); 36 | if (index === -1) { 37 | this.index_ = this.buffer_.length; 38 | // Do the check now rather than before entering the loop. If the new 39 | // chunk causes the threshold to be exceeded but contains the newline 40 | // that we're looking for, then we might as well parse the JSON. But 41 | // if there is still no newline, report a 'threshold exceeded' error. 42 | if (this.maxlen_ > 0 && this.buffer_.length > this.maxlen_) { 43 | var err = Error(util.format('Buffer size %d exceeds threshold.', 44 | this.buffer_.length)); 45 | this.emit('error', err); 46 | return done(); 47 | } 48 | break; 49 | } 50 | var length = index + 1; 51 | this.slice_ = this.buffer_.slice(0, length); 52 | this.buffer_ = this.buffer_.slice(length); 53 | this.index_ -= index; 54 | if (length === 1) { // A single newline is not a JSON object. 55 | continue; 56 | } 57 | try { 58 | var object = JSON.parse(this.slice_); 59 | this.slice_ = null; 60 | } catch (ex) { 61 | var err = ex; 62 | // Make the property non-enumerable so that printing the error object 63 | // won't also print possibly megabytes of JSON data. 64 | Object.defineProperty(err, 'data', {value: this.slice_}); 65 | } 66 | if (err) { 67 | this.emit('error', err); 68 | err = null; 69 | } else { 70 | this.push(object); 71 | } 72 | if (this.paused) { 73 | break; 74 | } 75 | } 76 | done(); 77 | }; 78 | 79 | function JsonEncoder() { 80 | if (!(this instanceof JsonEncoder)) return new JsonEncoder; 81 | this.constructor.call(this, {objectMode: true}); 82 | } 83 | 84 | JsonEncoder.prototype = Object.create(stream.Transform.prototype); 85 | 86 | JsonEncoder.prototype._transform = function(chunk, encoding, done) { 87 | this.push(JSON.stringify(chunk) + '\n', 'utf8'); 88 | done(); 89 | }; 90 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./agent'); 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/probes/http.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var agent = require('../agent'); 5 | var proxy = require('../proxy'); 6 | var samples = require('../samples'); 7 | var tiers = require('../tiers'); 8 | var topFunctions = require('../top-functions'); 9 | var graphHelper = require('../graph-helper'); 10 | 11 | module.exports = function(http) { 12 | var config = agent.config; 13 | 14 | // server probe 15 | proxy.before(http.Server.prototype, ['on', 'addListener'], 16 | function(server, args) { 17 | 18 | // store ref to server so we can pull current connections 19 | agent.httpServer = server; 20 | 21 | var kContextPropertyName = '__STRONGOPS_HTTP_CONTEXT__'; 22 | var context = server[kContextPropertyName]; 23 | if (context == null) { 24 | // Index [0] is current connection counter, [1] is the previous one. 25 | context = {connectionCounts: [0, 0]}; 26 | Object.defineProperty(server, kContextPropertyName, {value: context}); 27 | } 28 | 29 | if (args[0] !== 'request' && args[0] !== 'upgrade') return; 30 | 31 | proxy.callback(args, -1, function(obj, args) { 32 | context.connectionCounts[0] += 1; 33 | 34 | if (agent.paused) return; 35 | 36 | var req = args[0]; 37 | var res = args[1]; 38 | var timer = samples.timer("HTTP Server", req.url, true); 39 | req.tiers = timer.tiers = agent.extra = {}; 40 | 41 | var graph = agent.graph = {nodes: [{name: req.url}], links: []}; 42 | req.graph = graph; 43 | var currentNode = agent.currentNode = 0; 44 | 45 | proxy.after(res, 'end', function(obj, args) { 46 | timer.end(); 47 | 48 | graph.nodes[0].value = timer.ms; 49 | topFunctions.add('httpCalls', req.url, timer.ms, timer.tiers, graph); 50 | tiers.sample('http', timer); 51 | 52 | timer.tiers.closed = true; 53 | }); 54 | }); 55 | }); 56 | 57 | // client probe 58 | function getClientResponseHandler(url, host, timer, graphNode) { 59 | return function handleResponseCb(obj, args, extra) { 60 | var res = args[0]; 61 | 62 | proxy.before(res, ['on', 'addListener', 'once'], function(res, args) { 63 | if (args[0] !== 'end') return; 64 | 65 | proxy.callback(args, -1, function(obj, args, extra) { 66 | timer.end(); 67 | // if (!time || !timer.done()) return; 68 | 69 | topFunctions.add('externalCalls', url, timer.ms); 70 | graphHelper.updateTimes(graphNode, timer); 71 | 72 | if (extra) { 73 | extra[host] = extra[host] || 0; 74 | extra[host] += timer.ms; 75 | 76 | if (extra.closed) { 77 | if (typeof host === 'string') tiers.sample(host + '_out', timer); 78 | } else { 79 | if (typeof host === 'string') tiers.sample(host + '_in', timer); 80 | } 81 | } 82 | }); // res end cb 83 | 84 | }); // res end 85 | } 86 | } 87 | 88 | // handle http.request with callback 89 | proxy.before(http, 'request', function(obj, args) { 90 | var opts = args[0]; 91 | var cb = args[1]; 92 | 93 | if (typeof cb != 'function') return; 94 | 95 | if (opts.headers || opts._headers) { 96 | // get the url 97 | var headers = opts._headers || opts.headers; 98 | var method = opts.method || ''; 99 | var host = headers.Host || headers.host || ''; 100 | var path = opts.path; 101 | var url = util.format('%s http://%s%s', method, host, path); 102 | 103 | var timer = samples.timer("HTTP Client", url, true); 104 | var graphNode = graphHelper.startNode('Outgoing HTTP', url, agent); 105 | 106 | proxy.callback(args, -1, 107 | getClientResponseHandler(url, host, timer, graphNode)); 108 | if (graphNode) agent.currentNode = graphNode.prevNode; 109 | } 110 | }); 111 | 112 | // handle ClientRequest, evented. 113 | if (http.ClientRequest && http.ClientRequest.prototype) { 114 | proxy.before(http.ClientRequest.prototype, ['on', 'addListener', 'once'], 115 | function onResponse(req, args) { 116 | if (args[0] !== 'response') return; 117 | 118 | if (req._headers || req.headers) { 119 | var headers = req._headers || req.headers; 120 | var method = req.method || ''; 121 | var host = headers.Host || headers.host || ''; 122 | var path = req.path; 123 | var url = util.format('%s http://%s%s', method, host, path); 124 | 125 | var timer = samples.timer("HTTP Server", url, true); 126 | var graphNode = graphHelper.startNode('Outgoing HTTP', url, agent); 127 | 128 | proxy.callback(args, -1, 129 | getClientResponseHandler(url, host, timer, graphNode)); 130 | if (graphNode) agent.currentNode = graphNode.prevNode; 131 | } 132 | 133 | }); // before on/add/once 134 | 135 | } // http.ClientRequest 136 | 137 | }; 138 | -------------------------------------------------------------------------------- /lib/probes/https.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./http'); 4 | -------------------------------------------------------------------------------- /lib/probes/loopback-datasource-juggler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../agent'); 4 | var samples = require('../samples'); 5 | var tiers = require('../tiers'); 6 | var proxy = require('../proxy'); 7 | var extend = require('util')._extend; 8 | var topFunctions = require('../top-functions'); 9 | 10 | var commands = { 11 | create: 'create', 12 | findOrCreate: 'find_or_create', 13 | exists: 'exists', 14 | find: 'find', 15 | findById: 'find_by_id', 16 | remove: 'remove', 17 | removeById: 'remove_by_id', 18 | count: 'count' 19 | }; 20 | 21 | var instanceCommands = { 22 | save: 'save', 23 | remove: 'remove', 24 | updateAttribute: 'update_attribute', 25 | updateAttributes: 'update_attributes', 26 | reload: 'reload' 27 | }; 28 | 29 | module.exports = function(juggler) { 30 | var dao = juggler.Schema.DataAccessObject; 31 | 32 | function createPatcher(obj) { 33 | return function patch(command) { 34 | var _old = dao[command]; 35 | proxy.before(obj, command, function(obj, args) { 36 | if (args.length === 0 || 37 | typeof args[args.length - 1] !== 'function') { 38 | // TODO(bnoordhuis) loopback-datasource-juggler methods return 39 | // promises when the callback is omitted. We don't yet support 40 | // instrumenting those. 41 | return; 42 | } 43 | var cmd = commands[command]; 44 | agent.strongTraceLink('DAO', command, args); 45 | var timer = samples.timer('DataAccessObject', cmd); 46 | proxy.callback(args, -1, 47 | function(obj, args, extra, graph, currentNode) { 48 | timer.end(); 49 | topFunctions.add('jugglerCall', command, timer.ms); 50 | if (extra) { 51 | extra.dao = extra.dao || 0; 52 | extra.dao += timer.ms; 53 | if (extra.closed) { 54 | tiers.sample('dao_out', timer); 55 | } else { 56 | tiers.sample('dao_in', timer); 57 | } 58 | } else { 59 | tiers.sample('dao_in', timer); 60 | } 61 | }); 62 | }); 63 | extend(dao[command], _old); 64 | }; 65 | } 66 | 67 | Object.keys(commands).forEach(createPatcher(dao)); 68 | Object.keys(instanceCommands).forEach(createPatcher(dao.prototype)); 69 | }; 70 | -------------------------------------------------------------------------------- /lib/probes/memcache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../agent'); 4 | var proxy = require('../proxy'); 5 | var samples = require('../samples'); 6 | var counts = require('../counts'); 7 | var tiers = require('../tiers'); 8 | var topFunctions = require('../top-functions'); 9 | var graphHelper = require('../graph-helper'); 10 | var memcached = require('./memcached'); 11 | 12 | var commands = [ 13 | 'get', 14 | 'set', 15 | 'delete', 16 | 'add', 17 | 'replace', 18 | 'append', 19 | 'prepend', 20 | 'cas', 21 | 'increment', 22 | 'decrement', 23 | 'samples' 24 | ]; 25 | 26 | var findCallback = function(args) { 27 | for (var i = 0; i < args.length; i++) 28 | if (typeof args[i] === 'function') return i; 29 | }; 30 | 31 | module.exports = function(obj) { 32 | 33 | function strongTraceInject(query, args){ 34 | var callbackIndex = args.length - 1; 35 | if (typeof args[callbackIndex] === 'function') { 36 | args[callbackIndex] = 37 | strongTraceTransaction(query, args[callbackIndex]); 38 | } 39 | } 40 | 41 | function strongTraceTransaction(query, callback){ 42 | var linkName = "Memcache " + query; 43 | return agent.transactionLink(linkName, callback); 44 | } 45 | 46 | // connect 47 | proxy.after(obj.Client.prototype, 'connect', function(obj, args, ret) { 48 | obj.__timer__ = samples.timer("Memcached", "connect"); 49 | }); 50 | 51 | proxy.before(obj.Client.prototype, 'on', function(obj, args) { 52 | var client = obj; 53 | var event = args[0]; 54 | if (event !== 'connect' && event !== 'timeout' && event !== 'error') return; 55 | 56 | strongTraceInject(event, args); 57 | proxy.callback(args, -1, function(obj, args) { 58 | if (agent.paused) return; 59 | 60 | var timer = client.__timer__; 61 | // if(!time || !timer.done()) return; 62 | timer.end(); 63 | 64 | // not doing anything with on connect/timeout/error events just yet 65 | }); 66 | }); 67 | 68 | // commands 69 | commands.forEach(function(command) { 70 | proxy.before(obj.Client.prototype, command, function(client, args) { 71 | if (agent.paused) return; 72 | 73 | var timer = samples.timer("Memcached", command); 74 | counts.sample('memcached'); 75 | 76 | // there might be args after callback, need to do extra callback search 77 | var pos = findCallback(args); 78 | if (pos == undefined) return; 79 | 80 | var query = command + ' ' + args[0]; 81 | var graphNode = graphHelper.startNode('Memcached', query, agent); 82 | 83 | args[pos] = 84 | strongTraceTransaction(memcached.getCommandAndKey(command, args), 85 | args[pos]); 86 | 87 | proxy.callback(args, pos, function(obj, args, extra) { 88 | timer.end(); 89 | 90 | topFunctions.add('memcacheCalls', query, timer.ms); 91 | graphHelper.updateTimes(graphNode, timer); 92 | if (extra) { 93 | extra.memcached = extra.memcached || 0; 94 | extra.memcached += timer.ms; 95 | 96 | if (extra.closed) { 97 | tiers.sample('memcached_out', timer); 98 | } else { 99 | tiers.sample('memcached_in', timer); 100 | } 101 | } else { 102 | tiers.sample('memcached_in', timer); 103 | } 104 | }); // callback 105 | 106 | if (graphNode) agent.currentNode = graphNode.prevNode; 107 | }); 108 | }); 109 | 110 | }; // exports 111 | -------------------------------------------------------------------------------- /lib/probes/memcached.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../agent'); 4 | var proxy = require('../proxy'); 5 | var samples = require('../samples'); 6 | var counts = require('../counts'); 7 | var tiers = require('../tiers'); 8 | var topFunctions = require('../top-functions'); 9 | var graphHelper = require('../graph-helper'); 10 | 11 | exports = module.exports = memcached; 12 | exports.getCommandAndKey = getCommandAndKey; 13 | 14 | var commands = [ 15 | 'get', 16 | 'gets', 17 | 'getMulti', 18 | 'set', 19 | 'replace', 20 | 'add', 21 | 'cas', 22 | 'append', 23 | 'prepend', 24 | 'increment', 25 | 'decrement', 26 | 'incr', 27 | 'decr', 28 | 'del', 29 | 'delete', 30 | 'version', 31 | 'flush', 32 | 'samples', 33 | 'slabs', 34 | 'items', 35 | 'flushAll', 36 | 'samplesSettings', 37 | 'samplesSlabs', 38 | 'samplesItems', 39 | 'cachedump' 40 | ]; 41 | 42 | function memcached(memcached) { 43 | 44 | commands.forEach(function(command) { 45 | proxy.before(memcached.prototype, command, function(client, args) { 46 | if (agent.paused) return; 47 | 48 | // ignore, getMulti will be called 49 | if (command === 'get' && Array.isArray(args[0])) return; 50 | 51 | var timer = samples.timer("Memcached", command); 52 | var graphNode = graphHelper.startNode('Memcached', command, agent); 53 | counts.sample('memcached'); 54 | 55 | var query = command + ' ' + args[0]; 56 | 57 | function strongTraceTransaction(query, callback){ 58 | var linkName = "Memcached " + query; 59 | return agent.transactionLink(linkName, callback); 60 | } 61 | 62 | function handle (obj, args, extra) { 63 | timer.end(); 64 | 65 | topFunctions.add('memcacheCalls', query, timer.ms); 66 | graphHelper.updateTimes(graphNode, timer); 67 | if (extra) { 68 | extra.memcached = extra.memcached || 0; 69 | extra.memcached += timer.ms; 70 | if (extra.closed) { 71 | tiers.sample('memcached_out', timer); 72 | } else { 73 | tiers.sample('memcached_in', timer); 74 | } 75 | } else { 76 | tiers.sample('memcached_in', timer); 77 | } 78 | } 79 | 80 | proxy.callback(args, -1, 81 | strongTraceTransaction(getCommandAndKey(command, args), handle)); 82 | 83 | if (graphNode) agent.currentNode = graphNode.prevNode; 84 | }); 85 | }); 86 | }; 87 | 88 | function getCommandAndKey(command, args) { 89 | var keyValue = ''; 90 | if (typeof args[0] === 'string') keyValue += ' "' + args[0] + '"'; 91 | if (typeof args[1] === 'string') keyValue += ' "' + args[1] + '"'; 92 | return command + keyValue; 93 | }; 94 | -------------------------------------------------------------------------------- /lib/probes/mongodb.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../agent'); 4 | var proxy = require('../proxy'); 5 | var samples = require('../samples'); 6 | var counts = require('../counts'); 7 | var tiers = require('../tiers'); 8 | var topFunctions = require('../top-functions'); 9 | var graphHelper = require('../graph-helper'); 10 | 11 | var internalCommands = [ 12 | '_executeQueryCommand', 13 | '_executeInsertCommand', 14 | '_executeUpdateCommand', 15 | '_executeRemoveCommand' 16 | ]; 17 | 18 | var commandMap = { 19 | '_executeQueryCommand': 'find', 20 | '_executeInsertCommand': 'insert', 21 | '_executeUpdateCommand': 'update', 22 | '_executeRemoveCommand': 'remove' 23 | }; 24 | 25 | var tier = 'mongodb'; 26 | function recordExtra(extra, timer) { 27 | if (extra) { 28 | extra[tier] = extra[tier] || 0; 29 | extra[tier] += timer.ms; 30 | 31 | if (extra.closed) { 32 | tiers.sample(tier + '_out', timer); 33 | } else { 34 | tiers.sample(tier + '_in', timer); 35 | } 36 | } else { 37 | tiers.sample(tier + '_in', timer); 38 | } 39 | } 40 | 41 | // Map to convert query property types to sane defaults 42 | var blank = { 43 | '[object String]': '', 44 | '[object Boolean]': false, 45 | '[object Number]': 0, 46 | '[object RegExp]': /.*/, 47 | '[object Date]': new Date 48 | }; 49 | 50 | var toString = Object.prototype.toString; 51 | 52 | module.exports = function(mongodb) { 53 | internalCommands.forEach(function(internalCommand) { 54 | var cmd = commandMap[internalCommand]; 55 | proxy.before(mongodb.Collection.prototype, cmd, function(obj, args) { 56 | var command = args[0] || {}; 57 | var q = typeof(command) === 'object' ? 58 | JSON.stringify(command) : command.toString(); 59 | var fullQuery = cmd + '(' + q + ')'; 60 | 61 | function strongTraceTransaction(query, callback){ 62 | var linkName = "MongoDB " + query; 63 | return agent.transactionLink(linkName, callback); 64 | } 65 | 66 | var timer = samples.timer("MongoDB", commandMap[internalCommand]); 67 | 68 | var callbackIndex = args.length - 1; 69 | while (callbackIndex >= 0 && typeof(args[callbackIndex]) !== 'function') { 70 | callbackIndex -= 1; 71 | } 72 | 73 | var graphNode = graphHelper.startNode('MongoDB', fullQuery, agent); 74 | counts.sample('mongodb'); 75 | 76 | function mongoCalls(obj, args, extra, graph, currentNode) { 77 | timer.end(); 78 | topFunctions.add('mongoCalls', fullQuery, timer.ms); 79 | recordExtra(extra, timer); 80 | graphHelper.updateTimes(graphNode, timer); 81 | } 82 | 83 | if (callbackIndex === -1) { 84 | // updates and inserts are fire and forget unless safe is set 85 | // record these in top functions, just for tracking 86 | topFunctions.add('mongoCalls', fullQuery, 0); 87 | tiers.sample(tier + '_in', timer); 88 | } else { 89 | args[callbackIndex] = 90 | strongTraceTransaction(fullQuery, args[callbackIndex]); 91 | proxy.callback(args, callbackIndex, mongoCalls); 92 | } 93 | 94 | if (graphNode) agent.currentNode = graphNode.prevNode; 95 | }); 96 | }); // all commands 97 | }; // require mongo 98 | -------------------------------------------------------------------------------- /lib/probes/mysql.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var agent = require('../agent'); 5 | var proxy = require('../proxy'); 6 | var samples = require('../samples'); 7 | var counts = require('../counts'); 8 | var tiers = require('../tiers'); 9 | var topFunctions = require('../top-functions'); 10 | var graphHelper = require('../graph-helper'); 11 | 12 | // |require_| is used by the test suite, lib/agent.js always passes undefined. 13 | module.exports = function(obj, require_) { 14 | 15 | var Connection = findConnection(obj, require_ || require); 16 | if (!Connection) { 17 | return agent.error('failed to instrument mysql'); 18 | } 19 | 20 | proxy.before(Connection.prototype, 'query', function(obj, args) { 21 | if (agent.paused) return; 22 | 23 | var command = args.length > 0 ? args[0] : undefined; 24 | 25 | var params = 26 | args.length > 1 && Array.isArray(args[1]) ? args[1] : undefined; 27 | var timer = samples.timer("MySQL", "query"); 28 | 29 | var graphNode = graphHelper.startNode('MySQL', command, agent); 30 | counts.sample('mysql'); 31 | 32 | function handle(obj, args, extra, graph, currentNode) { 33 | timer.end(); 34 | topFunctions.add('mysqlCalls', command, timer.ms); 35 | 36 | graphHelper.updateTimes(graphNode, timer); 37 | 38 | if (extra) { 39 | extra.mysql = extra.mysql || 0; 40 | extra.mysql += timer.ms; 41 | if (extra.closed) { 42 | tiers.sample('mysql_out', timer); 43 | } else { 44 | tiers.sample('mysql_in', timer); 45 | } 46 | } else { 47 | tiers.sample('mysql_in', timer); 48 | } 49 | } 50 | 51 | agent.strongTraceLink("MySQL", command, args); 52 | proxy.callback(args, -1, handle, null, true); 53 | 54 | if (graphNode) agent.currentNode = graphNode.prevNode; 55 | }); 56 | }; 57 | 58 | function findConnection(mysql, require) { 59 | var cache = require.cache; 60 | for (var key in cache) { 61 | var candidate = cache[key]; 62 | if (candidate.exports !== mysql) { 63 | continue; 64 | } 65 | var dirname = path.dirname(candidate.filename); 66 | var filename = path.join(dirname, 'lib', 'Connection.js'); 67 | try { 68 | return require(filename); 69 | } catch (e) { 70 | return null; 71 | } 72 | } 73 | return null; 74 | } 75 | -------------------------------------------------------------------------------- /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/oracledb.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 | var counts = require('../counts'); 8 | var tiers = require('../tiers'); 9 | var graphHelper = require('../graph-helper'); 10 | 11 | exports = module.exports = oracledb; 12 | exports.oracleBase = oracleBase; 13 | 14 | function oracledb(oracle) { 15 | oracleBase(oracle, 'getConnection', 'getConnection', 'Oracledb'); 16 | } 17 | 18 | function oracleBase(oracle, connSyncCmd, connCmd, linkName) { 19 | 20 | proxy.after(oracle, connSyncCmd, function(obj, args, connection) { 21 | proxy_connection(connection, linkName); 22 | }); 23 | 24 | proxy.before(oracle, connCmd, function(obj, args) { 25 | 26 | agent.strongTraceLink(linkName, connCmd, args); 27 | proxy.callback(args, -1, function (obj, args) { 28 | var connection = args[1]; 29 | proxy_connection(connection, linkName); 30 | }); 31 | }); 32 | } 33 | 34 | function proxy_connection(connection, linkName) { 35 | if (!connection) return; 36 | 37 | proxy.around( 38 | connection, 'executeSync', 39 | // before 40 | function(obj, args, locals) { query_before(args, locals, linkName); }, 41 | // after 42 | function(obj, args, ret, locals) { query_after(locals); }); 43 | 44 | proxy.before(connection, ['execute'], function(obj, args) { 45 | // query starts 46 | var locals = {}; 47 | query_before(args, locals, linkName); 48 | 49 | proxy.callback(args, -1, function(obj, args) { 50 | // query ends 51 | query_after(locals); 52 | }); 53 | }); 54 | 55 | ['commit', 'rollback'].forEach(function(method) { 56 | proxy.before(connection, method, function(obj, args) { 57 | // query starts 58 | var locals = {command: method}; 59 | query_before(args, locals, linkName); 60 | proxy.callback(args, -1, function(obj, args, extra) { 61 | // query ends 62 | query_after(locals, extra); 63 | }); 64 | }); 65 | }); 66 | } 67 | 68 | function query_before(args, locals, linkName) { 69 | locals.command = locals.command || (args.length > 0 ? args[0] : undefined); 70 | locals.timer = samples.timer('Oracle', 'query'); 71 | locals.graphNode = graphHelper.startNode('Oracle', locals.command, agent); 72 | if (locals.graphNode) { 73 | agent.currentNode = locals.graphNode.prevNode; 74 | } 75 | counts.sample('oracle'); 76 | agent.strongTraceLink(linkName, locals.command, args); 77 | } 78 | 79 | function query_after(locals, extra) { 80 | var tier = extra && extra.closed ? 'oracle_out' : 'oracle_in'; 81 | locals.timer.end(); 82 | topFunctions.add('oracleCalls', locals.command, locals.timer.ms); 83 | graphHelper.updateTimes(locals.graphNode, locals.timer); 84 | tiers.sample(tier, locals.timer); 85 | } 86 | -------------------------------------------------------------------------------- /lib/probes/pg.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 samples = require('../samples'); 7 | var counts = require('../counts'); 8 | var tiers = require('../tiers'); 9 | var topFunctions = require('../top-functions'); 10 | 11 | var tier = 'postgres'; 12 | function recordExtra(extra, timer) { 13 | if (extra) { 14 | extra[tier] = extra[tier] || 0; 15 | extra[tier] += timer.ms; 16 | 17 | if (extra.closed) { 18 | tiers.sample(tier + '_out', timer); 19 | } else { 20 | tiers.sample(tier + '_in', timer); 21 | } 22 | } else { 23 | tiers.sample(tier + '_in', timer); 24 | } 25 | } 26 | 27 | module.exports = function(obj) { 28 | 29 | function probe(obj) { 30 | if (obj.__probeInstalled__) return; 31 | obj.__probeInstalled__ = true; 32 | 33 | function strongTraceTransaction(query, callback){ 34 | var linkName = "PostgreSQL " + query; 35 | return agent.transactionLink(linkName, callback); 36 | } 37 | 38 | // Callback API 39 | proxy.before(obj, 'query', function(obj, args, ret) { 40 | var command = args.length > 0 ? args[0] : undefined; 41 | var params = 42 | args.length > 1 && Array.isArray(args[1]) ? args[1] : undefined; 43 | var timer = samples.timer("PostgreSQL", "query"); 44 | counts.sample('postgres'); 45 | 46 | function handleCallback(obj, args, extra) { 47 | timer.end(); 48 | 49 | topFunctions.add('postgresCalls', command, timer.ms); 50 | recordExtra(extra, timer); 51 | } 52 | 53 | proxy.callback(args, -1, strongTraceTransaction(command, handleCallback)); 54 | }); 55 | 56 | // Evented API 57 | proxy.after(obj, 'query', function(obj, args, ret) { 58 | // If has a callback, ignore 59 | if (args.length > 0 && typeof args[args.length - 1] === 'function') 60 | return; 61 | 62 | var client = obj; 63 | var command = args.length > 0 ? args[0] : undefined; 64 | var params = 65 | args.length > 1 && Array.isArray(args[1]) ? args[1] : undefined; 66 | var timer = samples.timer("PostgreSQL", "query"); 67 | counts.sample('postgres'); 68 | 69 | proxy.before(ret, 'on', function(obj, args) { 70 | var event = args[0]; 71 | if (event !== 'end' && event !== 'error') return; 72 | 73 | function handleEvent(obj, args, extra) { 74 | timer.end(); 75 | 76 | topFunctions.add('postgresCalls', command, timer.ms); 77 | recordExtra(extra, timer); 78 | } 79 | 80 | proxy.callback(args, -1, strongTraceTransaction(command, handleEvent)); 81 | }); 82 | }); 83 | } 84 | 85 | // Native, reinitialize probe 86 | proxy.getter(obj, 'native', function(obj, ret) { 87 | proxy.after(ret, 'Client', 88 | function(obj, args, ret) { probe(ret.__proto__); }); 89 | }); 90 | 91 | probe(obj.Client.prototype); 92 | }; 93 | -------------------------------------------------------------------------------- /lib/probes/redis.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../agent'); 4 | var debug = require('../debug')('probes:redis'); 5 | var proxy = require('../proxy'); 6 | var samples = require('../samples'); 7 | var counts = require('../counts'); 8 | var tiers = require('../tiers'); 9 | var topFunctions = require('../top-functions'); 10 | var graphHelper = require('../graph-helper'); 11 | 12 | module.exports = function(redis) { 13 | 14 | proxy.before(redis.RedisClient.prototype, 'send_command', 15 | function(obj, args, ret) { 16 | if (agent.paused) return; 17 | 18 | var command = args[0]; 19 | var input = args[1]; 20 | 21 | if (!Array.isArray(input)) return; 22 | 23 | var timer = samples.timer('Redis', command); 24 | var query = command + 25 | (typeof input[0] === 'string' ? ' "' + input[0] + '"' : ''); 26 | var graphNode = graphHelper.startNode('Redis', query, agent); 27 | 28 | counts.sample('redis'); 29 | debug('command: %s', command); 30 | 31 | function handle(obj, args, extra) { 32 | timer.end(); 33 | 34 | debug('%s callback', command); 35 | topFunctions.add('redisCalls', query, timer.ms); 36 | graphHelper.updateTimes(graphNode, timer); 37 | 38 | if (extra) { 39 | debug('%s extra: ', extra); 40 | extra.redis = extra.redis || 0; 41 | extra.redis += timer.ms; 42 | tiers.sample(extra.closed ? 'redis_out' : 'redis_in', timer); 43 | } else { 44 | tiers.sample('redis_in', timer); 45 | } 46 | } 47 | 48 | function getCommandAndKey(command, input){ 49 | while(Array.isArray(input)){ 50 | if (typeof input[0] === 'string') { 51 | var keyValue = '"' + input[0] + '"'; 52 | if (typeof input[1] === 'string') keyValue += ' "' + input[1] + '"'; 53 | return command + " " + keyValue; 54 | } 55 | input = input[0]; 56 | continue; 57 | } 58 | return command; 59 | } 60 | 61 | function strongTraceTransaction(query, callback){ 62 | var linkName = "Redis " + query; 63 | return agent.transactionLink(linkName, callback); 64 | } 65 | 66 | // Support send_command(com, [arg, cb]) and send_command(com, [arg], cb) 67 | var callbackIndex = args.length - 1; 68 | if (typeof args[callbackIndex] === 'function') { 69 | args[callbackIndex] = strongTraceTransaction( 70 | getCommandAndKey(command, input), args[callbackIndex]); 71 | proxy.callback(args, callbackIndex, handle); 72 | } else { 73 | // Hack to support optional functions by adding noop function when 74 | // blank 75 | callbackIndex = input.length - 1; 76 | if (typeof input[callbackIndex] !== 'function') { 77 | input.push(function() {}); 78 | callbackIndex += 1; 79 | } 80 | input[callbackIndex] = 81 | strongTraceTransaction(getCommandAndKey(command, input), 82 | input[callbackIndex]); 83 | proxy.callback(input, callbackIndex, handle); 84 | } 85 | 86 | if (graphNode) agent.currentNode = graphNode.prevNode; 87 | }); 88 | }; 89 | -------------------------------------------------------------------------------- /lib/probes/riak-js.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../agent'); 4 | var proxy = require('../proxy'); 5 | var samples = require('../samples'); 6 | var counts = require('../counts'); 7 | var tiers = require('../tiers'); 8 | var topFunctions = require('../top-functions'); 9 | var graphHelper = require('../graph-helper'); 10 | 11 | module.exports = function(riak) { 12 | proxy.after(riak, ['getClient'], function(obj, args, ret) { 13 | var client = ret; 14 | var clientType = args[0].api || "http"; // http or protobuf 15 | 16 | proxy.before(client, ['get', 'save', 'head', 'exists', 'remove'], 17 | function(obj, args, method) { 18 | if (agent.paused) { 19 | return; 20 | } 21 | 22 | var time = samples.time("Riak", method); 23 | var graphNode = graphHelper.startNode('Riak', method, agent); 24 | counts.sample('riak'); 25 | 26 | // get(): (bucket, key, options, callback) 27 | // save(): (bucket, key, data, options, callback) 28 | // head(): (bucket, key, options, callback) 29 | // exists(): (bucket, key, options, callback) -> calls head() 30 | // remove(): (bucket, key, options, callback) 31 | 32 | var bucket = args.length > 0 ? args[0] : undefined; 33 | var key = args.length > 1 ? args[1] : undefined; 34 | 35 | // q = clientType.bucket.key.get() 36 | var q = clientType + '.' + bucket + '.' + key + '.' + method; 37 | 38 | function handle(obj, args, extra) { 39 | if (!time.done()) return; 40 | 41 | topFunctions.add('riakCalls', q, time.ms); 42 | graphHelper.updateTimes(graphNode, time); 43 | 44 | if (extra) { 45 | extra.riak = extra.riak || 0; 46 | extra.riak += time.ms; 47 | } 48 | tiers.sample('riak_in', time); 49 | } 50 | 51 | proxy.callback(args, -1, handle, null, true); 52 | if (graphNode) agent.currentNode = graphNode.prevNode; 53 | }); 54 | }); 55 | }; 56 | -------------------------------------------------------------------------------- /lib/probes/sl-mq.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports = module.exports = require('./strong-mq'); 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/probes/strong-oracle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./oracle'); 4 | -------------------------------------------------------------------------------- /lib/proc.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * proc 3 | * Copyright(c) 2012 Daniel D. Shaw 4 | * MIT Licensed 5 | */ 6 | 7 | // is there anything even left of the original version by dshaw? 8 | 9 | 'use strict'; 10 | 11 | var fs = require('fs'); 12 | 13 | // 14 | // This gets all messed up on 64-bit Solaris because the initial version assumed 15 | // 32 bits for all longs. If this module ever get used for anything *other* 16 | // than Solaris, you'll want to verify that the sizes of these types are 17 | // still valid. 18 | // 19 | // typedef struct timespec { /* definition per POSIX.4 */ 20 | // time_t tv_sec; /* seconds */ 21 | // long tv_nsec; /* and nanoseconds */ 22 | // } timespec_t; 23 | // ... 24 | // typedef long time_t; /* time of day in seconds */ 25 | 26 | var sizeof_long = 4; // 32-bit 27 | var sizeof_timespec = 8; 28 | if (process.arch == 'x64') { 29 | sizeof_long = 8; 30 | sizeof_timespec = 16; 31 | } 32 | 33 | function readTimespec(buf, offset) { 34 | // this would be pretty janky for 64-bit, but because that 32-bit timestamp 35 | // isn't going to hit the 64-bit space until 2037, it's Good Enough[tm] 36 | return buf.readInt32LE(offset) + 37 | (buf.readInt32LE(offset + sizeof_long) / 1000000000); 38 | } 39 | 40 | exports.usage = function usage(pid, callback) { 41 | 42 | fs.readFile('/proc/' + pid + '/usage', function(err, buf) { 43 | if (err) { 44 | return callback(err, null); 45 | }; 46 | 47 | var data = {}; 48 | var offset = 0; 49 | 50 | // lwp id. 0: process or defunct 51 | data.lwpid = buf.readUInt32LE(offset); 52 | offset += 4; 53 | 54 | // number of contributing lwps 55 | data.count = buf.readUInt32LE(offset); 56 | offset += 4; 57 | 58 | // current time stamp 59 | data.tstamp = readTimespec(buf, offset); 60 | offset += sizeof_timespec; 61 | 62 | // process/lwp creation time stamp 63 | data.create = readTimespec(buf, offset); 64 | offset += sizeof_timespec; 65 | 66 | // process/lwp termination time stamp 67 | data.term = readTimespec(buf, offset); 68 | offset += sizeof_timespec; 69 | 70 | // total lwp real (elapsed) time 71 | data.rtime = readTimespec(buf, offset); 72 | offset += sizeof_timespec; 73 | 74 | // user level cpu time 75 | data.utime = readTimespec(buf, offset); 76 | offset += sizeof_timespec; 77 | 78 | // system call cpu time 79 | data.stime = readTimespec(buf, offset); 80 | offset += sizeof_timespec; 81 | 82 | // there's normally another big pile of values we could read, but 83 | // don't need any of them 84 | 85 | callback(null, data); 86 | }); 87 | }; 88 | 89 | // we only check this on linux, need to make sure that the field numbers 90 | // are valid on other platforms if we want to use it there as well 91 | 92 | exports.stat = 93 | function stat(pid, callback) { 94 | 95 | fs.readFile('/proc/' + pid + '/stat', 'ascii', function(err, data) { 96 | if (err) { 97 | return callback(err, null); 98 | } 99 | 100 | var stats_pid = data.split(' '); 101 | var ret = {}; 102 | 103 | ret.utime = parseInt(stats_pid[13]); 104 | ret.stime = parseInt(stats_pid[14]); 105 | ret.ptime = ret.utime + ret.stime; 106 | 107 | fs.readFile('/proc/stat', 'ascii', function(err, data) { 108 | if (err) return; 109 | 110 | ret.all = data.match('cpu +(.*)\n')[1].split(' '); 111 | ret.all = array_sum(ret.all); 112 | 113 | callback(null, ret); 114 | }) 115 | }); 116 | 117 | } 118 | 119 | function array_sum(a) { 120 | var k, s = 0; 121 | if (typeof a !== 'object') return null; 122 | for (k in a) { 123 | s += (a[k] * 1); 124 | } 125 | return s; 126 | } 127 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /lib/top-functions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | 5 | var MAX_SIZE = 10; 6 | 7 | function TopFunctions() { 8 | this._data = null; 9 | } 10 | 11 | TopFunctions.prototype.poll = function() { 12 | var snapshot = this._data; 13 | this._data = null; 14 | return snapshot; 15 | }; 16 | 17 | TopFunctions.prototype.add = 18 | function add(collectionName, url, ms, tiers, graph) { 19 | var now = Date.now(); 20 | var data = [now, url, ms, tiers, graph]; 21 | var update = false; 22 | 23 | if (this._data == null) { 24 | this._data = {}; 25 | } 26 | 27 | var list; 28 | if (this._data[collectionName]) { 29 | list = this._data[collectionName].list; 30 | } else { 31 | this._data[collectionName] = { 32 | start: Date.now(), 33 | collectionName: collectionName, 34 | list: [] 35 | }; 36 | list = this._data[collectionName].list; 37 | } 38 | 39 | // on the list 40 | var found = false; 41 | list.forEach(function(item) { 42 | if (item[1] == data[1]) { 43 | found = true; 44 | if (item[2] < data[2]) { 45 | util._extend(item, data); 46 | update = true; 47 | } 48 | } 49 | }); 50 | 51 | // not on list 52 | if (!found) { 53 | // list has room 54 | if (list.length < MAX_SIZE) { 55 | list.push(data); 56 | update = true; 57 | } else { 58 | // it ranks on list (it's walltime is greater than the last item on the 59 | // list 60 | if (data[2] > last(list)[2]) { 61 | list.pop(); 62 | list.push(data); 63 | update = true; 64 | } 65 | } 66 | } 67 | 68 | // we changed the content of the window, sort and emit time 69 | if (update) { 70 | list.sort(function(a, b) { 71 | if (a[2] < b[2]) return 1; 72 | if (a[2] == b[2]) return 0; 73 | if (a[2] > b[2]) return -1; 74 | }); 75 | } 76 | }; 77 | 78 | module.exports = new TopFunctions(); 79 | 80 | function last(l) { 81 | if (l.length > 0) 82 | return l[l.length-1]; 83 | else 84 | return; // undefined 85 | } 86 | -------------------------------------------------------------------------------- /lib/wrapping-probes/leveldown.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var agent = require('../agent'); 4 | var proxy = require('../proxy'); 5 | var samples = require('../samples'); 6 | var counts = require('../counts'); 7 | var tiers = require('../tiers'); 8 | var topFunctions = require('../top-functions'); 9 | var graphHelper = require('../graph-helper'); 10 | 11 | /* 12 | * Instrumentation for LevelDB via leveldown module, which is the de facto 13 | * canonical module for LevelDB in Node. 14 | * 15 | * github: https://github.com/rvagg/node-leveldown 16 | * npm: https://npmjs.org/package/leveldown 17 | * 18 | */ 19 | module.exports = function(leveldown) { 20 | /* 21 | This is leveldown, as we receive it: 22 | leveldown() 23 | leveldown.destroy() 24 | leveldown.repair() 25 | 26 | We wrap leveldown() to instrument the instance methods of the 27 | returned Database object. 28 | 29 | These are probably not worth instrumenting: 30 | leveldown#open() 31 | leveldown#close() 32 | 33 | These are set up with timers below: 34 | leveldown#put() 35 | leveldown#get() 36 | leveldown#del() 37 | 38 | These should probably be timed: 39 | leveldown#batch() 40 | leveldown#approximateSize() 41 | leveldown#getProperty() 42 | 43 | Spy on #iterator() to attach instrumentation to each iterator instance 44 | leveldown#iterator() 45 | iterator#next() 46 | iterator#end() 47 | */ 48 | 49 | // leveldown() 50 | function wrappedLeveldown(location) { 51 | var db = leveldown(location); 52 | 53 | function instrumentAsync(obj, args, method) { 54 | if (agent.paused) { 55 | return; 56 | } 57 | 58 | var time = samples.timer('LevelDown', method); 59 | var graphNode = graphHelper.startNode('LevelDown', method, agent); 60 | counts.sample('leveldown'); 61 | 62 | // get(key[, options], callback) 63 | // put(key, value[, options], callback) 64 | // del(key[, options], callback) 65 | // batch(operations[, options], callback) 66 | var key = (method == 'batch' ? args[0].length : args[0]); 67 | 68 | var query = location + '.' + method + ':' + key; 69 | 70 | function handle(obj, args, extra) { 71 | time.end(); 72 | 73 | topFunctions.add('leveldownCalls', query, time.ms); 74 | graphHelper.updateTimes(graphNode, time); 75 | 76 | if (extra) { 77 | extra.leveldown = extra.leveldown || 0; 78 | extra.leveldown += time.ms; 79 | } 80 | 81 | tiers.sample('leveldown_in', time); 82 | } 83 | 84 | proxy.callback(args, -1, handle, null, true); 85 | if (graphNode) { 86 | agent.currentNode = graphNode.prevNode; 87 | } 88 | } 89 | 90 | // TODO: Investigate if we should instead be attaching this instrumentaiton 91 | // on to Database's and Iterator's prototypes instead. 92 | 93 | proxy.before(db, ['get', 'put', 'del', 'batch'], instrumentAsync); 94 | 95 | proxy.after(db, 'iterator', function(obj, args, ret) { 96 | proxy.before(ret, ['next', 'end'], instrumentAsync); 97 | }); 98 | 99 | return db; 100 | } 101 | 102 | // Need to maintain leveldown's exported API 103 | wrappedLeveldown.destroy = leveldown.destroy; 104 | wrappedLeveldown.repair = leveldown.repair; 105 | 106 | return wrappedLeveldown; 107 | }; 108 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /samples/di.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 assert = require('assert'); 7 | var json = require('../lib/json'); 8 | var net = require('net'); 9 | var path = require('path'); 10 | 11 | // This is the instrumentation that we are going to inject into the other 12 | // process. It measures request/response times and sends off the metrics 13 | // to a server in this process. 14 | // 15 | // Note that instrumentation only has access to what is visible in the 16 | // lexical scope at the point of insertion: locals, globals and captured 17 | // (i.e. referenced) variables from enclosing scopes. Variables from outer 18 | // scopes that are not referenced in the original function are not visible 19 | // to instrumentation; trying to inject code that references them will fail. 20 | var instrumentation = (function() { 21 | (function(agent, req, res) { 22 | var socket = global.__DYNINST_SOCKET; 23 | if (socket == null) { 24 | socket = agent.require('dgram').createSocket('udp4'); 25 | socket.unref(); 26 | Object.defineProperty(global, '__DYNINST_SOCKET', { value: socket }); 27 | } 28 | /* Keep references to the metrics we want to capture, they may have changed 29 | * or gone away by the time we send off the datagram. */ 30 | var host = req.socket.remoteAddress; 31 | var port = req.socket.remotePort; 32 | var path = req.url; 33 | var time = process.hrtime(); 34 | res.once('finish', function() { 35 | time = process.hrtime(time); 36 | var message = Buffer(JSON.stringify({ 37 | time: time[0] + time[1] / 1e6, 38 | host: host, 39 | port: port, 40 | path: path, 41 | })); 42 | socket.send(message, 0, message.length, $PORT, '127.0.0.1'); 43 | }); 44 | })(STRONGAGENT, req, res); 45 | }).toString().slice(14, -2); 46 | 47 | // TODO(bnoordhuis) Could be minified further using e.g. uglifyjs. 48 | instrumentation = instrumentation.trim().replace(/\s+/g, ' '); 49 | 50 | // This is the server that receives the metrics. 51 | var server = require('dgram').createSocket('udp4'); 52 | server.on('message', function(message) { 53 | message = JSON.parse('' + message); 54 | console.log('%s %d %j %d', 55 | message.host, 56 | message.port, 57 | message.path, 58 | message.time); 59 | }); 60 | server.bind(0); 61 | 62 | net.connect(7000).once('connect', function() { 63 | var socket = this; 64 | var reader = json.JsonDecoder(); 65 | var writer = json.JsonEncoder(); 66 | socket.pipe(reader); 67 | writer.pipe(socket); 68 | step0(); 69 | 70 | // First find all the scripts. 71 | function step0() { 72 | writer.write({ cmd: 'scripts', version: 0 }); 73 | reader.once('data', step1); 74 | } 75 | 76 | // Now find the script that we want to patch, app.js. 77 | function step1(scripts) { 78 | assert.equal(Array.isArray(scripts), true); 79 | assert.equal(scripts.length % 2, 0); 80 | var scriptname = path.join(__dirname, 'app.js'); 81 | var scriptid = -1; 82 | for (var i = 0, n = scripts.length; i < n; i += 2) { 83 | if (scriptname !== scripts[i + 1]) continue; 84 | scriptid = scripts[i + 0]; 85 | break; 86 | } 87 | if (scriptid === -1) { 88 | throw Error('No script for ' + scriptname); 89 | } 90 | writer.write({ cmd: 'sources', scriptids: [scriptid], version: 0 }); 91 | reader.once('data', step2.bind(null, scriptid)); 92 | } 93 | 94 | // Find the position in the source where we want to inject our code. 95 | function step2(scriptid, sources) { 96 | var source = sources[0]; 97 | var eols = sources[1]; 98 | var pos = source.indexOf('function onrequest'); 99 | var eol = eols.filter(function(npos) { return pos < npos })[0]; 100 | var change = instrumentation.replace(/\$PORT/g, server.address().port); 101 | if (change === source.slice(eol - change.length, eol)) { 102 | console.log('Already instrumented.'); 103 | return done(); 104 | } 105 | var changes = [eol, change]; 106 | writer.write({ cmd: 'patch', changes: changes, 107 | scriptid: scriptid, version: 0 }); 108 | reader.once('data', step3); 109 | } 110 | 111 | // Check that the patch applied. 112 | function step3(result) { 113 | if (result.error) { 114 | var err = new Error(result.error); 115 | err.stack = result.stack; // Error stack from other process. 116 | throw err; 117 | } 118 | var changelog = result.changelog; 119 | for (var i = 0, n = changelog.length; i < n; i += 1) { 120 | if (changelog[i].function_patched === 'onrequest') { 121 | console.log('Instrumented. Now start making requests.'); 122 | return done(); 123 | } 124 | } 125 | console.error('Instrumentation FAILED:', result); 126 | done(); 127 | } 128 | 129 | function done() { 130 | socket.destroy(); 131 | } 132 | }); 133 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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