camstack 1.2.187 → 1.2.188
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +7 -10
- package/dist/{launcher-RCLUYTAW.js → launcher-FYTHPW44.js} +3365 -873
- package/package.json +1 -1
|
@@ -24024,9 +24024,9 @@ var require_zod = __commonJS({
|
|
|
24024
24024
|
}
|
|
24025
24025
|
});
|
|
24026
24026
|
|
|
24027
|
-
// ../system/dist/dist-
|
|
24028
|
-
var
|
|
24029
|
-
"../system/dist/dist-
|
|
24027
|
+
// ../system/dist/dist-Ct88catN.js
|
|
24028
|
+
var require_dist_Ct88catN = __commonJS({
|
|
24029
|
+
"../system/dist/dist-Ct88catN.js"(exports) {
|
|
24030
24030
|
"use strict";
|
|
24031
24031
|
var zod = require_zod();
|
|
24032
24032
|
__require("crypto");
|
|
@@ -27460,6 +27460,7 @@ var require_dist_BkNfEJKa = __commonJS({
|
|
|
27460
27460
|
field: zod.z.string().min(1),
|
|
27461
27461
|
on: zod.z.literal("advance")
|
|
27462
27462
|
});
|
|
27463
|
+
var HISTORY_DEFAULT_MIN_INTERVAL_MS = 1e4;
|
|
27463
27464
|
function resolveCapMount(def) {
|
|
27464
27465
|
if (def.mount) return def.mount;
|
|
27465
27466
|
if (def.deviceNative === true) return { kind: "device-native" };
|
|
@@ -98797,7 +98798,284 @@ var require_dist_BkNfEJKa = __commonJS({
|
|
|
98797
98798
|
durability: "session",
|
|
98798
98799
|
volatileFields: []
|
|
98799
98800
|
};
|
|
98800
|
-
|
|
98801
|
+
var HISTORY_CATALOG = {
|
|
98802
|
+
"air-quality-sensor": {
|
|
98803
|
+
"co2Ppm": {
|
|
98804
|
+
kind: "gauge",
|
|
98805
|
+
unit: "ppm",
|
|
98806
|
+
deadband: 5
|
|
98807
|
+
},
|
|
98808
|
+
"vocPpb": {
|
|
98809
|
+
kind: "gauge",
|
|
98810
|
+
unit: "ppb",
|
|
98811
|
+
deadband: 5
|
|
98812
|
+
},
|
|
98813
|
+
"pm25": {
|
|
98814
|
+
kind: "gauge",
|
|
98815
|
+
unit: "ug/m3",
|
|
98816
|
+
deadband: 1
|
|
98817
|
+
},
|
|
98818
|
+
"pm10": {
|
|
98819
|
+
kind: "gauge",
|
|
98820
|
+
unit: "ug/m3",
|
|
98821
|
+
deadband: 1
|
|
98822
|
+
},
|
|
98823
|
+
"aqi": {
|
|
98824
|
+
kind: "gauge",
|
|
98825
|
+
unit: "count",
|
|
98826
|
+
deadband: 1
|
|
98827
|
+
}
|
|
98828
|
+
},
|
|
98829
|
+
"ambient-light-sensor": { "lux": {
|
|
98830
|
+
kind: "gauge",
|
|
98831
|
+
unit: "lx",
|
|
98832
|
+
deadbandPct: 10,
|
|
98833
|
+
minIntervalMs: 6e4
|
|
98834
|
+
} },
|
|
98835
|
+
"battery": { "percentage": {
|
|
98836
|
+
kind: "gauge",
|
|
98837
|
+
unit: "%"
|
|
98838
|
+
} },
|
|
98839
|
+
"binary": { "on": { kind: "boolean" } },
|
|
98840
|
+
"brightness": { "percentage": {
|
|
98841
|
+
kind: "gauge",
|
|
98842
|
+
unit: "%",
|
|
98843
|
+
minIntervalMs: 0
|
|
98844
|
+
} },
|
|
98845
|
+
"carbon-monoxide": { "detected": { kind: "boolean" } },
|
|
98846
|
+
"climate-control": {
|
|
98847
|
+
"currentTemp": {
|
|
98848
|
+
kind: "gauge",
|
|
98849
|
+
unit: "\xB0C",
|
|
98850
|
+
deadband: 0.1
|
|
98851
|
+
},
|
|
98852
|
+
"currentHumidity": {
|
|
98853
|
+
kind: "gauge",
|
|
98854
|
+
unit: "%",
|
|
98855
|
+
deadband: 0.5
|
|
98856
|
+
},
|
|
98857
|
+
"target": {
|
|
98858
|
+
kind: "gauge",
|
|
98859
|
+
unit: "\xB0C",
|
|
98860
|
+
minIntervalMs: 0
|
|
98861
|
+
},
|
|
98862
|
+
"targetHigh": {
|
|
98863
|
+
kind: "gauge",
|
|
98864
|
+
unit: "\xB0C",
|
|
98865
|
+
minIntervalMs: 0
|
|
98866
|
+
},
|
|
98867
|
+
"targetLow": {
|
|
98868
|
+
kind: "gauge",
|
|
98869
|
+
unit: "\xB0C",
|
|
98870
|
+
minIntervalMs: 0
|
|
98871
|
+
}
|
|
98872
|
+
},
|
|
98873
|
+
"contact": { "entryOpen": { kind: "boolean" } },
|
|
98874
|
+
"cover": { "position": {
|
|
98875
|
+
kind: "gauge",
|
|
98876
|
+
unit: "%",
|
|
98877
|
+
minIntervalMs: 0
|
|
98878
|
+
} },
|
|
98879
|
+
"device-status": { "online": { kind: "boolean" } },
|
|
98880
|
+
"dishwasher": { "progressPercent": {
|
|
98881
|
+
kind: "gauge",
|
|
98882
|
+
unit: "%",
|
|
98883
|
+
deadband: 2
|
|
98884
|
+
} },
|
|
98885
|
+
"fan-control": {
|
|
98886
|
+
"percentage": {
|
|
98887
|
+
kind: "gauge",
|
|
98888
|
+
unit: "%",
|
|
98889
|
+
minIntervalMs: 0
|
|
98890
|
+
},
|
|
98891
|
+
"oscillating": { kind: "boolean" }
|
|
98892
|
+
},
|
|
98893
|
+
"flood": { "flooded": { kind: "boolean" } },
|
|
98894
|
+
"gas": { "detected": { kind: "boolean" } },
|
|
98895
|
+
"heating-valve": { "position": {
|
|
98896
|
+
kind: "gauge",
|
|
98897
|
+
unit: "%",
|
|
98898
|
+
deadband: 1
|
|
98899
|
+
} },
|
|
98900
|
+
"humidifier": {
|
|
98901
|
+
"currentHumidity": {
|
|
98902
|
+
kind: "gauge",
|
|
98903
|
+
unit: "%",
|
|
98904
|
+
deadband: 0.5
|
|
98905
|
+
},
|
|
98906
|
+
"on": { kind: "boolean" }
|
|
98907
|
+
},
|
|
98908
|
+
"humidity-sensor": { "percent": {
|
|
98909
|
+
kind: "gauge",
|
|
98910
|
+
unit: "%",
|
|
98911
|
+
deadband: 0.5
|
|
98912
|
+
} },
|
|
98913
|
+
"lawn-mower-control": { "batteryLevel": {
|
|
98914
|
+
kind: "gauge",
|
|
98915
|
+
unit: "%",
|
|
98916
|
+
deadband: 2
|
|
98917
|
+
} },
|
|
98918
|
+
"motion": { "detected": {
|
|
98919
|
+
kind: "boolean",
|
|
98920
|
+
excludeDeviceTypes: [DeviceType.Camera]
|
|
98921
|
+
} },
|
|
98922
|
+
"network-link": {
|
|
98923
|
+
"signalPercent": {
|
|
98924
|
+
kind: "gauge",
|
|
98925
|
+
unit: "%",
|
|
98926
|
+
deadband: 3,
|
|
98927
|
+
minIntervalMs: 6e4
|
|
98928
|
+
},
|
|
98929
|
+
"rssiDbm": {
|
|
98930
|
+
kind: "gauge",
|
|
98931
|
+
unit: "dBm",
|
|
98932
|
+
deadband: 2,
|
|
98933
|
+
minIntervalMs: 6e4
|
|
98934
|
+
}
|
|
98935
|
+
},
|
|
98936
|
+
"numeric-sensor": { "value": {
|
|
98937
|
+
kind: "gauge",
|
|
98938
|
+
unitFrom: "unit",
|
|
98939
|
+
minIntervalMs: 3e4
|
|
98940
|
+
} },
|
|
98941
|
+
"pet-feeder": {
|
|
98942
|
+
"foodLevel": { kind: "gauge" },
|
|
98943
|
+
"food1": { kind: "gauge" },
|
|
98944
|
+
"food2": { kind: "gauge" },
|
|
98945
|
+
"batteryPower": {
|
|
98946
|
+
kind: "gauge",
|
|
98947
|
+
unit: "%",
|
|
98948
|
+
deadband: 2
|
|
98949
|
+
},
|
|
98950
|
+
"desiccantLeftDays": {
|
|
98951
|
+
kind: "gauge",
|
|
98952
|
+
unit: "days"
|
|
98953
|
+
}
|
|
98954
|
+
},
|
|
98955
|
+
"power-meter": {
|
|
98956
|
+
"watts": {
|
|
98957
|
+
kind: "gauge",
|
|
98958
|
+
unit: "W",
|
|
98959
|
+
deadband: 1,
|
|
98960
|
+
minIntervalMs: 3e4
|
|
98961
|
+
},
|
|
98962
|
+
"kwhTotal": {
|
|
98963
|
+
kind: "counter",
|
|
98964
|
+
unit: "kWh",
|
|
98965
|
+
deadband: 0.01,
|
|
98966
|
+
minIntervalMs: 6e4
|
|
98967
|
+
},
|
|
98968
|
+
"volts": {
|
|
98969
|
+
kind: "gauge",
|
|
98970
|
+
unit: "V",
|
|
98971
|
+
deadband: 1,
|
|
98972
|
+
minIntervalMs: 3e4
|
|
98973
|
+
},
|
|
98974
|
+
"amps": {
|
|
98975
|
+
kind: "gauge",
|
|
98976
|
+
unit: "A",
|
|
98977
|
+
deadband: 0.05,
|
|
98978
|
+
minIntervalMs: 3e4
|
|
98979
|
+
},
|
|
98980
|
+
"va": {
|
|
98981
|
+
kind: "gauge",
|
|
98982
|
+
unit: "VA",
|
|
98983
|
+
deadband: 1,
|
|
98984
|
+
minIntervalMs: 3e4
|
|
98985
|
+
},
|
|
98986
|
+
"powerFactor": {
|
|
98987
|
+
kind: "gauge",
|
|
98988
|
+
unit: "ratio",
|
|
98989
|
+
deadband: 0.01,
|
|
98990
|
+
minIntervalMs: 6e4
|
|
98991
|
+
}
|
|
98992
|
+
},
|
|
98993
|
+
"presence": { "batteryPercent": {
|
|
98994
|
+
kind: "gauge",
|
|
98995
|
+
unit: "%",
|
|
98996
|
+
deadband: 1
|
|
98997
|
+
} },
|
|
98998
|
+
"pressure-sensor": { "hpa": {
|
|
98999
|
+
kind: "gauge",
|
|
99000
|
+
unit: "hPa",
|
|
99001
|
+
deadband: 0.1
|
|
99002
|
+
} },
|
|
99003
|
+
"smoke": { "detected": { kind: "boolean" } },
|
|
99004
|
+
"switch": { "on": { kind: "boolean" } },
|
|
99005
|
+
"tamper": { "tampered": { kind: "boolean" } },
|
|
99006
|
+
"temperature-sensor": { "celsius": {
|
|
99007
|
+
kind: "gauge",
|
|
99008
|
+
unit: "\xB0C",
|
|
99009
|
+
deadband: 0.1
|
|
99010
|
+
} },
|
|
99011
|
+
"vacuum-control": {
|
|
99012
|
+
"batteryLevel": {
|
|
99013
|
+
kind: "gauge",
|
|
99014
|
+
unit: "%",
|
|
99015
|
+
deadband: 2
|
|
99016
|
+
},
|
|
99017
|
+
"cleanWater.level": {
|
|
99018
|
+
kind: "gauge",
|
|
99019
|
+
unit: "%",
|
|
99020
|
+
deadband: 2
|
|
99021
|
+
},
|
|
99022
|
+
"dirtyWater.level": {
|
|
99023
|
+
kind: "gauge",
|
|
99024
|
+
unit: "%",
|
|
99025
|
+
deadband: 2
|
|
99026
|
+
},
|
|
99027
|
+
"detergent.level": {
|
|
99028
|
+
kind: "gauge",
|
|
99029
|
+
unit: "%",
|
|
99030
|
+
deadband: 2
|
|
99031
|
+
},
|
|
99032
|
+
"dustBin.level": {
|
|
99033
|
+
kind: "gauge",
|
|
99034
|
+
unit: "%",
|
|
99035
|
+
deadband: 2
|
|
99036
|
+
}
|
|
99037
|
+
},
|
|
99038
|
+
"valve": { "position": {
|
|
99039
|
+
kind: "gauge",
|
|
99040
|
+
unit: "%",
|
|
99041
|
+
minIntervalMs: 0
|
|
99042
|
+
} },
|
|
99043
|
+
"vibration": { "detected": { kind: "boolean" } },
|
|
99044
|
+
"water-heater": {
|
|
99045
|
+
"currentTemp": {
|
|
99046
|
+
kind: "gauge",
|
|
99047
|
+
unit: "\xB0C",
|
|
99048
|
+
deadband: 0.5
|
|
99049
|
+
},
|
|
99050
|
+
"targetTemp": {
|
|
99051
|
+
kind: "gauge",
|
|
99052
|
+
unit: "\xB0C",
|
|
99053
|
+
minIntervalMs: 0
|
|
99054
|
+
}
|
|
99055
|
+
},
|
|
99056
|
+
"weather": {
|
|
99057
|
+
"temperature": {
|
|
99058
|
+
kind: "gauge",
|
|
99059
|
+
unitFrom: "temperatureUnit",
|
|
99060
|
+
deadband: 0.2
|
|
99061
|
+
},
|
|
99062
|
+
"humidity": {
|
|
99063
|
+
kind: "gauge",
|
|
99064
|
+
unit: "%",
|
|
99065
|
+
deadband: 1
|
|
99066
|
+
},
|
|
99067
|
+
"pressure": {
|
|
99068
|
+
kind: "gauge",
|
|
99069
|
+
unitFrom: "pressureUnit",
|
|
99070
|
+
deadband: 0.5
|
|
99071
|
+
},
|
|
99072
|
+
"windSpeed": {
|
|
99073
|
+
kind: "gauge",
|
|
99074
|
+
unitFrom: "windSpeedUnit",
|
|
99075
|
+
deadband: 0.5
|
|
99076
|
+
}
|
|
99077
|
+
}
|
|
99078
|
+
};
|
|
98801
99079
|
var G711_SCALE_CORRECTION_DB = {
|
|
98802
99080
|
PCMU: 20 * Math.log10(4),
|
|
98803
99081
|
PCMA: 20 * Math.log10(8)
|
|
@@ -99724,6 +100002,70 @@ var require_dist_BkNfEJKa = __commonJS({
|
|
|
99724
100002
|
}
|
|
99725
100003
|
return out;
|
|
99726
100004
|
}
|
|
100005
|
+
var REMOVAL_RECONCILE_INTERVAL_MS = 360 * 60 * 1e3;
|
|
100006
|
+
async function reconcileRemovedDevices(opts) {
|
|
100007
|
+
const now = opts.now ?? (() => Date.now());
|
|
100008
|
+
const api = opts.api;
|
|
100009
|
+
if (api === void 0) {
|
|
100010
|
+
opts.logger.warn("device removal reconcile skipped \u2014 no device-manager api");
|
|
100011
|
+
return {
|
|
100012
|
+
read: false,
|
|
100013
|
+
applied: 0,
|
|
100014
|
+
failed: 0
|
|
100015
|
+
};
|
|
100016
|
+
}
|
|
100017
|
+
let tombstones;
|
|
100018
|
+
try {
|
|
100019
|
+
tombstones = await api.deviceManager.listDeletedSince.query({ sinceMs: now() - DEVICE_TOMBSTONE_RETENTION_MS });
|
|
100020
|
+
} catch (err) {
|
|
100021
|
+
opts.logger.warn("device removal reconcile skipped \u2014 tombstones unreadable, nothing deleted", { meta: { error: errMsg(err) } });
|
|
100022
|
+
return {
|
|
100023
|
+
read: false,
|
|
100024
|
+
applied: 0,
|
|
100025
|
+
failed: 0
|
|
100026
|
+
};
|
|
100027
|
+
}
|
|
100028
|
+
let applied = 0;
|
|
100029
|
+
let failed = 0;
|
|
100030
|
+
for (const tombstone of tombstones) try {
|
|
100031
|
+
const result = await opts.remove(tombstone);
|
|
100032
|
+
applied += 1;
|
|
100033
|
+
if (Object.values(result.counts).reduce((sum, n) => sum + n, 0) > 0) opts.logger.info("device removal reconcile cleaned a missed deletion", {
|
|
100034
|
+
tags: { deviceId: tombstone.deviceId },
|
|
100035
|
+
meta: {
|
|
100036
|
+
counts: result.counts,
|
|
100037
|
+
deletedAt: tombstone.deletedAt
|
|
100038
|
+
}
|
|
100039
|
+
});
|
|
100040
|
+
} catch (err) {
|
|
100041
|
+
failed += 1;
|
|
100042
|
+
opts.logger.warn("device removal reconcile failed for a device \u2014 retried next pass", {
|
|
100043
|
+
tags: { deviceId: tombstone.deviceId },
|
|
100044
|
+
meta: { error: errMsg(err) }
|
|
100045
|
+
});
|
|
100046
|
+
}
|
|
100047
|
+
return {
|
|
100048
|
+
read: true,
|
|
100049
|
+
applied,
|
|
100050
|
+
failed
|
|
100051
|
+
};
|
|
100052
|
+
}
|
|
100053
|
+
function armRemovalReconcile(opts) {
|
|
100054
|
+
let running = false;
|
|
100055
|
+
const pass = () => {
|
|
100056
|
+
if (running) return;
|
|
100057
|
+
running = true;
|
|
100058
|
+
reconcileRemovedDevices(opts).catch((err) => {
|
|
100059
|
+
opts.logger.warn("device removal reconcile pass crashed", { meta: { error: errMsg(err) } });
|
|
100060
|
+
}).finally(() => {
|
|
100061
|
+
running = false;
|
|
100062
|
+
});
|
|
100063
|
+
};
|
|
100064
|
+
pass();
|
|
100065
|
+
const timer = setInterval(pass, REMOVAL_RECONCILE_INTERVAL_MS);
|
|
100066
|
+
timer.unref?.();
|
|
100067
|
+
return () => clearInterval(timer);
|
|
100068
|
+
}
|
|
99727
100069
|
Object.defineProperty(exports, "AGENT_MDNS_SERVICE_TYPE", {
|
|
99728
100070
|
enumerable: true,
|
|
99729
100071
|
get: function() {
|
|
@@ -100024,6 +100366,18 @@ var require_dist_BkNfEJKa = __commonJS({
|
|
|
100024
100366
|
return ForeignSecretMarkerError;
|
|
100025
100367
|
}
|
|
100026
100368
|
});
|
|
100369
|
+
Object.defineProperty(exports, "HISTORY_CATALOG", {
|
|
100370
|
+
enumerable: true,
|
|
100371
|
+
get: function() {
|
|
100372
|
+
return HISTORY_CATALOG;
|
|
100373
|
+
}
|
|
100374
|
+
});
|
|
100375
|
+
Object.defineProperty(exports, "HISTORY_DEFAULT_MIN_INTERVAL_MS", {
|
|
100376
|
+
enumerable: true,
|
|
100377
|
+
get: function() {
|
|
100378
|
+
return HISTORY_DEFAULT_MIN_INTERVAL_MS;
|
|
100379
|
+
}
|
|
100380
|
+
});
|
|
100027
100381
|
Object.defineProperty(exports, "HUB_MDNS_SERVICE_TYPE", {
|
|
100028
100382
|
enumerable: true,
|
|
100029
100383
|
get: function() {
|
|
@@ -100330,6 +100684,12 @@ var require_dist_BkNfEJKa = __commonJS({
|
|
|
100330
100684
|
return alertsCapability;
|
|
100331
100685
|
}
|
|
100332
100686
|
});
|
|
100687
|
+
Object.defineProperty(exports, "armRemovalReconcile", {
|
|
100688
|
+
enumerable: true,
|
|
100689
|
+
get: function() {
|
|
100690
|
+
return armRemovalReconcile;
|
|
100691
|
+
}
|
|
100692
|
+
});
|
|
100333
100693
|
Object.defineProperty(exports, "asJsonObject", {
|
|
100334
100694
|
enumerable: true,
|
|
100335
100695
|
get: function() {
|
|
@@ -100648,12 +101008,24 @@ var require_dist_BkNfEJKa = __commonJS({
|
|
|
100648
101008
|
return deviceExtensionCapability;
|
|
100649
101009
|
}
|
|
100650
101010
|
});
|
|
101011
|
+
Object.defineProperty(exports, "deviceHistoryCapability", {
|
|
101012
|
+
enumerable: true,
|
|
101013
|
+
get: function() {
|
|
101014
|
+
return deviceHistoryCapability;
|
|
101015
|
+
}
|
|
101016
|
+
});
|
|
100651
101017
|
Object.defineProperty(exports, "deviceManagerCapability", {
|
|
100652
101018
|
enumerable: true,
|
|
100653
101019
|
get: function() {
|
|
100654
101020
|
return deviceManagerCapability;
|
|
100655
101021
|
}
|
|
100656
101022
|
});
|
|
101023
|
+
Object.defineProperty(exports, "deviceRemovalHookCapability", {
|
|
101024
|
+
enumerable: true,
|
|
101025
|
+
get: function() {
|
|
101026
|
+
return deviceRemovalHookCapability;
|
|
101027
|
+
}
|
|
101028
|
+
});
|
|
100657
101029
|
Object.defineProperty(exports, "deviceStateCapability", {
|
|
100658
101030
|
enumerable: true,
|
|
100659
101031
|
get: function() {
|
|
@@ -112906,11 +113278,11 @@ ${values.join("\n")}` : `${blockName} :`;
|
|
|
112906
113278
|
}
|
|
112907
113279
|
});
|
|
112908
113280
|
|
|
112909
|
-
// ../system/dist/device-ca-
|
|
112910
|
-
var
|
|
112911
|
-
"../system/dist/device-ca-
|
|
113281
|
+
// ../system/dist/device-ca-75rt3U_U.js
|
|
113282
|
+
var require_device_ca_75rt3U_U = __commonJS({
|
|
113283
|
+
"../system/dist/device-ca-75rt3U_U.js"(exports) {
|
|
112912
113284
|
"use strict";
|
|
112913
|
-
var require_dist10 =
|
|
113285
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
112914
113286
|
var require_x509_es = require_x509_es_C4cOo48M();
|
|
112915
113287
|
var node_crypto = __require("crypto");
|
|
112916
113288
|
var SYSTEM_SECRET_OWNER = "system";
|
|
@@ -113325,11 +113697,11 @@ var require_device_ca_CnbrgfmW = __commonJS({
|
|
|
113325
113697
|
}
|
|
113326
113698
|
});
|
|
113327
113699
|
|
|
113328
|
-
// ../system/dist/phase6-declare-gate-
|
|
113329
|
-
var
|
|
113330
|
-
"../system/dist/phase6-declare-gate-
|
|
113700
|
+
// ../system/dist/phase6-declare-gate-C6WqagkC.js
|
|
113701
|
+
var require_phase6_declare_gate_C6WqagkC = __commonJS({
|
|
113702
|
+
"../system/dist/phase6-declare-gate-C6WqagkC.js"(exports) {
|
|
113331
113703
|
"use strict";
|
|
113332
|
-
var require_dist10 =
|
|
113704
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
113333
113705
|
function legacyTableRowCountFromList(tables, collection) {
|
|
113334
113706
|
return tables.find((t) => t.name === collection)?.rows ?? 0;
|
|
113335
113707
|
}
|
|
@@ -113423,9 +113795,9 @@ var require_alerts_addon = __commonJS({
|
|
|
113423
113795
|
[Symbol.toStringTag]: { value: "Module" }
|
|
113424
113796
|
});
|
|
113425
113797
|
require_chunk_Cek0wNdY();
|
|
113426
|
-
var require_dist10 =
|
|
113798
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
113427
113799
|
var require_read_all_rows$1 = require_read_all_rows_CVoO1ONM();
|
|
113428
|
-
var require_phase6_declare_gate =
|
|
113800
|
+
var require_phase6_declare_gate = require_phase6_declare_gate_C6WqagkC();
|
|
113429
113801
|
var CRITICAL_CATEGORIES = /* @__PURE__ */ new Set([
|
|
113430
113802
|
"addon.runner-failed",
|
|
113431
113803
|
"addon.runner-refused",
|
|
@@ -115075,7 +115447,7 @@ var require_calendars_addon = __commonJS({
|
|
|
115075
115447
|
[Symbol.toStringTag]: { value: "Module" }
|
|
115076
115448
|
});
|
|
115077
115449
|
require_chunk_Cek0wNdY();
|
|
115078
|
-
var require_dist10 =
|
|
115450
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
115079
115451
|
var require_settle_sources = require_settle_sources_Bhsy57y();
|
|
115080
115452
|
var zod = require_zod();
|
|
115081
115453
|
var CALENDAR_COLOR_PALETTE = [
|
|
@@ -115293,11 +115665,11 @@ var require_calendars = __commonJS({
|
|
|
115293
115665
|
}
|
|
115294
115666
|
});
|
|
115295
115667
|
|
|
115296
|
-
// ../system/dist/composition-sources-
|
|
115297
|
-
var
|
|
115298
|
-
"../system/dist/composition-sources-
|
|
115668
|
+
// ../system/dist/composition-sources-BQmhc3se.js
|
|
115669
|
+
var require_composition_sources_BQmhc3se = __commonJS({
|
|
115670
|
+
"../system/dist/composition-sources-BQmhc3se.js"(exports) {
|
|
115299
115671
|
"use strict";
|
|
115300
|
-
var require_dist10 =
|
|
115672
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
115301
115673
|
function normalizedNativeOwner(owner) {
|
|
115302
115674
|
const trimmed = owner?.trim();
|
|
115303
115675
|
return trimmed ? trimmed : null;
|
|
@@ -115412,8 +115784,8 @@ var require_composer_addon = __commonJS({
|
|
|
115412
115784
|
[Symbol.toStringTag]: { value: "Module" }
|
|
115413
115785
|
});
|
|
115414
115786
|
require_chunk_Cek0wNdY();
|
|
115415
|
-
var require_dist10 =
|
|
115416
|
-
var require_composition_sources =
|
|
115787
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
115788
|
+
var require_composition_sources = require_composition_sources_BQmhc3se();
|
|
115417
115789
|
var zod = require_zod();
|
|
115418
115790
|
function isRecord$2(value) {
|
|
115419
115791
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -119223,7 +119595,7 @@ var require_console_logging = __commonJS({
|
|
|
119223
119595
|
[Symbol.toStringTag]: { value: "Module" }
|
|
119224
119596
|
});
|
|
119225
119597
|
require_chunk_Cek0wNdY();
|
|
119226
|
-
var require_dist10 =
|
|
119598
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
119227
119599
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
119228
119600
|
var LEVEL_RANK = {
|
|
119229
119601
|
debug: 0,
|
|
@@ -123616,9 +123988,9 @@ var require_core_blocks_addon = __commonJS({
|
|
|
123616
123988
|
"use strict";
|
|
123617
123989
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
123618
123990
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
123619
|
-
var require_dist10 =
|
|
123991
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
123620
123992
|
var require_read_all_rows$1 = require_read_all_rows_CVoO1ONM();
|
|
123621
|
-
var require_composition_sources =
|
|
123993
|
+
var require_composition_sources = require_composition_sources_BQmhc3se();
|
|
123622
123994
|
var require_prefix_range = require_prefix_range_D_KueXbQ();
|
|
123623
123995
|
var require_prefix_walk = require_prefix_walk_Dv6MLZlM();
|
|
123624
123996
|
var zod = require_zod();
|
|
@@ -136160,7 +136532,7 @@ var require_device_manager_addon = __commonJS({
|
|
|
136160
136532
|
[Symbol.toStringTag]: { value: "Module" }
|
|
136161
136533
|
});
|
|
136162
136534
|
require_chunk_Cek0wNdY();
|
|
136163
|
-
var require_dist10 =
|
|
136535
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
136164
136536
|
var require_read_all_rows$1 = require_read_all_rows_CVoO1ONM();
|
|
136165
136537
|
var require_prefix_range = require_prefix_range_D_KueXbQ();
|
|
136166
136538
|
var require_prefix_walk = require_prefix_walk_Dv6MLZlM();
|
|
@@ -145234,7 +145606,7 @@ var require_hub_forwarder = __commonJS({
|
|
|
145234
145606
|
[Symbol.toStringTag]: { value: "Module" }
|
|
145235
145607
|
});
|
|
145236
145608
|
require_chunk_Cek0wNdY();
|
|
145237
|
-
var require_dist10 =
|
|
145609
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
145238
145610
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
145239
145611
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
145240
145612
|
var HubForwarderDestination = class {
|
|
@@ -145371,7 +145743,7 @@ var require_liveness_monitor_addon = __commonJS({
|
|
|
145371
145743
|
"use strict";
|
|
145372
145744
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
145373
145745
|
require_chunk_Cek0wNdY();
|
|
145374
|
-
var require_dist10 =
|
|
145746
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
145375
145747
|
var NO_DEVICES = "liveness:no-devices";
|
|
145376
145748
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
145377
145749
|
var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
|
|
@@ -145734,12 +146106,12 @@ var require_local_auth_addon = __commonJS({
|
|
|
145734
146106
|
[Symbol.toStringTag]: { value: "Module" }
|
|
145735
146107
|
});
|
|
145736
146108
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
145737
|
-
var require_dist10 =
|
|
146109
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
145738
146110
|
var require_read_all_rows$1 = require_read_all_rows_CVoO1ONM();
|
|
145739
146111
|
var require_safe_buffer$1 = require_safe_buffer_D3aoa_7X();
|
|
145740
146112
|
var require_ms$1 = require_ms_LB3giGoU();
|
|
145741
|
-
var require_device_ca =
|
|
145742
|
-
var require_phase6_declare_gate =
|
|
146113
|
+
var require_device_ca = require_device_ca_75rt3U_U();
|
|
146114
|
+
var require_phase6_declare_gate = require_phase6_declare_gate_C6WqagkC();
|
|
145743
146115
|
var zod = require_zod();
|
|
145744
146116
|
var node_crypto = __require("crypto");
|
|
145745
146117
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
@@ -156534,7 +156906,7 @@ var require_loki_logging = __commonJS({
|
|
|
156534
156906
|
[Symbol.toStringTag]: { value: "Module" }
|
|
156535
156907
|
});
|
|
156536
156908
|
require_chunk_Cek0wNdY();
|
|
156537
|
-
var require_dist10 =
|
|
156909
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
156538
156910
|
function sanitizeLabelName(raw) {
|
|
156539
156911
|
const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
|
|
156540
156912
|
return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
|
|
@@ -157206,7 +157578,7 @@ var require_native_metrics_addon = __commonJS({
|
|
|
157206
157578
|
[Symbol.toStringTag]: { value: "Module" }
|
|
157207
157579
|
});
|
|
157208
157580
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
157209
|
-
var require_dist10 =
|
|
157581
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
157210
157582
|
var node_fs_promises = __require("fs/promises");
|
|
157211
157583
|
var node_util = __require("util");
|
|
157212
157584
|
var node_os = __require("os");
|
|
@@ -157701,11 +158073,9 @@ var require_native_metrics_addon = __commonJS({
|
|
|
157701
158073
|
* rather than a replay of a timestamp this node has already delivered.
|
|
157702
158074
|
*
|
|
157703
158075
|
* The return value is not diagnostics. It is the idempotence gate the
|
|
157704
|
-
* DURABLE tier rides on (`
|
|
157705
|
-
*
|
|
157706
|
-
*
|
|
157707
|
-
* gate is one monotonic comparison in memory; the index it replaces was
|
|
157708
|
-
* measured at ~40 bytes per row.
|
|
158076
|
+
* DURABLE tier rides on this return value (`node-metrics-history-recorder.ts`):
|
|
158077
|
+
* only an accepted sample is sent to device-history. The gate is one monotonic
|
|
158078
|
+
* comparison in memory.
|
|
157709
158079
|
*/
|
|
157710
158080
|
record(nodeId, atMs, processes) {
|
|
157711
158081
|
if (nodeId.length === 0) return false;
|
|
@@ -157830,138 +158200,7 @@ var require_native_metrics_addon = __commonJS({
|
|
|
157830
158200
|
this.lastWriteSeq.delete(nodeId);
|
|
157831
158201
|
}
|
|
157832
158202
|
};
|
|
157833
|
-
var CONTAINER_MEMORY_COLLECTION = "metrics:node-memory-samples";
|
|
157834
158203
|
var CONTAINER_MEMORY_MAX_TOTAL_ROWS = 2880;
|
|
157835
|
-
var CONTAINER_MEMORY_MAX_ROWS = 5e4;
|
|
157836
|
-
var CONTAINER_MEMORY_COLUMNS = [
|
|
157837
|
-
{
|
|
157838
|
-
name: "id",
|
|
157839
|
-
type: "INTEGER",
|
|
157840
|
-
primaryKey: true,
|
|
157841
|
-
notNull: true
|
|
157842
|
-
},
|
|
157843
|
-
{
|
|
157844
|
-
name: "nodeId",
|
|
157845
|
-
type: "TEXT",
|
|
157846
|
-
notNull: true
|
|
157847
|
-
},
|
|
157848
|
-
/** Shared with the process rows of the same tick — that is the whole point. */
|
|
157849
|
-
{
|
|
157850
|
-
name: "atMs",
|
|
157851
|
-
type: "INTEGER",
|
|
157852
|
-
notNull: true
|
|
157853
|
-
},
|
|
157854
|
-
{
|
|
157855
|
-
name: "source",
|
|
157856
|
-
type: "TEXT",
|
|
157857
|
-
notNull: true
|
|
157858
|
-
},
|
|
157859
|
-
/**
|
|
157860
|
-
* Whole MiB, like `rssMib` on the process row and for the same reason: a
|
|
157861
|
-
* byte-exact residency on a value in the tens of gigabytes is precision no
|
|
157862
|
-
* chart can draw, and it costs bytes on every row forever.
|
|
157863
|
-
*/
|
|
157864
|
-
{
|
|
157865
|
-
name: "currentMib",
|
|
157866
|
-
type: "INTEGER",
|
|
157867
|
-
notNull: true
|
|
157868
|
-
},
|
|
157869
|
-
/** `NULL` = NO LIMIT. Never a sentinel. */
|
|
157870
|
-
{
|
|
157871
|
-
name: "limitMib",
|
|
157872
|
-
type: "INTEGER"
|
|
157873
|
-
},
|
|
157874
|
-
/** `NULL` = UNKNOWN on all four. Never zero. */
|
|
157875
|
-
{
|
|
157876
|
-
name: "anonMib",
|
|
157877
|
-
type: "INTEGER"
|
|
157878
|
-
},
|
|
157879
|
-
{
|
|
157880
|
-
name: "fileMib",
|
|
157881
|
-
type: "INTEGER"
|
|
157882
|
-
},
|
|
157883
|
-
{
|
|
157884
|
-
name: "shmemMib",
|
|
157885
|
-
type: "INTEGER"
|
|
157886
|
-
},
|
|
157887
|
-
{
|
|
157888
|
-
name: "slabMib",
|
|
157889
|
-
type: "INTEGER"
|
|
157890
|
-
},
|
|
157891
|
-
/** Host-wide i915 GEM bytes. `NULL` wherever debugfs is not mounted. */
|
|
157892
|
-
{
|
|
157893
|
-
name: "gpuShmemMib",
|
|
157894
|
-
type: "INTEGER"
|
|
157895
|
-
}
|
|
157896
|
-
];
|
|
157897
|
-
var CONTAINER_MEMORY_INDEXES = [
|
|
157898
|
-
/** The one index, and it serves both the read and the prune — see the
|
|
157899
|
-
* process table's `idx_load_samples_node_at` for the full reasoning. */
|
|
157900
|
-
{
|
|
157901
|
-
name: "idx_node_memory_node_at",
|
|
157902
|
-
columns: ["nodeId", "atMs"]
|
|
157903
|
-
}
|
|
157904
|
-
];
|
|
157905
|
-
var BYTES_PER_MIB$1 = 1048576;
|
|
157906
|
-
function toMib(value) {
|
|
157907
|
-
return value === null ? null : Math.round(value / BYTES_PER_MIB$1);
|
|
157908
|
-
}
|
|
157909
|
-
function fromMib(value) {
|
|
157910
|
-
return value === null ? null : value * BYTES_PER_MIB$1;
|
|
157911
|
-
}
|
|
157912
|
-
function snapshotToRow(nodeId, atMs, snapshot) {
|
|
157913
|
-
return {
|
|
157914
|
-
nodeId,
|
|
157915
|
-
atMs,
|
|
157916
|
-
source: snapshot.source,
|
|
157917
|
-
currentMib: Math.round(snapshot.currentBytes / BYTES_PER_MIB$1),
|
|
157918
|
-
limitMib: toMib(snapshot.limitBytes),
|
|
157919
|
-
anonMib: toMib(snapshot.anonBytes),
|
|
157920
|
-
fileMib: toMib(snapshot.fileBytes),
|
|
157921
|
-
shmemMib: toMib(snapshot.shmemBytes),
|
|
157922
|
-
slabMib: toMib(snapshot.slabBytes),
|
|
157923
|
-
gpuShmemMib: toMib(snapshot.gpuShmemBytes)
|
|
157924
|
-
};
|
|
157925
|
-
}
|
|
157926
|
-
function rowToPoint(row) {
|
|
157927
|
-
if (row.source !== "cgroup-v2" && row.source !== "cgroup-v1") return null;
|
|
157928
|
-
return {
|
|
157929
|
-
atMs: row.atMs,
|
|
157930
|
-
source: row.source,
|
|
157931
|
-
currentBytes: row.currentMib * BYTES_PER_MIB$1,
|
|
157932
|
-
limitBytes: fromMib(row.limitMib),
|
|
157933
|
-
anonBytes: fromMib(row.anonMib),
|
|
157934
|
-
fileBytes: fromMib(row.fileMib),
|
|
157935
|
-
shmemBytes: fromMib(row.shmemMib),
|
|
157936
|
-
slabBytes: fromMib(row.slabMib),
|
|
157937
|
-
gpuShmemBytes: fromMib(row.gpuShmemMib)
|
|
157938
|
-
};
|
|
157939
|
-
}
|
|
157940
|
-
function recordToMemoryRow(data) {
|
|
157941
|
-
const nodeId = data["nodeId"];
|
|
157942
|
-
const source = data["source"];
|
|
157943
|
-
if (typeof nodeId !== "string" || typeof source !== "string") return null;
|
|
157944
|
-
const atMs = Number(data["atMs"]);
|
|
157945
|
-
const currentMib = Number(data["currentMib"]);
|
|
157946
|
-
if (!Number.isFinite(atMs) || !Number.isFinite(currentMib)) return null;
|
|
157947
|
-
const optional = (raw) => {
|
|
157948
|
-
if (raw === null || raw === void 0) return null;
|
|
157949
|
-
const n = Number(raw);
|
|
157950
|
-
return Number.isFinite(n) ? n : null;
|
|
157951
|
-
};
|
|
157952
|
-
return {
|
|
157953
|
-
nodeId,
|
|
157954
|
-
atMs,
|
|
157955
|
-
source,
|
|
157956
|
-
currentMib,
|
|
157957
|
-
limitMib: optional(data["limitMib"]),
|
|
157958
|
-
anonMib: optional(data["anonMib"]),
|
|
157959
|
-
fileMib: optional(data["fileMib"]),
|
|
157960
|
-
shmemMib: optional(data["shmemMib"]),
|
|
157961
|
-
slabMib: optional(data["slabMib"]),
|
|
157962
|
-
gpuShmemMib: optional(data["gpuShmemMib"])
|
|
157963
|
-
};
|
|
157964
|
-
}
|
|
157965
158204
|
var ContainerMemoryRing = class {
|
|
157966
158205
|
ring;
|
|
157967
158206
|
constructor(options = {}) {
|
|
@@ -157971,17 +158210,9 @@ var require_native_metrics_addon = __commonJS({
|
|
|
157971
158210
|
...options.now !== void 0 ? { now: options.now } : {}
|
|
157972
158211
|
});
|
|
157973
158212
|
}
|
|
157974
|
-
/** Retain one reading. `false` = a timestamp this node already delivered. */
|
|
157975
158213
|
record(nodeId, atMs, snapshot) {
|
|
157976
158214
|
return this.ring.record(nodeId, atMs, [snapshot]);
|
|
157977
158215
|
}
|
|
157978
|
-
/**
|
|
157979
|
-
* Read one node's retained readings, oldest-first. `sinceMs` is EXCLUSIVE,
|
|
157980
|
-
* matching every other reader in this subsystem.
|
|
157981
|
-
*
|
|
157982
|
-
* A node nobody has recorded answers EMPTY — which is what "this node has no
|
|
157983
|
-
* cgroup" looks like, and it must stay distinguishable from a zero.
|
|
157984
|
-
*/
|
|
157985
158216
|
read(nodeId, sinceMs) {
|
|
157986
158217
|
const series = this.ring.read(nodeId, sinceMs);
|
|
157987
158218
|
const out = [];
|
|
@@ -158015,623 +158246,7 @@ var require_native_metrics_addon = __commonJS({
|
|
|
158015
158246
|
for (const point of hot) byAt.set(point.atMs, point);
|
|
158016
158247
|
return [...byAt.values()].toSorted((a, b) => a.atMs - b.atMs);
|
|
158017
158248
|
}
|
|
158018
|
-
var ContainerMemoryStore = class {
|
|
158019
|
-
declared = false;
|
|
158020
|
-
store;
|
|
158021
|
-
logger;
|
|
158022
|
-
nowFn;
|
|
158023
|
-
prunePageRows;
|
|
158024
|
-
constructor(deps) {
|
|
158025
|
-
this.store = deps.store;
|
|
158026
|
-
this.logger = deps.logger;
|
|
158027
|
-
this.nowFn = deps.now ?? (() => Date.now());
|
|
158028
|
-
this.prunePageRows = deps.prunePageRows ?? 2e4;
|
|
158029
|
-
}
|
|
158030
|
-
/** Idempotently declare the collection. `false` when the store refused. */
|
|
158031
|
-
async declare() {
|
|
158032
|
-
if (this.declared) return true;
|
|
158033
|
-
try {
|
|
158034
|
-
await this.store.declareCollection.mutate({
|
|
158035
|
-
collection: CONTAINER_MEMORY_COLLECTION,
|
|
158036
|
-
columns: [...CONTAINER_MEMORY_COLUMNS],
|
|
158037
|
-
indexes: CONTAINER_MEMORY_INDEXES.map((i) => ({
|
|
158038
|
-
name: i.name,
|
|
158039
|
-
columns: [...i.columns]
|
|
158040
|
-
}))
|
|
158041
|
-
});
|
|
158042
|
-
this.declared = true;
|
|
158043
|
-
return true;
|
|
158044
|
-
} catch (err) {
|
|
158045
|
-
this.logger.warn("container memory declareCollection failed \u2014 nothing will be retained on disk", { meta: {
|
|
158046
|
-
collection: CONTAINER_MEMORY_COLLECTION,
|
|
158047
|
-
error: require_dist10.errMsg(err)
|
|
158048
|
-
} });
|
|
158049
|
-
return false;
|
|
158050
|
-
}
|
|
158051
|
-
}
|
|
158052
|
-
/** Append ONE reading. */
|
|
158053
|
-
async append(nodeId, atMs, snapshot) {
|
|
158054
|
-
if (!await this.declare()) return 0;
|
|
158055
|
-
const record = { ...snapshotToRow(nodeId, atMs, snapshot) };
|
|
158056
|
-
try {
|
|
158057
|
-
const { inserted } = await this.store.insertMany.mutate({
|
|
158058
|
-
collection: CONTAINER_MEMORY_COLLECTION,
|
|
158059
|
-
records: [{ data: record }]
|
|
158060
|
-
});
|
|
158061
|
-
return inserted;
|
|
158062
|
-
} catch (err) {
|
|
158063
|
-
this.logger.warn("container memory reading not retained \u2014 this interval will be missing", { meta: {
|
|
158064
|
-
nodeId,
|
|
158065
|
-
atMs,
|
|
158066
|
-
error: require_dist10.errMsg(err)
|
|
158067
|
-
} });
|
|
158068
|
-
return 0;
|
|
158069
|
-
}
|
|
158070
|
-
}
|
|
158071
|
-
/** Read one node's cold readings, oldest-first. `sinceMs` is EXCLUSIVE. */
|
|
158072
|
-
async read(nodeId, sinceMs, limitRows) {
|
|
158073
|
-
if (!await this.declare()) return [];
|
|
158074
|
-
try {
|
|
158075
|
-
const records = await this.store.query.query({
|
|
158076
|
-
collection: CONTAINER_MEMORY_COLLECTION,
|
|
158077
|
-
filter: {
|
|
158078
|
-
where: { nodeId },
|
|
158079
|
-
whereBetween: { atMs: [sinceMs + 1, Number.MAX_SAFE_INTEGER] },
|
|
158080
|
-
orderBy: {
|
|
158081
|
-
field: "atMs",
|
|
158082
|
-
direction: "asc"
|
|
158083
|
-
},
|
|
158084
|
-
limit: limitRows
|
|
158085
|
-
}
|
|
158086
|
-
});
|
|
158087
|
-
const points = [];
|
|
158088
|
-
for (const record of records) {
|
|
158089
|
-
const row = recordToMemoryRow(record.data);
|
|
158090
|
-
if (row === null) continue;
|
|
158091
|
-
const point = rowToPoint(row);
|
|
158092
|
-
if (point !== null) points.push(point);
|
|
158093
|
-
}
|
|
158094
|
-
return points;
|
|
158095
|
-
} catch (err) {
|
|
158096
|
-
this.logger.warn("container memory cold read failed \u2014 answering from the hot window only", { meta: {
|
|
158097
|
-
nodeId,
|
|
158098
|
-
sinceMs,
|
|
158099
|
-
error: require_dist10.errMsg(err)
|
|
158100
|
-
} });
|
|
158101
|
-
return [];
|
|
158102
|
-
}
|
|
158103
|
-
}
|
|
158104
|
-
/**
|
|
158105
|
-
* Enforce BOTH bounds, oldest-first, through a bounded page each.
|
|
158106
|
-
*
|
|
158107
|
-
* Not rate-limited here: it is driven by the process store's own prune
|
|
158108
|
-
* cadence, so it runs at most once a minute by construction and a second
|
|
158109
|
-
* timer would be a second thing to get wrong.
|
|
158110
|
-
*/
|
|
158111
|
-
async prune(nodeIds, retention) {
|
|
158112
|
-
if (!await this.declare()) return null;
|
|
158113
|
-
const ageCutoff = this.nowFn() - retention.retentionHours * 36e5;
|
|
158114
|
-
let deletedByAge = 0;
|
|
158115
|
-
let rowsExamined = 0;
|
|
158116
|
-
for (const nodeId of nodeIds) {
|
|
158117
|
-
const outcome = await this.deleteOldestPage(nodeId, ageCutoff, this.prunePageRows);
|
|
158118
|
-
deletedByAge += outcome.deleted;
|
|
158119
|
-
rowsExamined += outcome.examined;
|
|
158120
|
-
}
|
|
158121
|
-
const total = await this.count();
|
|
158122
|
-
const excess = total === null ? 0 : total - CONTAINER_MEMORY_MAX_ROWS;
|
|
158123
|
-
let deletedByCap = 0;
|
|
158124
|
-
if (excess > 0) {
|
|
158125
|
-
let remaining = Math.min(excess, this.prunePageRows);
|
|
158126
|
-
for (const nodeId of nodeIds) {
|
|
158127
|
-
if (remaining <= 0) break;
|
|
158128
|
-
const outcome = await this.deleteOldestPage(nodeId, null, remaining);
|
|
158129
|
-
deletedByCap += outcome.deleted;
|
|
158130
|
-
remaining -= outcome.deleted;
|
|
158131
|
-
rowsExamined += outcome.examined;
|
|
158132
|
-
}
|
|
158133
|
-
this.logger.warn("container memory ROW CAP bit \u2014 evicting the oldest readings", { meta: {
|
|
158134
|
-
collection: CONTAINER_MEMORY_COLLECTION,
|
|
158135
|
-
rows: total,
|
|
158136
|
-
cap: CONTAINER_MEMORY_MAX_ROWS,
|
|
158137
|
-
over: excess,
|
|
158138
|
-
deleted: deletedByCap,
|
|
158139
|
-
hint: "lower the retention or the sampling cadence \u2014 the cap is the guarantee, not the intention"
|
|
158140
|
-
} });
|
|
158141
|
-
}
|
|
158142
|
-
return {
|
|
158143
|
-
deletedByAge,
|
|
158144
|
-
deletedByCap,
|
|
158145
|
-
rowsExamined,
|
|
158146
|
-
capBit: excess > 0
|
|
158147
|
-
};
|
|
158148
|
-
}
|
|
158149
|
-
/** Total rows, or `null` when the store could not answer. */
|
|
158150
|
-
async count() {
|
|
158151
|
-
if (!await this.declare()) return null;
|
|
158152
|
-
try {
|
|
158153
|
-
return await this.store.count.query({ collection: CONTAINER_MEMORY_COLLECTION });
|
|
158154
|
-
} catch (err) {
|
|
158155
|
-
this.logger.warn("container memory count failed \u2014 the row cap is not enforced this pass", { meta: { error: require_dist10.errMsg(err) } });
|
|
158156
|
-
return null;
|
|
158157
|
-
}
|
|
158158
|
-
}
|
|
158159
|
-
/**
|
|
158160
|
-
* Delete this node's oldest rows, at most one page's worth.
|
|
158161
|
-
*
|
|
158162
|
-
* `cutoff === null` means "the oldest rows, whatever their age" — the
|
|
158163
|
-
* row-cap path, which is bounded by `limitRows` instead. Either way the page
|
|
158164
|
-
* is READ FIRST, ordered by the one index, and its last `atMs` becomes the
|
|
158165
|
-
* effective cutoff for a single bounded delete. An unbounded `deleteWhere`
|
|
158166
|
-
* would be one statement and one stalling transaction the first time a
|
|
158167
|
-
* retention is lowered.
|
|
158168
|
-
*/
|
|
158169
|
-
async deleteOldestPage(nodeId, cutoff, limitRows) {
|
|
158170
|
-
if (limitRows <= 0) return {
|
|
158171
|
-
deleted: 0,
|
|
158172
|
-
examined: 0
|
|
158173
|
-
};
|
|
158174
|
-
try {
|
|
158175
|
-
const page = await this.store.query.query({
|
|
158176
|
-
collection: CONTAINER_MEMORY_COLLECTION,
|
|
158177
|
-
filter: {
|
|
158178
|
-
where: { nodeId },
|
|
158179
|
-
...cutoff === null ? {} : { whereBetween: { atMs: [0, cutoff] } },
|
|
158180
|
-
orderBy: {
|
|
158181
|
-
field: "atMs",
|
|
158182
|
-
direction: "asc"
|
|
158183
|
-
},
|
|
158184
|
-
limit: Math.min(limitRows, this.prunePageRows)
|
|
158185
|
-
},
|
|
158186
|
-
columns: ["atMs"]
|
|
158187
|
-
});
|
|
158188
|
-
if (page.length === 0) return {
|
|
158189
|
-
deleted: 0,
|
|
158190
|
-
examined: 0
|
|
158191
|
-
};
|
|
158192
|
-
const effectiveCutoff = Number(page.at(-1)?.data["atMs"]);
|
|
158193
|
-
if (!Number.isFinite(effectiveCutoff)) return {
|
|
158194
|
-
deleted: 0,
|
|
158195
|
-
examined: page.length
|
|
158196
|
-
};
|
|
158197
|
-
const { deleted } = await this.store.deleteWhere.mutate({
|
|
158198
|
-
collection: CONTAINER_MEMORY_COLLECTION,
|
|
158199
|
-
filter: {
|
|
158200
|
-
where: { nodeId },
|
|
158201
|
-
whereBetween: { atMs: [0, effectiveCutoff] }
|
|
158202
|
-
}
|
|
158203
|
-
});
|
|
158204
|
-
return {
|
|
158205
|
-
deleted,
|
|
158206
|
-
examined: page.length
|
|
158207
|
-
};
|
|
158208
|
-
} catch (err) {
|
|
158209
|
-
this.logger.warn("container memory prune failed \u2014 the table keeps growing this pass", { meta: {
|
|
158210
|
-
nodeId,
|
|
158211
|
-
cutoff,
|
|
158212
|
-
error: require_dist10.errMsg(err)
|
|
158213
|
-
} });
|
|
158214
|
-
return {
|
|
158215
|
-
deleted: 0,
|
|
158216
|
-
examined: 0
|
|
158217
|
-
};
|
|
158218
|
-
}
|
|
158219
|
-
}
|
|
158220
|
-
};
|
|
158221
|
-
var LOAD_SERIES_COLLECTION = "metrics:node-load-samples";
|
|
158222
158249
|
var DEFAULT_MAX_ROWS = 5e5;
|
|
158223
|
-
var LOAD_SERIES_COLUMNS = [
|
|
158224
|
-
/** `INTEGER PRIMARY KEY` = SQLite rowid alias: the key IS the row's address,
|
|
158225
|
-
* so it costs no separate index and no stored string. */
|
|
158226
|
-
{
|
|
158227
|
-
name: "id",
|
|
158228
|
-
type: "INTEGER",
|
|
158229
|
-
primaryKey: true,
|
|
158230
|
-
notNull: true
|
|
158231
|
-
},
|
|
158232
|
-
{
|
|
158233
|
-
name: "nodeId",
|
|
158234
|
-
type: "TEXT",
|
|
158235
|
-
notNull: true
|
|
158236
|
-
},
|
|
158237
|
-
/** The EMITTING node's timestamp for the sample this row belongs to. Every
|
|
158238
|
-
* row of one sample shares it — that is what makes a sample reassemblable. */
|
|
158239
|
-
{
|
|
158240
|
-
name: "atMs",
|
|
158241
|
-
type: "INTEGER",
|
|
158242
|
-
notNull: true
|
|
158243
|
-
},
|
|
158244
|
-
{
|
|
158245
|
-
name: "pid",
|
|
158246
|
-
type: "INTEGER",
|
|
158247
|
-
notNull: true
|
|
158248
|
-
},
|
|
158249
|
-
/** `NULL` for a process no addon owns — `root` and `system` both. */
|
|
158250
|
-
{
|
|
158251
|
-
name: "addonId",
|
|
158252
|
-
type: "TEXT"
|
|
158253
|
-
},
|
|
158254
|
-
{
|
|
158255
|
-
name: "classification",
|
|
158256
|
-
type: "TEXT",
|
|
158257
|
-
notNull: true
|
|
158258
|
-
},
|
|
158259
|
-
/** `ps pcpu` x 10. A LIFETIME average, not a rate — see `NodeProcess`. */
|
|
158260
|
-
{
|
|
158261
|
-
name: "cpuDeci",
|
|
158262
|
-
type: "INTEGER",
|
|
158263
|
-
notNull: true
|
|
158264
|
-
},
|
|
158265
|
-
{
|
|
158266
|
-
name: "rssMib",
|
|
158267
|
-
type: "INTEGER",
|
|
158268
|
-
notNull: true
|
|
158269
|
-
},
|
|
158270
|
-
/** Instantaneous main-thread CPU% x 10. `NULL` = UNKNOWN, never zero. */
|
|
158271
|
-
{
|
|
158272
|
-
name: "cpuMainDeci",
|
|
158273
|
-
type: "INTEGER"
|
|
158274
|
-
},
|
|
158275
|
-
/** Instantaneous V8-helper-pool CPU% x 10. `NULL` = UNKNOWN, never zero. */
|
|
158276
|
-
{
|
|
158277
|
-
name: "cpuGcDeci",
|
|
158278
|
-
type: "INTEGER"
|
|
158279
|
-
}
|
|
158280
|
-
];
|
|
158281
|
-
var LOAD_SERIES_INDEXES = [
|
|
158282
|
-
/**
|
|
158283
|
-
* The ONE index, and it serves both jobs.
|
|
158284
|
-
*
|
|
158285
|
-
* Reads are always "this node, newer than T" — `nodeId` leads so the
|
|
158286
|
-
* equality is a range scan and `atMs` supplies the order without a sort.
|
|
158287
|
-
* Prunes are always "this node, oldest first" — the same index, walked from
|
|
158288
|
-
* the other end. A second index on `atMs` alone was measured and rejected:
|
|
158289
|
-
* it cost 23 B/row (196.0 → 172.8 with the two column drops) to serve a
|
|
158290
|
-
* cross-node prune that a per-node loop over three nodes already serves.
|
|
158291
|
-
*/
|
|
158292
|
-
{
|
|
158293
|
-
name: "idx_load_samples_node_at",
|
|
158294
|
-
columns: ["nodeId", "atMs"]
|
|
158295
|
-
}
|
|
158296
|
-
];
|
|
158297
|
-
var BYTES_PER_MIB = 1048576;
|
|
158298
|
-
function fromDeci(value) {
|
|
158299
|
-
return value === null ? null : Math.round(value) / 10;
|
|
158300
|
-
}
|
|
158301
|
-
function toDeci(value) {
|
|
158302
|
-
return value === null ? null : Math.round(value * 10);
|
|
158303
|
-
}
|
|
158304
|
-
function rowToProcess(row) {
|
|
158305
|
-
return {
|
|
158306
|
-
pid: row.pid,
|
|
158307
|
-
addonId: row.addonId,
|
|
158308
|
-
classification: row.classification,
|
|
158309
|
-
cpuPercent: fromDeci(row.cpuDeci) ?? 0,
|
|
158310
|
-
memoryRssBytes: row.rssMib * BYTES_PER_MIB,
|
|
158311
|
-
cpuMainPercent: fromDeci(row.cpuMainDeci),
|
|
158312
|
-
cpuGcPercent: fromDeci(row.cpuGcDeci)
|
|
158313
|
-
};
|
|
158314
|
-
}
|
|
158315
|
-
function processToRow(nodeId, atMs, process2) {
|
|
158316
|
-
return {
|
|
158317
|
-
nodeId,
|
|
158318
|
-
atMs,
|
|
158319
|
-
pid: process2.pid,
|
|
158320
|
-
addonId: process2.addonId,
|
|
158321
|
-
classification: process2.classification,
|
|
158322
|
-
cpuDeci: toDeci(process2.cpuPercent) ?? 0,
|
|
158323
|
-
rssMib: Math.round(process2.memoryRssBytes / BYTES_PER_MIB),
|
|
158324
|
-
cpuMainDeci: toDeci(process2.cpuMainPercent),
|
|
158325
|
-
cpuGcDeci: toDeci(process2.cpuGcPercent)
|
|
158326
|
-
};
|
|
158327
|
-
}
|
|
158328
|
-
function rowsToSamples(rows) {
|
|
158329
|
-
const byAt = /* @__PURE__ */ new Map();
|
|
158330
|
-
for (const row of rows) {
|
|
158331
|
-
const held = byAt.get(row.atMs);
|
|
158332
|
-
if (held === void 0) byAt.set(row.atMs, [rowToProcess(row)]);
|
|
158333
|
-
else held.push(rowToProcess(row));
|
|
158334
|
-
}
|
|
158335
|
-
return [...byAt.entries()].toSorted((a, b) => a[0] - b[0]).map(([atMs, processes]) => ({
|
|
158336
|
-
atMs,
|
|
158337
|
-
processes
|
|
158338
|
-
}));
|
|
158339
|
-
}
|
|
158340
|
-
var LoadSeriesStore = class {
|
|
158341
|
-
declared = false;
|
|
158342
|
-
lastPruneAtMs = 0;
|
|
158343
|
-
store;
|
|
158344
|
-
logger;
|
|
158345
|
-
nowFn;
|
|
158346
|
-
prunePageRows;
|
|
158347
|
-
pruneIntervalMs;
|
|
158348
|
-
constructor(deps) {
|
|
158349
|
-
this.store = deps.store;
|
|
158350
|
-
this.logger = deps.logger;
|
|
158351
|
-
this.nowFn = deps.now ?? (() => Date.now());
|
|
158352
|
-
this.prunePageRows = deps.prunePageRows ?? 2e4;
|
|
158353
|
-
this.pruneIntervalMs = deps.pruneIntervalMs ?? 6e4;
|
|
158354
|
-
}
|
|
158355
|
-
/** Idempotently declare the collection. `false` when the store refused. */
|
|
158356
|
-
async declare() {
|
|
158357
|
-
if (this.declared) return true;
|
|
158358
|
-
try {
|
|
158359
|
-
await this.store.declareCollection.mutate({
|
|
158360
|
-
collection: LOAD_SERIES_COLLECTION,
|
|
158361
|
-
columns: [...LOAD_SERIES_COLUMNS],
|
|
158362
|
-
indexes: LOAD_SERIES_INDEXES.map((i) => ({
|
|
158363
|
-
name: i.name,
|
|
158364
|
-
columns: [...i.columns]
|
|
158365
|
-
}))
|
|
158366
|
-
});
|
|
158367
|
-
this.declared = true;
|
|
158368
|
-
return true;
|
|
158369
|
-
} catch (err) {
|
|
158370
|
-
this.logger.warn("load series declareCollection failed \u2014 nothing will be retained on disk", { meta: {
|
|
158371
|
-
collection: LOAD_SERIES_COLLECTION,
|
|
158372
|
-
error: require_dist10.errMsg(err)
|
|
158373
|
-
} });
|
|
158374
|
-
return false;
|
|
158375
|
-
}
|
|
158376
|
-
}
|
|
158377
|
-
/**
|
|
158378
|
-
* Append ONE sample — every process row of it — in ONE transaction.
|
|
158379
|
-
*
|
|
158380
|
-
* Never a write per row. `insertMany` exists for exactly this: at the 10 s
|
|
158381
|
-
* default the fleet produces 7.6 rows/s, and 7.6 separate commits per second
|
|
158382
|
-
* on the connection that also serves every cluster-wide configuration read
|
|
158383
|
-
* is a constant load nobody asked for.
|
|
158384
|
-
*/
|
|
158385
|
-
async append(nodeId, atMs, processes) {
|
|
158386
|
-
if (processes.length === 0) return 0;
|
|
158387
|
-
if (!await this.declare()) return 0;
|
|
158388
|
-
const records = processes.map((p) => ({ data: { ...processToRow(nodeId, atMs, p) } }));
|
|
158389
|
-
try {
|
|
158390
|
-
const { inserted } = await this.store.insertMany.mutate({
|
|
158391
|
-
collection: LOAD_SERIES_COLLECTION,
|
|
158392
|
-
records
|
|
158393
|
-
});
|
|
158394
|
-
return inserted;
|
|
158395
|
-
} catch (err) {
|
|
158396
|
-
this.logger.warn("load series sample not retained \u2014 this interval will be missing", { meta: {
|
|
158397
|
-
nodeId,
|
|
158398
|
-
atMs,
|
|
158399
|
-
rows: processes.length,
|
|
158400
|
-
error: require_dist10.errMsg(err)
|
|
158401
|
-
} });
|
|
158402
|
-
return 0;
|
|
158403
|
-
}
|
|
158404
|
-
}
|
|
158405
|
-
/**
|
|
158406
|
-
* Read one node's cold samples, oldest-first.
|
|
158407
|
-
*
|
|
158408
|
-
* `sinceMs` is EXCLUSIVE, matching the ring, so a caller passing the newest
|
|
158409
|
-
* timestamp it holds gets only what it is missing. `limitRows` bounds the
|
|
158410
|
-
* read in ROWS (not samples) because rows are what the query costs.
|
|
158411
|
-
*/
|
|
158412
|
-
async read(nodeId, sinceMs, limitRows) {
|
|
158413
|
-
if (!await this.declare()) return [];
|
|
158414
|
-
try {
|
|
158415
|
-
const records = await this.store.query.query({
|
|
158416
|
-
collection: LOAD_SERIES_COLLECTION,
|
|
158417
|
-
filter: {
|
|
158418
|
-
where: { nodeId },
|
|
158419
|
-
whereBetween: { atMs: [sinceMs + 1, Number.MAX_SAFE_INTEGER] },
|
|
158420
|
-
orderBy: {
|
|
158421
|
-
field: "atMs",
|
|
158422
|
-
direction: "asc"
|
|
158423
|
-
},
|
|
158424
|
-
limit: limitRows
|
|
158425
|
-
}
|
|
158426
|
-
});
|
|
158427
|
-
const rows = [];
|
|
158428
|
-
for (const record of records) {
|
|
158429
|
-
const row = recordToRow(record.data);
|
|
158430
|
-
if (row !== null) rows.push(row);
|
|
158431
|
-
}
|
|
158432
|
-
return rowsToSamples(rows);
|
|
158433
|
-
} catch (err) {
|
|
158434
|
-
this.logger.warn("load series cold read failed \u2014 answering from the hot window only", { meta: {
|
|
158435
|
-
nodeId,
|
|
158436
|
-
sinceMs,
|
|
158437
|
-
error: require_dist10.errMsg(err)
|
|
158438
|
-
} });
|
|
158439
|
-
return [];
|
|
158440
|
-
}
|
|
158441
|
-
}
|
|
158442
|
-
/**
|
|
158443
|
-
* Enforce BOTH bounds, oldest-first, through a bounded page each.
|
|
158444
|
-
*
|
|
158445
|
-
* Rate-limited to {@link pruneIntervalMs}: a bound is not a deadline, and the
|
|
158446
|
-
* append path must not pay a sweep on every sample.
|
|
158447
|
-
*/
|
|
158448
|
-
async prune(nodeIds, retention, force = false) {
|
|
158449
|
-
const now = this.nowFn();
|
|
158450
|
-
if (!force && now - this.lastPruneAtMs < this.pruneIntervalMs) return null;
|
|
158451
|
-
this.lastPruneAtMs = now;
|
|
158452
|
-
if (!await this.declare()) return null;
|
|
158453
|
-
let deletedByAge = 0;
|
|
158454
|
-
let deletedByCap = 0;
|
|
158455
|
-
let rowsExamined = 0;
|
|
158456
|
-
const ageCutoff = now - retention.retentionHours * 36e5;
|
|
158457
|
-
for (const nodeId of nodeIds) {
|
|
158458
|
-
const outcome = await this.pruneNodeToCutoff(nodeId, ageCutoff);
|
|
158459
|
-
deletedByAge += outcome.deleted;
|
|
158460
|
-
rowsExamined += outcome.examined;
|
|
158461
|
-
}
|
|
158462
|
-
const total = await this.count();
|
|
158463
|
-
const excess = total === null ? 0 : total - retention.maxRows;
|
|
158464
|
-
if (excess > 0) {
|
|
158465
|
-
const outcome = await this.pruneOldestRows(nodeIds, excess);
|
|
158466
|
-
deletedByCap = outcome.deleted;
|
|
158467
|
-
rowsExamined += outcome.examined;
|
|
158468
|
-
this.logger.warn("load series ROW CAP bit \u2014 evicting the oldest samples", { meta: {
|
|
158469
|
-
collection: LOAD_SERIES_COLLECTION,
|
|
158470
|
-
rows: total,
|
|
158471
|
-
cap: retention.maxRows,
|
|
158472
|
-
over: excess,
|
|
158473
|
-
deleted: outcome.deleted,
|
|
158474
|
-
retentionHours: retention.retentionHours,
|
|
158475
|
-
hint: "lower the retention or the sampling cadence \u2014 the cap is the guarantee, not the intention"
|
|
158476
|
-
} });
|
|
158477
|
-
}
|
|
158478
|
-
return {
|
|
158479
|
-
deletedByAge,
|
|
158480
|
-
deletedByCap,
|
|
158481
|
-
rowsExamined,
|
|
158482
|
-
capBit: excess > 0
|
|
158483
|
-
};
|
|
158484
|
-
}
|
|
158485
|
-
/** Total rows, or `null` when the store could not answer. */
|
|
158486
|
-
async count() {
|
|
158487
|
-
if (!await this.declare()) return null;
|
|
158488
|
-
try {
|
|
158489
|
-
return await this.store.count.query({ collection: LOAD_SERIES_COLLECTION });
|
|
158490
|
-
} catch (err) {
|
|
158491
|
-
this.logger.warn("load series count failed \u2014 the row cap is not enforced this pass", { meta: { error: require_dist10.errMsg(err) } });
|
|
158492
|
-
return null;
|
|
158493
|
-
}
|
|
158494
|
-
}
|
|
158495
|
-
/**
|
|
158496
|
-
* Delete this node's rows older than `cutoff`, at most one page's worth.
|
|
158497
|
-
*
|
|
158498
|
-
* The page is the whole point. `deleteWhere({ atMs: [0, cutoff] })` on its
|
|
158499
|
-
* own is one statement but an UNBOUNDED one — a first pass after a retention
|
|
158500
|
-
* change would delete millions of rows inside a single stalling transaction.
|
|
158501
|
-
* So the page is read first (keys only, ordered by the index), its last
|
|
158502
|
-
* `atMs` becomes the EFFECTIVE cutoff, and the delete is bounded by it.
|
|
158503
|
-
*/
|
|
158504
|
-
async pruneNodeToCutoff(nodeId, cutoff) {
|
|
158505
|
-
try {
|
|
158506
|
-
const page = await this.store.query.query({
|
|
158507
|
-
collection: LOAD_SERIES_COLLECTION,
|
|
158508
|
-
filter: {
|
|
158509
|
-
where: { nodeId },
|
|
158510
|
-
whereBetween: { atMs: [0, cutoff] },
|
|
158511
|
-
orderBy: {
|
|
158512
|
-
field: "atMs",
|
|
158513
|
-
direction: "asc"
|
|
158514
|
-
},
|
|
158515
|
-
limit: this.prunePageRows
|
|
158516
|
-
},
|
|
158517
|
-
columns: ["atMs"]
|
|
158518
|
-
});
|
|
158519
|
-
if (page.length === 0) return {
|
|
158520
|
-
deleted: 0,
|
|
158521
|
-
examined: 0
|
|
158522
|
-
};
|
|
158523
|
-
const effectiveCutoff = Number(page.at(-1)?.data["atMs"]);
|
|
158524
|
-
if (!Number.isFinite(effectiveCutoff)) return {
|
|
158525
|
-
deleted: 0,
|
|
158526
|
-
examined: page.length
|
|
158527
|
-
};
|
|
158528
|
-
const { deleted } = await this.store.deleteWhere.mutate({
|
|
158529
|
-
collection: LOAD_SERIES_COLLECTION,
|
|
158530
|
-
filter: {
|
|
158531
|
-
where: { nodeId },
|
|
158532
|
-
whereBetween: { atMs: [0, effectiveCutoff] }
|
|
158533
|
-
}
|
|
158534
|
-
});
|
|
158535
|
-
return {
|
|
158536
|
-
deleted,
|
|
158537
|
-
examined: page.length
|
|
158538
|
-
};
|
|
158539
|
-
} catch (err) {
|
|
158540
|
-
this.logger.warn("load series age prune failed \u2014 the table keeps growing this pass", { meta: {
|
|
158541
|
-
nodeId,
|
|
158542
|
-
cutoff,
|
|
158543
|
-
error: require_dist10.errMsg(err)
|
|
158544
|
-
} });
|
|
158545
|
-
return {
|
|
158546
|
-
deleted: 0,
|
|
158547
|
-
examined: 0
|
|
158548
|
-
};
|
|
158549
|
-
}
|
|
158550
|
-
}
|
|
158551
|
-
/**
|
|
158552
|
-
* Drop the oldest rows across the known nodes until `excess` is covered.
|
|
158553
|
-
*
|
|
158554
|
-
* Same bounded-page technique, walked per node so the one index serves it.
|
|
158555
|
-
* The node holding the oldest rows pays first, which is also the node
|
|
158556
|
-
* producing the pressure when a runaway process count is the cause.
|
|
158557
|
-
*/
|
|
158558
|
-
async pruneOldestRows(nodeIds, excess) {
|
|
158559
|
-
let remaining = Math.min(excess, this.prunePageRows);
|
|
158560
|
-
let deleted = 0;
|
|
158561
|
-
let examined = 0;
|
|
158562
|
-
for (const nodeId of nodeIds) {
|
|
158563
|
-
if (remaining <= 0) break;
|
|
158564
|
-
try {
|
|
158565
|
-
const page = await this.store.query.query({
|
|
158566
|
-
collection: LOAD_SERIES_COLLECTION,
|
|
158567
|
-
filter: {
|
|
158568
|
-
where: { nodeId },
|
|
158569
|
-
orderBy: {
|
|
158570
|
-
field: "atMs",
|
|
158571
|
-
direction: "asc"
|
|
158572
|
-
},
|
|
158573
|
-
limit: remaining
|
|
158574
|
-
},
|
|
158575
|
-
columns: ["atMs"]
|
|
158576
|
-
});
|
|
158577
|
-
examined += page.length;
|
|
158578
|
-
if (page.length === 0) continue;
|
|
158579
|
-
const cutoff = Number(page.at(-1)?.data["atMs"]);
|
|
158580
|
-
if (!Number.isFinite(cutoff)) continue;
|
|
158581
|
-
const result = await this.store.deleteWhere.mutate({
|
|
158582
|
-
collection: LOAD_SERIES_COLLECTION,
|
|
158583
|
-
filter: {
|
|
158584
|
-
where: { nodeId },
|
|
158585
|
-
whereBetween: { atMs: [0, cutoff] }
|
|
158586
|
-
}
|
|
158587
|
-
});
|
|
158588
|
-
deleted += result.deleted;
|
|
158589
|
-
remaining -= result.deleted;
|
|
158590
|
-
} catch (err) {
|
|
158591
|
-
this.logger.warn("load series cap prune failed \u2014 the cap is not enforced this pass", { meta: {
|
|
158592
|
-
nodeId,
|
|
158593
|
-
error: require_dist10.errMsg(err)
|
|
158594
|
-
} });
|
|
158595
|
-
}
|
|
158596
|
-
}
|
|
158597
|
-
return {
|
|
158598
|
-
deleted,
|
|
158599
|
-
examined
|
|
158600
|
-
};
|
|
158601
|
-
}
|
|
158602
|
-
};
|
|
158603
|
-
function recordToRow(data) {
|
|
158604
|
-
const nodeId = data["nodeId"];
|
|
158605
|
-
const classification = data["classification"];
|
|
158606
|
-
if (typeof nodeId !== "string" || typeof classification !== "string") return null;
|
|
158607
|
-
const atMs = Number(data["atMs"]);
|
|
158608
|
-
const pid = Number(data["pid"]);
|
|
158609
|
-
const cpuDeci = Number(data["cpuDeci"]);
|
|
158610
|
-
const rssMib = Number(data["rssMib"]);
|
|
158611
|
-
if (![
|
|
158612
|
-
atMs,
|
|
158613
|
-
pid,
|
|
158614
|
-
cpuDeci,
|
|
158615
|
-
rssMib
|
|
158616
|
-
].every((n) => Number.isFinite(n))) return null;
|
|
158617
|
-
const rawAddon = data["addonId"];
|
|
158618
|
-
const optional = (raw) => {
|
|
158619
|
-
if (raw === null || raw === void 0) return null;
|
|
158620
|
-
const n = Number(raw);
|
|
158621
|
-
return Number.isFinite(n) ? n : null;
|
|
158622
|
-
};
|
|
158623
|
-
return {
|
|
158624
|
-
nodeId,
|
|
158625
|
-
atMs,
|
|
158626
|
-
pid,
|
|
158627
|
-
addonId: typeof rawAddon === "string" ? rawAddon : null,
|
|
158628
|
-
classification,
|
|
158629
|
-
cpuDeci,
|
|
158630
|
-
rssMib,
|
|
158631
|
-
cpuMainDeci: optional(data["cpuMainDeci"]),
|
|
158632
|
-
cpuGcDeci: optional(data["cpuGcDeci"])
|
|
158633
|
-
};
|
|
158634
|
-
}
|
|
158635
158250
|
var LoadSeriesConfigError = class extends Error {
|
|
158636
158251
|
constructor(message) {
|
|
158637
158252
|
super(message);
|
|
@@ -158730,6 +158345,201 @@ var require_native_metrics_addon = __commonJS({
|
|
|
158730
158345
|
oldestAtMs
|
|
158731
158346
|
};
|
|
158732
158347
|
}
|
|
158348
|
+
var ADDON_ID = "metrics-native";
|
|
158349
|
+
var NODE_PROCESS_CPU_KEY = `addon:${ADDON_ID}/process-cpu-percent`;
|
|
158350
|
+
var NODE_PROCESS_RSS_KEY = `addon:${ADDON_ID}/process-rss-mib`;
|
|
158351
|
+
var NODE_CONTAINER_CURRENT_KEY = `addon:${ADDON_ID}/container-current-mib`;
|
|
158352
|
+
function nodeMetricsHistoryClient(api) {
|
|
158353
|
+
return {
|
|
158354
|
+
declareSeries: (input) => api.declareSeries.mutate(input),
|
|
158355
|
+
record: (input) => api.record.mutate({ points: [...input.points] }),
|
|
158356
|
+
queryNode: async (input) => {
|
|
158357
|
+
return { series: (await api.queryNode.query(input)).series.map((s) => ({
|
|
158358
|
+
scope: s.scope,
|
|
158359
|
+
points: s.points.map((p) => ({
|
|
158360
|
+
t: p.t,
|
|
158361
|
+
samples: p.samples,
|
|
158362
|
+
value: p.last ?? p.mean
|
|
158363
|
+
}))
|
|
158364
|
+
})) };
|
|
158365
|
+
}
|
|
158366
|
+
};
|
|
158367
|
+
}
|
|
158368
|
+
var NodeMetricsHistoryRecorder = class {
|
|
158369
|
+
deps;
|
|
158370
|
+
declared = false;
|
|
158371
|
+
constructor(deps) {
|
|
158372
|
+
this.deps = deps;
|
|
158373
|
+
}
|
|
158374
|
+
async declare() {
|
|
158375
|
+
if (this.declared) return;
|
|
158376
|
+
const { deviceHistory } = this.deps;
|
|
158377
|
+
await deviceHistory.declareSeries({
|
|
158378
|
+
name: "process-cpu-percent",
|
|
158379
|
+
subjectKind: "node",
|
|
158380
|
+
kind: "gauge",
|
|
158381
|
+
displayName: "Process CPU %",
|
|
158382
|
+
ingest: "points",
|
|
158383
|
+
unit: "%"
|
|
158384
|
+
});
|
|
158385
|
+
await deviceHistory.declareSeries({
|
|
158386
|
+
name: "process-rss-mib",
|
|
158387
|
+
subjectKind: "node",
|
|
158388
|
+
kind: "gauge",
|
|
158389
|
+
displayName: "Process RSS (MiB)",
|
|
158390
|
+
ingest: "points",
|
|
158391
|
+
unit: "MiB"
|
|
158392
|
+
});
|
|
158393
|
+
await deviceHistory.declareSeries({
|
|
158394
|
+
name: "container-current-mib",
|
|
158395
|
+
subjectKind: "node",
|
|
158396
|
+
kind: "gauge",
|
|
158397
|
+
displayName: "Container memory current (MiB)",
|
|
158398
|
+
ingest: "points",
|
|
158399
|
+
unit: "MiB"
|
|
158400
|
+
});
|
|
158401
|
+
this.declared = true;
|
|
158402
|
+
}
|
|
158403
|
+
async recordSample(nodeId, atMs, processes, containerMemory) {
|
|
158404
|
+
if (!await this.ensureDeclared()) return;
|
|
158405
|
+
const points = [];
|
|
158406
|
+
for (const p of processes) {
|
|
158407
|
+
const scope = String(p.pid);
|
|
158408
|
+
points.push({
|
|
158409
|
+
key: NODE_PROCESS_CPU_KEY,
|
|
158410
|
+
subject: {
|
|
158411
|
+
kind: "node",
|
|
158412
|
+
nodeId
|
|
158413
|
+
},
|
|
158414
|
+
scope,
|
|
158415
|
+
ts: atMs,
|
|
158416
|
+
value: p.cpuPercent
|
|
158417
|
+
});
|
|
158418
|
+
points.push({
|
|
158419
|
+
key: NODE_PROCESS_RSS_KEY,
|
|
158420
|
+
subject: {
|
|
158421
|
+
kind: "node",
|
|
158422
|
+
nodeId
|
|
158423
|
+
},
|
|
158424
|
+
scope,
|
|
158425
|
+
ts: atMs,
|
|
158426
|
+
value: Math.round(p.memoryRssBytes / 1048576)
|
|
158427
|
+
});
|
|
158428
|
+
}
|
|
158429
|
+
if (containerMemory !== null) points.push({
|
|
158430
|
+
key: NODE_CONTAINER_CURRENT_KEY,
|
|
158431
|
+
subject: {
|
|
158432
|
+
kind: "node",
|
|
158433
|
+
nodeId
|
|
158434
|
+
},
|
|
158435
|
+
scope: "",
|
|
158436
|
+
ts: atMs,
|
|
158437
|
+
value: Math.round(containerMemory.currentBytes / 1048576)
|
|
158438
|
+
});
|
|
158439
|
+
if (points.length === 0) return;
|
|
158440
|
+
try {
|
|
158441
|
+
await this.deps.deviceHistory.record({ points });
|
|
158442
|
+
} catch (err) {
|
|
158443
|
+
this.deps.logger.warn("node metrics history sample not stored", { meta: {
|
|
158444
|
+
nodeId,
|
|
158445
|
+
atMs,
|
|
158446
|
+
error: err instanceof Error ? err.message : String(err)
|
|
158447
|
+
} });
|
|
158448
|
+
}
|
|
158449
|
+
}
|
|
158450
|
+
async readContainerPoints(nodeId, sinceMs, maxPoints) {
|
|
158451
|
+
if (!await this.ensureDeclared()) return [];
|
|
158452
|
+
const result = await this.deps.deviceHistory.queryNode({
|
|
158453
|
+
nodeId,
|
|
158454
|
+
key: NODE_CONTAINER_CURRENT_KEY,
|
|
158455
|
+
from: sinceMs + 1,
|
|
158456
|
+
to: Date.now(),
|
|
158457
|
+
resolution: "raw",
|
|
158458
|
+
maxPoints
|
|
158459
|
+
});
|
|
158460
|
+
const out = [];
|
|
158461
|
+
for (const s of result.series) for (const p of s.points) {
|
|
158462
|
+
if (p.value === null || !Number.isFinite(p.value)) continue;
|
|
158463
|
+
out.push({
|
|
158464
|
+
atMs: p.t,
|
|
158465
|
+
source: "cgroup-v2",
|
|
158466
|
+
currentBytes: p.value * 1048576,
|
|
158467
|
+
limitBytes: null,
|
|
158468
|
+
anonBytes: null,
|
|
158469
|
+
fileBytes: null,
|
|
158470
|
+
shmemBytes: null,
|
|
158471
|
+
slabBytes: null,
|
|
158472
|
+
gpuShmemBytes: null
|
|
158473
|
+
});
|
|
158474
|
+
}
|
|
158475
|
+
return out.toSorted((a, b) => a.atMs - b.atMs);
|
|
158476
|
+
}
|
|
158477
|
+
async readProcessSamples(nodeId, sinceMs, maxPoints) {
|
|
158478
|
+
if (!await this.ensureDeclared()) return [];
|
|
158479
|
+
const to = Date.now();
|
|
158480
|
+
const [cpuResult, rssResult] = await Promise.all([this.deps.deviceHistory.queryNode({
|
|
158481
|
+
nodeId,
|
|
158482
|
+
key: NODE_PROCESS_CPU_KEY,
|
|
158483
|
+
from: sinceMs + 1,
|
|
158484
|
+
to,
|
|
158485
|
+
resolution: "raw",
|
|
158486
|
+
maxPoints
|
|
158487
|
+
}), this.deps.deviceHistory.queryNode({
|
|
158488
|
+
nodeId,
|
|
158489
|
+
key: NODE_PROCESS_RSS_KEY,
|
|
158490
|
+
from: sinceMs + 1,
|
|
158491
|
+
to,
|
|
158492
|
+
resolution: "raw",
|
|
158493
|
+
maxPoints
|
|
158494
|
+
})]);
|
|
158495
|
+
const byAt = /* @__PURE__ */ new Map();
|
|
158496
|
+
const ingest = (series, field) => {
|
|
158497
|
+
for (const s of series) {
|
|
158498
|
+
const pid = Number(s.scope);
|
|
158499
|
+
if (!Number.isFinite(pid)) continue;
|
|
158500
|
+
for (const p of s.points) {
|
|
158501
|
+
if (p.value === null || !Number.isFinite(p.value)) continue;
|
|
158502
|
+
let perPid = byAt.get(p.t);
|
|
158503
|
+
if (perPid === void 0) {
|
|
158504
|
+
perPid = /* @__PURE__ */ new Map();
|
|
158505
|
+
byAt.set(p.t, perPid);
|
|
158506
|
+
}
|
|
158507
|
+
const base = perPid.get(pid) ?? {
|
|
158508
|
+
pid,
|
|
158509
|
+
addonId: null,
|
|
158510
|
+
classification: "managed",
|
|
158511
|
+
cpuPercent: 0,
|
|
158512
|
+
memoryRssBytes: 0,
|
|
158513
|
+
cpuMainPercent: null,
|
|
158514
|
+
cpuGcPercent: null
|
|
158515
|
+
};
|
|
158516
|
+
perPid.set(pid, field === "cpuPercent" ? {
|
|
158517
|
+
...base,
|
|
158518
|
+
cpuPercent: p.value
|
|
158519
|
+
} : {
|
|
158520
|
+
...base,
|
|
158521
|
+
memoryRssBytes: p.value * 1048576
|
|
158522
|
+
});
|
|
158523
|
+
}
|
|
158524
|
+
}
|
|
158525
|
+
};
|
|
158526
|
+
ingest(cpuResult.series, "cpuPercent");
|
|
158527
|
+
ingest(rssResult.series, "memoryRssBytes");
|
|
158528
|
+
return [...byAt.entries()].toSorted((a, b) => a[0] - b[0]).map(([atMs, perPid]) => ({
|
|
158529
|
+
atMs,
|
|
158530
|
+
processes: [...perPid.values()]
|
|
158531
|
+
}));
|
|
158532
|
+
}
|
|
158533
|
+
async ensureDeclared() {
|
|
158534
|
+
try {
|
|
158535
|
+
await this.declare();
|
|
158536
|
+
return true;
|
|
158537
|
+
} catch (err) {
|
|
158538
|
+
this.deps.logger.warn("node metrics history declare failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
158539
|
+
return false;
|
|
158540
|
+
}
|
|
158541
|
+
}
|
|
158542
|
+
};
|
|
158733
158543
|
var nodeAccessCheck = (path) => (0, node_fs_promises.access)(path, node_fs.constants.X_OK);
|
|
158734
158544
|
async function findOnPath(binary, pathEnv, check = nodeAccessCheck) {
|
|
158735
158545
|
const dirs = (pathEnv ?? "").split(":").filter((dir) => dir.length > 0);
|
|
@@ -159577,7 +159387,7 @@ var require_native_metrics_addon = __commonJS({
|
|
|
159577
159387
|
* the `settings-store` singleton would ship its rows over Moleculer, once
|
|
159578
159388
|
* per sample, to reach that very same table.
|
|
159579
159389
|
*/
|
|
159580
|
-
|
|
159390
|
+
metricsHistory = null;
|
|
159581
159391
|
/**
|
|
159582
159392
|
* The container's OWN memory, sampled on the process-snapshot tick.
|
|
159583
159393
|
*
|
|
@@ -159589,10 +159399,8 @@ var require_native_metrics_addon = __commonJS({
|
|
|
159589
159399
|
*/
|
|
159590
159400
|
memoryProbe = null;
|
|
159591
159401
|
pidsProbe = null;
|
|
159592
|
-
/** Hot tier for the container reading. Bounds in `container-memory-
|
|
159402
|
+
/** Hot tier for the container reading. Bounds in `container-memory-ring.ts`. */
|
|
159593
159403
|
memoryRing = new ContainerMemoryRing();
|
|
159594
|
-
/** Cold tier, HUB only — same reasoning as `loadStore`. */
|
|
159595
|
-
memoryStore = null;
|
|
159596
159404
|
/** The resolved knobs. Re-resolved on every settings write. */
|
|
159597
159405
|
loadConfig = {
|
|
159598
159406
|
cadenceSec: 10,
|
|
@@ -159665,13 +159473,12 @@ var require_native_metrics_addon = __commonJS({
|
|
|
159665
159473
|
};
|
|
159666
159474
|
this.applyLoadSeriesConfig();
|
|
159667
159475
|
if (this.isHub) {
|
|
159668
|
-
this.
|
|
159669
|
-
|
|
159670
|
-
logger: this.ctx.logger.child("
|
|
159476
|
+
this.metricsHistory = new NodeMetricsHistoryRecorder({
|
|
159477
|
+
deviceHistory: nodeMetricsHistoryClient(this.ctx.api.deviceHistory),
|
|
159478
|
+
logger: this.ctx.logger.child("NodeMetricsHistory")
|
|
159671
159479
|
});
|
|
159672
|
-
this.
|
|
159673
|
-
|
|
159674
|
-
logger: this.ctx.logger.child("ContainerMemory")
|
|
159480
|
+
this.metricsHistory.declare().catch((err) => {
|
|
159481
|
+
this.ctx.logger.warn("node metrics history declare failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
159675
159482
|
});
|
|
159676
159483
|
}
|
|
159677
159484
|
this.memoryProbe = new ContainerMemoryProbe({ logger: this.ctx.logger.child("ContainerMemory") });
|
|
@@ -159692,7 +159499,7 @@ var require_native_metrics_addon = __commonJS({
|
|
|
159692
159499
|
* this node has already delivered, and only an accepted sample reaches the
|
|
159693
159500
|
* table. That is what makes the append idempotent without a unique index
|
|
159694
159501
|
* over two million rows — the composite key it replaces was measured at ~40
|
|
159695
|
-
* bytes per row (
|
|
159502
|
+
* bytes per row (measured on the old SQLite tier; see D850).
|
|
159696
159503
|
*
|
|
159697
159504
|
* The durable append is fire-and-forget: a storage stall must cost a gap in
|
|
159698
159505
|
* the cold window, never a blocked event-bus handler. Every failure logs.
|
|
@@ -159702,26 +159509,9 @@ var require_native_metrics_addon = __commonJS({
|
|
|
159702
159509
|
if (!this.loadRing.record(nodeId, atMs, retained)) return;
|
|
159703
159510
|
this.observedRowsByNode.set(nodeId, retained.length);
|
|
159704
159511
|
if (containerMemory !== null) this.memoryRing.record(nodeId, atMs, containerMemory);
|
|
159705
|
-
const
|
|
159706
|
-
if (
|
|
159707
|
-
|
|
159708
|
-
if (outcome === null) return;
|
|
159709
|
-
await this.memoryStore?.prune([...this.observedRowsByNode.keys()], this.loadConfig);
|
|
159710
|
-
}).catch((err) => {
|
|
159711
|
-
this.ctx.logger.warn("durable load series write failed", { meta: {
|
|
159712
|
-
nodeId,
|
|
159713
|
-
atMs,
|
|
159714
|
-
error: err instanceof Error ? err.message : String(err)
|
|
159715
|
-
} });
|
|
159716
|
-
});
|
|
159717
|
-
if (containerMemory === null) return;
|
|
159718
|
-
this.memoryStore?.append(nodeId, atMs, containerMemory).catch((err) => {
|
|
159719
|
-
this.ctx.logger.warn("durable container memory write failed", { meta: {
|
|
159720
|
-
nodeId,
|
|
159721
|
-
atMs,
|
|
159722
|
-
error: err instanceof Error ? err.message : String(err)
|
|
159723
|
-
} });
|
|
159724
|
-
});
|
|
159512
|
+
const history = this.metricsHistory;
|
|
159513
|
+
if (history === null) return;
|
|
159514
|
+
history.recordSample(nodeId, atMs, retained, containerMemory);
|
|
159725
159515
|
}
|
|
159726
159516
|
/**
|
|
159727
159517
|
* The ONE reader, over both tiers.
|
|
@@ -159740,9 +159530,9 @@ var require_native_metrics_addon = __commonJS({
|
|
|
159740
159530
|
atMs: sample.atMs,
|
|
159741
159531
|
processes: sample.processes
|
|
159742
159532
|
}));
|
|
159743
|
-
const
|
|
159533
|
+
const history = this.metricsHistory;
|
|
159744
159534
|
const merged = mergeLoadSeries({
|
|
159745
|
-
cold:
|
|
159535
|
+
cold: history === null ? [] : await history.readProcessSamples(params.forNodeId, sinceMs, this.loadConfig.maxRows),
|
|
159746
159536
|
hot: hotSamples,
|
|
159747
159537
|
cadenceMs,
|
|
159748
159538
|
...params.maxPoints !== void 0 ? { maxPoints: params.maxPoints } : {}
|
|
@@ -159762,7 +159552,7 @@ var require_native_metrics_addon = __commonJS({
|
|
|
159762
159552
|
retainedSamples: merged.retainedSamples,
|
|
159763
159553
|
oldestAtMs: merged.oldestAtMs,
|
|
159764
159554
|
cadenceMs,
|
|
159765
|
-
durable:
|
|
159555
|
+
durable: history !== null
|
|
159766
159556
|
};
|
|
159767
159557
|
}
|
|
159768
159558
|
/**
|
|
@@ -159778,8 +159568,8 @@ var require_native_metrics_addon = __commonJS({
|
|
|
159778
159568
|
*/
|
|
159779
159569
|
async readContainerMemory(params) {
|
|
159780
159570
|
const hot = this.memoryRing.read(params.forNodeId, params.exclusiveSinceMs);
|
|
159781
|
-
const
|
|
159782
|
-
const merged = mergeContainerMemory(
|
|
159571
|
+
const history = this.metricsHistory;
|
|
159572
|
+
const merged = mergeContainerMemory(history === null ? [] : await history.readContainerPoints(params.forNodeId, params.sinceMs, 5e3), hot);
|
|
159783
159573
|
if (params.bucketMs <= params.cadenceMs) return merged;
|
|
159784
159574
|
return reduceContainerMemory(merged, params.bucketMs, params.origin ?? 0);
|
|
159785
159575
|
}
|
|
@@ -160160,6 +159950,2685 @@ var require_native_metrics_addon = __commonJS({
|
|
|
160160
159950
|
}
|
|
160161
159951
|
});
|
|
160162
159952
|
|
|
159953
|
+
// ../system/dist/builtins/device-history/device-history.addon.js
|
|
159954
|
+
var require_device_history_addon = __commonJS({
|
|
159955
|
+
"../system/dist/builtins/device-history/device-history.addon.js"(exports) {
|
|
159956
|
+
"use strict";
|
|
159957
|
+
Object.defineProperties(exports, {
|
|
159958
|
+
__esModule: { value: true },
|
|
159959
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
159960
|
+
});
|
|
159961
|
+
require_chunk_Cek0wNdY();
|
|
159962
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
159963
|
+
function subjectIdOf(subject) {
|
|
159964
|
+
return subject.kind === "device" ? `d:${subject.deviceId}` : `n:${subject.nodeId}`;
|
|
159965
|
+
}
|
|
159966
|
+
function deviceIdOf(subject) {
|
|
159967
|
+
return subject.kind === "device" ? subject.deviceId : null;
|
|
159968
|
+
}
|
|
159969
|
+
function identityOf(key, subjectId, scope) {
|
|
159970
|
+
return `${key}\0${subjectId}\0${scope}`;
|
|
159971
|
+
}
|
|
159972
|
+
var LOAD_PAGE = 5e3;
|
|
159973
|
+
var SeriesRegistry = class {
|
|
159974
|
+
store;
|
|
159975
|
+
metrics = /* @__PURE__ */ new Map();
|
|
159976
|
+
byIdentity = /* @__PURE__ */ new Map();
|
|
159977
|
+
byId = /* @__PURE__ */ new Map();
|
|
159978
|
+
inflight = /* @__PURE__ */ new Map();
|
|
159979
|
+
removeListeners = [];
|
|
159980
|
+
constructor(store) {
|
|
159981
|
+
this.store = store;
|
|
159982
|
+
}
|
|
159983
|
+
async load() {
|
|
159984
|
+
this.metrics.clear();
|
|
159985
|
+
this.byIdentity.clear();
|
|
159986
|
+
this.byId.clear();
|
|
159987
|
+
for (const m of await this.store.loadMetrics()) this.metrics.set(m.key, m);
|
|
159988
|
+
let afterId = 0;
|
|
159989
|
+
for (; ; ) {
|
|
159990
|
+
const page = await this.store.loadSeriesPage(afterId, LOAD_PAGE);
|
|
159991
|
+
for (const s of page) this.index(s);
|
|
159992
|
+
const last = page[page.length - 1];
|
|
159993
|
+
if (last === void 0 || page.length < LOAD_PAGE) break;
|
|
159994
|
+
afterId = last.id;
|
|
159995
|
+
}
|
|
159996
|
+
}
|
|
159997
|
+
index(series) {
|
|
159998
|
+
this.byId.set(series.id, series);
|
|
159999
|
+
this.byIdentity.set(identityOf(series.key, series.subject, series.scope), series);
|
|
160000
|
+
}
|
|
160001
|
+
metric(key) {
|
|
160002
|
+
return this.metrics.get(key);
|
|
160003
|
+
}
|
|
160004
|
+
allMetrics() {
|
|
160005
|
+
return [...this.metrics.values()];
|
|
160006
|
+
}
|
|
160007
|
+
/** Persist and remember a metric; idempotent. */
|
|
160008
|
+
async declareMetric(metric) {
|
|
160009
|
+
const existing = this.metrics.get(metric.key);
|
|
160010
|
+
if (existing !== void 0 && sameMetric(existing, metric)) return;
|
|
160011
|
+
await this.store.upsertMetric(metric);
|
|
160012
|
+
this.metrics.set(metric.key, metric);
|
|
160013
|
+
}
|
|
160014
|
+
find(key, subjectId, scope) {
|
|
160015
|
+
return this.byIdentity.get(identityOf(key, subjectId, scope));
|
|
160016
|
+
}
|
|
160017
|
+
byIdOf(id) {
|
|
160018
|
+
return this.byId.get(id);
|
|
160019
|
+
}
|
|
160020
|
+
all() {
|
|
160021
|
+
return [...this.byId.values()];
|
|
160022
|
+
}
|
|
160023
|
+
forSubject(subjectId) {
|
|
160024
|
+
return this.all().filter((s) => s.subject === subjectId);
|
|
160025
|
+
}
|
|
160026
|
+
forKeyAndSubject(key, subjectId) {
|
|
160027
|
+
return this.all().filter((s) => s.key === key && s.subject === subjectId);
|
|
160028
|
+
}
|
|
160029
|
+
/** The series, created on first sight. `ts` stamps `first_ts`/`last_ts` of a new one. */
|
|
160030
|
+
async ensure(key, subject, scope, ts) {
|
|
160031
|
+
const subjectId = subjectIdOf(subject);
|
|
160032
|
+
const identity = identityOf(key, subjectId, scope);
|
|
160033
|
+
const known = this.byIdentity.get(identity);
|
|
160034
|
+
if (known !== void 0) return known;
|
|
160035
|
+
const pending = this.inflight.get(identity);
|
|
160036
|
+
if (pending !== void 0) return pending;
|
|
160037
|
+
const created = this.create(key, subjectId, deviceIdOf(subject), scope, ts).finally(() => {
|
|
160038
|
+
this.inflight.delete(identity);
|
|
160039
|
+
});
|
|
160040
|
+
this.inflight.set(identity, created);
|
|
160041
|
+
return created;
|
|
160042
|
+
}
|
|
160043
|
+
async create(key, subjectId, deviceId, scope, ts) {
|
|
160044
|
+
const stored = await this.store.findSeries(key, subjectId, scope) ?? await this.store.insertSeries({
|
|
160045
|
+
key,
|
|
160046
|
+
subject: subjectId,
|
|
160047
|
+
deviceId,
|
|
160048
|
+
scope,
|
|
160049
|
+
ts
|
|
160050
|
+
});
|
|
160051
|
+
this.index(stored);
|
|
160052
|
+
return stored;
|
|
160053
|
+
}
|
|
160054
|
+
/** Remember a widened span (the store write is the caller's). */
|
|
160055
|
+
noteSpan(id, firstTs, lastTs) {
|
|
160056
|
+
const current = this.byId.get(id);
|
|
160057
|
+
if (current === void 0) return;
|
|
160058
|
+
this.index({
|
|
160059
|
+
...current,
|
|
160060
|
+
firstTs: Math.min(current.firstTs, firstTs),
|
|
160061
|
+
lastTs: Math.max(current.lastTs, lastTs)
|
|
160062
|
+
});
|
|
160063
|
+
}
|
|
160064
|
+
remove(id) {
|
|
160065
|
+
const current = this.byId.get(id);
|
|
160066
|
+
if (current === void 0) return;
|
|
160067
|
+
this.byId.delete(id);
|
|
160068
|
+
this.byIdentity.delete(identityOf(current.key, current.subject, current.scope));
|
|
160069
|
+
for (const listener of this.removeListeners) listener(current);
|
|
160070
|
+
}
|
|
160071
|
+
/** Per-series state elsewhere (last recorded value, dirty marks, rate limits) is dropped through this. */
|
|
160072
|
+
onRemove(listener) {
|
|
160073
|
+
this.removeListeners.push(listener);
|
|
160074
|
+
}
|
|
160075
|
+
};
|
|
160076
|
+
function sameMetric(a, b) {
|
|
160077
|
+
return a.source === b.source && a.ownerAddon === b.ownerAddon && a.subjectKind === b.subjectKind && a.kind === b.kind && a.unit === b.unit && a.displayName === b.displayName && a.ingest === b.ingest;
|
|
160078
|
+
}
|
|
160079
|
+
var DEADBAND_EPSILON = 1e-9;
|
|
160080
|
+
function minIntervalOf(spec) {
|
|
160081
|
+
if (spec.minIntervalMs !== void 0) return spec.minIntervalMs;
|
|
160082
|
+
return spec.kind === "boolean" ? 0 : require_dist10.HISTORY_DEFAULT_MIN_INTERVAL_MS;
|
|
160083
|
+
}
|
|
160084
|
+
function clearsDeadband(spec, last, value) {
|
|
160085
|
+
if (spec.kind === "boolean") return true;
|
|
160086
|
+
const delta = Math.abs(value - last);
|
|
160087
|
+
if (spec.deadband !== void 0 && delta < spec.deadband - DEADBAND_EPSILON) return false;
|
|
160088
|
+
if (spec.deadbandPct !== void 0) {
|
|
160089
|
+
if (delta < Math.abs(last) * spec.deadbandPct / 100 - DEADBAND_EPSILON) return false;
|
|
160090
|
+
}
|
|
160091
|
+
return true;
|
|
160092
|
+
}
|
|
160093
|
+
function decideCapture(spec, recorded, value, now, options = {}) {
|
|
160094
|
+
if (!Number.isFinite(value)) return {
|
|
160095
|
+
action: "skip",
|
|
160096
|
+
reason: "non-finite"
|
|
160097
|
+
};
|
|
160098
|
+
if (recorded === null) return { action: "emit" };
|
|
160099
|
+
if (value === recorded.value) return {
|
|
160100
|
+
action: "skip",
|
|
160101
|
+
reason: "unchanged"
|
|
160102
|
+
};
|
|
160103
|
+
if (!clearsDeadband(spec, recorded.value, value)) return {
|
|
160104
|
+
action: "skip",
|
|
160105
|
+
reason: "deadband"
|
|
160106
|
+
};
|
|
160107
|
+
if (options.ignoreFloor !== true) {
|
|
160108
|
+
const notBefore = recorded.ts + minIntervalOf(spec);
|
|
160109
|
+
if (now < notBefore) return {
|
|
160110
|
+
action: "defer",
|
|
160111
|
+
notBefore
|
|
160112
|
+
};
|
|
160113
|
+
}
|
|
160114
|
+
return { action: "emit" };
|
|
160115
|
+
}
|
|
160116
|
+
function isRealDeviceType(type) {
|
|
160117
|
+
return type !== require_dist10.DeviceType.Hub && type !== require_dist10.DeviceType.Container;
|
|
160118
|
+
}
|
|
160119
|
+
var DeviceDirectory = class {
|
|
160120
|
+
source;
|
|
160121
|
+
counters;
|
|
160122
|
+
logger;
|
|
160123
|
+
clock;
|
|
160124
|
+
types = /* @__PURE__ */ new Map();
|
|
160125
|
+
inflight = /* @__PURE__ */ new Map();
|
|
160126
|
+
refreshInflight = null;
|
|
160127
|
+
lastFullRefreshAt = null;
|
|
160128
|
+
constructor(source, counters, logger, clock) {
|
|
160129
|
+
this.source = source;
|
|
160130
|
+
this.counters = counters;
|
|
160131
|
+
this.logger = logger;
|
|
160132
|
+
this.clock = clock;
|
|
160133
|
+
}
|
|
160134
|
+
typeOf(deviceId) {
|
|
160135
|
+
return this.types.get(deviceId);
|
|
160136
|
+
}
|
|
160137
|
+
size() {
|
|
160138
|
+
return this.types.size;
|
|
160139
|
+
}
|
|
160140
|
+
/** When the full listing last ANSWERED; `null` before the first answer. */
|
|
160141
|
+
lastRefreshAt() {
|
|
160142
|
+
return this.lastFullRefreshAt;
|
|
160143
|
+
}
|
|
160144
|
+
/** Ids in the directory now (the last full listing plus per-device lookups). */
|
|
160145
|
+
listedIds() {
|
|
160146
|
+
return new Set(this.types.keys());
|
|
160147
|
+
}
|
|
160148
|
+
knows(deviceId) {
|
|
160149
|
+
return this.types.has(deviceId);
|
|
160150
|
+
}
|
|
160151
|
+
/** Replace the map from the full listing. Returns false (map untouched) when the read failed. */
|
|
160152
|
+
refresh() {
|
|
160153
|
+
if (this.refreshInflight !== null) return this.refreshInflight;
|
|
160154
|
+
const run = this.doRefresh().finally(() => {
|
|
160155
|
+
this.refreshInflight = null;
|
|
160156
|
+
});
|
|
160157
|
+
this.refreshInflight = run;
|
|
160158
|
+
return run;
|
|
160159
|
+
}
|
|
160160
|
+
async doRefresh() {
|
|
160161
|
+
try {
|
|
160162
|
+
const all = await this.source.listAll();
|
|
160163
|
+
this.types = new Map(all.map((d) => [d.id, d.type]));
|
|
160164
|
+
this.lastFullRefreshAt = this.clock();
|
|
160165
|
+
this.counters.directoryRefreshAt = this.lastFullRefreshAt;
|
|
160166
|
+
return true;
|
|
160167
|
+
} catch (err) {
|
|
160168
|
+
this.logger.warn("device directory: listing failed \u2014 keeping the previous map", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
160169
|
+
return false;
|
|
160170
|
+
}
|
|
160171
|
+
}
|
|
160172
|
+
/** The device's type, asking the hub once when the map does not know it. */
|
|
160173
|
+
ensure(deviceId) {
|
|
160174
|
+
const known = this.types.get(deviceId);
|
|
160175
|
+
if (known !== void 0) return Promise.resolve(known);
|
|
160176
|
+
const pending = this.inflight.get(deviceId);
|
|
160177
|
+
if (pending !== void 0) return pending;
|
|
160178
|
+
const run = this.lookup(deviceId).finally(() => {
|
|
160179
|
+
this.inflight.delete(deviceId);
|
|
160180
|
+
});
|
|
160181
|
+
this.inflight.set(deviceId, run);
|
|
160182
|
+
return run;
|
|
160183
|
+
}
|
|
160184
|
+
async lookup(deviceId) {
|
|
160185
|
+
try {
|
|
160186
|
+
const device = await this.source.getDevice(deviceId);
|
|
160187
|
+
if (device === null) return void 0;
|
|
160188
|
+
this.types.set(device.id, device.type);
|
|
160189
|
+
return device.type;
|
|
160190
|
+
} catch (err) {
|
|
160191
|
+
this.logger.warn("device directory: lookup failed", {
|
|
160192
|
+
tags: { deviceId },
|
|
160193
|
+
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
160194
|
+
});
|
|
160195
|
+
return;
|
|
160196
|
+
}
|
|
160197
|
+
}
|
|
160198
|
+
};
|
|
160199
|
+
function isRecord$1(value) {
|
|
160200
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
160201
|
+
}
|
|
160202
|
+
function readPath(slice, path) {
|
|
160203
|
+
let current = slice;
|
|
160204
|
+
for (const step of path.split(".")) {
|
|
160205
|
+
if (!isRecord$1(current)) return void 0;
|
|
160206
|
+
current = current[step];
|
|
160207
|
+
}
|
|
160208
|
+
return current;
|
|
160209
|
+
}
|
|
160210
|
+
function readNumeric(slice, path, kind) {
|
|
160211
|
+
const raw = readPath(slice, path);
|
|
160212
|
+
if (kind === "boolean") {
|
|
160213
|
+
if (typeof raw === "boolean") return raw ? 1 : 0;
|
|
160214
|
+
return null;
|
|
160215
|
+
}
|
|
160216
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return raw;
|
|
160217
|
+
return null;
|
|
160218
|
+
}
|
|
160219
|
+
function readUnit(slice, unitFrom) {
|
|
160220
|
+
const raw = readPath(slice, unitFrom);
|
|
160221
|
+
return typeof raw === "string" && raw.length > 0 ? raw : null;
|
|
160222
|
+
}
|
|
160223
|
+
var CAP_KEY_PREFIX = "cap:";
|
|
160224
|
+
function capSeriesKey(capName, fieldPath) {
|
|
160225
|
+
return `${CAP_KEY_PREFIX}${capName}.${fieldPath}`;
|
|
160226
|
+
}
|
|
160227
|
+
function humanize(capName, fieldPath) {
|
|
160228
|
+
const field = fieldPath.replace(/([A-Z])/g, " $1").replace(/\./g, " ").toLowerCase();
|
|
160229
|
+
return `${capName.replace(/-/g, " ")} ${field}`;
|
|
160230
|
+
}
|
|
160231
|
+
function catalogMetrics(catalog, now) {
|
|
160232
|
+
return Object.entries(catalog).flatMap(([capName, fields]) => Object.entries(fields).map(([path, spec]) => ({
|
|
160233
|
+
key: capSeriesKey(capName, path),
|
|
160234
|
+
source: "cap",
|
|
160235
|
+
ownerAddon: "device-history",
|
|
160236
|
+
subjectKind: "device",
|
|
160237
|
+
kind: spec.kind,
|
|
160238
|
+
unit: spec.unit ?? null,
|
|
160239
|
+
displayName: spec.displayName ?? humanize(capName, path),
|
|
160240
|
+
ingest: "points",
|
|
160241
|
+
declaredAt: now
|
|
160242
|
+
})));
|
|
160243
|
+
}
|
|
160244
|
+
var MAX_PARKED_DEVICES = 5e3;
|
|
160245
|
+
var SEED_CONCURRENCY = 8;
|
|
160246
|
+
var CaptureEngine = class {
|
|
160247
|
+
deps;
|
|
160248
|
+
recorded = /* @__PURE__ */ new Map();
|
|
160249
|
+
pending = /* @__PURE__ */ new Map();
|
|
160250
|
+
parked = /* @__PURE__ */ new Map();
|
|
160251
|
+
constructor(deps) {
|
|
160252
|
+
this.deps = deps;
|
|
160253
|
+
}
|
|
160254
|
+
/** Learn the last recorded value of every cap series from the store, so a restart repeats nothing. */
|
|
160255
|
+
async seed() {
|
|
160256
|
+
const { registry, store } = this.deps;
|
|
160257
|
+
const capSeries = registry.all().filter((s) => s.key.startsWith(CAP_KEY_PREFIX));
|
|
160258
|
+
for (let i = 0; i < capSeries.length; i += SEED_CONCURRENCY) await Promise.all(capSeries.slice(i, i + SEED_CONCURRENCY).map(async (series) => {
|
|
160259
|
+
const newest = await store.rawBefore(series.id, Number.MAX_SAFE_INTEGER);
|
|
160260
|
+
if (newest === null) return;
|
|
160261
|
+
this.recorded.set(identityOf(series.key, series.subject, series.scope), newest);
|
|
160262
|
+
}));
|
|
160263
|
+
}
|
|
160264
|
+
/** One `DeviceStateChanged`. Synchronous: the bus must never wait on us. */
|
|
160265
|
+
handleState(deviceId, capName, slice, at) {
|
|
160266
|
+
const fields = this.deps.catalog[capName];
|
|
160267
|
+
if (fields === void 0) return;
|
|
160268
|
+
const { counters, directory } = this.deps;
|
|
160269
|
+
counters.eventsSeen += 1;
|
|
160270
|
+
if (this.deps.optedOut().has(deviceId)) {
|
|
160271
|
+
counters.skip("opted-out");
|
|
160272
|
+
return;
|
|
160273
|
+
}
|
|
160274
|
+
const type = directory.typeOf(deviceId);
|
|
160275
|
+
if (type === void 0) {
|
|
160276
|
+
this.park(deviceId, capName, slice, at);
|
|
160277
|
+
return;
|
|
160278
|
+
}
|
|
160279
|
+
if (!isRealDeviceType(type)) {
|
|
160280
|
+
counters.skip("not-a-real-device");
|
|
160281
|
+
return;
|
|
160282
|
+
}
|
|
160283
|
+
for (const [path, spec] of Object.entries(fields)) {
|
|
160284
|
+
if (spec.excludeDeviceTypes?.includes(type) === true) {
|
|
160285
|
+
counters.skip("excluded-device-type");
|
|
160286
|
+
continue;
|
|
160287
|
+
}
|
|
160288
|
+
const value = readNumeric(slice, path, spec.kind);
|
|
160289
|
+
if (value === null) continue;
|
|
160290
|
+
this.consider(deviceId, capName, path, spec, value, at, false);
|
|
160291
|
+
}
|
|
160292
|
+
}
|
|
160293
|
+
consider(deviceId, capName, path, spec, value, at, reconciling) {
|
|
160294
|
+
const key = capSeriesKey(capName, path);
|
|
160295
|
+
const subject = {
|
|
160296
|
+
kind: "device",
|
|
160297
|
+
deviceId
|
|
160298
|
+
};
|
|
160299
|
+
const identity = identityOf(key, `d:${deviceId}`, "");
|
|
160300
|
+
const decision = decideCapture(spec, this.recorded.get(identity) ?? null, value, at, { ignoreFloor: reconciling });
|
|
160301
|
+
if (decision.action === "emit") {
|
|
160302
|
+
this.emit(identity, key, subject, value, at);
|
|
160303
|
+
if (reconciling) this.deps.counters.reconciledPoints += 1;
|
|
160304
|
+
return;
|
|
160305
|
+
}
|
|
160306
|
+
if (decision.action === "defer") {
|
|
160307
|
+
this.pending.set(identity, {
|
|
160308
|
+
key,
|
|
160309
|
+
subject,
|
|
160310
|
+
spec,
|
|
160311
|
+
value
|
|
160312
|
+
});
|
|
160313
|
+
return;
|
|
160314
|
+
}
|
|
160315
|
+
this.pending.delete(identity);
|
|
160316
|
+
this.deps.counters.skip(decision.reason);
|
|
160317
|
+
}
|
|
160318
|
+
emit(identity, key, subject, value, ts) {
|
|
160319
|
+
this.recorded.set(identity, {
|
|
160320
|
+
value,
|
|
160321
|
+
ts
|
|
160322
|
+
});
|
|
160323
|
+
this.pending.delete(identity);
|
|
160324
|
+
this.deps.counters.pointsEmitted += 1;
|
|
160325
|
+
this.deps.writer.enqueue({
|
|
160326
|
+
key,
|
|
160327
|
+
subject,
|
|
160328
|
+
scope: "",
|
|
160329
|
+
ts,
|
|
160330
|
+
value
|
|
160331
|
+
});
|
|
160332
|
+
}
|
|
160333
|
+
/** Emit every pending change whose capture floor has passed, stamped where the floor ended. */
|
|
160334
|
+
tickPending(now) {
|
|
160335
|
+
for (const [identity, p] of this.pending) {
|
|
160336
|
+
const recorded = this.recorded.get(identity);
|
|
160337
|
+
const deviceId = p.subject.kind === "device" ? p.subject.deviceId : -1;
|
|
160338
|
+
if (this.deps.optedOut().has(deviceId)) {
|
|
160339
|
+
this.pending.delete(identity);
|
|
160340
|
+
this.deps.counters.skip("opted-out");
|
|
160341
|
+
continue;
|
|
160342
|
+
}
|
|
160343
|
+
const notBefore = (recorded?.ts ?? now) + minIntervalOf(p.spec);
|
|
160344
|
+
if (now < notBefore) continue;
|
|
160345
|
+
this.emit(identity, p.key, p.subject, p.value, notBefore);
|
|
160346
|
+
this.deps.counters.trailingEmitted += 1;
|
|
160347
|
+
}
|
|
160348
|
+
}
|
|
160349
|
+
park(deviceId, capName, slice, at) {
|
|
160350
|
+
const existing = this.parked.get(deviceId);
|
|
160351
|
+
if (existing !== void 0) {
|
|
160352
|
+
existing.set(capName, {
|
|
160353
|
+
capName,
|
|
160354
|
+
slice,
|
|
160355
|
+
at
|
|
160356
|
+
});
|
|
160357
|
+
return;
|
|
160358
|
+
}
|
|
160359
|
+
if (this.parked.size >= MAX_PARKED_DEVICES) {
|
|
160360
|
+
const oldest = this.parked.keys().next();
|
|
160361
|
+
if (oldest.done !== true) this.parked.delete(oldest.value);
|
|
160362
|
+
this.deps.counters.skip("parked-overflow");
|
|
160363
|
+
}
|
|
160364
|
+
this.parked.set(deviceId, /* @__PURE__ */ new Map([[capName, {
|
|
160365
|
+
capName,
|
|
160366
|
+
slice,
|
|
160367
|
+
at
|
|
160368
|
+
}]]));
|
|
160369
|
+
this.deps.directory.ensure(deviceId).then((type) => {
|
|
160370
|
+
const waiting = this.parked.get(deviceId);
|
|
160371
|
+
this.parked.delete(deviceId);
|
|
160372
|
+
if (waiting === void 0) return;
|
|
160373
|
+
if (type === void 0) {
|
|
160374
|
+
this.deps.counters.skip("unknown-device", waiting.size);
|
|
160375
|
+
return;
|
|
160376
|
+
}
|
|
160377
|
+
for (const p of waiting.values()) this.handleState(deviceId, p.capName, p.slice, p.at);
|
|
160378
|
+
});
|
|
160379
|
+
}
|
|
160380
|
+
/**
|
|
160381
|
+
* Restore a missed change from the state mirror's whole snapshot: a field whose
|
|
160382
|
+
* value differs from the last recorded one gets a point stamped `now`.
|
|
160383
|
+
* Additive only — it never removes anything, and a snapshot that could not be
|
|
160384
|
+
* read is the CALLER's failure to count (D49), not an empty snapshot.
|
|
160385
|
+
*/
|
|
160386
|
+
reconcile(snapshots, now) {
|
|
160387
|
+
const optedOut = this.deps.optedOut();
|
|
160388
|
+
for (const [deviceKey, caps] of Object.entries(snapshots)) {
|
|
160389
|
+
const deviceId = Number(deviceKey);
|
|
160390
|
+
if (!Number.isInteger(deviceId) || optedOut.has(deviceId)) continue;
|
|
160391
|
+
const type = this.deps.directory.typeOf(deviceId);
|
|
160392
|
+
if (type === void 0 || !isRealDeviceType(type)) continue;
|
|
160393
|
+
for (const [capName, fields] of Object.entries(this.deps.catalog)) {
|
|
160394
|
+
const slice = caps[capName];
|
|
160395
|
+
if (slice === void 0) continue;
|
|
160396
|
+
for (const [path, spec] of Object.entries(fields)) {
|
|
160397
|
+
if (spec.excludeDeviceTypes?.includes(type) === true) continue;
|
|
160398
|
+
const value = readNumeric(slice, path, spec.kind);
|
|
160399
|
+
if (value === null) continue;
|
|
160400
|
+
this.consider(deviceId, capName, path, spec, value, now, true);
|
|
160401
|
+
}
|
|
160402
|
+
}
|
|
160403
|
+
}
|
|
160404
|
+
}
|
|
160405
|
+
/**
|
|
160406
|
+
* Forget what was "recorded" for points that never reached the store (dropped),
|
|
160407
|
+
* so the next change-only comparison — and the next reconcile — starts from what
|
|
160408
|
+
* is really there instead of leaving a hole until the value moves.
|
|
160409
|
+
*/
|
|
160410
|
+
forget(points) {
|
|
160411
|
+
for (const p of points) {
|
|
160412
|
+
const identity = identityOf(p.key, subjectIdOf(p.subject), p.scope);
|
|
160413
|
+
this.recorded.delete(identity);
|
|
160414
|
+
this.pending.delete(identity);
|
|
160415
|
+
}
|
|
160416
|
+
}
|
|
160417
|
+
/** A purged series leaves no state behind. */
|
|
160418
|
+
forgetSeries(series) {
|
|
160419
|
+
const identity = identityOf(series.key, series.subject, series.scope);
|
|
160420
|
+
this.recorded.delete(identity);
|
|
160421
|
+
this.pending.delete(identity);
|
|
160422
|
+
}
|
|
160423
|
+
/** For status and specs. */
|
|
160424
|
+
pendingCount() {
|
|
160425
|
+
return this.pending.size;
|
|
160426
|
+
}
|
|
160427
|
+
/** The last recorded state of a series, for specs. */
|
|
160428
|
+
lastRecorded(key, subjectId) {
|
|
160429
|
+
return this.recorded.get(identityOf(key, subjectId, ""));
|
|
160430
|
+
}
|
|
160431
|
+
};
|
|
160432
|
+
var FLUSH_INTERVAL_MS = 1e4;
|
|
160433
|
+
var MAX_BUFFER_AGE_MS = 6e4;
|
|
160434
|
+
var MAX_QUEUE_POINTS = 1e5;
|
|
160435
|
+
var QUEUE_DROP_FRACTION = 0.1;
|
|
160436
|
+
var MAX_DEVICES_IN_DROP_LINE = 20;
|
|
160437
|
+
var MAX_DEVICE_LINES_PER_DROP = 10;
|
|
160438
|
+
var DEVICE_LINES_MIN_GAP_MS = 1e4;
|
|
160439
|
+
var PointWriter = class {
|
|
160440
|
+
deps;
|
|
160441
|
+
queue = [];
|
|
160442
|
+
flushing = null;
|
|
160443
|
+
lastDeviceLinesAt = Number.NEGATIVE_INFINITY;
|
|
160444
|
+
constructor(deps) {
|
|
160445
|
+
this.deps = deps;
|
|
160446
|
+
}
|
|
160447
|
+
enqueue(point) {
|
|
160448
|
+
this.queue.push({
|
|
160449
|
+
...point,
|
|
160450
|
+
bufferedAt: this.deps.clock()
|
|
160451
|
+
});
|
|
160452
|
+
if (this.queue.length > 1e5) {
|
|
160453
|
+
const count = Math.ceil(MAX_QUEUE_POINTS * QUEUE_DROP_FRACTION);
|
|
160454
|
+
this.drop(this.queue.splice(0, count), "queue full");
|
|
160455
|
+
}
|
|
160456
|
+
}
|
|
160457
|
+
depth() {
|
|
160458
|
+
return this.queue.length;
|
|
160459
|
+
}
|
|
160460
|
+
/** One flush at a time; a call while one runs waits for it and returns. */
|
|
160461
|
+
flush() {
|
|
160462
|
+
if (this.flushing !== null) return this.flushing;
|
|
160463
|
+
const run = this.doFlush().finally(() => {
|
|
160464
|
+
this.flushing = null;
|
|
160465
|
+
});
|
|
160466
|
+
this.flushing = run;
|
|
160467
|
+
return run;
|
|
160468
|
+
}
|
|
160469
|
+
async doFlush() {
|
|
160470
|
+
const batch = this.queue;
|
|
160471
|
+
if (batch.length === 0) return;
|
|
160472
|
+
this.queue = [];
|
|
160473
|
+
const { registry, store, counters, logger, clock } = this.deps;
|
|
160474
|
+
try {
|
|
160475
|
+
const bySeries = /* @__PURE__ */ new Map();
|
|
160476
|
+
for (const point of batch) {
|
|
160477
|
+
const series = await registry.ensure(point.key, point.subject, point.scope, point.ts);
|
|
160478
|
+
const list = bySeries.get(series.id) ?? [];
|
|
160479
|
+
list.push({
|
|
160480
|
+
ts: point.ts,
|
|
160481
|
+
value: point.value
|
|
160482
|
+
});
|
|
160483
|
+
bySeries.set(series.id, list);
|
|
160484
|
+
}
|
|
160485
|
+
const rows = [...bySeries].flatMap(([seriesId, points]) => points.map((p) => ({
|
|
160486
|
+
seriesId,
|
|
160487
|
+
ts: p.ts,
|
|
160488
|
+
value: p.value
|
|
160489
|
+
})));
|
|
160490
|
+
await store.upsertRaw(rows);
|
|
160491
|
+
await this.recordSpans(bySeries);
|
|
160492
|
+
counters.pointsWritten += rows.length;
|
|
160493
|
+
counters.addRawRows(rows.length);
|
|
160494
|
+
counters.lastFlushAt = clock();
|
|
160495
|
+
this.deps.onWritten([...bySeries].map(([seriesId, points]) => ({
|
|
160496
|
+
seriesId,
|
|
160497
|
+
timestamps: points.map((p) => p.ts)
|
|
160498
|
+
})));
|
|
160499
|
+
} catch (err) {
|
|
160500
|
+
counters.flushFailures += 1;
|
|
160501
|
+
logger.warn("device-history: raw flush failed \u2014 keeping the batch", { meta: {
|
|
160502
|
+
points: batch.length,
|
|
160503
|
+
error: err instanceof Error ? err.message : String(err)
|
|
160504
|
+
} });
|
|
160505
|
+
this.requeue(batch);
|
|
160506
|
+
}
|
|
160507
|
+
}
|
|
160508
|
+
async recordSpans(bySeries) {
|
|
160509
|
+
const { registry, store } = this.deps;
|
|
160510
|
+
const touch = [];
|
|
160511
|
+
for (const [seriesId, points] of bySeries) {
|
|
160512
|
+
const current = registry.byIdOf(seriesId);
|
|
160513
|
+
if (current === void 0) continue;
|
|
160514
|
+
let minTs = Number.POSITIVE_INFINITY;
|
|
160515
|
+
let maxTs = Number.NEGATIVE_INFINITY;
|
|
160516
|
+
for (const p of points) {
|
|
160517
|
+
if (p.ts < minTs) minTs = p.ts;
|
|
160518
|
+
if (p.ts > maxTs) maxTs = p.ts;
|
|
160519
|
+
}
|
|
160520
|
+
if (minTs < current.firstTs) await store.widenSeriesStart(seriesId, minTs);
|
|
160521
|
+
if (maxTs > current.lastTs) touch.push({
|
|
160522
|
+
id: seriesId,
|
|
160523
|
+
lastTs: maxTs
|
|
160524
|
+
});
|
|
160525
|
+
registry.noteSpan(seriesId, minTs, maxTs);
|
|
160526
|
+
}
|
|
160527
|
+
await store.touchSeries(touch);
|
|
160528
|
+
}
|
|
160529
|
+
requeue(batch) {
|
|
160530
|
+
const now = this.deps.clock();
|
|
160531
|
+
const keep = [];
|
|
160532
|
+
const dropped = [];
|
|
160533
|
+
for (const point of batch) if (now - point.bufferedAt > 6e4) dropped.push(point);
|
|
160534
|
+
else keep.push(point);
|
|
160535
|
+
this.queue = [...keep, ...this.queue];
|
|
160536
|
+
if (dropped.length > 0) this.drop(dropped, `older than ${MAX_BUFFER_AGE_MS} ms`);
|
|
160537
|
+
}
|
|
160538
|
+
/** Account for, say, and report to the owner a set of points that will never be written. */
|
|
160539
|
+
drop(dropped, reason) {
|
|
160540
|
+
if (dropped.length === 0) return;
|
|
160541
|
+
const { counters, logger, clock } = this.deps;
|
|
160542
|
+
counters.pointsDropped += dropped.length;
|
|
160543
|
+
const perDevice = /* @__PURE__ */ new Map();
|
|
160544
|
+
for (const p of dropped) if (p.subject.kind === "device") perDevice.set(p.subject.deviceId, (perDevice.get(p.subject.deviceId) ?? 0) + 1);
|
|
160545
|
+
logger.error("device-history: dropped points the store would not take", { meta: {
|
|
160546
|
+
dropped: dropped.length,
|
|
160547
|
+
reason,
|
|
160548
|
+
maxAgeMs: MAX_BUFFER_AGE_MS,
|
|
160549
|
+
deviceIds: [...perDevice.keys()].slice(0, MAX_DEVICES_IN_DROP_LINE)
|
|
160550
|
+
} });
|
|
160551
|
+
const now = clock();
|
|
160552
|
+
if (now - this.lastDeviceLinesAt >= DEVICE_LINES_MIN_GAP_MS) {
|
|
160553
|
+
this.lastDeviceLinesAt = now;
|
|
160554
|
+
for (const [deviceId, count] of [...perDevice].slice(0, MAX_DEVICE_LINES_PER_DROP)) logger.warn("device-history: points dropped for this device", {
|
|
160555
|
+
tags: { deviceId },
|
|
160556
|
+
meta: {
|
|
160557
|
+
dropped: count,
|
|
160558
|
+
reason
|
|
160559
|
+
}
|
|
160560
|
+
});
|
|
160561
|
+
}
|
|
160562
|
+
this.deps.onDropped(dropped);
|
|
160563
|
+
}
|
|
160564
|
+
};
|
|
160565
|
+
var FIVE_MINUTES_MS = 5 * 6e4;
|
|
160566
|
+
var HOUR_MS = 60 * 6e4;
|
|
160567
|
+
var DAY_MS = 24 * HOUR_MS;
|
|
160568
|
+
function floorTo(ts, widthMs) {
|
|
160569
|
+
return Math.floor(ts / widthMs) * widthMs;
|
|
160570
|
+
}
|
|
160571
|
+
function startOfLocalDay(ts) {
|
|
160572
|
+
const d = new Date(ts);
|
|
160573
|
+
d.setHours(0, 0, 0, 0);
|
|
160574
|
+
return d.getTime();
|
|
160575
|
+
}
|
|
160576
|
+
function nextLocalDayStart(dayStart) {
|
|
160577
|
+
const d = new Date(dayStart);
|
|
160578
|
+
d.setDate(d.getDate() + 1);
|
|
160579
|
+
d.setHours(0, 0, 0, 0);
|
|
160580
|
+
return d.getTime();
|
|
160581
|
+
}
|
|
160582
|
+
function bucketStartOf(tier, ts) {
|
|
160583
|
+
if (tier === "5m") return floorTo(ts, FIVE_MINUTES_MS);
|
|
160584
|
+
if (tier === "1h") return floorTo(ts, HOUR_MS);
|
|
160585
|
+
return startOfLocalDay(ts);
|
|
160586
|
+
}
|
|
160587
|
+
function bucketEndOf(tier, start) {
|
|
160588
|
+
if (tier === "5m") return start + FIVE_MINUTES_MS;
|
|
160589
|
+
if (tier === "1h") return start + HOUR_MS;
|
|
160590
|
+
return nextLocalDayStart(start);
|
|
160591
|
+
}
|
|
160592
|
+
var ROLLUP_GRACE_MS = {
|
|
160593
|
+
"5m": 3e4,
|
|
160594
|
+
"1h": 5 * 6e4,
|
|
160595
|
+
"1d": 15 * 6e4
|
|
160596
|
+
};
|
|
160597
|
+
function addRefusal(refusals, key, reason) {
|
|
160598
|
+
const byReason = refusals.get(key) ?? /* @__PURE__ */ new Map();
|
|
160599
|
+
byReason.set(reason, (byReason.get(reason) ?? 0) + 1);
|
|
160600
|
+
refusals.set(key, byReason);
|
|
160601
|
+
}
|
|
160602
|
+
function toResult(accepted, refusals) {
|
|
160603
|
+
return {
|
|
160604
|
+
accepted,
|
|
160605
|
+
refused: [...refusals].flatMap(([key, byReason]) => [...byReason].map(([reason, count]) => ({
|
|
160606
|
+
key,
|
|
160607
|
+
reason,
|
|
160608
|
+
count
|
|
160609
|
+
})))
|
|
160610
|
+
};
|
|
160611
|
+
}
|
|
160612
|
+
var ProducerIngest = class {
|
|
160613
|
+
deps;
|
|
160614
|
+
/** Last accepted point time per series, for the sustained-rate bound. */
|
|
160615
|
+
lastAccepted = /* @__PURE__ */ new Map();
|
|
160616
|
+
constructor(deps) {
|
|
160617
|
+
this.deps = deps;
|
|
160618
|
+
}
|
|
160619
|
+
/** A purged series leaves no rate-limit state behind. */
|
|
160620
|
+
forgetSeries(series) {
|
|
160621
|
+
this.lastAccepted.delete(identityOf(series.key, series.subject, series.scope));
|
|
160622
|
+
}
|
|
160623
|
+
async declareSeries(callerAddonId, input) {
|
|
160624
|
+
if (callerAddonId === null) throw new Error("declareSeries: the caller addon could not be identified");
|
|
160625
|
+
const key = `addon:${callerAddonId}/${input.name}`;
|
|
160626
|
+
const metric = {
|
|
160627
|
+
key,
|
|
160628
|
+
source: "addon",
|
|
160629
|
+
ownerAddon: callerAddonId,
|
|
160630
|
+
subjectKind: input.subjectKind,
|
|
160631
|
+
kind: input.kind,
|
|
160632
|
+
unit: input.unit ?? null,
|
|
160633
|
+
displayName: input.displayName,
|
|
160634
|
+
ingest: input.ingest,
|
|
160635
|
+
declaredAt: this.deps.clock()
|
|
160636
|
+
};
|
|
160637
|
+
await this.deps.registry.declareMetric(metric);
|
|
160638
|
+
return { key };
|
|
160639
|
+
}
|
|
160640
|
+
/** Why a write to `metric` by `caller` for this subject must be refused, or null. */
|
|
160641
|
+
async refusalFor(callerAddonId, metric, ingest, subject) {
|
|
160642
|
+
if (metric === void 0) return "undeclared";
|
|
160643
|
+
if (callerAddonId === null || metric.ownerAddon !== callerAddonId) return "not-owner";
|
|
160644
|
+
if (metric.ingest !== ingest) return "ingest-mismatch";
|
|
160645
|
+
if (metric.subjectKind !== subject.kind) return "undeclared";
|
|
160646
|
+
if (subject.kind === "device") {
|
|
160647
|
+
if (this.deps.optedOut().has(subject.deviceId)) return "opted-out";
|
|
160648
|
+
const type = await this.deps.directory.ensure(subject.deviceId);
|
|
160649
|
+
if (type === void 0 || !isRealDeviceType(type)) return "not-a-real-device";
|
|
160650
|
+
}
|
|
160651
|
+
return null;
|
|
160652
|
+
}
|
|
160653
|
+
async record(callerAddonId, points) {
|
|
160654
|
+
const { clock, counters } = this.deps;
|
|
160655
|
+
const now = clock();
|
|
160656
|
+
const { rawMs } = this.deps.retention();
|
|
160657
|
+
const refusals = /* @__PURE__ */ new Map();
|
|
160658
|
+
let accepted = 0;
|
|
160659
|
+
for (const point of points) {
|
|
160660
|
+
const reason = await this.refusalFor(callerAddonId, this.deps.registry.metric(point.key), "points", point.subject);
|
|
160661
|
+
if (reason !== null) {
|
|
160662
|
+
addRefusal(refusals, point.key, reason);
|
|
160663
|
+
counters.refuse(reason);
|
|
160664
|
+
continue;
|
|
160665
|
+
}
|
|
160666
|
+
if (!Number.isFinite(point.value) || !Number.isFinite(point.ts) || point.ts > now + 3e5 || point.ts < now - rawMs) {
|
|
160667
|
+
addRefusal(refusals, point.key, "out-of-range");
|
|
160668
|
+
counters.refuse("out-of-range");
|
|
160669
|
+
continue;
|
|
160670
|
+
}
|
|
160671
|
+
const identity = identityOf(point.key, subjectIdOf(point.subject), point.scope);
|
|
160672
|
+
const last = this.lastAccepted.get(identity);
|
|
160673
|
+
if (last !== void 0 && point.ts >= last && point.ts - last < 1e4) {
|
|
160674
|
+
addRefusal(refusals, point.key, "rate");
|
|
160675
|
+
counters.refuse("rate");
|
|
160676
|
+
continue;
|
|
160677
|
+
}
|
|
160678
|
+
this.lastAccepted.set(identity, Math.max(last ?? point.ts, point.ts));
|
|
160679
|
+
this.deps.writer.enqueue({
|
|
160680
|
+
key: point.key,
|
|
160681
|
+
subject: point.subject,
|
|
160682
|
+
scope: point.scope,
|
|
160683
|
+
ts: point.ts,
|
|
160684
|
+
value: point.value
|
|
160685
|
+
});
|
|
160686
|
+
accepted += 1;
|
|
160687
|
+
}
|
|
160688
|
+
counters.ingestAccepted += accepted;
|
|
160689
|
+
return toResult(accepted, refusals);
|
|
160690
|
+
}
|
|
160691
|
+
async recordBuckets(callerAddonId, buckets) {
|
|
160692
|
+
const { clock, counters, registry, store } = this.deps;
|
|
160693
|
+
const now = clock();
|
|
160694
|
+
const { fiveMinuteMs } = this.deps.retention();
|
|
160695
|
+
const refusals = /* @__PURE__ */ new Map();
|
|
160696
|
+
const rows = [];
|
|
160697
|
+
for (const bucket of buckets) {
|
|
160698
|
+
const reason = await this.refusalFor(callerAddonId, registry.metric(bucket.key), "buckets", bucket.subject);
|
|
160699
|
+
if (reason !== null) {
|
|
160700
|
+
addRefusal(refusals, bucket.key, reason);
|
|
160701
|
+
counters.refuse(reason);
|
|
160702
|
+
continue;
|
|
160703
|
+
}
|
|
160704
|
+
const aligned = bucket.bucketStart % FIVE_MINUTES_MS === 0;
|
|
160705
|
+
const consistent = Number.isFinite(bucket.sum) && Number.isFinite(bucket.min) && Number.isFinite(bucket.max) && bucket.min <= bucket.max;
|
|
160706
|
+
if (!aligned || !consistent || bucket.bucketStart > now + 3e5 || bucket.bucketStart < now - fiveMinuteMs) {
|
|
160707
|
+
addRefusal(refusals, bucket.key, "out-of-range");
|
|
160708
|
+
counters.refuse("out-of-range");
|
|
160709
|
+
continue;
|
|
160710
|
+
}
|
|
160711
|
+
const series = await registry.ensure(bucket.key, bucket.subject, bucket.scope, bucket.bucketStart);
|
|
160712
|
+
rows.push({
|
|
160713
|
+
seriesId: series.id,
|
|
160714
|
+
bucketStart: bucket.bucketStart,
|
|
160715
|
+
samples: bucket.samples,
|
|
160716
|
+
sum: bucket.sum,
|
|
160717
|
+
min: bucket.min,
|
|
160718
|
+
max: bucket.max,
|
|
160719
|
+
last: bucket.last,
|
|
160720
|
+
lastTs: bucket.lastTs,
|
|
160721
|
+
twSum: null,
|
|
160722
|
+
twMs: null
|
|
160723
|
+
});
|
|
160724
|
+
}
|
|
160725
|
+
await store.upsertRollups("5m", rows);
|
|
160726
|
+
const bySeries = /* @__PURE__ */ new Map();
|
|
160727
|
+
for (const row of rows) bySeries.set(row.seriesId, [...bySeries.get(row.seriesId) ?? [], row.bucketStart]);
|
|
160728
|
+
for (const [seriesId, starts] of bySeries) counters.noteRollupRows("5m", seriesId, starts.toSorted((a, b) => a - b));
|
|
160729
|
+
await this.recordSpans(rows);
|
|
160730
|
+
for (const row of rows) this.deps.onBucketWritten(row.seriesId, row.bucketStart);
|
|
160731
|
+
counters.ingestAccepted += rows.length;
|
|
160732
|
+
return toResult(rows.length, refusals);
|
|
160733
|
+
}
|
|
160734
|
+
/** Persist the widened span of every series a bucket batch touched, then remember it. */
|
|
160735
|
+
async recordSpans(rows) {
|
|
160736
|
+
const { registry, store } = this.deps;
|
|
160737
|
+
const spans = /* @__PURE__ */ new Map();
|
|
160738
|
+
for (const row of rows) {
|
|
160739
|
+
const end = row.lastTs ?? row.bucketStart;
|
|
160740
|
+
const span = spans.get(row.seriesId);
|
|
160741
|
+
spans.set(row.seriesId, {
|
|
160742
|
+
first: span === void 0 ? row.bucketStart : Math.min(span.first, row.bucketStart),
|
|
160743
|
+
last: span === void 0 ? end : Math.max(span.last, end)
|
|
160744
|
+
});
|
|
160745
|
+
}
|
|
160746
|
+
const touch = [];
|
|
160747
|
+
for (const [id, span] of spans) {
|
|
160748
|
+
const current = registry.byIdOf(id);
|
|
160749
|
+
if (current === void 0) continue;
|
|
160750
|
+
if (span.first < current.firstTs) await store.widenSeriesStart(id, span.first);
|
|
160751
|
+
if (span.last > current.lastTs) touch.push({
|
|
160752
|
+
id,
|
|
160753
|
+
lastTs: span.last
|
|
160754
|
+
});
|
|
160755
|
+
registry.noteSpan(id, span.first, span.last);
|
|
160756
|
+
}
|
|
160757
|
+
await store.touchSeries(touch);
|
|
160758
|
+
}
|
|
160759
|
+
};
|
|
160760
|
+
var TIER_ORDER = [
|
|
160761
|
+
"raw",
|
|
160762
|
+
"5m",
|
|
160763
|
+
"1h",
|
|
160764
|
+
"1d"
|
|
160765
|
+
];
|
|
160766
|
+
var BUCKET_MS = {
|
|
160767
|
+
"5m": FIVE_MINUTES_MS,
|
|
160768
|
+
"1h": HOUR_MS,
|
|
160769
|
+
"1d": DAY_MS
|
|
160770
|
+
};
|
|
160771
|
+
var MAX_SCOPES = 50;
|
|
160772
|
+
function retentionMsOf(tier, policy) {
|
|
160773
|
+
if (tier === "raw") return policy.rawDays * DAY_MS;
|
|
160774
|
+
if (tier === "5m") return policy.fiveMinuteDays * DAY_MS;
|
|
160775
|
+
if (tier === "1h") return policy.hourDays * DAY_MS;
|
|
160776
|
+
return policy.dayDays === null || policy.dayDays <= 0 ? null : policy.dayDays * DAY_MS;
|
|
160777
|
+
}
|
|
160778
|
+
function rawPoint(ts, value) {
|
|
160779
|
+
return {
|
|
160780
|
+
t: ts,
|
|
160781
|
+
samples: 1,
|
|
160782
|
+
mean: value,
|
|
160783
|
+
twMean: null,
|
|
160784
|
+
min: value,
|
|
160785
|
+
max: value,
|
|
160786
|
+
last: value
|
|
160787
|
+
};
|
|
160788
|
+
}
|
|
160789
|
+
function rollupPoint(row) {
|
|
160790
|
+
return {
|
|
160791
|
+
t: row.bucketStart,
|
|
160792
|
+
samples: row.samples,
|
|
160793
|
+
mean: row.samples > 0 ? row.sum / row.samples : null,
|
|
160794
|
+
twMean: row.twSum !== null && row.twMs !== null && row.twMs > 0 ? row.twSum / row.twMs : null,
|
|
160795
|
+
min: row.min,
|
|
160796
|
+
max: row.max,
|
|
160797
|
+
last: row.last
|
|
160798
|
+
};
|
|
160799
|
+
}
|
|
160800
|
+
var HistoryQuery = class {
|
|
160801
|
+
deps;
|
|
160802
|
+
constructor(deps) {
|
|
160803
|
+
this.deps = deps;
|
|
160804
|
+
}
|
|
160805
|
+
async query(input) {
|
|
160806
|
+
const { registry } = this.deps;
|
|
160807
|
+
const subjectId = subjectIdOf(input.subject);
|
|
160808
|
+
const wanted = registry.forKeyAndSubject(input.key, subjectId).filter((s) => input.scope === void 0 || s.scope === input.scope).toSorted((a, b) => a.scope.localeCompare(b.scope)).slice(0, MAX_SCOPES);
|
|
160809
|
+
const candidates = this.coveringTiers(input.from);
|
|
160810
|
+
const forced = input.resolution === "auto" ? null : input.resolution;
|
|
160811
|
+
const order = forced === null ? candidates : [forced];
|
|
160812
|
+
let chosen = order[order.length - 1] ?? "1d";
|
|
160813
|
+
let reads = [];
|
|
160814
|
+
for (const [index, tier] of order.entries()) {
|
|
160815
|
+
const last = index === order.length - 1;
|
|
160816
|
+
if (!this.fits(tier, input, wanted) && !last) continue;
|
|
160817
|
+
chosen = tier;
|
|
160818
|
+
reads = await Promise.all(wanted.map(async (s) => [s, await this.readTier(tier, s, input)]));
|
|
160819
|
+
if (reads.some(([, r]) => r.overflow) && !last) continue;
|
|
160820
|
+
break;
|
|
160821
|
+
}
|
|
160822
|
+
const series = [];
|
|
160823
|
+
for (const [s, read] of reads) series.push({
|
|
160824
|
+
scope: s.scope,
|
|
160825
|
+
before: await this.before(chosen, s, input.from),
|
|
160826
|
+
points: read.points
|
|
160827
|
+
});
|
|
160828
|
+
return {
|
|
160829
|
+
resolution: chosen,
|
|
160830
|
+
series,
|
|
160831
|
+
truncated: reads.some(([, r]) => r.overflow)
|
|
160832
|
+
};
|
|
160833
|
+
}
|
|
160834
|
+
/** Tiers whose retention covers `from`, finest first; the coarsest is always present. */
|
|
160835
|
+
coveringTiers(from) {
|
|
160836
|
+
const policy = this.deps.retention();
|
|
160837
|
+
const now = this.deps.clock();
|
|
160838
|
+
const covering = TIER_ORDER.filter((tier) => {
|
|
160839
|
+
const ms = retentionMsOf(tier, policy);
|
|
160840
|
+
return ms === null || from >= now - ms;
|
|
160841
|
+
});
|
|
160842
|
+
return covering.length > 0 ? covering : ["1d"];
|
|
160843
|
+
}
|
|
160844
|
+
/** Cheap fit test for rollup tiers; raw is decided by the read itself. */
|
|
160845
|
+
fits(tier, input, series) {
|
|
160846
|
+
if (tier === "raw" || series.length === 0) return true;
|
|
160847
|
+
const width = BUCKET_MS[tier];
|
|
160848
|
+
return Math.ceil((input.to - input.from) / width) <= input.maxPoints;
|
|
160849
|
+
}
|
|
160850
|
+
async readTier(tier, series, input) {
|
|
160851
|
+
const { store } = this.deps;
|
|
160852
|
+
const limit = input.maxPoints + 1;
|
|
160853
|
+
if (tier === "raw") {
|
|
160854
|
+
const rows2 = await store.readRaw(series.id, input.from, input.to, limit, "desc");
|
|
160855
|
+
return this.shape(rows2.map((r) => rawPoint(r.ts, r.value)), input.maxPoints);
|
|
160856
|
+
}
|
|
160857
|
+
const lower = bucketStartOf(tier, input.from);
|
|
160858
|
+
const rows = await store.readRollups(tier, series.id, lower, input.to, limit, "desc");
|
|
160859
|
+
return this.shape(rows.map(rollupPoint), input.maxPoints);
|
|
160860
|
+
}
|
|
160861
|
+
/** Rows arrive newest first and one over the bound; keep the newest `maxPoints`, oldest first. */
|
|
160862
|
+
shape(newestFirst, maxPoints) {
|
|
160863
|
+
const overflow = newestFirst.length > maxPoints;
|
|
160864
|
+
return {
|
|
160865
|
+
points: (overflow ? newestFirst.slice(0, maxPoints) : newestFirst).toReversed(),
|
|
160866
|
+
overflow
|
|
160867
|
+
};
|
|
160868
|
+
}
|
|
160869
|
+
async before(tier, series, from) {
|
|
160870
|
+
const { store } = this.deps;
|
|
160871
|
+
if (tier === "raw") {
|
|
160872
|
+
const p = await store.rawBefore(series.id, from);
|
|
160873
|
+
return p === null ? null : {
|
|
160874
|
+
t: p.ts,
|
|
160875
|
+
value: p.value
|
|
160876
|
+
};
|
|
160877
|
+
}
|
|
160878
|
+
const row = await store.rollupBefore(tier, series.id, bucketStartOf(tier, from));
|
|
160879
|
+
return row === null ? null : {
|
|
160880
|
+
t: row.bucketStart,
|
|
160881
|
+
value: row.last
|
|
160882
|
+
};
|
|
160883
|
+
}
|
|
160884
|
+
async listSeries(subject) {
|
|
160885
|
+
const { registry } = this.deps;
|
|
160886
|
+
const slices = /* @__PURE__ */ new Map();
|
|
160887
|
+
const infos = [];
|
|
160888
|
+
for (const s of registry.forSubject(subjectIdOf(subject))) {
|
|
160889
|
+
const metric = registry.metric(s.key);
|
|
160890
|
+
if (metric === void 0) continue;
|
|
160891
|
+
infos.push({
|
|
160892
|
+
key: s.key,
|
|
160893
|
+
scope: s.scope,
|
|
160894
|
+
kind: metric.kind,
|
|
160895
|
+
unit: await this.unitOf(subject, s, metric.unit, slices),
|
|
160896
|
+
displayName: metric.displayName,
|
|
160897
|
+
source: metric.source,
|
|
160898
|
+
firstTs: s.firstTs,
|
|
160899
|
+
lastTs: s.lastTs
|
|
160900
|
+
});
|
|
160901
|
+
}
|
|
160902
|
+
return infos.toSorted((a, b) => a.key.localeCompare(b.key) || a.scope.localeCompare(b.scope));
|
|
160903
|
+
}
|
|
160904
|
+
/** The metric's fixed unit, else the device's own (a `unitFrom` field), else unknown. */
|
|
160905
|
+
async unitOf(subject, series, fixed, slices) {
|
|
160906
|
+
if (fixed !== null || subject.kind !== "device" || !series.key.startsWith("cap:")) return fixed;
|
|
160907
|
+
const spec = this.specOf(series.key);
|
|
160908
|
+
if (spec?.unitFrom === void 0) return null;
|
|
160909
|
+
const capName = series.key.slice(CAP_KEY_PREFIX.length, series.key.indexOf("."));
|
|
160910
|
+
const pending = slices.get(capName) ?? this.deps.readCapSlice(subject.deviceId, capName).catch(() => null);
|
|
160911
|
+
slices.set(capName, pending);
|
|
160912
|
+
const slice = await pending;
|
|
160913
|
+
return slice === null ? null : readUnit(slice, spec.unitFrom);
|
|
160914
|
+
}
|
|
160915
|
+
specOf(key) {
|
|
160916
|
+
const body = key.slice(CAP_KEY_PREFIX.length);
|
|
160917
|
+
const dot = body.indexOf(".");
|
|
160918
|
+
if (dot < 0) return void 0;
|
|
160919
|
+
return this.deps.catalog[body.slice(0, dot)]?.[body.slice(dot + 1)];
|
|
160920
|
+
}
|
|
160921
|
+
};
|
|
160922
|
+
var DEPARTED_ABSENCE_MS = 7 * DAY_MS;
|
|
160923
|
+
var DepartureTracker = class {
|
|
160924
|
+
store;
|
|
160925
|
+
registry;
|
|
160926
|
+
logger;
|
|
160927
|
+
missing = /* @__PURE__ */ new Map();
|
|
160928
|
+
constructor(store, registry, logger) {
|
|
160929
|
+
this.store = store;
|
|
160930
|
+
this.registry = registry;
|
|
160931
|
+
this.logger = logger;
|
|
160932
|
+
}
|
|
160933
|
+
async load() {
|
|
160934
|
+
this.missing = await this.store.loadDepartures();
|
|
160935
|
+
}
|
|
160936
|
+
missingSince(deviceId) {
|
|
160937
|
+
return this.missing.get(deviceId);
|
|
160938
|
+
}
|
|
160939
|
+
/** Forget a device's clock — its series were removed because it was DELETED (D865). */
|
|
160940
|
+
async forget(deviceId) {
|
|
160941
|
+
await this.store.clearMissing([deviceId]);
|
|
160942
|
+
const next = new Map(this.missing);
|
|
160943
|
+
next.delete(deviceId);
|
|
160944
|
+
this.missing = next;
|
|
160945
|
+
}
|
|
160946
|
+
/** Feed one SUCCESSFUL listing. A failed read never reaches here. */
|
|
160947
|
+
async observe(listed, now) {
|
|
160948
|
+
const withSeries = new Set(this.registry.all().flatMap((s) => s.deviceId === null ? [] : [s.deviceId]));
|
|
160949
|
+
const back = [...this.missing.keys()].filter((id) => listed.has(id));
|
|
160950
|
+
const gone = [...withSeries].filter((id) => !listed.has(id) && !this.missing.has(id));
|
|
160951
|
+
try {
|
|
160952
|
+
await this.store.clearMissing(back);
|
|
160953
|
+
await this.store.markMissing(gone, now);
|
|
160954
|
+
} catch (err) {
|
|
160955
|
+
this.logger.warn("device-history: departure clock not written", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
160956
|
+
return;
|
|
160957
|
+
}
|
|
160958
|
+
const next = new Map(this.missing);
|
|
160959
|
+
for (const id of back) next.delete(id);
|
|
160960
|
+
for (const id of gone) next.set(id, now);
|
|
160961
|
+
this.missing = next;
|
|
160962
|
+
}
|
|
160963
|
+
};
|
|
160964
|
+
var DEFAULT_RETENTION = {
|
|
160965
|
+
rawDays: 7,
|
|
160966
|
+
fiveMinuteDays: 14,
|
|
160967
|
+
hourDays: 365,
|
|
160968
|
+
dayDays: null
|
|
160969
|
+
};
|
|
160970
|
+
function retentionCutoffs(policy, now) {
|
|
160971
|
+
return {
|
|
160972
|
+
raw: now - policy.rawDays * DAY_MS,
|
|
160973
|
+
"5m": now - policy.fiveMinuteDays * DAY_MS,
|
|
160974
|
+
"1h": now - policy.hourDays * DAY_MS,
|
|
160975
|
+
"1d": policy.dayDays === null || policy.dayDays <= 0 ? null : now - policy.dayDays * DAY_MS
|
|
160976
|
+
};
|
|
160977
|
+
}
|
|
160978
|
+
var TIERS = [
|
|
160979
|
+
"raw",
|
|
160980
|
+
"5m",
|
|
160981
|
+
"1h",
|
|
160982
|
+
"1d"
|
|
160983
|
+
];
|
|
160984
|
+
var RETENTION_STEP_MS = {
|
|
160985
|
+
raw: HOUR_MS,
|
|
160986
|
+
"5m": HOUR_MS,
|
|
160987
|
+
"1h": DAY_MS,
|
|
160988
|
+
"1d": DAY_MS
|
|
160989
|
+
};
|
|
160990
|
+
var MAX_PURGES_PER_RUN = 20;
|
|
160991
|
+
var SLICE_MS = {
|
|
160992
|
+
raw: DAY_MS,
|
|
160993
|
+
"5m": DAY_MS,
|
|
160994
|
+
"1h": 30 * DAY_MS,
|
|
160995
|
+
"1d": 30 * DAY_MS
|
|
160996
|
+
};
|
|
160997
|
+
var BUCKET_LEAD_MS = 2 * DAY_MS;
|
|
160998
|
+
var SHORT_TIER_LOOKBACK_MS = 400 * DAY_MS;
|
|
160999
|
+
var yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
|
|
161000
|
+
var RetentionJob = class {
|
|
161001
|
+
deps;
|
|
161002
|
+
appliedUpTo = /* @__PURE__ */ new Map();
|
|
161003
|
+
running = null;
|
|
161004
|
+
constructor(deps) {
|
|
161005
|
+
this.deps = deps;
|
|
161006
|
+
}
|
|
161007
|
+
run(policy, now) {
|
|
161008
|
+
if (this.running !== null) return this.running;
|
|
161009
|
+
const run = this.doRun(policy, now).finally(() => {
|
|
161010
|
+
this.running = null;
|
|
161011
|
+
});
|
|
161012
|
+
this.running = run;
|
|
161013
|
+
return run;
|
|
161014
|
+
}
|
|
161015
|
+
async doRun(policy, now) {
|
|
161016
|
+
const { registry, counters, logger } = this.deps;
|
|
161017
|
+
const cutoffs = retentionCutoffs(policy, now);
|
|
161018
|
+
try {
|
|
161019
|
+
for (const tier of TIERS) {
|
|
161020
|
+
const cutoff = cutoffs[tier];
|
|
161021
|
+
if (cutoff === null) continue;
|
|
161022
|
+
for (const series of registry.all()) {
|
|
161023
|
+
if (series.firstTs - 864e5 >= cutoff) continue;
|
|
161024
|
+
if (tier === "raw" && registry.metric(series.key)?.ingest === "buckets") continue;
|
|
161025
|
+
const marker = `${tier}:${series.id}`;
|
|
161026
|
+
const applied = this.appliedUpTo.get(marker);
|
|
161027
|
+
if (applied !== void 0 && cutoff - applied < RETENTION_STEP_MS[tier]) continue;
|
|
161028
|
+
const lo = Math.max(0, applied === void 0 ? series.firstTs - BUCKET_LEAD_MS : applied - RETENTION_STEP_MS[tier]);
|
|
161029
|
+
counters.addDeleted(tier, await this.deleteSliced(tier, series.id, lo, cutoff - 1));
|
|
161030
|
+
this.appliedUpTo.set(marker, cutoff);
|
|
161031
|
+
}
|
|
161032
|
+
}
|
|
161033
|
+
await this.purgeDeparted(now);
|
|
161034
|
+
counters.retentionLastRunAt = now;
|
|
161035
|
+
} catch (err) {
|
|
161036
|
+
logger.warn("device-history: retention pass failed \u2014 retried at the next run", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
161037
|
+
}
|
|
161038
|
+
}
|
|
161039
|
+
/** Delete `[lo, hiInclusive]` of one tier in time slices, yielding between them. */
|
|
161040
|
+
/** Drop the visit markers of a purged series. */
|
|
161041
|
+
forgetSeries(seriesId) {
|
|
161042
|
+
for (const tier of TIERS) this.appliedUpTo.delete(`${tier}:${seriesId}`);
|
|
161043
|
+
}
|
|
161044
|
+
async deleteSliced(tier, seriesId, lo, hiInclusive) {
|
|
161045
|
+
const yieldNow = this.deps.yieldNow ?? yieldToEventLoop;
|
|
161046
|
+
let deleted = 0;
|
|
161047
|
+
for (let from = lo; from <= hiInclusive; from += SLICE_MS[tier]) {
|
|
161048
|
+
const to = Math.min(from + SLICE_MS[tier] - 1, hiInclusive);
|
|
161049
|
+
deleted += await this.deps.store.deleteRange(tier, seriesId, from, to);
|
|
161050
|
+
await yieldNow();
|
|
161051
|
+
}
|
|
161052
|
+
return deleted;
|
|
161053
|
+
}
|
|
161054
|
+
/** Tier by tier in slices, then the series row (and whatever the slices missed). */
|
|
161055
|
+
async purgeSeries(series, now) {
|
|
161056
|
+
for (const tier of TIERS) {
|
|
161057
|
+
const lo = tier === "raw" || tier === "5m" ? Math.max(series.firstTs - BUCKET_LEAD_MS, now - SHORT_TIER_LOOKBACK_MS) : series.firstTs - BUCKET_LEAD_MS;
|
|
161058
|
+
await this.deleteSliced(tier, series.id, Math.max(0, lo), now + DAY_MS);
|
|
161059
|
+
}
|
|
161060
|
+
await this.deps.store.deleteSeries(series.id);
|
|
161061
|
+
}
|
|
161062
|
+
/**
|
|
161063
|
+
* Delete every series of a DELETED device now (D865). Driven by a tombstone
|
|
161064
|
+
* through the `device-removal-hook`, never by a listing: the week-long
|
|
161065
|
+
* absence clock above remains the backstop for a device removed before
|
|
161066
|
+
* tombstones existed.
|
|
161067
|
+
*/
|
|
161068
|
+
async purgeDevice(deviceId, now) {
|
|
161069
|
+
const { registry, counters } = this.deps;
|
|
161070
|
+
let purged = 0;
|
|
161071
|
+
for (const series of registry.all()) {
|
|
161072
|
+
if (series.deviceId !== deviceId) continue;
|
|
161073
|
+
await this.purgeSeries(series, now);
|
|
161074
|
+
registry.remove(series.id);
|
|
161075
|
+
counters.purgedSeries += 1;
|
|
161076
|
+
purged += 1;
|
|
161077
|
+
}
|
|
161078
|
+
return purged;
|
|
161079
|
+
}
|
|
161080
|
+
async purgeDeparted(now) {
|
|
161081
|
+
const { registry, departures, counters, logger } = this.deps;
|
|
161082
|
+
let purged = 0;
|
|
161083
|
+
for (const series of registry.all()) {
|
|
161084
|
+
if (purged >= MAX_PURGES_PER_RUN) break;
|
|
161085
|
+
if (series.deviceId === null) continue;
|
|
161086
|
+
const since = departures.missingSince(series.deviceId);
|
|
161087
|
+
if (since === void 0 || now - since < DEPARTED_ABSENCE_MS) continue;
|
|
161088
|
+
await this.purgeSeries(series, now);
|
|
161089
|
+
registry.remove(series.id);
|
|
161090
|
+
counters.purgedSeries += 1;
|
|
161091
|
+
purged += 1;
|
|
161092
|
+
logger.info("device-history: purged the series of an unregistered device", {
|
|
161093
|
+
tags: { deviceId: series.deviceId },
|
|
161094
|
+
meta: { key: series.key }
|
|
161095
|
+
});
|
|
161096
|
+
}
|
|
161097
|
+
}
|
|
161098
|
+
};
|
|
161099
|
+
var HISTORY_NAMESPACE = "device-history";
|
|
161100
|
+
var COLLECTIONS = {
|
|
161101
|
+
metrics: "metrics",
|
|
161102
|
+
series: "series",
|
|
161103
|
+
raw: "raw",
|
|
161104
|
+
rollup5m: "rollup-5m",
|
|
161105
|
+
rollup1h: "rollup-1h",
|
|
161106
|
+
rollup1d: "rollup-1d",
|
|
161107
|
+
optOuts: "device-opt-outs",
|
|
161108
|
+
departures: "device-departures"
|
|
161109
|
+
};
|
|
161110
|
+
function rollupCollectionOf(tier) {
|
|
161111
|
+
if (tier === "5m") return COLLECTIONS.rollup5m;
|
|
161112
|
+
if (tier === "1h") return COLLECTIONS.rollup1h;
|
|
161113
|
+
return COLLECTIONS.rollup1d;
|
|
161114
|
+
}
|
|
161115
|
+
function rollupDeclaration(collection) {
|
|
161116
|
+
return {
|
|
161117
|
+
namespace: HISTORY_NAMESPACE,
|
|
161118
|
+
collection,
|
|
161119
|
+
database: "history",
|
|
161120
|
+
primaryKey: ["series_id", "bucket_start"],
|
|
161121
|
+
withoutRowid: true,
|
|
161122
|
+
columns: [
|
|
161123
|
+
{
|
|
161124
|
+
name: "series_id",
|
|
161125
|
+
type: "INTEGER"
|
|
161126
|
+
},
|
|
161127
|
+
{
|
|
161128
|
+
name: "bucket_start",
|
|
161129
|
+
type: "INTEGER"
|
|
161130
|
+
},
|
|
161131
|
+
{
|
|
161132
|
+
name: "samples",
|
|
161133
|
+
type: "INTEGER",
|
|
161134
|
+
notNull: true
|
|
161135
|
+
},
|
|
161136
|
+
{
|
|
161137
|
+
name: "sum",
|
|
161138
|
+
type: "REAL",
|
|
161139
|
+
notNull: true
|
|
161140
|
+
},
|
|
161141
|
+
{
|
|
161142
|
+
name: "min",
|
|
161143
|
+
type: "REAL",
|
|
161144
|
+
notNull: true
|
|
161145
|
+
},
|
|
161146
|
+
{
|
|
161147
|
+
name: "max",
|
|
161148
|
+
type: "REAL",
|
|
161149
|
+
notNull: true
|
|
161150
|
+
},
|
|
161151
|
+
{
|
|
161152
|
+
name: "last",
|
|
161153
|
+
type: "REAL"
|
|
161154
|
+
},
|
|
161155
|
+
{
|
|
161156
|
+
name: "last_ts",
|
|
161157
|
+
type: "INTEGER"
|
|
161158
|
+
},
|
|
161159
|
+
{
|
|
161160
|
+
name: "tw_sum",
|
|
161161
|
+
type: "REAL"
|
|
161162
|
+
},
|
|
161163
|
+
{
|
|
161164
|
+
name: "tw_ms",
|
|
161165
|
+
type: "INTEGER"
|
|
161166
|
+
}
|
|
161167
|
+
]
|
|
161168
|
+
};
|
|
161169
|
+
}
|
|
161170
|
+
var HISTORY_DECLARATIONS = [
|
|
161171
|
+
{
|
|
161172
|
+
namespace: HISTORY_NAMESPACE,
|
|
161173
|
+
collection: COLLECTIONS.metrics,
|
|
161174
|
+
database: "history",
|
|
161175
|
+
columns: [
|
|
161176
|
+
{
|
|
161177
|
+
name: "key",
|
|
161178
|
+
type: "TEXT",
|
|
161179
|
+
primaryKey: true,
|
|
161180
|
+
notNull: true
|
|
161181
|
+
},
|
|
161182
|
+
{
|
|
161183
|
+
name: "source",
|
|
161184
|
+
type: "TEXT",
|
|
161185
|
+
notNull: true
|
|
161186
|
+
},
|
|
161187
|
+
{
|
|
161188
|
+
name: "owner_addon",
|
|
161189
|
+
type: "TEXT",
|
|
161190
|
+
notNull: true
|
|
161191
|
+
},
|
|
161192
|
+
{
|
|
161193
|
+
name: "subject_kind",
|
|
161194
|
+
type: "TEXT",
|
|
161195
|
+
notNull: true
|
|
161196
|
+
},
|
|
161197
|
+
{
|
|
161198
|
+
name: "kind",
|
|
161199
|
+
type: "TEXT",
|
|
161200
|
+
notNull: true
|
|
161201
|
+
},
|
|
161202
|
+
{
|
|
161203
|
+
name: "unit",
|
|
161204
|
+
type: "TEXT"
|
|
161205
|
+
},
|
|
161206
|
+
{
|
|
161207
|
+
name: "display_name",
|
|
161208
|
+
type: "TEXT",
|
|
161209
|
+
notNull: true
|
|
161210
|
+
},
|
|
161211
|
+
{
|
|
161212
|
+
name: "ingest",
|
|
161213
|
+
type: "TEXT",
|
|
161214
|
+
notNull: true
|
|
161215
|
+
},
|
|
161216
|
+
{
|
|
161217
|
+
name: "declared_at",
|
|
161218
|
+
type: "INTEGER",
|
|
161219
|
+
notNull: true
|
|
161220
|
+
}
|
|
161221
|
+
]
|
|
161222
|
+
},
|
|
161223
|
+
{
|
|
161224
|
+
namespace: HISTORY_NAMESPACE,
|
|
161225
|
+
collection: COLLECTIONS.series,
|
|
161226
|
+
database: "history",
|
|
161227
|
+
columns: [
|
|
161228
|
+
{
|
|
161229
|
+
name: "id",
|
|
161230
|
+
type: "INTEGER",
|
|
161231
|
+
primaryKey: true
|
|
161232
|
+
},
|
|
161233
|
+
{
|
|
161234
|
+
name: "key",
|
|
161235
|
+
type: "TEXT",
|
|
161236
|
+
notNull: true
|
|
161237
|
+
},
|
|
161238
|
+
{
|
|
161239
|
+
name: "subject",
|
|
161240
|
+
type: "TEXT",
|
|
161241
|
+
notNull: true
|
|
161242
|
+
},
|
|
161243
|
+
{
|
|
161244
|
+
name: "device_id",
|
|
161245
|
+
type: "INTEGER"
|
|
161246
|
+
},
|
|
161247
|
+
{
|
|
161248
|
+
name: "scope",
|
|
161249
|
+
type: "TEXT",
|
|
161250
|
+
notNull: true,
|
|
161251
|
+
defaultValue: ""
|
|
161252
|
+
},
|
|
161253
|
+
{
|
|
161254
|
+
name: "first_ts",
|
|
161255
|
+
type: "INTEGER",
|
|
161256
|
+
notNull: true
|
|
161257
|
+
},
|
|
161258
|
+
{
|
|
161259
|
+
name: "last_ts",
|
|
161260
|
+
type: "INTEGER",
|
|
161261
|
+
notNull: true
|
|
161262
|
+
}
|
|
161263
|
+
],
|
|
161264
|
+
indexes: [{
|
|
161265
|
+
name: "idx_dh_series_identity",
|
|
161266
|
+
columns: [
|
|
161267
|
+
"key",
|
|
161268
|
+
"subject",
|
|
161269
|
+
"scope"
|
|
161270
|
+
],
|
|
161271
|
+
unique: true
|
|
161272
|
+
}, {
|
|
161273
|
+
name: "idx_dh_series_device",
|
|
161274
|
+
columns: ["device_id"]
|
|
161275
|
+
}]
|
|
161276
|
+
},
|
|
161277
|
+
{
|
|
161278
|
+
namespace: HISTORY_NAMESPACE,
|
|
161279
|
+
collection: COLLECTIONS.raw,
|
|
161280
|
+
database: "history",
|
|
161281
|
+
primaryKey: ["series_id", "ts"],
|
|
161282
|
+
withoutRowid: true,
|
|
161283
|
+
columns: [
|
|
161284
|
+
{
|
|
161285
|
+
name: "series_id",
|
|
161286
|
+
type: "INTEGER"
|
|
161287
|
+
},
|
|
161288
|
+
{
|
|
161289
|
+
name: "ts",
|
|
161290
|
+
type: "INTEGER"
|
|
161291
|
+
},
|
|
161292
|
+
{
|
|
161293
|
+
name: "value",
|
|
161294
|
+
type: "REAL",
|
|
161295
|
+
notNull: true
|
|
161296
|
+
}
|
|
161297
|
+
]
|
|
161298
|
+
},
|
|
161299
|
+
rollupDeclaration(COLLECTIONS.rollup5m),
|
|
161300
|
+
rollupDeclaration(COLLECTIONS.rollup1h),
|
|
161301
|
+
rollupDeclaration(COLLECTIONS.rollup1d),
|
|
161302
|
+
{
|
|
161303
|
+
namespace: HISTORY_NAMESPACE,
|
|
161304
|
+
collection: COLLECTIONS.optOuts,
|
|
161305
|
+
columns: [
|
|
161306
|
+
{
|
|
161307
|
+
name: "id",
|
|
161308
|
+
type: "INTEGER",
|
|
161309
|
+
primaryKey: true
|
|
161310
|
+
},
|
|
161311
|
+
{
|
|
161312
|
+
name: "device_id",
|
|
161313
|
+
type: "INTEGER",
|
|
161314
|
+
notNull: true
|
|
161315
|
+
},
|
|
161316
|
+
{
|
|
161317
|
+
name: "opted_out_at",
|
|
161318
|
+
type: "INTEGER",
|
|
161319
|
+
notNull: true
|
|
161320
|
+
}
|
|
161321
|
+
],
|
|
161322
|
+
indexes: [{
|
|
161323
|
+
name: "idx_dh_opt_outs_device",
|
|
161324
|
+
columns: ["device_id"],
|
|
161325
|
+
unique: true
|
|
161326
|
+
}]
|
|
161327
|
+
},
|
|
161328
|
+
{
|
|
161329
|
+
namespace: HISTORY_NAMESPACE,
|
|
161330
|
+
collection: COLLECTIONS.departures,
|
|
161331
|
+
columns: [
|
|
161332
|
+
{
|
|
161333
|
+
name: "id",
|
|
161334
|
+
type: "INTEGER",
|
|
161335
|
+
primaryKey: true
|
|
161336
|
+
},
|
|
161337
|
+
{
|
|
161338
|
+
name: "device_id",
|
|
161339
|
+
type: "INTEGER",
|
|
161340
|
+
notNull: true
|
|
161341
|
+
},
|
|
161342
|
+
{
|
|
161343
|
+
name: "missing_since",
|
|
161344
|
+
type: "INTEGER",
|
|
161345
|
+
notNull: true
|
|
161346
|
+
}
|
|
161347
|
+
],
|
|
161348
|
+
indexes: [{
|
|
161349
|
+
name: "idx_dh_departures_device",
|
|
161350
|
+
columns: ["device_id"],
|
|
161351
|
+
unique: true
|
|
161352
|
+
}]
|
|
161353
|
+
}
|
|
161354
|
+
];
|
|
161355
|
+
function readNumber(data, column) {
|
|
161356
|
+
const v = data[column];
|
|
161357
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
161358
|
+
throw new Error(`history row: column "${column}" is not a number (${typeof v})`);
|
|
161359
|
+
}
|
|
161360
|
+
function readNumberOrNull(data, column) {
|
|
161361
|
+
const v = data[column];
|
|
161362
|
+
if (v === null || v === void 0) return null;
|
|
161363
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
161364
|
+
throw new Error(`history row: column "${column}" is not a number or null (${typeof v})`);
|
|
161365
|
+
}
|
|
161366
|
+
function readText(data, column) {
|
|
161367
|
+
const v = data[column];
|
|
161368
|
+
if (typeof v === "string") return v;
|
|
161369
|
+
throw new Error(`history row: column "${column}" is not a string (${typeof v})`);
|
|
161370
|
+
}
|
|
161371
|
+
function readTextOrNull(data, column) {
|
|
161372
|
+
const v = data[column];
|
|
161373
|
+
if (v === null || v === void 0) return null;
|
|
161374
|
+
if (typeof v === "string") return v;
|
|
161375
|
+
throw new Error(`history row: column "${column}" is not a string or null (${typeof v})`);
|
|
161376
|
+
}
|
|
161377
|
+
var WRITE_CHUNK = 1e3;
|
|
161378
|
+
var TRANSACT_CHUNK = 200;
|
|
161379
|
+
var READ_PAGE = 5e3;
|
|
161380
|
+
function chunks(items, size) {
|
|
161381
|
+
const out = [];
|
|
161382
|
+
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
161383
|
+
return out;
|
|
161384
|
+
}
|
|
161385
|
+
function parseKind(value) {
|
|
161386
|
+
if (value === "gauge" || value === "counter" || value === "boolean") return value;
|
|
161387
|
+
throw new Error(`history row: unknown kind "${value}"`);
|
|
161388
|
+
}
|
|
161389
|
+
function parseMetric(data) {
|
|
161390
|
+
const source = readText(data, "source");
|
|
161391
|
+
const subjectKind = readText(data, "subject_kind");
|
|
161392
|
+
const ingest = readText(data, "ingest");
|
|
161393
|
+
if (source !== "cap" && source !== "addon") throw new Error(`history row: unknown source "${source}"`);
|
|
161394
|
+
if (subjectKind !== "device" && subjectKind !== "node") throw new Error(`history row: unknown subject_kind "${subjectKind}"`);
|
|
161395
|
+
if (ingest !== "points" && ingest !== "buckets") throw new Error(`history row: unknown ingest "${ingest}"`);
|
|
161396
|
+
return {
|
|
161397
|
+
key: readText(data, "key"),
|
|
161398
|
+
source,
|
|
161399
|
+
ownerAddon: readText(data, "owner_addon"),
|
|
161400
|
+
subjectKind,
|
|
161401
|
+
kind: parseKind(readText(data, "kind")),
|
|
161402
|
+
unit: readTextOrNull(data, "unit"),
|
|
161403
|
+
displayName: readText(data, "display_name"),
|
|
161404
|
+
ingest,
|
|
161405
|
+
declaredAt: readNumber(data, "declared_at")
|
|
161406
|
+
};
|
|
161407
|
+
}
|
|
161408
|
+
function parseSeries(data) {
|
|
161409
|
+
return {
|
|
161410
|
+
id: readNumber(data, "id"),
|
|
161411
|
+
key: readText(data, "key"),
|
|
161412
|
+
subject: readText(data, "subject"),
|
|
161413
|
+
deviceId: readNumberOrNull(data, "device_id"),
|
|
161414
|
+
scope: readText(data, "scope"),
|
|
161415
|
+
firstTs: readNumber(data, "first_ts"),
|
|
161416
|
+
lastTs: readNumber(data, "last_ts")
|
|
161417
|
+
};
|
|
161418
|
+
}
|
|
161419
|
+
function parseRollup(data) {
|
|
161420
|
+
return {
|
|
161421
|
+
seriesId: readNumber(data, "series_id"),
|
|
161422
|
+
bucketStart: readNumber(data, "bucket_start"),
|
|
161423
|
+
samples: readNumber(data, "samples"),
|
|
161424
|
+
sum: readNumber(data, "sum"),
|
|
161425
|
+
min: readNumber(data, "min"),
|
|
161426
|
+
max: readNumber(data, "max"),
|
|
161427
|
+
last: readNumberOrNull(data, "last"),
|
|
161428
|
+
lastTs: readNumberOrNull(data, "last_ts"),
|
|
161429
|
+
twSum: readNumberOrNull(data, "tw_sum"),
|
|
161430
|
+
twMs: readNumberOrNull(data, "tw_ms")
|
|
161431
|
+
};
|
|
161432
|
+
}
|
|
161433
|
+
function rollupData(row) {
|
|
161434
|
+
return {
|
|
161435
|
+
series_id: row.seriesId,
|
|
161436
|
+
bucket_start: row.bucketStart,
|
|
161437
|
+
samples: row.samples,
|
|
161438
|
+
sum: row.sum,
|
|
161439
|
+
min: row.min,
|
|
161440
|
+
max: row.max,
|
|
161441
|
+
last: row.last,
|
|
161442
|
+
last_ts: row.lastTs,
|
|
161443
|
+
tw_sum: row.twSum,
|
|
161444
|
+
tw_ms: row.twMs
|
|
161445
|
+
};
|
|
161446
|
+
}
|
|
161447
|
+
function collectionOf(tier) {
|
|
161448
|
+
return tier === "raw" ? COLLECTIONS.raw : rollupCollectionOf(tier);
|
|
161449
|
+
}
|
|
161450
|
+
function orderColumnOf(tier) {
|
|
161451
|
+
return tier === "raw" ? "ts" : "bucket_start";
|
|
161452
|
+
}
|
|
161453
|
+
var HistoryStore = class {
|
|
161454
|
+
door;
|
|
161455
|
+
constructor(door) {
|
|
161456
|
+
this.door = door;
|
|
161457
|
+
}
|
|
161458
|
+
async declare() {
|
|
161459
|
+
for (const declaration of HISTORY_DECLARATIONS) await this.door.declareCollection.mutate(declaration);
|
|
161460
|
+
}
|
|
161461
|
+
async loadMetrics() {
|
|
161462
|
+
return (await this.door.query.query({
|
|
161463
|
+
namespace: HISTORY_NAMESPACE,
|
|
161464
|
+
collection: COLLECTIONS.metrics,
|
|
161465
|
+
filter: { limit: 2e4 }
|
|
161466
|
+
})).map((r) => parseMetric(r.data));
|
|
161467
|
+
}
|
|
161468
|
+
async upsertMetric(metric) {
|
|
161469
|
+
await this.door.upsertMany.mutate({
|
|
161470
|
+
namespace: HISTORY_NAMESPACE,
|
|
161471
|
+
collection: COLLECTIONS.metrics,
|
|
161472
|
+
conflict: ["key"],
|
|
161473
|
+
records: [{ data: {
|
|
161474
|
+
key: metric.key,
|
|
161475
|
+
source: metric.source,
|
|
161476
|
+
owner_addon: metric.ownerAddon,
|
|
161477
|
+
subject_kind: metric.subjectKind,
|
|
161478
|
+
kind: metric.kind,
|
|
161479
|
+
unit: metric.unit,
|
|
161480
|
+
display_name: metric.displayName,
|
|
161481
|
+
ingest: metric.ingest,
|
|
161482
|
+
declared_at: metric.declaredAt
|
|
161483
|
+
} }]
|
|
161484
|
+
});
|
|
161485
|
+
}
|
|
161486
|
+
/** Series with `id > afterId`, ascending — the boot load pages with this. */
|
|
161487
|
+
async loadSeriesPage(afterId, limit) {
|
|
161488
|
+
return (await this.door.query.query({
|
|
161489
|
+
namespace: HISTORY_NAMESPACE,
|
|
161490
|
+
collection: COLLECTIONS.series,
|
|
161491
|
+
filter: {
|
|
161492
|
+
whereBetween: { id: [afterId + 1, Number.MAX_SAFE_INTEGER] },
|
|
161493
|
+
orderBy: {
|
|
161494
|
+
field: "id",
|
|
161495
|
+
direction: "asc"
|
|
161496
|
+
},
|
|
161497
|
+
limit
|
|
161498
|
+
}
|
|
161499
|
+
})).map((r) => parseSeries(r.data));
|
|
161500
|
+
}
|
|
161501
|
+
async findSeries(key, subject, scope) {
|
|
161502
|
+
const first = (await this.door.query.query({
|
|
161503
|
+
namespace: HISTORY_NAMESPACE,
|
|
161504
|
+
collection: COLLECTIONS.series,
|
|
161505
|
+
filter: {
|
|
161506
|
+
where: {
|
|
161507
|
+
key,
|
|
161508
|
+
subject,
|
|
161509
|
+
scope
|
|
161510
|
+
},
|
|
161511
|
+
limit: 1
|
|
161512
|
+
}
|
|
161513
|
+
}))[0];
|
|
161514
|
+
return first === void 0 ? null : parseSeries(first.data);
|
|
161515
|
+
}
|
|
161516
|
+
async insertSeries(series) {
|
|
161517
|
+
const { id } = await this.door.insert.mutate({
|
|
161518
|
+
namespace: HISTORY_NAMESPACE,
|
|
161519
|
+
collection: COLLECTIONS.series,
|
|
161520
|
+
record: { data: {
|
|
161521
|
+
key: series.key,
|
|
161522
|
+
subject: series.subject,
|
|
161523
|
+
device_id: series.deviceId,
|
|
161524
|
+
scope: series.scope,
|
|
161525
|
+
first_ts: series.ts,
|
|
161526
|
+
last_ts: series.ts
|
|
161527
|
+
} }
|
|
161528
|
+
});
|
|
161529
|
+
return {
|
|
161530
|
+
id: typeof id === "number" ? id : Number(id),
|
|
161531
|
+
key: series.key,
|
|
161532
|
+
subject: series.subject,
|
|
161533
|
+
deviceId: series.deviceId,
|
|
161534
|
+
scope: series.scope,
|
|
161535
|
+
firstTs: series.ts,
|
|
161536
|
+
lastTs: series.ts
|
|
161537
|
+
};
|
|
161538
|
+
}
|
|
161539
|
+
async touchSeries(updates) {
|
|
161540
|
+
for (const batch of chunks(updates, TRANSACT_CHUNK)) await this.door.transact.mutate({
|
|
161541
|
+
namespace: HISTORY_NAMESPACE,
|
|
161542
|
+
ops: batch.map((u) => ({
|
|
161543
|
+
op: "update",
|
|
161544
|
+
collection: COLLECTIONS.series,
|
|
161545
|
+
id: String(u.id),
|
|
161546
|
+
data: { last_ts: u.lastTs }
|
|
161547
|
+
}))
|
|
161548
|
+
});
|
|
161549
|
+
}
|
|
161550
|
+
async widenSeriesStart(id, firstTs) {
|
|
161551
|
+
await this.door.update.mutate({
|
|
161552
|
+
namespace: HISTORY_NAMESPACE,
|
|
161553
|
+
collection: COLLECTIONS.series,
|
|
161554
|
+
id: String(id),
|
|
161555
|
+
data: { first_ts: firstTs }
|
|
161556
|
+
});
|
|
161557
|
+
}
|
|
161558
|
+
async upsertRaw(rows) {
|
|
161559
|
+
for (const batch of chunks(rows, WRITE_CHUNK)) await this.door.upsertMany.mutate({
|
|
161560
|
+
namespace: HISTORY_NAMESPACE,
|
|
161561
|
+
collection: COLLECTIONS.raw,
|
|
161562
|
+
conflict: ["series_id", "ts"],
|
|
161563
|
+
records: batch.map((r) => ({ data: {
|
|
161564
|
+
series_id: r.seriesId,
|
|
161565
|
+
ts: r.ts,
|
|
161566
|
+
value: r.value
|
|
161567
|
+
} }))
|
|
161568
|
+
});
|
|
161569
|
+
}
|
|
161570
|
+
/** Points of a series in `[from, toInclusive]`, ascending, at most `limit`. */
|
|
161571
|
+
async readRaw(seriesId, from, toInclusive, limit, direction = "asc") {
|
|
161572
|
+
return (await this.door.query.query({
|
|
161573
|
+
namespace: HISTORY_NAMESPACE,
|
|
161574
|
+
collection: COLLECTIONS.raw,
|
|
161575
|
+
filter: {
|
|
161576
|
+
where: { series_id: seriesId },
|
|
161577
|
+
whereBetween: { ts: [from, toInclusive] },
|
|
161578
|
+
orderBy: {
|
|
161579
|
+
field: "ts",
|
|
161580
|
+
direction
|
|
161581
|
+
},
|
|
161582
|
+
limit
|
|
161583
|
+
}
|
|
161584
|
+
})).map((r) => ({
|
|
161585
|
+
ts: readNumber(r.data, "ts"),
|
|
161586
|
+
value: readNumber(r.data, "value")
|
|
161587
|
+
}));
|
|
161588
|
+
}
|
|
161589
|
+
/** The newest raw point strictly before `ts`, if any. */
|
|
161590
|
+
async rawBefore(seriesId, ts) {
|
|
161591
|
+
return (await this.readRaw(seriesId, 0, ts - 1, 1, "desc"))[0] ?? null;
|
|
161592
|
+
}
|
|
161593
|
+
async upsertRollups(tier, rows) {
|
|
161594
|
+
for (const batch of chunks(rows, WRITE_CHUNK)) await this.door.upsertMany.mutate({
|
|
161595
|
+
namespace: HISTORY_NAMESPACE,
|
|
161596
|
+
collection: rollupCollectionOf(tier),
|
|
161597
|
+
conflict: ["series_id", "bucket_start"],
|
|
161598
|
+
records: batch.map((r) => ({ data: rollupData(r) }))
|
|
161599
|
+
});
|
|
161600
|
+
}
|
|
161601
|
+
async readRollups(tier, seriesId, from, toInclusive, limit, direction = "asc") {
|
|
161602
|
+
return (await this.door.query.query({
|
|
161603
|
+
namespace: HISTORY_NAMESPACE,
|
|
161604
|
+
collection: rollupCollectionOf(tier),
|
|
161605
|
+
filter: {
|
|
161606
|
+
where: { series_id: seriesId },
|
|
161607
|
+
whereBetween: { bucket_start: [from, toInclusive] },
|
|
161608
|
+
orderBy: {
|
|
161609
|
+
field: "bucket_start",
|
|
161610
|
+
direction
|
|
161611
|
+
},
|
|
161612
|
+
limit
|
|
161613
|
+
}
|
|
161614
|
+
})).map((r) => parseRollup(r.data));
|
|
161615
|
+
}
|
|
161616
|
+
/** The newest rollup row of `tier` strictly before `bucketStart`, if any. */
|
|
161617
|
+
async rollupBefore(tier, seriesId, bucketStart) {
|
|
161618
|
+
return (await this.readRollups(tier, seriesId, 0, bucketStart - 1, 1, "desc"))[0] ?? null;
|
|
161619
|
+
}
|
|
161620
|
+
/** Delete one series' rows of a tier with the order column in `[lo, hiInclusive]`. */
|
|
161621
|
+
async deleteRange(tier, seriesId, lo, hiInclusive) {
|
|
161622
|
+
const { deleted } = await this.door.deleteWhere.mutate({
|
|
161623
|
+
namespace: HISTORY_NAMESPACE,
|
|
161624
|
+
collection: collectionOf(tier),
|
|
161625
|
+
filter: {
|
|
161626
|
+
where: { series_id: seriesId },
|
|
161627
|
+
whereBetween: { [orderColumnOf(tier)]: [lo, hiInclusive] }
|
|
161628
|
+
}
|
|
161629
|
+
});
|
|
161630
|
+
return deleted;
|
|
161631
|
+
}
|
|
161632
|
+
/** Delete every row of one series, in every tier, then the series row. */
|
|
161633
|
+
async deleteSeries(seriesId) {
|
|
161634
|
+
for (const tier of [
|
|
161635
|
+
"raw",
|
|
161636
|
+
"5m",
|
|
161637
|
+
"1h",
|
|
161638
|
+
"1d"
|
|
161639
|
+
]) await this.door.deleteWhere.mutate({
|
|
161640
|
+
namespace: HISTORY_NAMESPACE,
|
|
161641
|
+
collection: collectionOf(tier),
|
|
161642
|
+
filter: { where: { series_id: seriesId } }
|
|
161643
|
+
});
|
|
161644
|
+
await this.door.delete.mutate({
|
|
161645
|
+
namespace: HISTORY_NAMESPACE,
|
|
161646
|
+
collection: COLLECTIONS.series,
|
|
161647
|
+
key: String(seriesId)
|
|
161648
|
+
});
|
|
161649
|
+
}
|
|
161650
|
+
async countRows(tier) {
|
|
161651
|
+
return this.door.count.query({
|
|
161652
|
+
namespace: HISTORY_NAMESPACE,
|
|
161653
|
+
collection: collectionOf(tier)
|
|
161654
|
+
});
|
|
161655
|
+
}
|
|
161656
|
+
async loadOptOuts() {
|
|
161657
|
+
const rows = await this.door.query.query({
|
|
161658
|
+
namespace: HISTORY_NAMESPACE,
|
|
161659
|
+
collection: COLLECTIONS.optOuts,
|
|
161660
|
+
filter: { limit: 2e4 }
|
|
161661
|
+
});
|
|
161662
|
+
return new Set(rows.map((r) => readNumber(r.data, "device_id")));
|
|
161663
|
+
}
|
|
161664
|
+
async setOptOut(deviceId, at) {
|
|
161665
|
+
await this.door.upsertMany.mutate({
|
|
161666
|
+
namespace: HISTORY_NAMESPACE,
|
|
161667
|
+
collection: COLLECTIONS.optOuts,
|
|
161668
|
+
conflict: ["device_id"],
|
|
161669
|
+
records: [{ data: {
|
|
161670
|
+
device_id: deviceId,
|
|
161671
|
+
opted_out_at: at
|
|
161672
|
+
} }]
|
|
161673
|
+
});
|
|
161674
|
+
}
|
|
161675
|
+
async loadDepartures() {
|
|
161676
|
+
const rows = await this.door.query.query({
|
|
161677
|
+
namespace: HISTORY_NAMESPACE,
|
|
161678
|
+
collection: COLLECTIONS.departures,
|
|
161679
|
+
filter: { limit: 2e4 }
|
|
161680
|
+
});
|
|
161681
|
+
return new Map(rows.map((r) => [readNumber(r.data, "device_id"), readNumber(r.data, "missing_since")]));
|
|
161682
|
+
}
|
|
161683
|
+
async markMissing(deviceIds, at) {
|
|
161684
|
+
for (const batch of chunks(deviceIds, WRITE_CHUNK)) await this.door.upsertMany.mutate({
|
|
161685
|
+
namespace: HISTORY_NAMESPACE,
|
|
161686
|
+
collection: COLLECTIONS.departures,
|
|
161687
|
+
conflict: ["device_id"],
|
|
161688
|
+
records: batch.map((id) => ({ data: {
|
|
161689
|
+
device_id: id,
|
|
161690
|
+
missing_since: at
|
|
161691
|
+
} }))
|
|
161692
|
+
});
|
|
161693
|
+
}
|
|
161694
|
+
async clearMissing(deviceIds) {
|
|
161695
|
+
for (const batch of chunks(deviceIds, WRITE_CHUNK)) await this.door.deleteWhere.mutate({
|
|
161696
|
+
namespace: HISTORY_NAMESPACE,
|
|
161697
|
+
collection: COLLECTIONS.departures,
|
|
161698
|
+
filter: { whereIn: { device_id: [...batch] } }
|
|
161699
|
+
});
|
|
161700
|
+
}
|
|
161701
|
+
async clearOptOut(deviceId) {
|
|
161702
|
+
await this.door.deleteWhere.mutate({
|
|
161703
|
+
namespace: HISTORY_NAMESPACE,
|
|
161704
|
+
collection: COLLECTIONS.optOuts,
|
|
161705
|
+
filter: { where: { device_id: deviceId } }
|
|
161706
|
+
});
|
|
161707
|
+
}
|
|
161708
|
+
};
|
|
161709
|
+
function rawToFiveMinuteRows(input) {
|
|
161710
|
+
const rows = [];
|
|
161711
|
+
const { points, seriesId, horizon } = input;
|
|
161712
|
+
let held = input.carryIn === null ? null : input.carryIn.value;
|
|
161713
|
+
let i = 0;
|
|
161714
|
+
while (i < points.length) {
|
|
161715
|
+
const first = points[i];
|
|
161716
|
+
if (first === void 0) break;
|
|
161717
|
+
const bucketStart = floorTo(first.ts, FIVE_MINUTES_MS);
|
|
161718
|
+
const bucketEnd = bucketStart + FIVE_MINUTES_MS;
|
|
161719
|
+
const coverageEnd = Math.min(bucketEnd, Math.max(horizon, bucketStart));
|
|
161720
|
+
let samples = 0;
|
|
161721
|
+
let sum = 0;
|
|
161722
|
+
let min = Number.POSITIVE_INFINITY;
|
|
161723
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
161724
|
+
let twSum = 0;
|
|
161725
|
+
let twMs = 0;
|
|
161726
|
+
let cursor = bucketStart;
|
|
161727
|
+
let value = held;
|
|
161728
|
+
if (value !== null) {
|
|
161729
|
+
min = value;
|
|
161730
|
+
max = value;
|
|
161731
|
+
}
|
|
161732
|
+
let last = first;
|
|
161733
|
+
while (i < points.length) {
|
|
161734
|
+
const p = points[i];
|
|
161735
|
+
if (p === void 0 || p.ts >= bucketEnd) break;
|
|
161736
|
+
if (value !== null) {
|
|
161737
|
+
const dt = p.ts - cursor;
|
|
161738
|
+
twSum += value * dt;
|
|
161739
|
+
twMs += dt;
|
|
161740
|
+
}
|
|
161741
|
+
cursor = p.ts;
|
|
161742
|
+
value = p.value;
|
|
161743
|
+
samples += 1;
|
|
161744
|
+
sum += p.value;
|
|
161745
|
+
if (p.value < min) min = p.value;
|
|
161746
|
+
if (p.value > max) max = p.value;
|
|
161747
|
+
last = p;
|
|
161748
|
+
i += 1;
|
|
161749
|
+
}
|
|
161750
|
+
if (value !== null && coverageEnd > cursor) {
|
|
161751
|
+
twSum += value * (coverageEnd - cursor);
|
|
161752
|
+
twMs += coverageEnd - cursor;
|
|
161753
|
+
}
|
|
161754
|
+
held = last.value;
|
|
161755
|
+
rows.push({
|
|
161756
|
+
seriesId,
|
|
161757
|
+
bucketStart,
|
|
161758
|
+
samples,
|
|
161759
|
+
sum,
|
|
161760
|
+
min,
|
|
161761
|
+
max,
|
|
161762
|
+
last: last.value,
|
|
161763
|
+
lastTs: last.ts,
|
|
161764
|
+
twSum,
|
|
161765
|
+
twMs
|
|
161766
|
+
});
|
|
161767
|
+
}
|
|
161768
|
+
return rows;
|
|
161769
|
+
}
|
|
161770
|
+
function combineRows(input) {
|
|
161771
|
+
const { rows } = input;
|
|
161772
|
+
const lastRow = rows[rows.length - 1];
|
|
161773
|
+
if (lastRow === void 0) return null;
|
|
161774
|
+
const hasTw = rows.some((r) => r.twSum !== null && r.twMs !== null);
|
|
161775
|
+
let samples = 0;
|
|
161776
|
+
let sum = 0;
|
|
161777
|
+
let min = Number.POSITIVE_INFINITY;
|
|
161778
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
161779
|
+
let twSum = 0;
|
|
161780
|
+
let twMs = 0;
|
|
161781
|
+
let held = input.carryIn;
|
|
161782
|
+
let cursor = input.windowStart;
|
|
161783
|
+
for (const row of rows) {
|
|
161784
|
+
samples += row.samples;
|
|
161785
|
+
sum += row.sum;
|
|
161786
|
+
if (row.min < min) min = row.min;
|
|
161787
|
+
if (row.max > max) max = row.max;
|
|
161788
|
+
const gap = row.bucketStart - cursor;
|
|
161789
|
+
if (hasTw && gap > 0 && held !== null) {
|
|
161790
|
+
twSum += held * gap;
|
|
161791
|
+
twMs += gap;
|
|
161792
|
+
if (held < min) min = held;
|
|
161793
|
+
if (held > max) max = held;
|
|
161794
|
+
}
|
|
161795
|
+
if (row.twSum !== null && row.twMs !== null) {
|
|
161796
|
+
twSum += row.twSum;
|
|
161797
|
+
twMs += row.twMs;
|
|
161798
|
+
}
|
|
161799
|
+
cursor = row.bucketStart + input.childWidthMs;
|
|
161800
|
+
held = row.last;
|
|
161801
|
+
}
|
|
161802
|
+
const coverageEnd = Math.min(input.windowEnd, input.horizon);
|
|
161803
|
+
if (hasTw && held !== null && coverageEnd > cursor) {
|
|
161804
|
+
twSum += held * (coverageEnd - cursor);
|
|
161805
|
+
twMs += coverageEnd - cursor;
|
|
161806
|
+
}
|
|
161807
|
+
return {
|
|
161808
|
+
seriesId: input.seriesId,
|
|
161809
|
+
bucketStart: input.windowStart,
|
|
161810
|
+
samples,
|
|
161811
|
+
sum,
|
|
161812
|
+
min,
|
|
161813
|
+
max,
|
|
161814
|
+
last: lastRow.last,
|
|
161815
|
+
lastTs: lastRow.lastTs,
|
|
161816
|
+
twSum: hasTw ? twSum : null,
|
|
161817
|
+
twMs: hasTw ? twMs : null
|
|
161818
|
+
};
|
|
161819
|
+
}
|
|
161820
|
+
var BOOT_WINDOW_MS = 3 * HOUR_MS;
|
|
161821
|
+
var markVersion = 0;
|
|
161822
|
+
function mark(sets, seriesId, bucketStart) {
|
|
161823
|
+
const map = sets.get(seriesId) ?? /* @__PURE__ */ new Map();
|
|
161824
|
+
markVersion += 1;
|
|
161825
|
+
map.set(bucketStart, markVersion);
|
|
161826
|
+
sets.set(seriesId, map);
|
|
161827
|
+
}
|
|
161828
|
+
function settle(sets, seriesId, handled) {
|
|
161829
|
+
const map = sets.get(seriesId);
|
|
161830
|
+
if (map === void 0) return;
|
|
161831
|
+
for (const { bucket, version } of handled) if (map.get(bucket) === version) map.delete(bucket);
|
|
161832
|
+
if (map.size === 0) sets.delete(seriesId);
|
|
161833
|
+
}
|
|
161834
|
+
var MAX_RANGE_GAP_MS = HOUR_MS;
|
|
161835
|
+
function clusterRanges(sorted) {
|
|
161836
|
+
const ranges = [];
|
|
161837
|
+
for (const start of sorted) {
|
|
161838
|
+
const current = ranges[ranges.length - 1];
|
|
161839
|
+
if (current !== void 0 && start - current[1] <= MAX_RANGE_GAP_MS) current[1] = start + FIVE_MINUTES_MS;
|
|
161840
|
+
else ranges.push([start, start + FIVE_MINUTES_MS]);
|
|
161841
|
+
}
|
|
161842
|
+
return ranges;
|
|
161843
|
+
}
|
|
161844
|
+
var RollupEngine = class {
|
|
161845
|
+
deps;
|
|
161846
|
+
dirty5m = /* @__PURE__ */ new Map();
|
|
161847
|
+
dirty1h = /* @__PURE__ */ new Map();
|
|
161848
|
+
dirty1d = /* @__PURE__ */ new Map();
|
|
161849
|
+
ticking = null;
|
|
161850
|
+
constructor(deps) {
|
|
161851
|
+
this.deps = deps;
|
|
161852
|
+
}
|
|
161853
|
+
dirtyCounts() {
|
|
161854
|
+
const count = (sets) => [...sets.values()].reduce((n, set) => n + set.size, 0);
|
|
161855
|
+
return {
|
|
161856
|
+
m5: count(this.dirty5m),
|
|
161857
|
+
h1: count(this.dirty1h),
|
|
161858
|
+
d1: count(this.dirty1d)
|
|
161859
|
+
};
|
|
161860
|
+
}
|
|
161861
|
+
/** Raw points landed: their 5-minute buckets (and a late bucket's successor) are dirty. */
|
|
161862
|
+
markPoints(written) {
|
|
161863
|
+
const now = this.deps.clock();
|
|
161864
|
+
for (const { seriesId, timestamps } of written) for (const ts of timestamps) {
|
|
161865
|
+
const bucket = floorTo(ts, FIVE_MINUTES_MS);
|
|
161866
|
+
mark(this.dirty5m, seriesId, bucket);
|
|
161867
|
+
if (bucket + 3e5 + ROLLUP_GRACE_MS["5m"] <= now) mark(this.dirty5m, seriesId, bucket + FIVE_MINUTES_MS);
|
|
161868
|
+
}
|
|
161869
|
+
}
|
|
161870
|
+
/** A 5m row landed (computed, or a producer bucket): its hour is dirty. */
|
|
161871
|
+
markFiveMinuteRow(seriesId, bucketStart) {
|
|
161872
|
+
mark(this.dirty1h, seriesId, bucketStartOf("1h", bucketStart));
|
|
161873
|
+
}
|
|
161874
|
+
/** One pass over the three tiers. Single-flight; errors are counted and the work stays dirty. */
|
|
161875
|
+
tick() {
|
|
161876
|
+
if (this.ticking !== null) return this.ticking;
|
|
161877
|
+
const run = this.doTick().finally(() => {
|
|
161878
|
+
this.ticking = null;
|
|
161879
|
+
});
|
|
161880
|
+
this.ticking = run;
|
|
161881
|
+
return run;
|
|
161882
|
+
}
|
|
161883
|
+
async doTick() {
|
|
161884
|
+
const now = this.deps.clock();
|
|
161885
|
+
await this.guarded("5m", () => this.tickFiveMinute(now));
|
|
161886
|
+
await this.guarded("1h", () => this.tickHour(now));
|
|
161887
|
+
await this.guarded("1d", () => this.tickDay(now));
|
|
161888
|
+
}
|
|
161889
|
+
async guarded(tier, run) {
|
|
161890
|
+
try {
|
|
161891
|
+
await run();
|
|
161892
|
+
} catch (err) {
|
|
161893
|
+
this.deps.counters.rollupErrors += 1;
|
|
161894
|
+
this.deps.logger.warn(`device-history: ${tier} rollup pass failed \u2014 work stays dirty`, { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
161895
|
+
}
|
|
161896
|
+
}
|
|
161897
|
+
closedBuckets(map, tier, now) {
|
|
161898
|
+
return [...map].filter(([start]) => bucketEndOf(tier, start) + ROLLUP_GRACE_MS[tier] <= now).map(([bucket, version]) => ({
|
|
161899
|
+
bucket,
|
|
161900
|
+
version
|
|
161901
|
+
})).toSorted((a, b) => a.bucket - b.bucket);
|
|
161902
|
+
}
|
|
161903
|
+
/** Forget everything held for a series that was purged. */
|
|
161904
|
+
forgetSeries(seriesId) {
|
|
161905
|
+
this.dirty5m.delete(seriesId);
|
|
161906
|
+
this.dirty1h.delete(seriesId);
|
|
161907
|
+
this.dirty1d.delete(seriesId);
|
|
161908
|
+
this.deps.counters.forgetSeries(seriesId);
|
|
161909
|
+
}
|
|
161910
|
+
async tickFiveMinute(now) {
|
|
161911
|
+
let handled = 0;
|
|
161912
|
+
for (const [seriesId, set] of this.dirty5m) {
|
|
161913
|
+
if (handled >= 400) break;
|
|
161914
|
+
const closed = this.closedBuckets(set, "5m", now);
|
|
161915
|
+
if (closed.length === 0) continue;
|
|
161916
|
+
handled += 1;
|
|
161917
|
+
for (const [from, to] of clusterRanges(closed.map((c) => c.bucket))) await this.recomputeFiveMinute(seriesId, from, to, now);
|
|
161918
|
+
settle(this.dirty5m, seriesId, closed);
|
|
161919
|
+
}
|
|
161920
|
+
if (handled > 0) this.deps.counters.last5mAt = now;
|
|
161921
|
+
}
|
|
161922
|
+
async tickHour(now) {
|
|
161923
|
+
let handled = 0;
|
|
161924
|
+
for (const [seriesId, set] of this.dirty1h) {
|
|
161925
|
+
if (handled >= 400) break;
|
|
161926
|
+
const closed = this.closedBuckets(set, "1h", now);
|
|
161927
|
+
if (closed.length === 0) continue;
|
|
161928
|
+
handled += 1;
|
|
161929
|
+
for (const hour of closed) {
|
|
161930
|
+
await this.recomputeHour(seriesId, hour.bucket, now);
|
|
161931
|
+
settle(this.dirty1h, seriesId, [hour]);
|
|
161932
|
+
}
|
|
161933
|
+
}
|
|
161934
|
+
if (handled > 0) this.deps.counters.last1hAt = now;
|
|
161935
|
+
}
|
|
161936
|
+
async tickDay(now) {
|
|
161937
|
+
let handled = 0;
|
|
161938
|
+
for (const [seriesId, set] of this.dirty1d) {
|
|
161939
|
+
if (handled >= 400) break;
|
|
161940
|
+
const closed = this.closedBuckets(set, "1d", now);
|
|
161941
|
+
if (closed.length === 0) continue;
|
|
161942
|
+
handled += 1;
|
|
161943
|
+
for (const day of closed) {
|
|
161944
|
+
await this.recomputeDay(seriesId, day.bucket, now);
|
|
161945
|
+
settle(this.dirty1d, seriesId, [day]);
|
|
161946
|
+
}
|
|
161947
|
+
}
|
|
161948
|
+
if (handled > 0) this.deps.counters.last1dAt = now;
|
|
161949
|
+
}
|
|
161950
|
+
/** Raw → 5m rows for buckets in `[from, toExclusive)`; writes them and marks their hours. */
|
|
161951
|
+
async recomputeFiveMinute(seriesId, from, toExclusive, horizon) {
|
|
161952
|
+
const { store } = this.deps;
|
|
161953
|
+
const points = [];
|
|
161954
|
+
let cursor = from;
|
|
161955
|
+
for (; ; ) {
|
|
161956
|
+
const page = await store.readRaw(seriesId, cursor, toExclusive - 1, READ_PAGE);
|
|
161957
|
+
points.push(...page);
|
|
161958
|
+
const last = page[page.length - 1];
|
|
161959
|
+
if (last === void 0 || page.length < 5e3) break;
|
|
161960
|
+
cursor = last.ts + 1;
|
|
161961
|
+
}
|
|
161962
|
+
const rows = rawToFiveMinuteRows({
|
|
161963
|
+
seriesId,
|
|
161964
|
+
points,
|
|
161965
|
+
carryIn: await store.rawBefore(seriesId, from),
|
|
161966
|
+
horizon
|
|
161967
|
+
});
|
|
161968
|
+
if (rows.length === 0) return 0;
|
|
161969
|
+
await store.upsertRollups("5m", rows);
|
|
161970
|
+
this.deps.counters.noteRollupRows("5m", seriesId, rows.map((r) => r.bucketStart));
|
|
161971
|
+
for (const row of rows) this.markFiveMinuteRow(seriesId, row.bucketStart);
|
|
161972
|
+
return rows.length;
|
|
161973
|
+
}
|
|
161974
|
+
async recomputeHour(seriesId, hour, now) {
|
|
161975
|
+
await this.recomputeFrom("5m", "1h", FIVE_MINUTES_MS, seriesId, hour, now);
|
|
161976
|
+
}
|
|
161977
|
+
async recomputeDay(seriesId, day, now) {
|
|
161978
|
+
await this.recomputeFrom("1h", "1d", HOUR_MS, seriesId, day, now);
|
|
161979
|
+
}
|
|
161980
|
+
async recomputeFrom(child, parent, childWidthMs, seriesId, windowStart, now) {
|
|
161981
|
+
const { store } = this.deps;
|
|
161982
|
+
const windowEnd = bucketEndOf(parent, windowStart);
|
|
161983
|
+
const combined = combineRows({
|
|
161984
|
+
seriesId,
|
|
161985
|
+
windowStart,
|
|
161986
|
+
windowEnd,
|
|
161987
|
+
childWidthMs,
|
|
161988
|
+
rows: await store.readRollups(child, seriesId, windowStart, windowEnd - 1, READ_PAGE),
|
|
161989
|
+
carryIn: (await store.rollupBefore(child, seriesId, windowStart))?.last ?? null,
|
|
161990
|
+
horizon: now
|
|
161991
|
+
});
|
|
161992
|
+
if (combined === null) return;
|
|
161993
|
+
await store.upsertRollups(parent, [combined]);
|
|
161994
|
+
this.deps.counters.noteRollupRows(parent, seriesId, [combined.bucketStart]);
|
|
161995
|
+
if (parent === "1h") mark(this.dirty1d, seriesId, bucketStartOf("1d", windowStart));
|
|
161996
|
+
}
|
|
161997
|
+
/**
|
|
161998
|
+
* After a restart the dirty sets are gone. Rebuild the recent window from raw
|
|
161999
|
+
* (the still-open bucket included, time-weighted up to `now`), then mark the
|
|
162000
|
+
* 1h / 1d buckets of that window dirty so the next ticks recompute them.
|
|
162001
|
+
*/
|
|
162002
|
+
async bootRecompute(now) {
|
|
162003
|
+
const windowStart = floorTo(now - BOOT_WINDOW_MS, FIVE_MINUTES_MS);
|
|
162004
|
+
const openBucket = floorTo(now, FIVE_MINUTES_MS);
|
|
162005
|
+
let series = 0;
|
|
162006
|
+
for (const s of this.deps.registry.all()) {
|
|
162007
|
+
if (s.lastTs < windowStart) continue;
|
|
162008
|
+
const metric = this.deps.registry.metric(s.key);
|
|
162009
|
+
series += 1;
|
|
162010
|
+
if (metric?.ingest === "points") {
|
|
162011
|
+
await this.recomputeFiveMinute(s.id, windowStart, openBucket + FIVE_MINUTES_MS, now);
|
|
162012
|
+
mark(this.dirty5m, s.id, openBucket);
|
|
162013
|
+
mark(this.dirty5m, s.id, openBucket - FIVE_MINUTES_MS);
|
|
162014
|
+
}
|
|
162015
|
+
for (let hour = floorTo(windowStart, HOUR_MS); hour <= now; hour += HOUR_MS) mark(this.dirty1h, s.id, hour);
|
|
162016
|
+
mark(this.dirty1d, s.id, bucketStartOf("1d", windowStart));
|
|
162017
|
+
mark(this.dirty1d, s.id, bucketStartOf("1d", now));
|
|
162018
|
+
}
|
|
162019
|
+
return series;
|
|
162020
|
+
}
|
|
162021
|
+
};
|
|
162022
|
+
function deltas(now, before) {
|
|
162023
|
+
const out = {};
|
|
162024
|
+
for (const [k, v] of now) {
|
|
162025
|
+
const d = v - (before.get(k) ?? 0);
|
|
162026
|
+
if (d > 0) out[k] = d;
|
|
162027
|
+
}
|
|
162028
|
+
return out;
|
|
162029
|
+
}
|
|
162030
|
+
var HistoryCounters = class {
|
|
162031
|
+
eventsSeen = 0;
|
|
162032
|
+
pointsEmitted = 0;
|
|
162033
|
+
pointsWritten = 0;
|
|
162034
|
+
pointsDropped = 0;
|
|
162035
|
+
trailingEmitted = 0;
|
|
162036
|
+
reconciledPoints = 0;
|
|
162037
|
+
reconcileRuns = 0;
|
|
162038
|
+
reconcileFailures = 0;
|
|
162039
|
+
lastReconcileAt = null;
|
|
162040
|
+
lastFlushAt = null;
|
|
162041
|
+
flushFailures = 0;
|
|
162042
|
+
ingestAccepted = 0;
|
|
162043
|
+
rollupErrors = 0;
|
|
162044
|
+
last5mAt = null;
|
|
162045
|
+
last1hAt = null;
|
|
162046
|
+
last1dAt = null;
|
|
162047
|
+
retentionLastRunAt = null;
|
|
162048
|
+
purgedSeries = 0;
|
|
162049
|
+
directoryRefreshAt = null;
|
|
162050
|
+
skipped = /* @__PURE__ */ new Map();
|
|
162051
|
+
refused = /* @__PURE__ */ new Map();
|
|
162052
|
+
deleted = /* @__PURE__ */ new Map();
|
|
162053
|
+
reported = {
|
|
162054
|
+
skipped: /* @__PURE__ */ new Map(),
|
|
162055
|
+
refused: /* @__PURE__ */ new Map(),
|
|
162056
|
+
deleted: /* @__PURE__ */ new Map(),
|
|
162057
|
+
flushFailures: 0
|
|
162058
|
+
};
|
|
162059
|
+
/** Net rows written minus deleted per tier since boot — the estimate behind `getStatus` row counts. */
|
|
162060
|
+
rowDelta = /* @__PURE__ */ new Map();
|
|
162061
|
+
/** Newest bucket seen per (tier, series): a row beyond it is NEW, a rewrite of an older one is not. */
|
|
162062
|
+
newestBucket = /* @__PURE__ */ new Map();
|
|
162063
|
+
skip(reason, count = 1) {
|
|
162064
|
+
this.skipped.set(reason, (this.skipped.get(reason) ?? 0) + count);
|
|
162065
|
+
}
|
|
162066
|
+
refuse(reason, count = 1) {
|
|
162067
|
+
this.refused.set(reason, (this.refused.get(reason) ?? 0) + count);
|
|
162068
|
+
}
|
|
162069
|
+
addDeleted(tier, count) {
|
|
162070
|
+
if (count <= 0) return;
|
|
162071
|
+
this.deleted.set(tier, (this.deleted.get(tier) ?? 0) + count);
|
|
162072
|
+
if (tier === "raw" || tier === "5m" || tier === "1h" || tier === "1d") this.rowDelta.set(tier, (this.rowDelta.get(tier) ?? 0) - count);
|
|
162073
|
+
}
|
|
162074
|
+
addRawRows(count) {
|
|
162075
|
+
this.rowDelta.set("raw", (this.rowDelta.get("raw") ?? 0) + count);
|
|
162076
|
+
}
|
|
162077
|
+
/** Rollup rows written: only a bucket newer than the series' newest known is counted as a new row. */
|
|
162078
|
+
noteRollupRows(tier, seriesId, bucketStarts) {
|
|
162079
|
+
const marker = `${tier}:${seriesId}`;
|
|
162080
|
+
let newest = this.newestBucket.get(marker) ?? Number.NEGATIVE_INFINITY;
|
|
162081
|
+
let added = 0;
|
|
162082
|
+
for (const b of bucketStarts) if (b > newest) {
|
|
162083
|
+
added += 1;
|
|
162084
|
+
newest = b;
|
|
162085
|
+
}
|
|
162086
|
+
this.newestBucket.set(marker, newest);
|
|
162087
|
+
if (added > 0) this.rowDelta.set(tier, (this.rowDelta.get(tier) ?? 0) + added);
|
|
162088
|
+
}
|
|
162089
|
+
rowDeltaOf(tier) {
|
|
162090
|
+
return this.rowDelta.get(tier) ?? 0;
|
|
162091
|
+
}
|
|
162092
|
+
forgetSeries(seriesId) {
|
|
162093
|
+
for (const tier of [
|
|
162094
|
+
"5m",
|
|
162095
|
+
"1h",
|
|
162096
|
+
"1d"
|
|
162097
|
+
]) this.newestBucket.delete(`${tier}:${seriesId}`);
|
|
162098
|
+
}
|
|
162099
|
+
skippedRecord() {
|
|
162100
|
+
return Object.fromEntries(this.skipped);
|
|
162101
|
+
}
|
|
162102
|
+
refusedRecord() {
|
|
162103
|
+
return Object.fromEntries(this.refused);
|
|
162104
|
+
}
|
|
162105
|
+
deletedRecord() {
|
|
162106
|
+
return Object.fromEntries(this.deleted);
|
|
162107
|
+
}
|
|
162108
|
+
/** What was skipped, refused, deleted or failed since the last call; `null` when nothing. */
|
|
162109
|
+
summarySinceLast() {
|
|
162110
|
+
const summary = {
|
|
162111
|
+
skipped: deltas(this.skipped, this.reported.skipped),
|
|
162112
|
+
refused: deltas(this.refused, this.reported.refused),
|
|
162113
|
+
deleted: deltas(this.deleted, this.reported.deleted),
|
|
162114
|
+
flushFailures: this.flushFailures - this.reported.flushFailures
|
|
162115
|
+
};
|
|
162116
|
+
this.reported = {
|
|
162117
|
+
skipped: new Map(this.skipped),
|
|
162118
|
+
refused: new Map(this.refused),
|
|
162119
|
+
deleted: new Map(this.deleted),
|
|
162120
|
+
flushFailures: this.flushFailures
|
|
162121
|
+
};
|
|
162122
|
+
return Object.keys(summary.skipped).length === 0 && Object.keys(summary.refused).length === 0 && Object.keys(summary.deleted).length === 0 && summary.flushFailures === 0 ? null : summary;
|
|
162123
|
+
}
|
|
162124
|
+
};
|
|
162125
|
+
var ROLLUP_INTERVAL_MS = 6e4;
|
|
162126
|
+
var RECONCILE_INTERVAL_MS = 5 * 6e4;
|
|
162127
|
+
var RETENTION_INTERVAL_MS = 10 * 6e4;
|
|
162128
|
+
var FIRST_COUNT_DELAY_MS = 9e4;
|
|
162129
|
+
var APPROX_BYTES_PER_ROW = {
|
|
162130
|
+
raw: 24,
|
|
162131
|
+
"5m": 72,
|
|
162132
|
+
"1h": 72,
|
|
162133
|
+
"1d": 72
|
|
162134
|
+
};
|
|
162135
|
+
var HistoryRuntime = class {
|
|
162136
|
+
deps;
|
|
162137
|
+
counters = new HistoryCounters();
|
|
162138
|
+
store;
|
|
162139
|
+
registry;
|
|
162140
|
+
directory;
|
|
162141
|
+
rollup;
|
|
162142
|
+
writer;
|
|
162143
|
+
capture;
|
|
162144
|
+
ingest;
|
|
162145
|
+
query;
|
|
162146
|
+
retentionJob;
|
|
162147
|
+
departures;
|
|
162148
|
+
optedOutDevices = /* @__PURE__ */ new Set();
|
|
162149
|
+
tierCounts = /* @__PURE__ */ new Map();
|
|
162150
|
+
startedAt;
|
|
162151
|
+
timers = [];
|
|
162152
|
+
countTimeout = null;
|
|
162153
|
+
ready = false;
|
|
162154
|
+
constructor(deps) {
|
|
162155
|
+
this.deps = deps;
|
|
162156
|
+
const { logger, clock, catalog } = deps;
|
|
162157
|
+
this.startedAt = clock();
|
|
162158
|
+
this.store = new HistoryStore(deps.door);
|
|
162159
|
+
this.registry = new SeriesRegistry(this.store);
|
|
162160
|
+
this.directory = new DeviceDirectory(deps.deviceSource, this.counters, logger, clock);
|
|
162161
|
+
this.rollup = new RollupEngine({
|
|
162162
|
+
store: this.store,
|
|
162163
|
+
registry: this.registry,
|
|
162164
|
+
counters: this.counters,
|
|
162165
|
+
logger,
|
|
162166
|
+
clock
|
|
162167
|
+
});
|
|
162168
|
+
this.writer = new PointWriter({
|
|
162169
|
+
registry: this.registry,
|
|
162170
|
+
store: this.store,
|
|
162171
|
+
counters: this.counters,
|
|
162172
|
+
logger,
|
|
162173
|
+
clock,
|
|
162174
|
+
onWritten: (written) => this.rollup.markPoints(written),
|
|
162175
|
+
onDropped: (dropped) => this.capture.forget(dropped)
|
|
162176
|
+
});
|
|
162177
|
+
const optedOut = () => this.optedOutDevices;
|
|
162178
|
+
this.capture = new CaptureEngine({
|
|
162179
|
+
catalog,
|
|
162180
|
+
registry: this.registry,
|
|
162181
|
+
store: this.store,
|
|
162182
|
+
directory: this.directory,
|
|
162183
|
+
writer: this.writer,
|
|
162184
|
+
optedOut,
|
|
162185
|
+
counters: this.counters,
|
|
162186
|
+
logger,
|
|
162187
|
+
clock
|
|
162188
|
+
});
|
|
162189
|
+
this.ingest = new ProducerIngest({
|
|
162190
|
+
registry: this.registry,
|
|
162191
|
+
store: this.store,
|
|
162192
|
+
directory: this.directory,
|
|
162193
|
+
writer: this.writer,
|
|
162194
|
+
counters: this.counters,
|
|
162195
|
+
logger,
|
|
162196
|
+
clock,
|
|
162197
|
+
optedOut,
|
|
162198
|
+
retention: () => {
|
|
162199
|
+
const policy = deps.retention();
|
|
162200
|
+
return {
|
|
162201
|
+
rawMs: policy.rawDays * DAY_MS,
|
|
162202
|
+
fiveMinuteMs: policy.fiveMinuteDays * DAY_MS
|
|
162203
|
+
};
|
|
162204
|
+
},
|
|
162205
|
+
onBucketWritten: (seriesId, bucketStart) => this.rollup.markFiveMinuteRow(seriesId, bucketStart)
|
|
162206
|
+
});
|
|
162207
|
+
this.query = new HistoryQuery({
|
|
162208
|
+
registry: this.registry,
|
|
162209
|
+
store: this.store,
|
|
162210
|
+
clock,
|
|
162211
|
+
retention: deps.retention,
|
|
162212
|
+
catalog,
|
|
162213
|
+
readCapSlice: deps.readCapSlice
|
|
162214
|
+
});
|
|
162215
|
+
this.departures = new DepartureTracker(this.store, this.registry, logger);
|
|
162216
|
+
this.registry.onRemove((series) => {
|
|
162217
|
+
this.capture.forgetSeries(series);
|
|
162218
|
+
this.ingest.forgetSeries(series);
|
|
162219
|
+
this.rollup.forgetSeries(series.id);
|
|
162220
|
+
this.retentionJob.forgetSeries(series.id);
|
|
162221
|
+
});
|
|
162222
|
+
this.retentionJob = new RetentionJob({
|
|
162223
|
+
store: this.store,
|
|
162224
|
+
registry: this.registry,
|
|
162225
|
+
departures: this.departures,
|
|
162226
|
+
counters: this.counters,
|
|
162227
|
+
logger
|
|
162228
|
+
});
|
|
162229
|
+
}
|
|
162230
|
+
async prepare() {
|
|
162231
|
+
await this.store.declare();
|
|
162232
|
+
await this.registry.load();
|
|
162233
|
+
for (const metric of catalogMetrics(this.deps.catalog, this.deps.clock())) await this.registry.declareMetric(metric);
|
|
162234
|
+
this.optedOutDevices = await this.store.loadOptOuts();
|
|
162235
|
+
await this.departures.load();
|
|
162236
|
+
await this.directory.refresh();
|
|
162237
|
+
await this.capture.seed();
|
|
162238
|
+
}
|
|
162239
|
+
async finishBoot() {
|
|
162240
|
+
const series = await this.rollup.bootRecompute(this.deps.clock());
|
|
162241
|
+
this.deps.logger.info("device-history: recent rollup window rebuilt", { meta: { series } });
|
|
162242
|
+
await this.reconcile();
|
|
162243
|
+
}
|
|
162244
|
+
startTimers() {
|
|
162245
|
+
const every = (ms, run) => {
|
|
162246
|
+
const timer = setInterval(() => {
|
|
162247
|
+
run().catch((err) => {
|
|
162248
|
+
this.deps.logger.warn("device-history: scheduled pass threw", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
162249
|
+
});
|
|
162250
|
+
}, ms);
|
|
162251
|
+
timer.unref();
|
|
162252
|
+
this.timers.push(timer);
|
|
162253
|
+
};
|
|
162254
|
+
every(FLUSH_INTERVAL_MS, () => this.flushNow());
|
|
162255
|
+
every(ROLLUP_INTERVAL_MS, async () => {
|
|
162256
|
+
await this.rollup.tick();
|
|
162257
|
+
this.reportWindow();
|
|
162258
|
+
});
|
|
162259
|
+
every(RECONCILE_INTERVAL_MS, async () => {
|
|
162260
|
+
await this.reconcile();
|
|
162261
|
+
this.reportWindow();
|
|
162262
|
+
});
|
|
162263
|
+
every(RETENTION_INTERVAL_MS, async () => {
|
|
162264
|
+
await this.retentionJob.run(this.deps.retention(), this.deps.clock());
|
|
162265
|
+
this.reportWindow();
|
|
162266
|
+
});
|
|
162267
|
+
this.countTimeout = setTimeout(() => {
|
|
162268
|
+
this.refreshCounts().catch(() => void 0);
|
|
162269
|
+
}, FIRST_COUNT_DELAY_MS);
|
|
162270
|
+
this.countTimeout.unref();
|
|
162271
|
+
}
|
|
162272
|
+
async stop() {
|
|
162273
|
+
for (const t of this.timers) clearInterval(t);
|
|
162274
|
+
this.timers = [];
|
|
162275
|
+
if (this.countTimeout !== null) clearTimeout(this.countTimeout);
|
|
162276
|
+
this.countTimeout = null;
|
|
162277
|
+
await this.writer.flush();
|
|
162278
|
+
}
|
|
162279
|
+
/** Emit pending trailing points, then write the buffer. */
|
|
162280
|
+
async flushNow() {
|
|
162281
|
+
this.capture.tickPending(this.deps.clock());
|
|
162282
|
+
await this.writer.flush();
|
|
162283
|
+
}
|
|
162284
|
+
/**
|
|
162285
|
+
* Restore changes a dropped event missed. A FAILED read changes nothing (D49):
|
|
162286
|
+
* it is counted and logged, and the next pass tries again.
|
|
162287
|
+
*/
|
|
162288
|
+
async reconcile() {
|
|
162289
|
+
const { logger, clock } = this.deps;
|
|
162290
|
+
if (await this.directory.refresh()) await this.departures.observe(this.directory.listedIds(), clock());
|
|
162291
|
+
let snapshots;
|
|
162292
|
+
try {
|
|
162293
|
+
snapshots = await this.deps.readSnapshots();
|
|
162294
|
+
} catch (err) {
|
|
162295
|
+
this.counters.reconcileFailures += 1;
|
|
162296
|
+
logger.warn("device-history: reconcile read failed \u2014 nothing changed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
162297
|
+
return;
|
|
162298
|
+
}
|
|
162299
|
+
const now = clock();
|
|
162300
|
+
this.capture.reconcile(snapshots, now);
|
|
162301
|
+
this.counters.reconcileRuns += 1;
|
|
162302
|
+
this.counters.lastReconcileAt = now;
|
|
162303
|
+
this.ready = true;
|
|
162304
|
+
}
|
|
162305
|
+
async refreshCounts() {
|
|
162306
|
+
for (const tier of [
|
|
162307
|
+
"raw",
|
|
162308
|
+
"5m",
|
|
162309
|
+
"1h",
|
|
162310
|
+
"1d"
|
|
162311
|
+
]) {
|
|
162312
|
+
const deltaAtCount = this.counters.rowDeltaOf(tier);
|
|
162313
|
+
const rows = await this.store.countRows(tier);
|
|
162314
|
+
this.tierCounts.set(tier, {
|
|
162315
|
+
rows,
|
|
162316
|
+
countedAt: this.deps.clock(),
|
|
162317
|
+
deltaAtCount
|
|
162318
|
+
});
|
|
162319
|
+
}
|
|
162320
|
+
}
|
|
162321
|
+
/**
|
|
162322
|
+
* ONE aggregated INFO naming what was skipped, refused, deleted or failed since the
|
|
162323
|
+
* previous report (D391) — the counts a per-event line would drown. Silent when
|
|
162324
|
+
* nothing happened.
|
|
162325
|
+
*/
|
|
162326
|
+
reportWindow() {
|
|
162327
|
+
const summary = this.counters.summarySinceLast();
|
|
162328
|
+
if (summary === null) return;
|
|
162329
|
+
this.deps.logger.info("device-history: since the last report", { meta: { ...summary } });
|
|
162330
|
+
}
|
|
162331
|
+
isEnabled(deviceId) {
|
|
162332
|
+
return !this.optedOutDevices.has(deviceId);
|
|
162333
|
+
}
|
|
162334
|
+
async setEnabled(deviceId, enabled) {
|
|
162335
|
+
if (enabled) {
|
|
162336
|
+
await this.store.clearOptOut(deviceId);
|
|
162337
|
+
this.optedOutDevices = new Set([...this.optedOutDevices].filter((id) => id !== deviceId));
|
|
162338
|
+
} else {
|
|
162339
|
+
await this.store.setOptOut(deviceId, this.deps.clock());
|
|
162340
|
+
this.optedOutDevices = /* @__PURE__ */ new Set([...this.optedOutDevices, deviceId]);
|
|
162341
|
+
}
|
|
162342
|
+
return enabled;
|
|
162343
|
+
}
|
|
162344
|
+
/** Delete the history of a deleted device: its series, opt-out and absence clock. */
|
|
162345
|
+
async removeDevice(deviceId) {
|
|
162346
|
+
const series = await this.retentionJob.purgeDevice(deviceId, this.deps.clock());
|
|
162347
|
+
await this.store.clearOptOut(deviceId);
|
|
162348
|
+
await this.departures.forget(deviceId);
|
|
162349
|
+
this.optedOutDevices = new Set([...this.optedOutDevices].filter((id) => id !== deviceId));
|
|
162350
|
+
return { series };
|
|
162351
|
+
}
|
|
162352
|
+
/** Series held per device, for the admin clean-up listing. */
|
|
162353
|
+
seriesByDevice() {
|
|
162354
|
+
const out = /* @__PURE__ */ new Map();
|
|
162355
|
+
for (const s of this.registry.all()) {
|
|
162356
|
+
if (s.deviceId === null) continue;
|
|
162357
|
+
out.set(s.deviceId, (out.get(s.deviceId) ?? 0) + 1);
|
|
162358
|
+
}
|
|
162359
|
+
return out;
|
|
162360
|
+
}
|
|
162361
|
+
status() {
|
|
162362
|
+
const c = this.counters;
|
|
162363
|
+
const dirty = this.rollup.dirtyCounts();
|
|
162364
|
+
return {
|
|
162365
|
+
startedAt: this.startedAt,
|
|
162366
|
+
ready: this.ready,
|
|
162367
|
+
series: this.registry.all().length,
|
|
162368
|
+
metrics: this.registry.allMetrics().length,
|
|
162369
|
+
optedOutDevices: this.optedOutDevices.size,
|
|
162370
|
+
tiers: [
|
|
162371
|
+
"raw",
|
|
162372
|
+
"5m",
|
|
162373
|
+
"1h",
|
|
162374
|
+
"1d"
|
|
162375
|
+
].map((tier) => {
|
|
162376
|
+
const count = this.tierCounts.get(tier);
|
|
162377
|
+
const rows = count === void 0 ? null : Math.max(0, count.rows + this.counters.rowDeltaOf(tier) - count.deltaAtCount);
|
|
162378
|
+
return {
|
|
162379
|
+
tier,
|
|
162380
|
+
rows,
|
|
162381
|
+
approxBytes: rows === null ? null : rows * APPROX_BYTES_PER_ROW[tier],
|
|
162382
|
+
countedAt: count?.countedAt ?? null
|
|
162383
|
+
};
|
|
162384
|
+
}),
|
|
162385
|
+
capture: {
|
|
162386
|
+
eventsSeen: c.eventsSeen,
|
|
162387
|
+
pointsEmitted: c.pointsEmitted,
|
|
162388
|
+
pointsWritten: c.pointsWritten,
|
|
162389
|
+
pointsDropped: c.pointsDropped,
|
|
162390
|
+
bufferDepth: this.writer.depth(),
|
|
162391
|
+
trailingEmitted: c.trailingEmitted,
|
|
162392
|
+
reconciledPoints: c.reconciledPoints,
|
|
162393
|
+
reconcileRuns: c.reconcileRuns,
|
|
162394
|
+
reconcileFailures: c.reconcileFailures,
|
|
162395
|
+
lastReconcileAt: c.lastReconcileAt,
|
|
162396
|
+
lastFlushAt: c.lastFlushAt,
|
|
162397
|
+
flushFailures: c.flushFailures,
|
|
162398
|
+
skipped: c.skippedRecord()
|
|
162399
|
+
},
|
|
162400
|
+
ingest: {
|
|
162401
|
+
accepted: c.ingestAccepted,
|
|
162402
|
+
refused: c.refusedRecord()
|
|
162403
|
+
},
|
|
162404
|
+
rollup: {
|
|
162405
|
+
last5mAt: c.last5mAt,
|
|
162406
|
+
last1hAt: c.last1hAt,
|
|
162407
|
+
last1dAt: c.last1dAt,
|
|
162408
|
+
errors: c.rollupErrors,
|
|
162409
|
+
dirty5m: dirty.m5,
|
|
162410
|
+
dirty1h: dirty.h1,
|
|
162411
|
+
dirty1d: dirty.d1
|
|
162412
|
+
},
|
|
162413
|
+
retention: {
|
|
162414
|
+
lastRunAt: c.retentionLastRunAt,
|
|
162415
|
+
deleted: c.deletedRecord(),
|
|
162416
|
+
purgedSeries: c.purgedSeries
|
|
162417
|
+
},
|
|
162418
|
+
directory: {
|
|
162419
|
+
devices: this.directory.size(),
|
|
162420
|
+
lastRefreshAt: this.directory.lastRefreshAt()
|
|
162421
|
+
}
|
|
162422
|
+
};
|
|
162423
|
+
}
|
|
162424
|
+
};
|
|
162425
|
+
var DEFAULT_CONFIG = {
|
|
162426
|
+
rawDays: DEFAULT_RETENTION.rawDays,
|
|
162427
|
+
fiveMinuteDays: DEFAULT_RETENTION.fiveMinuteDays,
|
|
162428
|
+
hourDays: DEFAULT_RETENTION.hourDays,
|
|
162429
|
+
dayDays: 0
|
|
162430
|
+
};
|
|
162431
|
+
function isRecord(value) {
|
|
162432
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
162433
|
+
}
|
|
162434
|
+
function callerOf(input) {
|
|
162435
|
+
if (!isRecord(input)) return null;
|
|
162436
|
+
const raw = input["callerAddonId"];
|
|
162437
|
+
return typeof raw === "string" && raw.length > 0 ? raw : null;
|
|
162438
|
+
}
|
|
162439
|
+
function positiveOr(value, fallback) {
|
|
162440
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
162441
|
+
}
|
|
162442
|
+
var DeviceHistoryAddon = class extends require_dist10.BaseAddon {
|
|
162443
|
+
runtime = null;
|
|
162444
|
+
unsubscribe = null;
|
|
162445
|
+
removalHook = null;
|
|
162446
|
+
constructor() {
|
|
162447
|
+
super({ ...DEFAULT_CONFIG });
|
|
162448
|
+
}
|
|
162449
|
+
retention() {
|
|
162450
|
+
const c = this.config;
|
|
162451
|
+
return {
|
|
162452
|
+
rawDays: positiveOr(c.rawDays, DEFAULT_RETENTION.rawDays),
|
|
162453
|
+
fiveMinuteDays: positiveOr(c.fiveMinuteDays, DEFAULT_RETENTION.fiveMinuteDays),
|
|
162454
|
+
hourDays: positiveOr(c.hourDays, DEFAULT_RETENTION.hourDays),
|
|
162455
|
+
dayDays: positiveOr(c.dayDays, 0) > 0 ? c.dayDays : null
|
|
162456
|
+
};
|
|
162457
|
+
}
|
|
162458
|
+
requireRuntime() {
|
|
162459
|
+
if (this.runtime === null) throw new Error("device-history is starting \u2014 the history store is not open yet");
|
|
162460
|
+
return this.runtime;
|
|
162461
|
+
}
|
|
162462
|
+
async onInitialize() {
|
|
162463
|
+
const provider = {
|
|
162464
|
+
listSeries: async (input) => this.requireRuntime().query.listSeries({
|
|
162465
|
+
kind: "device",
|
|
162466
|
+
deviceId: input.deviceId
|
|
162467
|
+
}),
|
|
162468
|
+
listNodeSeries: async (input) => this.requireRuntime().query.listSeries({
|
|
162469
|
+
kind: "node",
|
|
162470
|
+
nodeId: input.nodeId
|
|
162471
|
+
}),
|
|
162472
|
+
query: async ({ deviceId, ...window2 }) => this.requireRuntime().query.query({
|
|
162473
|
+
...window2,
|
|
162474
|
+
subject: {
|
|
162475
|
+
kind: "device",
|
|
162476
|
+
deviceId
|
|
162477
|
+
}
|
|
162478
|
+
}),
|
|
162479
|
+
queryNode: async ({ nodeId, ...window2 }) => this.requireRuntime().query.query({
|
|
162480
|
+
...window2,
|
|
162481
|
+
subject: {
|
|
162482
|
+
kind: "node",
|
|
162483
|
+
nodeId
|
|
162484
|
+
}
|
|
162485
|
+
}),
|
|
162486
|
+
declareSeries: async (input) => this.requireRuntime().ingest.declareSeries(callerOf(input), input),
|
|
162487
|
+
record: async (input) => this.requireRuntime().ingest.record(callerOf(input), input.points),
|
|
162488
|
+
recordBuckets: async (input) => this.requireRuntime().ingest.recordBuckets(callerOf(input), input.buckets),
|
|
162489
|
+
getDeviceEnabled: async (input) => ({ enabled: this.requireRuntime().isEnabled(input.deviceId) }),
|
|
162490
|
+
setDeviceEnabled: async (input) => ({ enabled: await this.requireRuntime().setEnabled(input.deviceId, input.enabled) }),
|
|
162491
|
+
getStatus: async () => this.requireRuntime().status()
|
|
162492
|
+
};
|
|
162493
|
+
this.ctx.logger.info("Initialized \u2014 device-history provider registered (store opens on hub link)");
|
|
162494
|
+
const removalHook = {
|
|
162495
|
+
onDeviceRemoved: async (input) => {
|
|
162496
|
+
const { series } = await this.requireRuntime().removeDevice(input.deviceId);
|
|
162497
|
+
if (series > 0) this.ctx.logger.info("device-history: removed the history of a deleted device", {
|
|
162498
|
+
tags: { deviceId: input.deviceId },
|
|
162499
|
+
meta: {
|
|
162500
|
+
counts: { series },
|
|
162501
|
+
deletedAt: input.deletedAt
|
|
162502
|
+
}
|
|
162503
|
+
});
|
|
162504
|
+
return { counts: { series } };
|
|
162505
|
+
},
|
|
162506
|
+
listDeviceData: async () => [...this.requireRuntime().seriesByDevice()].map(([deviceId, count]) => ({
|
|
162507
|
+
store: "series",
|
|
162508
|
+
deviceId,
|
|
162509
|
+
count
|
|
162510
|
+
}))
|
|
162511
|
+
};
|
|
162512
|
+
this.removalHook = removalHook;
|
|
162513
|
+
return [{
|
|
162514
|
+
capability: require_dist10.deviceHistoryCapability,
|
|
162515
|
+
provider
|
|
162516
|
+
}, {
|
|
162517
|
+
capability: require_dist10.deviceRemovalHookCapability,
|
|
162518
|
+
provider: removalHook
|
|
162519
|
+
}];
|
|
162520
|
+
}
|
|
162521
|
+
async onHubReachable() {
|
|
162522
|
+
const api = this.ctx.api;
|
|
162523
|
+
const runtime = new HistoryRuntime({
|
|
162524
|
+
door: api.settingsStore,
|
|
162525
|
+
catalog: require_dist10.HISTORY_CATALOG,
|
|
162526
|
+
deviceSource: {
|
|
162527
|
+
listAll: async () => {
|
|
162528
|
+
return (await api.deviceManager.listAll.query({ projection: "slim" })).map((d) => ({
|
|
162529
|
+
id: d.id,
|
|
162530
|
+
type: d.type
|
|
162531
|
+
}));
|
|
162532
|
+
},
|
|
162533
|
+
getDevice: async (deviceId) => {
|
|
162534
|
+
const d = await api.deviceManager.getDevice.query({ deviceId });
|
|
162535
|
+
return d === null ? null : {
|
|
162536
|
+
id: d.id,
|
|
162537
|
+
type: d.type
|
|
162538
|
+
};
|
|
162539
|
+
}
|
|
162540
|
+
},
|
|
162541
|
+
readSnapshots: async () => api.deviceState.getAllSnapshots.query({}),
|
|
162542
|
+
readCapSlice: (deviceId, capName) => api.deviceState.getCapSlice.query({
|
|
162543
|
+
deviceId,
|
|
162544
|
+
capName
|
|
162545
|
+
}),
|
|
162546
|
+
retention: () => this.retention(),
|
|
162547
|
+
logger: this.ctx.logger,
|
|
162548
|
+
clock: () => Date.now()
|
|
162549
|
+
});
|
|
162550
|
+
await runtime.prepare();
|
|
162551
|
+
this.unsubscribe = this.ctx.eventBus.subscribe({ category: require_dist10.EventCategory.DeviceStateChanged }, (event) => {
|
|
162552
|
+
const data = event.data;
|
|
162553
|
+
if (!isRecord(data)) return;
|
|
162554
|
+
const { deviceId, capName, slice } = data;
|
|
162555
|
+
if (typeof deviceId !== "number" || typeof capName !== "string" || !isRecord(slice)) return;
|
|
162556
|
+
const at = event.timestamp instanceof Date ? event.timestamp.getTime() : Date.now();
|
|
162557
|
+
runtime.capture.handleState(deviceId, capName, slice, at);
|
|
162558
|
+
});
|
|
162559
|
+
this.ctx.addDisposer(async () => this.stopRuntime());
|
|
162560
|
+
this.runtime = runtime;
|
|
162561
|
+
await runtime.finishBoot();
|
|
162562
|
+
runtime.startTimers();
|
|
162563
|
+
const removalHook = this.removalHook;
|
|
162564
|
+
if (removalHook !== null) this.ctx.addDisposer(require_dist10.armRemovalReconcile({
|
|
162565
|
+
api: this.ctx.api,
|
|
162566
|
+
logger: this.ctx.logger,
|
|
162567
|
+
remove: (input) => removalHook.onDeviceRemoved(input)
|
|
162568
|
+
}));
|
|
162569
|
+
this.ctx.logger.info("device-history: capturing", { meta: {
|
|
162570
|
+
series: runtime.status().series,
|
|
162571
|
+
metrics: runtime.status().metrics
|
|
162572
|
+
} });
|
|
162573
|
+
}
|
|
162574
|
+
async stopRuntime() {
|
|
162575
|
+
this.unsubscribe?.();
|
|
162576
|
+
this.unsubscribe = null;
|
|
162577
|
+
await this.runtime?.stop();
|
|
162578
|
+
}
|
|
162579
|
+
async onShutdown() {
|
|
162580
|
+
await this.stopRuntime();
|
|
162581
|
+
this.runtime = null;
|
|
162582
|
+
}
|
|
162583
|
+
globalSettingsSchema() {
|
|
162584
|
+
return this.schema({ sections: [{
|
|
162585
|
+
id: "retention",
|
|
162586
|
+
title: "Retention",
|
|
162587
|
+
description: "How long each tier is kept. The raw points feed the 5-minute rollup; the 1-day tier is kept forever unless a number of days is set.",
|
|
162588
|
+
columns: 2,
|
|
162589
|
+
fields: [
|
|
162590
|
+
{
|
|
162591
|
+
type: "number",
|
|
162592
|
+
key: "rawDays",
|
|
162593
|
+
label: "Raw points (days)",
|
|
162594
|
+
min: 1,
|
|
162595
|
+
max: 60,
|
|
162596
|
+
step: 1
|
|
162597
|
+
},
|
|
162598
|
+
{
|
|
162599
|
+
type: "number",
|
|
162600
|
+
key: "fiveMinuteDays",
|
|
162601
|
+
label: "5-minute rollup (days)",
|
|
162602
|
+
min: 1,
|
|
162603
|
+
max: 365,
|
|
162604
|
+
step: 1
|
|
162605
|
+
},
|
|
162606
|
+
{
|
|
162607
|
+
type: "number",
|
|
162608
|
+
key: "hourDays",
|
|
162609
|
+
label: "1-hour rollup (days)",
|
|
162610
|
+
min: 7,
|
|
162611
|
+
max: 3650,
|
|
162612
|
+
step: 1
|
|
162613
|
+
},
|
|
162614
|
+
{
|
|
162615
|
+
type: "number",
|
|
162616
|
+
key: "dayDays",
|
|
162617
|
+
label: "1-day rollup (days, 0 = forever)",
|
|
162618
|
+
min: 0,
|
|
162619
|
+
max: 36500,
|
|
162620
|
+
step: 1
|
|
162621
|
+
}
|
|
162622
|
+
]
|
|
162623
|
+
}] });
|
|
162624
|
+
}
|
|
162625
|
+
};
|
|
162626
|
+
exports.DeviceHistoryAddon = DeviceHistoryAddon;
|
|
162627
|
+
exports.default = DeviceHistoryAddon;
|
|
162628
|
+
exports.callerOf = callerOf;
|
|
162629
|
+
}
|
|
162630
|
+
});
|
|
162631
|
+
|
|
160163
162632
|
// ../system/dist/builtins/sqlite-storage/filesystem-storage.addon.js
|
|
160164
162633
|
var require_filesystem_storage_addon = __commonJS({
|
|
160165
162634
|
"../system/dist/builtins/sqlite-storage/filesystem-storage.addon.js"(exports) {
|
|
@@ -160169,7 +162638,7 @@ var require_filesystem_storage_addon = __commonJS({
|
|
|
160169
162638
|
[Symbol.toStringTag]: { value: "Module" }
|
|
160170
162639
|
});
|
|
160171
162640
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
160172
|
-
var require_dist10 =
|
|
162641
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
160173
162642
|
var node_crypto = __require("crypto");
|
|
160174
162643
|
var node_fs_promises = __require("fs/promises");
|
|
160175
162644
|
var node_path = __require("path");
|
|
@@ -161279,12 +163748,12 @@ var require_lib = __commonJS({
|
|
|
161279
163748
|
}
|
|
161280
163749
|
});
|
|
161281
163750
|
|
|
161282
|
-
// ../system/dist/wal-maintenance-
|
|
161283
|
-
var
|
|
161284
|
-
"../system/dist/wal-maintenance-
|
|
163751
|
+
// ../system/dist/wal-maintenance-mwCBe3Vr.js
|
|
163752
|
+
var require_wal_maintenance_mwCBe3Vr = __commonJS({
|
|
163753
|
+
"../system/dist/wal-maintenance-mwCBe3Vr.js"(exports) {
|
|
161285
163754
|
"use strict";
|
|
161286
163755
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
161287
|
-
var require_dist10 =
|
|
163756
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
161288
163757
|
var require_prefix_range = require_prefix_range_D_KueXbQ();
|
|
161289
163758
|
var node_crypto = __require("crypto");
|
|
161290
163759
|
var node_module = __require("module");
|
|
@@ -164825,6 +167294,22 @@ CREATE TABLE IF NOT EXISTS _record_sequences (
|
|
|
164825
167294
|
...profiler ? { profiler } : {}
|
|
164826
167295
|
});
|
|
164827
167296
|
}
|
|
167297
|
+
function vectorProviderOfOwnedFile(openEvents) {
|
|
167298
|
+
const call = async (op, input) => {
|
|
167299
|
+
return (await openEvents()).vector(op, input);
|
|
167300
|
+
};
|
|
167301
|
+
return {
|
|
167302
|
+
declareIndex: async (input) => call("declareIndex", input),
|
|
167303
|
+
upsert: async (input) => call("upsert", input),
|
|
167304
|
+
query: async (input) => call("query", input),
|
|
167305
|
+
getByIds: async (input) => call("getByIds", input),
|
|
167306
|
+
fetchByIds: async (input) => call("fetchByIds", input),
|
|
167307
|
+
scan: async (input) => call("scan", input),
|
|
167308
|
+
deleteByIds: async (input) => call("deleteByIds", input),
|
|
167309
|
+
deleteByFilter: async (input) => call("deleteByFilter", input),
|
|
167310
|
+
stats: async (input) => call("stats", input)
|
|
167311
|
+
};
|
|
167312
|
+
}
|
|
164828
167313
|
function vectorProviderOf(vectorIndex) {
|
|
164829
167314
|
return {
|
|
164830
167315
|
declareIndex: async (input) => {
|
|
@@ -165268,6 +167753,12 @@ CREATE TABLE IF NOT EXISTS _record_sequences (
|
|
|
165268
167753
|
return vectorProviderOf;
|
|
165269
167754
|
}
|
|
165270
167755
|
});
|
|
167756
|
+
Object.defineProperty(exports, "vectorProviderOfOwnedFile", {
|
|
167757
|
+
enumerable: true,
|
|
167758
|
+
get: function() {
|
|
167759
|
+
return vectorProviderOfOwnedFile;
|
|
167760
|
+
}
|
|
167761
|
+
});
|
|
165271
167762
|
}
|
|
165272
167763
|
});
|
|
165273
167764
|
|
|
@@ -165446,9 +167937,9 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
165446
167937
|
[Symbol.toStringTag]: { value: "Module" }
|
|
165447
167938
|
});
|
|
165448
167939
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
165449
|
-
var require_dist10 =
|
|
167940
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
165450
167941
|
var require_retired_settings_keys = require_retired_settings_keys_D_VwZiDt();
|
|
165451
|
-
var require_wal_maintenance =
|
|
167942
|
+
var require_wal_maintenance = require_wal_maintenance_mwCBe3Vr();
|
|
165452
167943
|
var require_sqlite_snapshot_protocol = require_sqlite_snapshot_protocol_B9OndqZv();
|
|
165453
167944
|
var require_wal_checkpoint_policy = require_wal_checkpoint_policy_d2z2qPqx();
|
|
165454
167945
|
var node_fs_promises = __require("fs/promises");
|
|
@@ -167145,7 +169636,7 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
167145
169636
|
livePath: settingsPath,
|
|
167146
169637
|
thread: "main",
|
|
167147
169638
|
autoVacuum: "none",
|
|
167148
|
-
loadVectorExtension:
|
|
169639
|
+
loadVectorExtension: false
|
|
167149
169640
|
}, deps);
|
|
167150
169641
|
if (!(settings instanceof InlineOwnedFile)) throw new Error("sqlite-settings: the settings file must be served on the main thread");
|
|
167151
169642
|
this.settingsFile = settings;
|
|
@@ -167158,8 +169649,7 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
167158
169649
|
logger: this.ctx.logger.child("Placement")
|
|
167159
169650
|
});
|
|
167160
169651
|
this.router = router;
|
|
167161
|
-
const
|
|
167162
|
-
if (vectorIndex === null) throw new Error("vector-store: the settings file has no vector index");
|
|
169652
|
+
const vectorProvider = require_wal_maintenance.vectorProviderOfOwnedFile(() => router.recordsFileFor("events"));
|
|
167163
169653
|
const records = new RecordsProvider(router, this.ctx.logger.child("Records"));
|
|
167164
169654
|
await records.init();
|
|
167165
169655
|
this.startRecordsSweep(records);
|
|
@@ -167175,7 +169665,7 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
167175
169665
|
},
|
|
167176
169666
|
{
|
|
167177
169667
|
capability: require_dist10.vectorStoreCapability,
|
|
167178
|
-
provider:
|
|
169668
|
+
provider: vectorProvider
|
|
167179
169669
|
},
|
|
167180
169670
|
{
|
|
167181
169671
|
capability: require_dist10.databaseSnapshotCapability,
|
|
@@ -167244,9 +169734,9 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
167244
169734
|
getRouter() {
|
|
167245
169735
|
return this.router;
|
|
167246
169736
|
}
|
|
167247
|
-
/** Exposed for tests;
|
|
169737
|
+
/** Exposed for tests; vectors are served from the events file (P5). */
|
|
167248
169738
|
getVectorIndex() {
|
|
167249
|
-
return
|
|
169739
|
+
return null;
|
|
167250
169740
|
}
|
|
167251
169741
|
};
|
|
167252
169742
|
function workerSpec(databaseId, livePath) {
|
|
@@ -167255,7 +169745,7 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
167255
169745
|
livePath,
|
|
167256
169746
|
thread: "worker",
|
|
167257
169747
|
autoVacuum: "incremental",
|
|
167258
|
-
loadVectorExtension:
|
|
169748
|
+
loadVectorExtension: databaseId === "events"
|
|
167259
169749
|
};
|
|
167260
169750
|
}
|
|
167261
169751
|
exports.SqliteSettingsAddon = SqliteSettingsAddon;
|
|
@@ -167273,7 +169763,7 @@ var require_sqlite_storage = __commonJS({
|
|
|
167273
169763
|
});
|
|
167274
169764
|
require_chunk_Cek0wNdY();
|
|
167275
169765
|
var require_builtins_sqlite_storage_filesystem_storage_addon = require_filesystem_storage_addon();
|
|
167276
|
-
var require_wal_maintenance =
|
|
169766
|
+
var require_wal_maintenance = require_wal_maintenance_mwCBe3Vr();
|
|
167277
169767
|
var require_builtins_sqlite_storage_sqlite_settings_addon = require_sqlite_settings_addon();
|
|
167278
169768
|
var ConfigStore = class {
|
|
167279
169769
|
db;
|
|
@@ -167357,7 +169847,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
167357
169847
|
[Symbol.toStringTag]: { value: "Module" }
|
|
167358
169848
|
});
|
|
167359
169849
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
167360
|
-
var require_dist10 =
|
|
169850
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
167361
169851
|
var require_read_all_rows$1 = require_read_all_rows_CVoO1ONM();
|
|
167362
169852
|
var zod = require_zod();
|
|
167363
169853
|
var node_crypto = __require("crypto");
|
|
@@ -171488,11 +173978,11 @@ var require_storage_orchestrator = __commonJS({
|
|
|
171488
173978
|
}
|
|
171489
173979
|
});
|
|
171490
173980
|
|
|
171491
|
-
// ../system/dist/constants-
|
|
171492
|
-
var
|
|
171493
|
-
"../system/dist/constants-
|
|
173981
|
+
// ../system/dist/constants-BPWA-r-Y.js
|
|
173982
|
+
var require_constants_BPWA_r_Y = __commonJS({
|
|
173983
|
+
"../system/dist/constants-BPWA-r-Y.js"(exports) {
|
|
171494
173984
|
"use strict";
|
|
171495
|
-
var MESH_CA_KEY_REF =
|
|
173985
|
+
var MESH_CA_KEY_REF = require_dist_Ct88catN().buildSecretRef({
|
|
171496
173986
|
owner: "system",
|
|
171497
173987
|
scope: { kind: "global" },
|
|
171498
173988
|
fieldPath: "meshCaKey"
|
|
@@ -171555,14 +174045,14 @@ var require_constants_DA_tjzIk = __commonJS({
|
|
|
171555
174045
|
}
|
|
171556
174046
|
});
|
|
171557
174047
|
|
|
171558
|
-
// ../system/dist/agent-https-
|
|
171559
|
-
var
|
|
171560
|
-
"../system/dist/agent-https-
|
|
174048
|
+
// ../system/dist/agent-https-CMz4S56_.js
|
|
174049
|
+
var require_agent_https_CMz4S56 = __commonJS({
|
|
174050
|
+
"../system/dist/agent-https-CMz4S56_.js"(exports) {
|
|
171561
174051
|
"use strict";
|
|
171562
174052
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
171563
|
-
var require_dist10 =
|
|
174053
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
171564
174054
|
var require_x509_es = require_x509_es_C4cOo48M();
|
|
171565
|
-
var require_constants15 =
|
|
174055
|
+
var require_constants15 = require_constants_BPWA_r_Y();
|
|
171566
174056
|
var zod = require_zod();
|
|
171567
174057
|
var node_crypto = __require("crypto");
|
|
171568
174058
|
var _camstack_types_node = require_node();
|
|
@@ -190305,11 +192795,11 @@ var require_system_config_addon = __commonJS({
|
|
|
190305
192795
|
[Symbol.toStringTag]: { value: "Module" }
|
|
190306
192796
|
});
|
|
190307
192797
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
190308
|
-
var require_dist10 =
|
|
192798
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
190309
192799
|
var require_safe_buffer$1 = require_safe_buffer_D3aoa_7X();
|
|
190310
192800
|
var require_x509_es = require_x509_es_C4cOo48M();
|
|
190311
|
-
var require_constants$8 =
|
|
190312
|
-
var require_agent_https =
|
|
192801
|
+
var require_constants$8 = require_constants_BPWA_r_Y();
|
|
192802
|
+
var require_agent_https = require_agent_https_CMz4S56();
|
|
190313
192803
|
var require_src$1 = require_src_CnAqG6EF();
|
|
190314
192804
|
var zod = require_zod();
|
|
190315
192805
|
var node_crypto = __require("crypto");
|
|
@@ -270022,7 +272512,7 @@ var require_winston_logging = __commonJS({
|
|
|
270022
272512
|
[Symbol.toStringTag]: { value: "Module" }
|
|
270023
272513
|
});
|
|
270024
272514
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
270025
|
-
var require_dist10 =
|
|
272515
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
270026
272516
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
270027
272517
|
var node_path = __require("path");
|
|
270028
272518
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -277672,14 +280162,14 @@ var require_lib_8oZrELLm = __commonJS({
|
|
|
277672
280162
|
}
|
|
277673
280163
|
});
|
|
277674
280164
|
|
|
277675
|
-
// ../system/dist/hub-mdns-ciao-
|
|
277676
|
-
var
|
|
277677
|
-
"../system/dist/hub-mdns-ciao-
|
|
280165
|
+
// ../system/dist/hub-mdns-ciao-DAU1WttG.js
|
|
280166
|
+
var require_hub_mdns_ciao_DAU1WttG = __commonJS({
|
|
280167
|
+
"../system/dist/hub-mdns-ciao-DAU1WttG.js"(exports) {
|
|
277678
280168
|
"use strict";
|
|
277679
280169
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
277680
|
-
var require_dist10 =
|
|
280170
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
277681
280171
|
var require_x509_es = require_x509_es_C4cOo48M();
|
|
277682
|
-
var require_agent_https =
|
|
280172
|
+
var require_agent_https = require_agent_https_CMz4S56();
|
|
277683
280173
|
var node_crypto = __require("crypto");
|
|
277684
280174
|
var _camstack_types_node = require_node();
|
|
277685
280175
|
var node_fs_promises = __require("fs/promises");
|
|
@@ -278678,12 +281168,12 @@ var require_platform_key_source_Di7HrlGR = __commonJS({
|
|
|
278678
281168
|
}
|
|
278679
281169
|
});
|
|
278680
281170
|
|
|
278681
|
-
// ../system/dist/restore-archive-
|
|
278682
|
-
var
|
|
278683
|
-
"../system/dist/restore-archive-
|
|
281171
|
+
// ../system/dist/restore-archive-Cprdh3P9.js
|
|
281172
|
+
var require_restore_archive_Cprdh3P9 = __commonJS({
|
|
281173
|
+
"../system/dist/restore-archive-Cprdh3P9.js"(exports) {
|
|
278684
281174
|
"use strict";
|
|
278685
281175
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
278686
|
-
var require_dist10 =
|
|
281176
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
278687
281177
|
var require_read_all_rows$1 = require_read_all_rows_CVoO1ONM();
|
|
278688
281178
|
var zod = require_zod();
|
|
278689
281179
|
var node_crypto = __require("crypto");
|
|
@@ -290004,9 +292494,9 @@ var require_dist3 = __commonJS({
|
|
|
290004
292494
|
"use strict";
|
|
290005
292495
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
290006
292496
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
290007
|
-
var require_dist10 =
|
|
292497
|
+
var require_dist10 = require_dist_Ct88catN();
|
|
290008
292498
|
var require_read_all_rows$1 = require_read_all_rows_CVoO1ONM();
|
|
290009
|
-
var require_device_ca =
|
|
292499
|
+
var require_device_ca = require_device_ca_75rt3U_U();
|
|
290010
292500
|
var require_builtins_alerts_alerts_addon = require_alerts_addon();
|
|
290011
292501
|
require_alerts();
|
|
290012
292502
|
var require_builtins_calendars_calendars_addon = require_calendars_addon();
|
|
@@ -290029,22 +292519,23 @@ var require_dist3 = __commonJS({
|
|
|
290029
292519
|
require_local_auth();
|
|
290030
292520
|
var require_builtins_loki_logging_index = require_loki_logging();
|
|
290031
292521
|
var require_builtins_native_metrics_native_metrics_addon = require_native_metrics_addon();
|
|
292522
|
+
var require_builtins_device_history_device_history_addon = require_device_history_addon();
|
|
290032
292523
|
var require_builtins_sqlite_storage_filesystem_storage_addon = require_filesystem_storage_addon();
|
|
290033
|
-
var require_wal_maintenance =
|
|
292524
|
+
var require_wal_maintenance = require_wal_maintenance_mwCBe3Vr();
|
|
290034
292525
|
var require_builtins_sqlite_storage_sqlite_settings_addon = require_sqlite_settings_addon();
|
|
290035
292526
|
var require_builtins_sqlite_storage_index = require_sqlite_storage();
|
|
290036
292527
|
var require_builtins_storage_orchestrator_storage_orchestrator_addon = require_storage_orchestrator_addon();
|
|
290037
292528
|
require_storage_orchestrator();
|
|
290038
|
-
var require_constants15 =
|
|
290039
|
-
var require_agent_https =
|
|
292529
|
+
var require_constants15 = require_constants_BPWA_r_Y();
|
|
292530
|
+
var require_agent_https = require_agent_https_CMz4S56();
|
|
290040
292531
|
var require_builtins_system_config_system_config_addon = require_system_config_addon();
|
|
290041
292532
|
require_system_config();
|
|
290042
292533
|
var require_builtins_winston_logging_index = require_winston_logging();
|
|
290043
292534
|
var require_custom_action_registry = require_custom_action_registry_COyb49zm();
|
|
290044
|
-
var require_hub_mdns_ciao =
|
|
292535
|
+
var require_hub_mdns_ciao = require_hub_mdns_ciao_DAU1WttG();
|
|
290045
292536
|
var require_platform_key_source = require_platform_key_source_Di7HrlGR();
|
|
290046
292537
|
var require_resource_monitor = require_resource_monitor_mio9dSCT();
|
|
290047
|
-
var require_restore_archive =
|
|
292538
|
+
var require_restore_archive = require_restore_archive_Cprdh3P9();
|
|
290048
292539
|
var zod = require_zod();
|
|
290049
292540
|
var node_crypto = __require("crypto");
|
|
290050
292541
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
@@ -312707,6 +315198,7 @@ var require_dist3 = __commonJS({
|
|
|
312707
315198
|
exports.DataPlaneRegistry = DataPlaneRegistry;
|
|
312708
315199
|
exports.DeviceCaStore = require_builtins_local_auth_local_auth_addon.DeviceCaStore;
|
|
312709
315200
|
exports.DeviceCredentialManager = require_builtins_local_auth_local_auth_addon.DeviceCredentialManager;
|
|
315201
|
+
exports.DeviceHistoryAddon = require_builtins_device_history_device_history_addon.DeviceHistoryAddon;
|
|
312710
315202
|
exports.DeviceManagerAddon = require_builtins_device_manager_device_manager_addon.DeviceManagerAddon;
|
|
312711
315203
|
exports.DeviceRegistry = DeviceRegistry;
|
|
312712
315204
|
exports.DeviceStore = require_builtins_sqlite_storage_index.DeviceStore$1;
|