heimdalljs
Advanced tools
Comparing version 0.1.3 to 0.1.4
{ | ||
"name": "heimdalljs", | ||
"version": "0.1.3", | ||
"version": "0.1.4", | ||
"description": "Structured instrumentation library", | ||
"main": "index.js", | ||
"main": "dist/heimdalljs.cjs.js", | ||
"jsnext:main": "dist/heimdalljs.es.js", | ||
"scripts": { | ||
"test": "mocha tests/", | ||
"build": "npm run build:node && npm run build:browser && npm run build:test", | ||
"build:node": "rollup --no-strict -c node.config.js", | ||
"build:browser": "rollup --no-strict -c browser.config.js", | ||
"build:test": "rollup --no-strict -c test.config.js", | ||
"test": "npm run build:node && npm run build:test && mocha dist/tests/bundle.cjs", | ||
"test:debug": "mocha --no-timeouts debug tests/" | ||
@@ -23,3 +28,9 @@ }, | ||
], | ||
"files": [ | ||
"src", | ||
"dist" | ||
], | ||
"devDependencies": { | ||
"babel-preset-es2015": "6.13.0", | ||
"babel-preset-es2015-rollup": "^1.1.1", | ||
"broccoli": "^0.16.9", | ||
@@ -30,8 +41,12 @@ "chai": "^3.2.0", | ||
"mocha": "^2.2.5", | ||
"mocha-jshint": "~2.2.3" | ||
"mocha-jshint": "~2.2.3", | ||
"rollup": "^0.34.1", | ||
"rollup-plugin-babel": "^2.6.1", | ||
"rollup-plugin-buble": "^0.12.1", | ||
"rollup-plugin-commonjs": "^3.3.1", | ||
"rollup-plugin-node-resolve": "^1.7.1" | ||
}, | ||
"dependencies": { | ||
"rsvp": "^3.2.1", | ||
"semver": "^5.2.0" | ||
"rsvp": "^3.2.1" | ||
} | ||
} |
109
README.md
@@ -0,1 +1,94 @@ | ||
## Global Session | ||
Heimdall tracks a graph of timing and domain-specific stats for performance. | ||
Stat collection and monitoring is separated from graph construction to provide | ||
control over context detail. Users can create fewer nodes to have reduced | ||
performance overhead, or create more nodes to provide more detail. | ||
The graph obviously needs to be global. This is not a problem in the browser, | ||
but in node we may have multiple different versions of heimdalljs loaded at | ||
once. Each one will have its own `Heimdall` instance, but will use the same | ||
session, saved on `process`. This means that the session will have a | ||
heterogeneous graph of `HeimdallNode`s. For this reason, versions of heimdalljs | ||
that change `Session`, or the APIs of `HeimdallNode` or `Cookie` will use a | ||
different property to store their session (`process._heimdall_session_<n>`). It | ||
is quite easy for this to result in lost detail & lost stats, although it is | ||
also easy to detect this situation and issue a warning. | ||
## API | ||
### Heimdall | ||
### Creating a Heimdall instance | ||
`require('heimdalljs')` to access an instance using the globally shared session. | ||
You can create your own instance with its own session, but this is generally | ||
reommended only for testing. | ||
```js | ||
var Heimdall = require('heimdalljs/heimdall'); | ||
// this will create its own session and not use the global session | ||
var myInstance = new Heimdall(); | ||
``` | ||
#### Properties | ||
- `heimdall.current` Return the leaf `HeimdallNode` of the currently active nodes. | ||
- `heimdall.root` Return the root `HeimdallNode` | ||
#### Functions | ||
- `heimdall.start(id, Schema)` Create a new node with id `id`. This node becomes the | ||
active node. Its parent is the currently active node. Return this node's | ||
`Cookie`. | ||
- `heimdall.node(id, [Schema], callback, [context])` Create a new node, invoke | ||
`callback` passing in the newly created node's `stats` object and then stop | ||
the node. | ||
- `registerMonitor(name, Schema)` register a monitor under namespace `name`. An | ||
error will be thrown if the reserved names `own` or `time` are used, or if a | ||
monitor with that name has already been registered for this session. | ||
- `statsFor(name)` return the stats object for the monitor under namespace | ||
`name`. | ||
- `configFor(name)` return the config object under `name`. Heimdall does not do | ||
anything with these config objects: it is a place for downstream consumers to | ||
share config across a heimdall session (see eg | ||
[heimdalljs-logger](https://github.com/heimdalljs/heimdalljs-logger)). | ||
- `toJSON()` return the json for the entire graph. This is intended to be | ||
written via `JSON.stringify` and then consumed by downstream apps (see eg | ||
[broccoli-viz](https://github.com/stefanpenner/broccoli-viz)). | ||
- `visitPreOrder(callback)` sugar for `root.visitPreOrder(callback)` | ||
- `visitPostOrder(callback)`` sugar for `root.visitPostOrder(callback)` | ||
### HeimdallNode | ||
#### Identifiers | ||
#### Properties | ||
- `isRoot` returns true for the root node, and false for all other nodes. | ||
#### Functions | ||
- `visitPreOrder(callback)` visit the subtree rooted at this node with a | ||
depth-first pre-order traversal. | ||
- `visitPostOrder(callback)` visit the subtree rooted at this node with a | ||
depth-first post-order traversal. | ||
- `remove` remove this node from its parent. May only be called on an inactive, | ||
non-root node. Intended for long-running applications to free up memory after | ||
saving a subgraph via `toJSONSubgraph`. | ||
- `toJSON()` Return the serialized representation of this ndoe. | ||
- `toJSONSubgraph()` Return the serialized representation of the subtree rooted at | ||
this node. | ||
### Cookie | ||
#### Functions | ||
- `stop()` stop the node associated with this cookie. May only be called on the | ||
current node. | ||
- `resume()` resume a stopped node. This is useful for nodes that are restarted | ||
asynchronously. | ||
## Example Usage | ||
@@ -136,4 +229,4 @@ | ||
"fs": { | ||
lstatCount: 0, | ||
mkdirCount: 0, | ||
"lstatCount": 0, | ||
"mkdirCount": 0, | ||
}, | ||
@@ -154,4 +247,4 @@ }, | ||
"fs": { | ||
lstatCount: 0, | ||
mkdirCount: 0, | ||
"lstatCount": 0, | ||
"mkdirCount": 0, | ||
}, | ||
@@ -172,4 +265,4 @@ }, | ||
"fs": { | ||
lstatCount: 0, | ||
mkdirCount: 1, | ||
"lstatCount": 0, | ||
"mkdirCount": 1, | ||
}, | ||
@@ -186,4 +279,4 @@ }, | ||
"fs": { | ||
lstatCount: 1, | ||
mkdirCount: 0, | ||
"lstatCount": 1, | ||
"mkdirCount": 0, | ||
}, | ||
@@ -190,0 +283,0 @@ }, |
@@ -1,37 +0,36 @@ | ||
module.exports = Cookie; | ||
function Cookie(node, heimdall) { | ||
this.node = node; | ||
this.restoreNode = this.node.parent; | ||
this.heimdall = heimdall; | ||
this.stopped = false; | ||
} | ||
export default class Cookie { | ||
constructor(node, heimdall) { | ||
this._node = node; | ||
this._restoreNode = node.parent; | ||
this._heimdall = heimdall; | ||
this._stopped = false; | ||
} | ||
Object.defineProperty(Cookie.prototype, 'stats', { | ||
get: function() { | ||
return this.node.stats.own; | ||
get stats() { | ||
return this._node.stats.own; | ||
} | ||
}); | ||
Cookie.prototype.stop = function() { | ||
var monitor; | ||
stop() { | ||
let monitor; | ||
if (this.heimdall._current !== this.node) { | ||
throw new TypeError('cannot stop: not the current node'); | ||
} else if (this.stopped === true) { | ||
throw new TypeError('cannot stop: already stopped'); | ||
if (this._heimdall.current !== this._node) { | ||
throw new TypeError('cannot stop: not the current node'); | ||
} else if (this.stopped === true) { | ||
throw new TypeError('cannot stop: already stopped'); | ||
} | ||
this._stopped = true; | ||
this._heimdall._recordTime(); | ||
this._heimdall._session.current = this._restoreNode; | ||
} | ||
this.stopped = true; | ||
this.heimdall._recordTime(); | ||
this.heimdall._current = this.restoreNode; | ||
}; | ||
resume() { | ||
if (this._stopped === false) { | ||
throw new TypeError('cannot resume: not stopped'); | ||
} | ||
Cookie.prototype.resume = function() { | ||
if (this.stopped === false) { | ||
throw new TypeError('cannot resume: not stopped'); | ||
this._stopped = false; | ||
this._restoreNode = this._heimdall.current; | ||
this._heimdall._session.current = this._node; | ||
} | ||
this.stopped = false; | ||
this.restoreNode = this.heimdall._current; | ||
this.heimdall._current = this.node; | ||
}; | ||
} |
@@ -1,157 +0,150 @@ | ||
'use strict'; | ||
import { Promise } from 'rsvp'; | ||
var RSVP = require('rsvp'); | ||
import Cookie from './cookie'; | ||
import HeimdallNode from './node'; | ||
import Session from './session'; | ||
import timeNS from './time'; | ||
var VERSION = require('../package.json').version; | ||
var Cookie = require('./cookie'); | ||
var HeimdallNode = require('./node'); | ||
export default class Heimdall{ | ||
constructor(session) { | ||
if (arguments.length < 1) { | ||
session = new Session(); | ||
} | ||
this._session = session; | ||
this._reset(false); | ||
} | ||
module.exports = Heimdall; | ||
function Heimdall() { | ||
this.version = VERSION; | ||
get current() { | ||
return this._session.current; | ||
} | ||
this._reset(); | ||
} | ||
get root() { | ||
return this._session.root; | ||
} | ||
Object.defineProperty(Heimdall.prototype, 'current', { | ||
get: function() { | ||
return this._current; | ||
_reset(resetSession) { | ||
if (resetSession !== false) { | ||
this._session.reset(); | ||
} | ||
if (!this.root) { | ||
// The first heimdall to start will create the session and root. Subsequent | ||
// heimdall instances continue to use the existing graph | ||
this.start('heimdall'); | ||
this._session.root = this._session.current; | ||
} | ||
} | ||
}); | ||
Heimdall.prototype._reset = function () { | ||
this._nextId = 0; | ||
this._current = undefined; | ||
this._previousTime = undefined; | ||
this.start('heimdall'); | ||
this._root = this._current; | ||
this._monitorSchema = {}; | ||
this._configs = {}; | ||
}; | ||
start(name, Schema) { | ||
let id; | ||
let data; | ||
Heimdall.prototype.start = function (name, Schema) { | ||
var id; | ||
var data; | ||
if (typeof name === 'string') { | ||
id = { name: name }; | ||
} else { | ||
id = name; | ||
} | ||
if (typeof name === 'string') { | ||
id = { name: name }; | ||
} else { | ||
id = name; | ||
} | ||
if (typeof Schema === 'function') { | ||
data = new Schema(); | ||
} else { | ||
data = {}; | ||
} | ||
if (typeof Schema === 'function') { | ||
data = new Schema(); | ||
} else { | ||
data = {}; | ||
} | ||
this._recordTime(); | ||
this._recordTime(); | ||
let node = new HeimdallNode(this, id, data); | ||
if (this.current) { | ||
this.current.addChild(node); | ||
} | ||
var node = new HeimdallNode(this, id, data, this._current); | ||
// always true except for root | ||
if (this._current) { | ||
this._current.addChild(node); | ||
this._session.current = node; | ||
return new Cookie(node, this); | ||
} | ||
this._current = node; | ||
return new Cookie(node, this); | ||
}; | ||
_recordTime() { | ||
let time = timeNS(); | ||
Heimdall.prototype._recordTime = function () { | ||
var time = process.hrtime(); | ||
// always true except for root | ||
if (this._current) { | ||
var delta = (time[0] - this._previousTime[0]) * 1e9 + (time[1] - this._previousTime[1]); | ||
this._current.stats.time.self += delta; | ||
// always true except for root | ||
if (this.current) { | ||
let delta = time - this._session.previousTimeNS; | ||
this.current.stats.time.self += delta; | ||
} | ||
this._session.previousTimeNS = time; | ||
} | ||
this._previousTime = time; | ||
}; | ||
Heimdall.prototype.node = function (name, Schema, callback, context) { | ||
if (arguments.length < 3) { | ||
callback = Schema; | ||
Schema = undefined; | ||
} | ||
node(name, Schema, callback, context) { | ||
if (arguments.length < 3) { | ||
callback = Schema; | ||
Schema = undefined; | ||
} | ||
var cookie = this.start(name, Schema); | ||
let cookie = this.start(name, Schema); | ||
// NOTE: only works in very specific scenarios, specifically promises must | ||
// not escape their parents lifetime. In theory, promises could be augmented | ||
// to support those more advanced scenarios. | ||
return new RSVP.Promise(function(resolve) { | ||
resolve(callback.call(context, cookie.node.stats.own)); | ||
}).finally(function() { | ||
cookie.stop(); | ||
}); | ||
}; | ||
Heimdall.prototype.registerMonitor = function (name, Schema) { | ||
if (name === 'own' || name === 'time') { | ||
throw new Error('Cannot register monitor at namespace "' + name + '". "own" and "time" are reserved'); | ||
// NOTE: only works in very specific scenarios, specifically promises must | ||
// not escape their parents lifetime. In theory, promises could be augmented | ||
// to support those more advanced scenarios. | ||
return new Promise(resolve => resolve(callback.call(context, cookie._node.stats.own))). | ||
finally(() => cookie.stop()); | ||
} | ||
if (this._monitorSchema[name]) { | ||
throw new Error('A monitor for "' + name + '" is already registered"'); | ||
} | ||
this._monitorSchema[name] = Schema; | ||
}; | ||
Heimdall.prototype.statsFor = function(name) { | ||
var stats = this._current.stats; | ||
var Schema; | ||
registerMonitor(name, Schema) { | ||
if (name === 'own' || name === 'time') { | ||
throw new Error('Cannot register monitor at namespace "' + name + '". "own" and "time" are reserved'); | ||
} | ||
if (this._session.monitorSchemas.has(name)) { | ||
throw new Error('A monitor for "' + name + '" is already registered"'); | ||
} | ||
if (!stats[name]) { | ||
Schema = this._monitorSchema[name]; | ||
if (!Schema) { | ||
throw new Error('No monitor registered for "' + name + '"'); | ||
} | ||
stats[name] = new Schema(); | ||
this._session.monitorSchemas.set(name, Schema); | ||
} | ||
return stats[name]; | ||
}; | ||
statsFor(name) { | ||
let stats = this.current.stats; | ||
let Schema; | ||
Heimdall.prototype.configFor = function configFor(name) { | ||
var config = this._configs[name]; | ||
if (!stats[name]) { | ||
Schema = this._session.monitorSchemas.get(name); | ||
if (!Schema) { | ||
throw new Error('No monitor registered for "' + name + '"'); | ||
} | ||
stats[name] = new Schema(); | ||
} | ||
if (!config) { | ||
config = this._configs[name] = {}; | ||
return stats[name]; | ||
} | ||
return config; | ||
}; | ||
configFor(name) { | ||
let config = this._session.configs.get(name); | ||
Heimdall.prototype.toJSON = function () { | ||
var result = []; | ||
if (!config) { | ||
config = this._session.configs.set(name, {}); | ||
} | ||
this.visitPreOrder(function(node) { | ||
result.push(node.toJSON()); | ||
}); | ||
return config; | ||
} | ||
return { nodes: result } | ||
; | ||
}; | ||
toJSON() { | ||
return { nodes: this.root.toJSONSubgraph() }; | ||
} | ||
Heimdall.prototype.visitPreOrder = function (cb) { | ||
this._root.visitPreOrder(cb); | ||
}; | ||
visitPreOrder(cb) { | ||
return this.root.visitPreOrder(cb); | ||
} | ||
Heimdall.prototype.visitPostOrder = function (cb) { | ||
this._root.visitPostOrder(cb); | ||
}; | ||
visitPostOrder(cb) { | ||
return this.root.visitPostOrder(cb); | ||
} | ||
Heimdall.prototype._createStats = function (data) { | ||
var stats = { | ||
own: data, | ||
time: { self: 0 }, | ||
}; | ||
return stats; | ||
}; | ||
generateNextId() { | ||
return this._session.generateNextId(); | ||
} | ||
Object.defineProperty(Heimdall.prototype, 'stack', { | ||
get: function () { | ||
var stack = []; | ||
var top = this._current; | ||
get stack() { | ||
let stack = []; | ||
let top = this.current; | ||
while (top !== undefined && top !== this._root) { | ||
while (top !== undefined && top !== this.root) { | ||
stack.unshift(top); | ||
@@ -161,7 +154,4 @@ top = top.parent; | ||
return stack.map(function(node) { | ||
return node.id.name; | ||
}); | ||
return stack.map(node => node.id.name); | ||
} | ||
}); | ||
} |
126
src/node.js
@@ -1,73 +0,93 @@ | ||
module.exports = HeimdallNode; | ||
function HeimdallNode(heimdall, id, data, parent) { | ||
this.heimdall = heimdall; | ||
export default class HeimdallNode { | ||
constructor(heimdall, id, data) { | ||
this._heimdall = heimdall; | ||
this.id = id; | ||
this._id = heimdall._nextId++; | ||
this.stats = this.heimdall._createStats(data); | ||
this.children = []; | ||
this.parent = parent; | ||
} | ||
this._id = heimdall.generateNextId(); | ||
this.id = id; | ||
Object.defineProperty(HeimdallNode.prototype, 'isRoot', { | ||
get: function () { | ||
return this.parent === undefined; | ||
}, | ||
}); | ||
if (!(typeof this.id === 'object' && this.id !== null && typeof this.id.name === 'string')) { | ||
throw new TypeError('HeimdallNode#id.name must be a string'); | ||
} | ||
HeimdallNode.prototype.visitPreOrder = function (cb) { | ||
cb(this); | ||
this.stats = { | ||
own: data, | ||
time: { self: 0 }, | ||
}; | ||
for (var i = 0; i < this.children.length; i++) { | ||
this.children[i].visitPreOrder(cb); | ||
this._children = []; | ||
this.parent = null; | ||
} | ||
}; | ||
HeimdallNode.prototype.visitPostOrder = function (cb) { | ||
for (var i = 0; i < this.children.length; i++) { | ||
this.children[i].visitPostOrder(cb); | ||
get isRoot() { | ||
return this.parent === undefined; | ||
} | ||
cb(this); | ||
}; | ||
visitPreOrder(cb) { | ||
cb(this); | ||
HeimdallNode.prototype.remove = function () { | ||
if (!this.parent) { | ||
throw new Error('Cannot remove the root heimdalljs node.'); | ||
for (let i = 0; i < this._children.length; i++) { | ||
this._children[i].visitPreOrder(cb); | ||
} | ||
} | ||
if (this.heimdall.current === this) { | ||
throw new Error('Cannot remove an active heimdalljs node.'); | ||
visitPostOrder(cb) { | ||
for (let i = 0; i < this._children.length; i++) { | ||
this._children[i].visitPostOrder(cb); | ||
} | ||
cb(this); | ||
} | ||
var index = this.parent.children.indexOf(this); | ||
if (index < 0) { | ||
throw new Error('Child(' + this._id + ') not found in Parent(' + this.parent._id + '). Something is very wrong.'); | ||
remove() { | ||
if (!this.parent) { | ||
throw new Error('Cannot remove the root heimdalljs node.'); | ||
} | ||
if (this._heimdall.current === this) { | ||
throw new Error('Cannot remove an active heimdalljs node.'); | ||
} | ||
return this.parent.removeChild(this); | ||
} | ||
this.parent.children.splice(index, 1); | ||
return this; | ||
}; | ||
HeimdallNode.prototype.toJSON = function () { | ||
return { | ||
_id: this._id, | ||
id: this.id, | ||
stats: this.stats, | ||
children: this.children.map(function (child) { return child._id; }), | ||
toJSON() { | ||
return { | ||
_id: this._id, | ||
id: this.id, | ||
stats: this.stats, | ||
children: this._children.map(child => child._id ), | ||
} | ||
}; | ||
}; | ||
HeimdallNode.prototype.toJSONSubgraph = function () { | ||
var nodes = []; | ||
toJSONSubgraph() { | ||
let nodes = []; | ||
this.visitPreOrder(function(node) { | ||
nodes.push(node.toJSON()); | ||
}); | ||
this.visitPreOrder(node => nodes.push(node.toJSON())); | ||
return nodes; | ||
}; | ||
return nodes; | ||
} | ||
HeimdallNode.prototype.addChild = function (node) { | ||
this.children.push(node); | ||
}; | ||
addChild(node) { | ||
if (node.parent) { | ||
throw new TypeError('Node ' + node._id + ' already has a parent. Cannot add to ' + this._id); | ||
} | ||
this._children.push(node); | ||
node.parent = this; | ||
} | ||
removeChild(child) { | ||
let index = this._children.indexOf(child); | ||
if (index < 0) { | ||
throw new Error('Child(' + child._id + ') not found in Parent(' + this._id + '). Something is very wrong.'); | ||
} | ||
this._children.splice(index, 1); | ||
child.parent = null; | ||
return child; | ||
} | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
1172489
1
21
37625
283
13
10
1
- Removedsemver@^5.2.0
- Removedsemver@5.7.2(transitive)