@noego/telemetry 0.0.5 → 0.1.0
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/README.md +6 -0
- package/dist/browser/index.d.cts +6 -5
- package/dist/browser/index.d.ts +6 -5
- package/dist/{browser_telemetry_client-BSAMZ8Qb.d.cts → browser_telemetry_client-ClZl3awO.d.cts} +2 -2
- package/dist/{browser_telemetry_client-DPBU6NGw.d.ts → browser_telemetry_client-t4dPp8Ku.d.ts} +2 -2
- package/dist/cli/telemetry_bin.cjs +393 -35
- package/dist/cli/telemetry_bin.cjs.map +1 -1
- package/dist/cli/telemetry_bin.js +378 -20
- package/dist/cli/telemetry_bin.js.map +1 -1
- package/dist/cli/telemetry_cli.cjs +393 -35
- package/dist/cli/telemetry_cli.cjs.map +1 -1
- package/dist/cli/telemetry_cli.d.cts +35 -5
- package/dist/cli/telemetry_cli.d.ts +35 -5
- package/dist/cli/telemetry_cli.js +378 -20
- package/dist/cli/telemetry_cli.js.map +1 -1
- package/dist/{client_defaults-ShVYQYmn.d.ts → client_defaults-CZ8PysZX.d.ts} +2 -2
- package/dist/{client_defaults-Dk0MMzvx.d.cts → client_defaults-lDbWYWWK.d.cts} +2 -2
- package/dist/core/index.cjs +5 -1
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +15 -12
- package/dist/core/index.d.ts +15 -12
- package/dist/core/index.js +5 -1
- package/dist/core/index.js.map +1 -1
- package/dist/electron/index.d.cts +5 -4
- package/dist/electron/index.d.ts +5 -4
- package/dist/{envelope.types-rOLpWXRC.d.cts → envelope.types-DIvZD3xH.d.ts} +2 -6
- package/dist/{envelope.types-rOLpWXRC.d.ts → envelope.types-DyrlRG49.d.cts} +2 -6
- package/dist/{in_memory_batch_transport-DKW9EZnU.d.cts → in_memory_batch_transport-3T6gElXP.d.cts} +1 -1
- package/dist/{in_memory_batch_transport-oOwNxXnA.d.ts → in_memory_batch_transport-DpQU6JqM.d.ts} +1 -1
- package/dist/index.cjs +5 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -12
- package/dist/index.d.ts +14 -12
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/json.types-BqS77fcN.d.cts +7 -0
- package/dist/json.types-BqS77fcN.d.ts +7 -0
- package/dist/{maintenance_coordinator.types-C4ULi4A4.d.cts → maintenance_coordinator.types-Crhx9i9k.d.cts} +3 -3
- package/dist/{maintenance_coordinator.types-ikUrRXmO.d.ts → maintenance_coordinator.types-DprmgpVn.d.ts} +3 -3
- package/dist/metric.types-D8aOAnUm.d.ts +67 -0
- package/dist/metric.types-Dg4eZq6u.d.cts +67 -0
- package/dist/{metric_definition_contract-BYtnODhu.d.ts → metric_definition_contract-C6MERhJs.d.ts} +1 -1
- package/dist/{metric_definition_contract-DfhhmTZ1.d.cts → metric_definition_contract-DoAk7NOw.d.cts} +1 -1
- package/dist/node/index.cjs +5 -1
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.cts +11 -9
- package/dist/node/index.d.ts +11 -9
- package/dist/node/index.js +5 -1
- package/dist/node/index.js.map +1 -1
- package/dist/node/producer/index.d.cts +5 -4
- package/dist/node/producer/index.d.ts +5 -4
- package/dist/projector/index.cjs +1370 -0
- package/dist/projector/index.cjs.map +1 -0
- package/dist/projector/index.d.cts +246 -0
- package/dist/projector/index.d.ts +246 -0
- package/dist/projector/index.js +1328 -0
- package/dist/projector/index.js.map +1 -0
- package/dist/{query.types-DU5v7vM0.d.ts → query.types-CRqAsGTX.d.cts} +4 -66
- package/dist/{query.types-DuYWbFFe.d.cts → query.types-DnOMYXpC.d.ts} +4 -66
- package/dist/sqlite/index.cjs +5 -1
- package/dist/sqlite/index.cjs.map +1 -1
- package/dist/sqlite/index.d.cts +7 -5
- package/dist/sqlite/index.d.ts +7 -5
- package/dist/sqlite/index.js +5 -1
- package/dist/sqlite/index.js.map +1 -1
- package/dist/{storage.types-k7oElEB3.d.ts → storage.types-BKWoIOHp.d.ts} +3 -2
- package/dist/{storage.types-Bji_pWqr.d.cts → storage.types-DHbAm3j_.d.cts} +3 -2
- package/dist/{telemetry_client-BO5Pjp41.d.cts → telemetry_client-Bg5tbj5E.d.cts} +3 -2
- package/dist/{telemetry_client-D8uZ-mTL.d.ts → telemetry_client-DWKgufmc.d.ts} +3 -2
- package/dist/{telemetry_configuration_parser-BZ8Nvnle.d.ts → telemetry_configuration_parser-6BR3_dD_.d.ts} +2 -2
- package/dist/{telemetry_configuration_parser-CgSN7R6A.d.cts → telemetry_configuration_parser-ybeweUPE.d.cts} +2 -2
- package/dist/testing/index.d.cts +7 -5
- package/dist/testing/index.d.ts +7 -5
- package/dist/trace-adapter/index.d.cts +4 -3
- package/dist/trace-adapter/index.d.ts +4 -3
- package/dist/{transport.types-BAl_TRtU.d.cts → transport.types-C2dP-eed.d.cts} +1 -1
- package/dist/{transport.types-DDnZoKWi.d.ts → transport.types-SA8KpLiK.d.ts} +1 -1
- package/native/noego_telemetry_native.node +0 -0
- package/package.json +11 -4
|
@@ -35,7 +35,7 @@ __export(telemetry_cli_exports, {
|
|
|
35
35
|
runTelemetryCli: () => runTelemetryCli
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(telemetry_cli_exports);
|
|
38
|
-
var
|
|
38
|
+
var import_node_fs5 = require("fs");
|
|
39
39
|
|
|
40
40
|
// src/platform/node/telemetry_configuration_parser.ts
|
|
41
41
|
var import_node_path = require("path");
|
|
@@ -100,11 +100,15 @@ var MetricDefinitionContract = class {
|
|
|
100
100
|
if (!eventSelector || !bracket) {
|
|
101
101
|
return { ok: false, error: `Invalid metric selector ${selector}` };
|
|
102
102
|
}
|
|
103
|
+
const groupBy = bracket === "*" ? [] : bracket.split(",").map((field) => field.trim());
|
|
104
|
+
if (groupBy.some((field) => field.length === 0)) {
|
|
105
|
+
return { ok: false, error: `Invalid metric selector ${selector}` };
|
|
106
|
+
}
|
|
103
107
|
return {
|
|
104
108
|
ok: true,
|
|
105
109
|
value: {
|
|
106
110
|
selector: eventSelector,
|
|
107
|
-
groupBy
|
|
111
|
+
groupBy,
|
|
108
112
|
wildcardUngrouped: bracket === "*"
|
|
109
113
|
}
|
|
110
114
|
};
|
|
@@ -1820,24 +1824,200 @@ var MetricGraphRenderer = class {
|
|
|
1820
1824
|
}
|
|
1821
1825
|
};
|
|
1822
1826
|
|
|
1827
|
+
// src/cli/history_metric_reader.ts
|
|
1828
|
+
var import_node_fs2 = require("fs");
|
|
1829
|
+
var import_node_path2 = require("path");
|
|
1830
|
+
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
1831
|
+
var HISTORY_DATABASE_NAME = "history.db";
|
|
1832
|
+
var AGGREGATES = /* @__PURE__ */ new Set(["count", "sum", "min", "max", "last", "avg"]);
|
|
1833
|
+
function historyDatabasePath(target) {
|
|
1834
|
+
return (0, import_node_path2.join)((0, import_node_path2.dirname)(target.path), HISTORY_DATABASE_NAME);
|
|
1835
|
+
}
|
|
1836
|
+
var HistoryMetricReader = class _HistoryMetricReader {
|
|
1837
|
+
constructor(db, definitions) {
|
|
1838
|
+
this.db = db;
|
|
1839
|
+
this.definitions = definitions;
|
|
1840
|
+
}
|
|
1841
|
+
db;
|
|
1842
|
+
definitions;
|
|
1843
|
+
static open(databasePath, definitions) {
|
|
1844
|
+
const db = new import_better_sqlite3.default(databasePath, { readonly: true, fileMustExist: true });
|
|
1845
|
+
db.pragma("busy_timeout = 1000");
|
|
1846
|
+
db.pragma("query_only = ON");
|
|
1847
|
+
return new _HistoryMetricReader(db, definitions);
|
|
1848
|
+
}
|
|
1849
|
+
static exists(databasePath) {
|
|
1850
|
+
return (0, import_node_fs2.existsSync)(databasePath);
|
|
1851
|
+
}
|
|
1852
|
+
metrics(query, targetId) {
|
|
1853
|
+
if (query.toMs <= query.fromMs) {
|
|
1854
|
+
return { ok: false, error: { kind: "invalid-query", message: "--to must be after --from" } };
|
|
1855
|
+
}
|
|
1856
|
+
const invalidMeasurement = query.measurements.find((measurement) => !isMeasurement(measurement));
|
|
1857
|
+
if (invalidMeasurement !== void 0) {
|
|
1858
|
+
return {
|
|
1859
|
+
ok: false,
|
|
1860
|
+
error: {
|
|
1861
|
+
kind: "invalid-query",
|
|
1862
|
+
message: `Unknown measurement ${invalidMeasurement}; use count or <field>.<sum|min|max|last|count|avg>`
|
|
1863
|
+
}
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
const versions = this.registeredVersions(query.metric);
|
|
1867
|
+
if (versions.length === 0) {
|
|
1868
|
+
return {
|
|
1869
|
+
ok: false,
|
|
1870
|
+
error: { kind: "invalid-query", message: `Metric ${query.metric} has no projection in history.db` }
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
const resolutionMs = this.resolveResolution(query, versions);
|
|
1874
|
+
if (resolutionMs === null) {
|
|
1875
|
+
return {
|
|
1876
|
+
ok: false,
|
|
1877
|
+
error: { kind: "invalid-query", message: `Metric ${query.metric} is not projected at the requested resolution` }
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
for (let index = versions.length - 1; index >= 0; index -= 1) {
|
|
1881
|
+
const version = versions[index];
|
|
1882
|
+
const buckets = this.readBuckets(query, version, resolutionMs);
|
|
1883
|
+
if (buckets.length === 0) continue;
|
|
1884
|
+
const olderVersions = versions.slice(0, index);
|
|
1885
|
+
return {
|
|
1886
|
+
ok: true,
|
|
1887
|
+
value: Object.freeze({
|
|
1888
|
+
targetId,
|
|
1889
|
+
metric: query.metric,
|
|
1890
|
+
segments: [Object.freeze({
|
|
1891
|
+
fromMs: query.fromMs,
|
|
1892
|
+
toMs: query.toMs,
|
|
1893
|
+
resolutionMs,
|
|
1894
|
+
isAggregated: true,
|
|
1895
|
+
definitionVersion: version,
|
|
1896
|
+
buckets
|
|
1897
|
+
})],
|
|
1898
|
+
gaps: [],
|
|
1899
|
+
nextCursor: null,
|
|
1900
|
+
partialFailures: olderVersions.map((older) => Object.freeze({
|
|
1901
|
+
targetId,
|
|
1902
|
+
reason: `version ${older} buckets omitted; only version ${version} returned`
|
|
1903
|
+
}))
|
|
1904
|
+
})
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
return {
|
|
1908
|
+
ok: true,
|
|
1909
|
+
value: Object.freeze({
|
|
1910
|
+
targetId,
|
|
1911
|
+
metric: query.metric,
|
|
1912
|
+
segments: [],
|
|
1913
|
+
gaps: [Object.freeze({ fromMs: query.fromMs, toMs: query.toMs, reason: "not-materialized" })],
|
|
1914
|
+
nextCursor: null,
|
|
1915
|
+
partialFailures: []
|
|
1916
|
+
})
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
close() {
|
|
1920
|
+
if (this.db.open) this.db.close();
|
|
1921
|
+
}
|
|
1922
|
+
registeredVersions(metric) {
|
|
1923
|
+
const rows = this.db.prepare(
|
|
1924
|
+
"SELECT projection_version FROM trace_projection_definitions WHERE projection_name = ? ORDER BY projection_version"
|
|
1925
|
+
).all(metric);
|
|
1926
|
+
return rows.map((row) => row.projection_version);
|
|
1927
|
+
}
|
|
1928
|
+
resolveResolution(query, versions) {
|
|
1929
|
+
const available = this.definitions.filter((definition) => definition.name === query.metric && versions.includes(definition.version)).flatMap((definition) => [...definition.resolutionsMs]);
|
|
1930
|
+
const resolutions = [...new Set(available.length > 0 ? available : this.storedResolutions(query.metric))].sort((a, b) => a - b);
|
|
1931
|
+
if (resolutions.length === 0) return null;
|
|
1932
|
+
if (query.resolution !== "auto") {
|
|
1933
|
+
return resolutions.includes(query.resolution) ? query.resolution : null;
|
|
1934
|
+
}
|
|
1935
|
+
const span = query.toMs - query.fromMs;
|
|
1936
|
+
return resolutions.find((resolution) => Math.ceil(span / resolution) <= query.limit) ?? resolutions[resolutions.length - 1];
|
|
1937
|
+
}
|
|
1938
|
+
storedResolutions(metric) {
|
|
1939
|
+
const rows = this.db.prepare(
|
|
1940
|
+
"SELECT DISTINCT resolution_ms FROM trace_metric_buckets WHERE projection_name = ?"
|
|
1941
|
+
).all(metric);
|
|
1942
|
+
return rows.map((row) => row.resolution_ms);
|
|
1943
|
+
}
|
|
1944
|
+
readBuckets(query, version, resolutionMs) {
|
|
1945
|
+
const rows = this.db.prepare(`
|
|
1946
|
+
SELECT projection_version, bucket_start_ms, dimensions_json, count, values_json
|
|
1947
|
+
FROM trace_metric_buckets
|
|
1948
|
+
WHERE projection_name = ? AND projection_version = ? AND resolution_ms = ?
|
|
1949
|
+
AND bucket_start_ms >= ? AND bucket_start_ms < ?
|
|
1950
|
+
ORDER BY bucket_start_ms ${query.order === "desc" ? "DESC" : "ASC"}, dimension_key
|
|
1951
|
+
`).all(query.metric, version, resolutionMs, query.fromMs, query.toMs);
|
|
1952
|
+
const buckets = [];
|
|
1953
|
+
for (const row of rows) {
|
|
1954
|
+
const dimensions = JSON.parse(row.dimensions_json);
|
|
1955
|
+
if (!matchesWhere(dimensions, query.where)) continue;
|
|
1956
|
+
const values = JSON.parse(row.values_json);
|
|
1957
|
+
buckets.push(Object.freeze({
|
|
1958
|
+
startMs: row.bucket_start_ms,
|
|
1959
|
+
endMs: row.bucket_start_ms + resolutionMs,
|
|
1960
|
+
dimensions,
|
|
1961
|
+
values: selectMeasurements(query.measurements, row.count, values)
|
|
1962
|
+
}));
|
|
1963
|
+
if (buckets.length >= query.limit) break;
|
|
1964
|
+
}
|
|
1965
|
+
return buckets;
|
|
1966
|
+
}
|
|
1967
|
+
};
|
|
1968
|
+
function isMeasurement(measurement) {
|
|
1969
|
+
if (measurement === "count") return true;
|
|
1970
|
+
const dot = measurement.lastIndexOf(".");
|
|
1971
|
+
return dot > 0 && AGGREGATES.has(measurement.slice(dot + 1));
|
|
1972
|
+
}
|
|
1973
|
+
function selectMeasurements(measurements, bucketCount, values) {
|
|
1974
|
+
const selected = {};
|
|
1975
|
+
for (const measurement of measurements) {
|
|
1976
|
+
if (measurement === "count") {
|
|
1977
|
+
selected.count = bucketCount;
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
const dot = measurement.lastIndexOf(".");
|
|
1981
|
+
const field = measurement.slice(0, dot);
|
|
1982
|
+
const aggregate = measurement.slice(dot + 1);
|
|
1983
|
+
const state = values[field];
|
|
1984
|
+
if (state === void 0) continue;
|
|
1985
|
+
const value = aggregate === "avg" ? state.count > 0 ? state.sum / state.count : 0 : state[aggregate];
|
|
1986
|
+
if (!Number.isFinite(value)) continue;
|
|
1987
|
+
const existing = selected[field];
|
|
1988
|
+
selected[field] = { ...typeof existing === "object" ? existing : {}, [aggregate]: value };
|
|
1989
|
+
}
|
|
1990
|
+
return Object.freeze(selected);
|
|
1991
|
+
}
|
|
1992
|
+
function matchesWhere(dimensions, where) {
|
|
1993
|
+
for (const [key, filter] of Object.entries(where)) {
|
|
1994
|
+
const actual = dimensions[key];
|
|
1995
|
+
if (actual === void 0) return false;
|
|
1996
|
+
const accepted = Array.isArray(filter) ? filter : [filter];
|
|
1997
|
+
if (!accepted.some((candidate) => String(candidate) === actual)) return false;
|
|
1998
|
+
}
|
|
1999
|
+
return true;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
1823
2002
|
// src/cli/offline_sqlite_session.ts
|
|
1824
2003
|
var import_node_crypto2 = require("crypto");
|
|
1825
|
-
var
|
|
2004
|
+
var import_node_fs4 = require("fs");
|
|
2005
|
+
var import_node_path4 = require("path");
|
|
1826
2006
|
var import_node_url = require("url");
|
|
1827
2007
|
var import_proper = require("@noego/proper");
|
|
1828
|
-
var
|
|
2008
|
+
var import_better_sqlite32 = __toESM(require("better-sqlite3"), 1);
|
|
1829
2009
|
var import_adapters = require("sqlstack/adapters");
|
|
1830
2010
|
|
|
1831
2011
|
// src/storage/sqlite_ownership_lease.ts
|
|
1832
|
-
var
|
|
1833
|
-
var
|
|
2012
|
+
var import_node_fs3 = require("fs");
|
|
2013
|
+
var import_node_path3 = require("path");
|
|
1834
2014
|
var SqliteOwnershipLease = class {
|
|
1835
2015
|
constructor(options) {
|
|
1836
2016
|
this.options = options;
|
|
1837
|
-
(0,
|
|
1838
|
-
const canonicalDirectory = (0,
|
|
1839
|
-
const databaseName = (0,
|
|
1840
|
-
this.canonicalDatabasePath = (0,
|
|
2017
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(options.databasePath), { recursive: true });
|
|
2018
|
+
const canonicalDirectory = (0, import_node_fs3.realpathSync)((0, import_node_path3.dirname)((0, import_node_path3.resolve)(options.databasePath)));
|
|
2019
|
+
const databaseName = (0, import_node_path3.resolve)(options.databasePath).slice((0, import_node_path3.dirname)((0, import_node_path3.resolve)(options.databasePath)).length + 1);
|
|
2020
|
+
this.canonicalDatabasePath = (0, import_node_path3.resolve)(canonicalDirectory, databaseName);
|
|
1841
2021
|
this.leasePath = `${this.canonicalDatabasePath}.telemetry-owner.json`;
|
|
1842
2022
|
}
|
|
1843
2023
|
options;
|
|
@@ -1880,14 +2060,14 @@ var SqliteOwnershipLease = class {
|
|
|
1880
2060
|
});
|
|
1881
2061
|
}
|
|
1882
2062
|
release() {
|
|
1883
|
-
if (!this.held || !(0,
|
|
2063
|
+
if (!this.held || !(0, import_node_fs3.existsSync)(this.leasePath)) {
|
|
1884
2064
|
this.held = false;
|
|
1885
2065
|
return;
|
|
1886
2066
|
}
|
|
1887
2067
|
const current = this.readRecord();
|
|
1888
2068
|
if (current.ok && current.value.processToken === this.options.processToken) {
|
|
1889
2069
|
try {
|
|
1890
|
-
(0,
|
|
2070
|
+
(0, import_node_fs3.rmSync)(this.leasePath, { force: true });
|
|
1891
2071
|
} catch {
|
|
1892
2072
|
}
|
|
1893
2073
|
}
|
|
@@ -1916,14 +2096,14 @@ var SqliteOwnershipLease = class {
|
|
|
1916
2096
|
writeExclusive(record) {
|
|
1917
2097
|
let descriptor = null;
|
|
1918
2098
|
try {
|
|
1919
|
-
descriptor = (0,
|
|
1920
|
-
(0,
|
|
1921
|
-
(0,
|
|
2099
|
+
descriptor = (0, import_node_fs3.openSync)(this.leasePath, "wx", 384);
|
|
2100
|
+
(0, import_node_fs3.writeFileSync)(descriptor, JSON.stringify(record), { encoding: "utf8" });
|
|
2101
|
+
(0, import_node_fs3.closeSync)(descriptor);
|
|
1922
2102
|
return { ok: true, value: void 0 };
|
|
1923
2103
|
} catch (cause) {
|
|
1924
2104
|
if (descriptor !== null) {
|
|
1925
2105
|
try {
|
|
1926
|
-
(0,
|
|
2106
|
+
(0, import_node_fs3.closeSync)(descriptor);
|
|
1927
2107
|
} catch {
|
|
1928
2108
|
}
|
|
1929
2109
|
}
|
|
@@ -1933,12 +2113,12 @@ var SqliteOwnershipLease = class {
|
|
|
1933
2113
|
writeReplace(record) {
|
|
1934
2114
|
const temporaryPath = `${this.leasePath}.${this.options.processToken}.tmp`;
|
|
1935
2115
|
try {
|
|
1936
|
-
(0,
|
|
1937
|
-
(0,
|
|
2116
|
+
(0, import_node_fs3.writeFileSync)(temporaryPath, JSON.stringify(record), { encoding: "utf8", mode: 384 });
|
|
2117
|
+
(0, import_node_fs3.renameSync)(temporaryPath, this.leasePath);
|
|
1938
2118
|
return { ok: true, value: void 0 };
|
|
1939
2119
|
} catch (cause) {
|
|
1940
2120
|
try {
|
|
1941
|
-
(0,
|
|
2121
|
+
(0, import_node_fs3.rmSync)(temporaryPath, { force: true });
|
|
1942
2122
|
} catch {
|
|
1943
2123
|
}
|
|
1944
2124
|
return { ok: false, error: this.errorMessage(cause) };
|
|
@@ -1946,7 +2126,7 @@ var SqliteOwnershipLease = class {
|
|
|
1946
2126
|
}
|
|
1947
2127
|
readRecord() {
|
|
1948
2128
|
try {
|
|
1949
|
-
const parsed = JSON.parse((0,
|
|
2129
|
+
const parsed = JSON.parse((0, import_node_fs3.readFileSync)(this.leasePath, "utf8"));
|
|
1950
2130
|
if (!this.isLeaseRecord(parsed)) {
|
|
1951
2131
|
return { ok: false, error: `Invalid telemetry ownership lease at ${this.leasePath}` };
|
|
1952
2132
|
}
|
|
@@ -1970,8 +2150,8 @@ var SqliteOwnershipLease = class {
|
|
|
1970
2150
|
}
|
|
1971
2151
|
try {
|
|
1972
2152
|
const stalePath = `${this.leasePath}.stale.${current.value.processToken}`;
|
|
1973
|
-
(0,
|
|
1974
|
-
(0,
|
|
2153
|
+
(0, import_node_fs3.renameSync)(this.leasePath, stalePath);
|
|
2154
|
+
(0, import_node_fs3.rmSync)(stalePath, { force: true });
|
|
1975
2155
|
return { ok: true, value: void 0 };
|
|
1976
2156
|
} catch (cause) {
|
|
1977
2157
|
return { ok: false, error: this.errorMessage(cause) };
|
|
@@ -4366,7 +4546,7 @@ var ReadOnlySqlGuard = class {
|
|
|
4366
4546
|
|
|
4367
4547
|
// src/cli/offline_sqlite_session.ts
|
|
4368
4548
|
var import_meta = {};
|
|
4369
|
-
var MIGRATIONS_DIRECTORY = typeof __dirname === "string" ? (0,
|
|
4549
|
+
var MIGRATIONS_DIRECTORY = typeof __dirname === "string" ? (0, import_node_path4.resolve)(__dirname, "../../migrations") : (0, import_node_url.fileURLToPath)(new URL("../../migrations/", import_meta.url));
|
|
4370
4550
|
var OfflineSqliteTelemetrySession = class _OfflineSqliteTelemetrySession {
|
|
4371
4551
|
constructor(target, mode, definitions, now, sqlGuard) {
|
|
4372
4552
|
this.target = target;
|
|
@@ -4508,6 +4688,87 @@ var OfflineSqliteTelemetrySession = class _OfflineSqliteTelemetrySession {
|
|
|
4508
4688
|
this.verifySchema();
|
|
4509
4689
|
return this.migrationStatus();
|
|
4510
4690
|
}
|
|
4691
|
+
async compact() {
|
|
4692
|
+
this.requireMaintenanceMode();
|
|
4693
|
+
const connection = this.requireConnection();
|
|
4694
|
+
const maintenanceTable = this.requireTableMap().physical("maintenanceRuns");
|
|
4695
|
+
const runId = (0, import_node_crypto2.randomUUID)();
|
|
4696
|
+
const startedAtMs = this.now();
|
|
4697
|
+
connection.prepare(
|
|
4698
|
+
`INSERT INTO "${maintenanceTable}" (
|
|
4699
|
+
target_id, run_id, state, stage, started_at_ms,
|
|
4700
|
+
completed_at_ms, affected_rows, affected_pages, detail
|
|
4701
|
+
) VALUES (?, ?, 'started', 'compaction', ?, NULL, 0, 0, ?)`
|
|
4702
|
+
).run(this.target.targetId, runId, startedAtMs, "offline-cli; outcome=started");
|
|
4703
|
+
try {
|
|
4704
|
+
const before = this.compactionSnapshot();
|
|
4705
|
+
connection.pragma("wal_checkpoint(TRUNCATE)");
|
|
4706
|
+
const checkpointed = this.compactionSnapshot();
|
|
4707
|
+
this.assertCompactionSpace(checkpointed);
|
|
4708
|
+
connection.pragma("auto_vacuum = INCREMENTAL");
|
|
4709
|
+
connection.exec("VACUUM");
|
|
4710
|
+
connection.pragma("wal_checkpoint(TRUNCATE)");
|
|
4711
|
+
const after = this.compactionSnapshot();
|
|
4712
|
+
if (after.autoVacuum !== 2) {
|
|
4713
|
+
throw new Error("Telemetry compaction did not enable auto_vacuum=INCREMENTAL");
|
|
4714
|
+
}
|
|
4715
|
+
const completedAtMs = this.now();
|
|
4716
|
+
const result = Object.freeze({
|
|
4717
|
+
targetId: this.target.targetId,
|
|
4718
|
+
autoVacuum: "incremental",
|
|
4719
|
+
pageSize: checkpointed.pageSize,
|
|
4720
|
+
pagesBefore: checkpointed.pageCount,
|
|
4721
|
+
pagesAfter: after.pageCount,
|
|
4722
|
+
freePagesBefore: checkpointed.freePages,
|
|
4723
|
+
freePagesAfter: after.freePages,
|
|
4724
|
+
reclaimedPages: Math.max(0, checkpointed.pageCount - after.pageCount),
|
|
4725
|
+
databaseBytesBefore: before.databaseBytes,
|
|
4726
|
+
databaseBytesAfter: after.databaseBytes,
|
|
4727
|
+
walBytesBefore: before.walBytes,
|
|
4728
|
+
walBytesAfter: after.walBytes,
|
|
4729
|
+
reclaimedBytes: Math.max(
|
|
4730
|
+
0,
|
|
4731
|
+
before.databaseBytes + before.walBytes - after.databaseBytes - after.walBytes
|
|
4732
|
+
),
|
|
4733
|
+
elapsedMs: completedAtMs - startedAtMs
|
|
4734
|
+
});
|
|
4735
|
+
connection.prepare(
|
|
4736
|
+
`UPDATE "${maintenanceTable}"
|
|
4737
|
+
SET state = 'completed', completed_at_ms = ?, affected_pages = ?, detail = ?
|
|
4738
|
+
WHERE target_id = ? AND run_id = ? AND stage = 'compaction'`
|
|
4739
|
+
).run(
|
|
4740
|
+
completedAtMs,
|
|
4741
|
+
result.reclaimedPages,
|
|
4742
|
+
JSON.stringify({
|
|
4743
|
+
mode: "offline-cli",
|
|
4744
|
+
outcome: "completed",
|
|
4745
|
+
reclaimedBytes: result.reclaimedBytes,
|
|
4746
|
+
walBytesBefore: result.walBytesBefore,
|
|
4747
|
+
walBytesAfter: result.walBytesAfter,
|
|
4748
|
+
autoVacuum: result.autoVacuum
|
|
4749
|
+
}),
|
|
4750
|
+
this.target.targetId,
|
|
4751
|
+
runId
|
|
4752
|
+
);
|
|
4753
|
+
return result;
|
|
4754
|
+
} catch (cause) {
|
|
4755
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
4756
|
+
try {
|
|
4757
|
+
connection.prepare(
|
|
4758
|
+
`UPDATE "${maintenanceTable}"
|
|
4759
|
+
SET state = 'failed', completed_at_ms = ?, detail = ?
|
|
4760
|
+
WHERE target_id = ? AND run_id = ? AND stage = 'compaction'`
|
|
4761
|
+
).run(
|
|
4762
|
+
this.now(),
|
|
4763
|
+
JSON.stringify({ mode: "offline-cli", outcome: "failed", error: message }),
|
|
4764
|
+
this.target.targetId,
|
|
4765
|
+
runId
|
|
4766
|
+
);
|
|
4767
|
+
} catch {
|
|
4768
|
+
}
|
|
4769
|
+
throw cause;
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4511
4772
|
async close() {
|
|
4512
4773
|
try {
|
|
4513
4774
|
if (this.connection?.open) {
|
|
@@ -4541,7 +4802,7 @@ var OfflineSqliteTelemetrySession = class _OfflineSqliteTelemetrySession {
|
|
|
4541
4802
|
throw new Error(acquired.error);
|
|
4542
4803
|
}
|
|
4543
4804
|
}
|
|
4544
|
-
const connection = new
|
|
4805
|
+
const connection = new import_better_sqlite32.default(this.target.path, {
|
|
4545
4806
|
readonly: this.mode === "query",
|
|
4546
4807
|
fileMustExist: true
|
|
4547
4808
|
});
|
|
@@ -4568,6 +4829,32 @@ var OfflineSqliteTelemetrySession = class _OfflineSqliteTelemetrySession {
|
|
|
4568
4829
|
}
|
|
4569
4830
|
}
|
|
4570
4831
|
}
|
|
4832
|
+
compactionSnapshot() {
|
|
4833
|
+
const connection = this.requireConnection();
|
|
4834
|
+
return Object.freeze({
|
|
4835
|
+
pageSize: Number(connection.pragma("page_size", { simple: true })),
|
|
4836
|
+
pageCount: Number(connection.pragma("page_count", { simple: true })),
|
|
4837
|
+
freePages: Number(connection.pragma("freelist_count", { simple: true })),
|
|
4838
|
+
autoVacuum: Number(connection.pragma("auto_vacuum", { simple: true })),
|
|
4839
|
+
databaseBytes: this.fileBytes(this.target.path),
|
|
4840
|
+
walBytes: this.fileBytes(`${this.target.path}-wal`)
|
|
4841
|
+
});
|
|
4842
|
+
}
|
|
4843
|
+
assertCompactionSpace(snapshot) {
|
|
4844
|
+
const liveBytes = Math.max(0, snapshot.pageCount - snapshot.freePages) * snapshot.pageSize;
|
|
4845
|
+
const reserveBytes = Math.max(64 * 1024 * 1024, Math.ceil(liveBytes * 0.05));
|
|
4846
|
+
const filesystem = (0, import_node_fs4.statfsSync)((0, import_node_path4.dirname)(this.target.path));
|
|
4847
|
+
const availableBytes = Number(filesystem.bavail) * Number(filesystem.bsize);
|
|
4848
|
+
const requiredBytes = liveBytes + reserveBytes;
|
|
4849
|
+
if (availableBytes < requiredBytes) {
|
|
4850
|
+
throw new Error(
|
|
4851
|
+
`Telemetry compaction requires at least ${requiredBytes} free bytes; only ${availableBytes} are available`
|
|
4852
|
+
);
|
|
4853
|
+
}
|
|
4854
|
+
}
|
|
4855
|
+
fileBytes(path) {
|
|
4856
|
+
return (0, import_node_fs4.existsSync)(path) ? (0, import_node_fs4.statSync)(path).size : 0;
|
|
4857
|
+
}
|
|
4571
4858
|
verifyManifest() {
|
|
4572
4859
|
const rows = this.requireConnection().prepare(
|
|
4573
4860
|
`SELECT target_id, format_version, mapping_json, mapping_checksum,
|
|
@@ -4752,7 +5039,8 @@ var ALLOWED_OPTIONS = Object.freeze({
|
|
|
4752
5039
|
"metric",
|
|
4753
5040
|
"where",
|
|
4754
5041
|
"measurement",
|
|
4755
|
-
"resolution"
|
|
5042
|
+
"resolution",
|
|
5043
|
+
"source"
|
|
4756
5044
|
]),
|
|
4757
5045
|
"metrics-graph": /* @__PURE__ */ new Set([
|
|
4758
5046
|
...COMMON_QUERY_OPTIONS,
|
|
@@ -4760,6 +5048,7 @@ var ALLOWED_OPTIONS = Object.freeze({
|
|
|
4760
5048
|
"where",
|
|
4761
5049
|
"measurement",
|
|
4762
5050
|
"resolution",
|
|
5051
|
+
"source",
|
|
4763
5052
|
"type",
|
|
4764
5053
|
"output",
|
|
4765
5054
|
"width",
|
|
@@ -4777,6 +5066,7 @@ var ALLOWED_OPTIONS = Object.freeze({
|
|
|
4777
5066
|
"limit",
|
|
4778
5067
|
"max-duration"
|
|
4779
5068
|
]),
|
|
5069
|
+
"sqlite-compact": /* @__PURE__ */ new Set(["config", "target", "storage-base", "format"]),
|
|
4780
5070
|
"sqlite-migration-status": /* @__PURE__ */ new Set(["config", "target", "storage-base", "format"]),
|
|
4781
5071
|
"sqlite-migration-up": /* @__PURE__ */ new Set(["config", "target", "storage-base", "format"])
|
|
4782
5072
|
});
|
|
@@ -4786,6 +5076,7 @@ var COMMANDS = Object.freeze([
|
|
|
4786
5076
|
{ tokens: ["metrics", "query"], kind: "metrics-query" },
|
|
4787
5077
|
{ tokens: ["metrics", "graph"], kind: "metrics-graph" },
|
|
4788
5078
|
{ tokens: ["sqlite", "query"], kind: "sqlite-query" },
|
|
5079
|
+
{ tokens: ["sqlite", "compact"], kind: "sqlite-compact" },
|
|
4789
5080
|
{ tokens: ["sqlite", "migration", "status"], kind: "sqlite-migration-status" },
|
|
4790
5081
|
{ tokens: ["sqlite", "migration", "up"], kind: "sqlite-migration-up" }
|
|
4791
5082
|
]);
|
|
@@ -5064,6 +5355,22 @@ var TelemetryCliFormatter = class {
|
|
|
5064
5355
|
return `${lines.join("\n")}
|
|
5065
5356
|
`;
|
|
5066
5357
|
}
|
|
5358
|
+
compaction(result, format) {
|
|
5359
|
+
if (format === "json") {
|
|
5360
|
+
return this.json(result);
|
|
5361
|
+
}
|
|
5362
|
+
return [
|
|
5363
|
+
`Target: ${result.targetId}`,
|
|
5364
|
+
`Auto-vacuum: ${result.autoVacuum}`,
|
|
5365
|
+
`Pages: ${result.pagesBefore} -> ${result.pagesAfter} (${result.reclaimedPages} reclaimed)`,
|
|
5366
|
+
`Freelist: ${result.freePagesBefore} -> ${result.freePagesAfter}`,
|
|
5367
|
+
`Database bytes: ${result.databaseBytesBefore} -> ${result.databaseBytesAfter}`,
|
|
5368
|
+
`WAL bytes: ${result.walBytesBefore} -> ${result.walBytesAfter}`,
|
|
5369
|
+
`Filesystem bytes reclaimed: ${result.reclaimedBytes}`,
|
|
5370
|
+
`Elapsed ms: ${result.elapsedMs}`,
|
|
5371
|
+
""
|
|
5372
|
+
].join("\n");
|
|
5373
|
+
}
|
|
5067
5374
|
migrations(result, format) {
|
|
5068
5375
|
if (format === "json") {
|
|
5069
5376
|
return this.json(result);
|
|
@@ -5140,7 +5447,7 @@ var TelemetryCli = class {
|
|
|
5140
5447
|
this.configurationParser = options.configurationParser ?? new NodeTelemetryConfigurationParser();
|
|
5141
5448
|
this.stdout = options.stdout ?? ((text) => process.stdout.write(text));
|
|
5142
5449
|
this.stderr = options.stderr ?? ((text) => process.stderr.write(text));
|
|
5143
|
-
this.writeOutputFile = options.writeOutputFile ?? ((path, content) => (0,
|
|
5450
|
+
this.writeOutputFile = options.writeOutputFile ?? ((path, content) => (0, import_node_fs5.writeFileSync)(path, content, "utf8"));
|
|
5144
5451
|
this.now = options.now ?? (() => Date.now());
|
|
5145
5452
|
this.createSession = options.createSession ?? ((configuration, target, mode) => OfflineSqliteTelemetrySession.create(target, mode, configuration.metrics, this.now));
|
|
5146
5453
|
}
|
|
@@ -5171,6 +5478,10 @@ var TelemetryCli = class {
|
|
|
5171
5478
|
storageBaseDirectory === null ? {} : { storageBaseDirectory }
|
|
5172
5479
|
);
|
|
5173
5480
|
const target = this.resolveTarget(configuration, this.optionalOption(command, "target"));
|
|
5481
|
+
if (this.metricSource(command, target) === "history") {
|
|
5482
|
+
await this.executeHistoryMetrics(command, target, configuration);
|
|
5483
|
+
return 0;
|
|
5484
|
+
}
|
|
5174
5485
|
const mode = this.sessionMode(command);
|
|
5175
5486
|
const session = await this.createSession(configuration, target, mode);
|
|
5176
5487
|
this.activeSession = session;
|
|
@@ -5184,23 +5495,61 @@ var TelemetryCli = class {
|
|
|
5184
5495
|
}
|
|
5185
5496
|
}
|
|
5186
5497
|
}
|
|
5498
|
+
/**
|
|
5499
|
+
* Which store answers a metric command. `history` is the projector's
|
|
5500
|
+
* pre-aggregated buckets (history.db, current whenever the app runs with
|
|
5501
|
+
* ingest-time extraction off); `legacy` is the collector's own rollups in
|
|
5502
|
+
* telemetry.db. `auto` prefers history.db when it exists beside the target —
|
|
5503
|
+
* since ingestion moved to the projector the legacy rollups stopped
|
|
5504
|
+
* advancing, so answering from them silently reports stale gaps.
|
|
5505
|
+
*/
|
|
5506
|
+
metricSource(command, target) {
|
|
5507
|
+
if (command.kind !== "metrics-query" && command.kind !== "metrics-graph") return "legacy";
|
|
5508
|
+
const requested = this.optionalOption(command, "source") ?? "auto";
|
|
5509
|
+
if (requested === "history" || requested === "legacy") return requested;
|
|
5510
|
+
if (requested !== "auto") {
|
|
5511
|
+
throw new TelemetryCliUsageError("--source must be auto, history, or legacy");
|
|
5512
|
+
}
|
|
5513
|
+
return HistoryMetricReader.exists(historyDatabasePath(target)) ? "history" : "legacy";
|
|
5514
|
+
}
|
|
5515
|
+
async executeHistoryMetrics(command, target, configuration) {
|
|
5516
|
+
const databasePath = historyDatabasePath(target);
|
|
5517
|
+
if (!HistoryMetricReader.exists(databasePath)) {
|
|
5518
|
+
throw new TelemetryCliUsageError(`No projector history store at ${databasePath}`);
|
|
5519
|
+
}
|
|
5520
|
+
const reader = HistoryMetricReader.open(databasePath, configuration.metrics);
|
|
5521
|
+
try {
|
|
5522
|
+
const run = async (query) => reader.metrics(query, target.targetId);
|
|
5523
|
+
if (command.kind === "metrics-graph") {
|
|
5524
|
+
await this.metricGraph(command, target, run);
|
|
5525
|
+
} else {
|
|
5526
|
+
await this.metricQuery(command, target, run);
|
|
5527
|
+
}
|
|
5528
|
+
} finally {
|
|
5529
|
+
reader.close();
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5187
5532
|
async executeCommand(command, target, session) {
|
|
5188
5533
|
if (command.kind === "timeline-query") {
|
|
5189
5534
|
await this.timelineQuery(command, target, session);
|
|
5190
5535
|
return;
|
|
5191
5536
|
}
|
|
5192
5537
|
if (command.kind === "metrics-query") {
|
|
5193
|
-
await this.metricQuery(command, target, session);
|
|
5538
|
+
await this.metricQuery(command, target, (query) => session.metrics(query));
|
|
5194
5539
|
return;
|
|
5195
5540
|
}
|
|
5196
5541
|
if (command.kind === "metrics-graph") {
|
|
5197
|
-
await this.metricGraph(command, target, session);
|
|
5542
|
+
await this.metricGraph(command, target, (query) => session.metrics(query));
|
|
5198
5543
|
return;
|
|
5199
5544
|
}
|
|
5200
5545
|
if (command.kind === "sqlite-query") {
|
|
5201
5546
|
await this.sqliteQuery(command, target, session);
|
|
5202
5547
|
return;
|
|
5203
5548
|
}
|
|
5549
|
+
if (command.kind === "sqlite-compact") {
|
|
5550
|
+
await this.sqliteCompact(command, target, session);
|
|
5551
|
+
return;
|
|
5552
|
+
}
|
|
5204
5553
|
if (command.kind === "sqlite-migration-status" || command.kind === "sqlite-migration-up") {
|
|
5205
5554
|
await this.sqliteMigration(command, target, session);
|
|
5206
5555
|
return;
|
|
@@ -5230,8 +5579,8 @@ var TelemetryCli = class {
|
|
|
5230
5579
|
const output = this.formatter.timeline(result.value, format, this.timezoneOption(command));
|
|
5231
5580
|
this.writeBounded(output, target.query.maxResponseBytes);
|
|
5232
5581
|
}
|
|
5233
|
-
async metricQuery(command, target,
|
|
5234
|
-
const result = await
|
|
5582
|
+
async metricQuery(command, target, runMetrics) {
|
|
5583
|
+
const result = await runMetrics(this.metricQueryInput(command, target));
|
|
5235
5584
|
if (!result.ok) {
|
|
5236
5585
|
throw new Error(`${result.error.kind}: ${result.error.message}`);
|
|
5237
5586
|
}
|
|
@@ -5242,12 +5591,12 @@ var TelemetryCli = class {
|
|
|
5242
5591
|
);
|
|
5243
5592
|
this.writeBounded(output, target.query.maxResponseBytes);
|
|
5244
5593
|
}
|
|
5245
|
-
async metricGraph(command, target,
|
|
5594
|
+
async metricGraph(command, target, runMetrics) {
|
|
5246
5595
|
const measurements = this.options(command, "measurement");
|
|
5247
5596
|
if (measurements.length !== 1) {
|
|
5248
5597
|
throw new TelemetryCliUsageError("metrics graph requires exactly one --measurement");
|
|
5249
5598
|
}
|
|
5250
|
-
const result = await
|
|
5599
|
+
const result = await runMetrics(this.metricQueryInput(command, target));
|
|
5251
5600
|
if (!result.ok) {
|
|
5252
5601
|
throw new Error(`${result.error.kind}: ${result.error.message}`);
|
|
5253
5602
|
}
|
|
@@ -5298,6 +5647,14 @@ var TelemetryCli = class {
|
|
|
5298
5647
|
}
|
|
5299
5648
|
this.writeBounded(this.formatter.migrations(result, format), target.query.maxResponseBytes);
|
|
5300
5649
|
}
|
|
5650
|
+
async sqliteCompact(command, target, session) {
|
|
5651
|
+
const format = this.optionalOption(command, "format") ?? "table";
|
|
5652
|
+
if (format !== "table" && format !== "json") {
|
|
5653
|
+
throw new TelemetryCliUsageError("Compaction format must be table or json");
|
|
5654
|
+
}
|
|
5655
|
+
const result = await session.compact();
|
|
5656
|
+
this.writeBounded(this.formatter.compaction(result, format), target.query.maxResponseBytes);
|
|
5657
|
+
}
|
|
5301
5658
|
metricQueryInput(command, target) {
|
|
5302
5659
|
const measurements = this.options(command, "measurement");
|
|
5303
5660
|
if (measurements.length === 0) {
|
|
@@ -5332,7 +5689,7 @@ var TelemetryCli = class {
|
|
|
5332
5689
|
return target;
|
|
5333
5690
|
}
|
|
5334
5691
|
sessionMode(command) {
|
|
5335
|
-
return command.kind === "sqlite-migration-status" || command.kind === "sqlite-migration-up" ? "maintenance" : "query";
|
|
5692
|
+
return command.kind === "sqlite-compact" || command.kind === "sqlite-migration-status" || command.kind === "sqlite-migration-up" ? "maintenance" : "query";
|
|
5336
5693
|
}
|
|
5337
5694
|
structuredFormat(command) {
|
|
5338
5695
|
const value = this.optionalOption(command, "format") ?? "table";
|
|
@@ -5499,9 +5856,10 @@ var TelemetryCli = class {
|
|
|
5499
5856
|
help() {
|
|
5500
5857
|
return `Usage:
|
|
5501
5858
|
telemetry timeline query --config FILE --from TIME --to TIME [filters] [--format table|json|jsonl|csv]
|
|
5502
|
-
telemetry metrics query --config FILE --metric NAME --measurement NAME --resolution DURATION|auto --from TIME --to TIME [--where KEY=VALUE]
|
|
5503
|
-
telemetry metrics graph --config FILE --metric NAME --measurement NAME --resolution DURATION|auto --from TIME --to TIME [--type line|bar|stacked-bar] [--format terminal|svg|html]
|
|
5859
|
+
telemetry metrics query --config FILE --metric NAME --measurement NAME --resolution DURATION|auto --from TIME --to TIME [--where KEY=VALUE] [--source auto|history|legacy]
|
|
5860
|
+
telemetry metrics graph --config FILE --metric NAME --measurement NAME --resolution DURATION|auto --from TIME --to TIME [--type line|bar|stacked-bar] [--format terminal|svg|html] [--source auto|history|legacy]
|
|
5504
5861
|
telemetry sqlite query --config FILE --sql SQL [--param NAME=VALUE] [--format table|json|jsonl|csv]
|
|
5862
|
+
telemetry sqlite compact --config FILE [--format table|json]
|
|
5505
5863
|
telemetry sqlite migration status --config FILE [--format table|json]
|
|
5506
5864
|
telemetry sqlite migration up --config FILE [--format table|json]
|
|
5507
5865
|
|