nsolid-manager
Version:
A manager that sets up and runs N|Solid for you
214 lines (178 loc) • 5.19 kB
JavaScript
module.exports = createGraph
// create a width * height 2d array
function Array2D (width, height) {
var out = []
for (var i = 0; i < width; i++) {
out[i] = []
for (var j = 0; j < height; j++) {
out[i][j] = 0
}
}
return out
}
var graphs = {}
function createGraph (obj, storeTime) {
var iso8601Time = (new Date(Math.round(Date.now() / 1000) * 1000)).toISOString()
return {
// update line chart
add (graphName, value, longestGraph, now) {
var key = toKey(graphName)
if (!graphs[key]) {
graphs[key] = []
}
var graph = graphs[key]
now = now || Date.now()
var minDate = now - storeTime
// TODO: handle the case where the previous record is > 2*interval
var insert = [value, now]
graph.push(insert)
var l = graph.length
var i = 0
// flush the latest value for this graph into leveldb
createGraph.db && createGraph.db.put(toLevelKey(key), {
type: 'graph',
value: insert,
graphKey: key,
graphName: graphName
})
// Instead of relying on pollInterval, find the first result that is
// out of the allowed time range. Using that index we can slice off
// collected metrics before it and maintain a consistent array length.
for (; i < l; i++) {
if (graph[0][1] >= minDate) {
break
}
graph.shift()
}
if (longestGraph) {
var diff = longestGraph.length - graph.length
if (diff > 0) {
var j = diff
while (j--) {
var sampleTime = longestGraph[j][1]
// avoid adding 2 records for a single data point
if (sampleTime < now) {
graph.unshift([null, sampleTime])
}
}
}
}
},
get (graphName) {
return graphs[toKey(graphName)]
},
// spectrogram & spiffygram
grid (graphName, width, height, fn) {
var key = toKey(graphName)
if (!graphs[key]) {
graphs[key] = Array2D(width, height)
}
var graph = graphs[key]
// wrap grid updates since they can be done in place, and
// allow them to return updates (spectrogram)
var update = fn(graph) || graph
// flush the latest value for this graph into leveldb
createGraph.db && createGraph.db.put(toLevelKey(key), {
type: 'grid',
value: update,
graphKey: key,
graphName: graphName,
width: width,
height: height
})
}
}
function toLevelKey (key) {
return 'graph-' + iso8601Time + '-' + key
}
function toKey (graphName) {
return JSON.stringify(Object.assign({}, obj, {
graph: graphName
}))
}
}
function arrayRemoved (a, b) {
if (!b || !b.length) {
return a
}
var ret = []
var l = a.length
for (var i = 0; i < l; i++) {
if (b.indexOf(a[i]) === -1) {
ret.push(a[i])
}
}
return ret
}
function arrayAdded (a, b) {
if (!a || !a.length) {
return b
}
var ret = []
var l = b.length
for (var i = 0; i < l; i++) {
if (a.indexOf(b[i]) === -1) {
ret.push(b[i])
}
}
return ret
}
createGraph.updateGraphSubscriptions = function updateGraphSubscriptions (socket, subscriptions, fn) {
var added, removed
var activeSubscriptions = socket.graphSubscriptions
if (activeSubscriptions) {
removed = arrayRemoved(activeSubscriptions, subscriptions)
removed.forEach(function (graphName) {
activeSubscriptions.splice(activeSubscriptions.indexOf(graphName), 1)
})
added = arrayAdded(activeSubscriptions, subscriptions)
} else {
added = subscriptions
}
// if there were any changes, send the whole cache
if (added.length || (removed && removed.length)) {
socket.graphSubscriptions = subscriptions
fn(null, createGraph.buildGraphPayload(socket))
} else {
fn()
}
}
createGraph.buildGraphPayload = function buildGraphPayload (socket) {
if (!socket.graphSubscriptions) return null
var payload = { graphs: {} }
socket.graphSubscriptions.forEach(graphName => {
payload.graphs[graphName] = graphs[graphName]
})
return payload
}
createGraph.init = function init (db, pastSeconds, fn) {
if (!db) return fn()
createGraph.db = db
var since = (new Date(Date.now() - pastSeconds * 1000 * 60)).toISOString()
db.createReadStream({
gte: 'graph-' + since,
keys: false
})
.on('data', function onGraphData (d) {
if (d.type === 'graph') {
if (!graphs[d.graphKey]) {
graphs[d.graphKey] = []
}
graphs[d.graphKey].push(d.value)
} else if (d.type === 'grid') {
if (d.graphName.indexOf('pectrogram') > -1) {
// spectrograms need to be exactly the right dimensions or they will break the UI.
// So start off with an empty 2d array
if (!graphs[d.graphKey]) {
graphs[d.graphKey] = Array2D(d.width, d.height)
}
graphs[d.graphKey].push(d.value)
while (graphs[d.graphKey].length > d.width) {
graphs[d.graphKey].shift()
}
} else {
graphs[d.graphKey] = d.value
}
}
}).on('end', fn)
}