-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathcontroller.js
More file actions
142 lines (130 loc) · 5.28 KB
/
controller.js
File metadata and controls
142 lines (130 loc) · 5.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import configureMeasurements from 'convert-units';
import allMeasures from 'convert-units/definitions/all';
export const
convertUnits = configureMeasurements(allMeasures),
knownUnits = convertUnits().possibilities();
export var activeController; // hack to provide current context for gauges to register with
function maybeConvert(v, fromUnit, toUnit) {
var u = v;
if (fromUnit && toUnit) {
try {
u = convertUnits(v).from(fromUnit).to(toUnit);
} catch(err) {
console.log('Unit conversion error: ' + err.message);
}
} else if (typeof v === 'string' && v.match(/^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d/)) {
// convert string-serialized date metrics back to JS objects
u = new Date(v);
}
return u;
}
export function gaugeController() {
/*
create a gauge controller that we'll use to update values
`callbacks` is keyed by the panel indicator names,
which can be more precise than the actual metric name, e.g. fuel.copilot;
`updaters` is populated after the first call with best match to actual metrics
*/
var callbacks = {}, // nested dict of metric => (unit || '') => list of update fns
fakes = {}, // dict of metric => generator
updaters = null; // dict of metric keys => {metric: {unit: updaters: {unit: fns}}}
/*
After initial set up we build an updaters structure like this which lets us route
incoming metrics to clients in appropriate units:
{
altitude: { // incoming metric name
unit: meters, // incoming unit
updaters: {
feet: [ f1, f2, ... ], // clients segmented by desired unit
meters: [ f3, f4, ... ],
...
}
},
...
}
*/
// call the controller to display current metric values
function gaugeController(data, transition) {
/*
data is a dictionary {latest: 1234, metrics: {}, [units: {}]}
where metrics is a dictionary
with keys like: "metric.some.qualification"
and corresponding values
*/
if (!updaters) {
// First call, we establish the mapping from metric keys => callbacks
updaters = {};
Object.keys(data.metrics).forEach(m => {
updaters[m] = {unit: (data.units || {})[m] || '', updaters: null};
})
Object.entries(callbacks).forEach(([m, ufs]) => {
/*
for each callback, find best qualified metric from the input values,
which we'll convert to appropriate units
e.g. a gauge requesting fuel.copilot.rear will match
a metric called fuel.copilot.rear,
or else fuel.copilot or else simply fuel but never fuel.pilot
*/
var ks = m.split('.'),
matched = false;
while (ks.length && !matched) {
let k = ks.join('.');
if (k in updaters) {
updaters[k].updaters = ufs
matched = true;
}
ks.pop();
}
if (!matched) console.log('Warning: no source metric matching', m);
});
Object.entries(updaters).forEach(([m, d]) => {
if (!d.updaters) {
console.log('Warning: unmapped source metric', m)
delete updaters[m];
}
});
}
// Trigger updates for each source metric
Object.entries(updaters).forEach(([m, d]) => {
if (m in data.metrics) {
Object.entries(d.updaters).forEach(([unit, fs]) => {
let v = maybeConvert(data.metrics[m], d.unit, unit);
if (typeof v == 'undefined') {
console.log(`Warning: failed to convert ${data.metrics[m]} from ${d.unit} to ${unit}`);
} else {
fs.forEach(f => f(v, transition));
}
});
}
});
}
gaugeController.register = function(updater, metric, unit) {
if (!metric) return;
unit = unit || '';
if (!(metric in callbacks)) callbacks[metric] = {};
if (!(unit in callbacks[metric])) callbacks[metric][unit] = [];
callbacks[metric][unit].push(updater);
}
gaugeController.fake = function(metric, generator) {
fakes[metric] = generator;
}
gaugeController.fakeMetrics = function() {
return {
latest: 0,
units: {},
metrics: Object.fromEntries(
Object.entries(fakes).map(([m, g]) => [m, g()])
)
}
}
gaugeController.indicators = function() {
// return panel indicator keys, available at startup but possibly more specific than actual metrics
return Object.keys(callbacks);
}
gaugeController.mappedMetrics = function() {
// return matched metrics, available after first data update
return updaters && Object.keys(updaters);
}
activeController = gaugeController;
return gaugeController;
}