@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
|
@@ -25,7 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
));
|
|
26
26
|
|
|
27
27
|
// src/cli/telemetry_cli.ts
|
|
28
|
-
var
|
|
28
|
+
var import_node_fs5 = require("fs");
|
|
29
29
|
|
|
30
30
|
// src/platform/node/telemetry_configuration_parser.ts
|
|
31
31
|
var import_node_path = require("path");
|
|
@@ -90,11 +90,15 @@ var MetricDefinitionContract = class {
|
|
|
90
90
|
if (!eventSelector || !bracket) {
|
|
91
91
|
return { ok: false, error: `Invalid metric selector ${selector}` };
|
|
92
92
|
}
|
|
93
|
+
const groupBy = bracket === "*" ? [] : bracket.split(",").map((field) => field.trim());
|
|
94
|
+
if (groupBy.some((field) => field.length === 0)) {
|
|
95
|
+
return { ok: false, error: `Invalid metric selector ${selector}` };
|
|
96
|
+
}
|
|
93
97
|
return {
|
|
94
98
|
ok: true,
|
|
95
99
|
value: {
|
|
96
100
|
selector: eventSelector,
|
|
97
|
-
groupBy
|
|
101
|
+
groupBy,
|
|
98
102
|
wildcardUngrouped: bracket === "*"
|
|
99
103
|
}
|
|
100
104
|
};
|
|
@@ -1810,24 +1814,200 @@ var MetricGraphRenderer = class {
|
|
|
1810
1814
|
}
|
|
1811
1815
|
};
|
|
1812
1816
|
|
|
1817
|
+
// src/cli/history_metric_reader.ts
|
|
1818
|
+
var import_node_fs2 = require("fs");
|
|
1819
|
+
var import_node_path2 = require("path");
|
|
1820
|
+
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
1821
|
+
var HISTORY_DATABASE_NAME = "history.db";
|
|
1822
|
+
var AGGREGATES = /* @__PURE__ */ new Set(["count", "sum", "min", "max", "last", "avg"]);
|
|
1823
|
+
function historyDatabasePath(target) {
|
|
1824
|
+
return (0, import_node_path2.join)((0, import_node_path2.dirname)(target.path), HISTORY_DATABASE_NAME);
|
|
1825
|
+
}
|
|
1826
|
+
var HistoryMetricReader = class _HistoryMetricReader {
|
|
1827
|
+
constructor(db, definitions) {
|
|
1828
|
+
this.db = db;
|
|
1829
|
+
this.definitions = definitions;
|
|
1830
|
+
}
|
|
1831
|
+
db;
|
|
1832
|
+
definitions;
|
|
1833
|
+
static open(databasePath, definitions) {
|
|
1834
|
+
const db = new import_better_sqlite3.default(databasePath, { readonly: true, fileMustExist: true });
|
|
1835
|
+
db.pragma("busy_timeout = 1000");
|
|
1836
|
+
db.pragma("query_only = ON");
|
|
1837
|
+
return new _HistoryMetricReader(db, definitions);
|
|
1838
|
+
}
|
|
1839
|
+
static exists(databasePath) {
|
|
1840
|
+
return (0, import_node_fs2.existsSync)(databasePath);
|
|
1841
|
+
}
|
|
1842
|
+
metrics(query, targetId) {
|
|
1843
|
+
if (query.toMs <= query.fromMs) {
|
|
1844
|
+
return { ok: false, error: { kind: "invalid-query", message: "--to must be after --from" } };
|
|
1845
|
+
}
|
|
1846
|
+
const invalidMeasurement = query.measurements.find((measurement) => !isMeasurement(measurement));
|
|
1847
|
+
if (invalidMeasurement !== void 0) {
|
|
1848
|
+
return {
|
|
1849
|
+
ok: false,
|
|
1850
|
+
error: {
|
|
1851
|
+
kind: "invalid-query",
|
|
1852
|
+
message: `Unknown measurement ${invalidMeasurement}; use count or <field>.<sum|min|max|last|count|avg>`
|
|
1853
|
+
}
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
const versions = this.registeredVersions(query.metric);
|
|
1857
|
+
if (versions.length === 0) {
|
|
1858
|
+
return {
|
|
1859
|
+
ok: false,
|
|
1860
|
+
error: { kind: "invalid-query", message: `Metric ${query.metric} has no projection in history.db` }
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
const resolutionMs = this.resolveResolution(query, versions);
|
|
1864
|
+
if (resolutionMs === null) {
|
|
1865
|
+
return {
|
|
1866
|
+
ok: false,
|
|
1867
|
+
error: { kind: "invalid-query", message: `Metric ${query.metric} is not projected at the requested resolution` }
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
for (let index = versions.length - 1; index >= 0; index -= 1) {
|
|
1871
|
+
const version = versions[index];
|
|
1872
|
+
const buckets = this.readBuckets(query, version, resolutionMs);
|
|
1873
|
+
if (buckets.length === 0) continue;
|
|
1874
|
+
const olderVersions = versions.slice(0, index);
|
|
1875
|
+
return {
|
|
1876
|
+
ok: true,
|
|
1877
|
+
value: Object.freeze({
|
|
1878
|
+
targetId,
|
|
1879
|
+
metric: query.metric,
|
|
1880
|
+
segments: [Object.freeze({
|
|
1881
|
+
fromMs: query.fromMs,
|
|
1882
|
+
toMs: query.toMs,
|
|
1883
|
+
resolutionMs,
|
|
1884
|
+
isAggregated: true,
|
|
1885
|
+
definitionVersion: version,
|
|
1886
|
+
buckets
|
|
1887
|
+
})],
|
|
1888
|
+
gaps: [],
|
|
1889
|
+
nextCursor: null,
|
|
1890
|
+
partialFailures: olderVersions.map((older) => Object.freeze({
|
|
1891
|
+
targetId,
|
|
1892
|
+
reason: `version ${older} buckets omitted; only version ${version} returned`
|
|
1893
|
+
}))
|
|
1894
|
+
})
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
return {
|
|
1898
|
+
ok: true,
|
|
1899
|
+
value: Object.freeze({
|
|
1900
|
+
targetId,
|
|
1901
|
+
metric: query.metric,
|
|
1902
|
+
segments: [],
|
|
1903
|
+
gaps: [Object.freeze({ fromMs: query.fromMs, toMs: query.toMs, reason: "not-materialized" })],
|
|
1904
|
+
nextCursor: null,
|
|
1905
|
+
partialFailures: []
|
|
1906
|
+
})
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
close() {
|
|
1910
|
+
if (this.db.open) this.db.close();
|
|
1911
|
+
}
|
|
1912
|
+
registeredVersions(metric) {
|
|
1913
|
+
const rows = this.db.prepare(
|
|
1914
|
+
"SELECT projection_version FROM trace_projection_definitions WHERE projection_name = ? ORDER BY projection_version"
|
|
1915
|
+
).all(metric);
|
|
1916
|
+
return rows.map((row) => row.projection_version);
|
|
1917
|
+
}
|
|
1918
|
+
resolveResolution(query, versions) {
|
|
1919
|
+
const available = this.definitions.filter((definition) => definition.name === query.metric && versions.includes(definition.version)).flatMap((definition) => [...definition.resolutionsMs]);
|
|
1920
|
+
const resolutions = [...new Set(available.length > 0 ? available : this.storedResolutions(query.metric))].sort((a, b) => a - b);
|
|
1921
|
+
if (resolutions.length === 0) return null;
|
|
1922
|
+
if (query.resolution !== "auto") {
|
|
1923
|
+
return resolutions.includes(query.resolution) ? query.resolution : null;
|
|
1924
|
+
}
|
|
1925
|
+
const span = query.toMs - query.fromMs;
|
|
1926
|
+
return resolutions.find((resolution) => Math.ceil(span / resolution) <= query.limit) ?? resolutions[resolutions.length - 1];
|
|
1927
|
+
}
|
|
1928
|
+
storedResolutions(metric) {
|
|
1929
|
+
const rows = this.db.prepare(
|
|
1930
|
+
"SELECT DISTINCT resolution_ms FROM trace_metric_buckets WHERE projection_name = ?"
|
|
1931
|
+
).all(metric);
|
|
1932
|
+
return rows.map((row) => row.resolution_ms);
|
|
1933
|
+
}
|
|
1934
|
+
readBuckets(query, version, resolutionMs) {
|
|
1935
|
+
const rows = this.db.prepare(`
|
|
1936
|
+
SELECT projection_version, bucket_start_ms, dimensions_json, count, values_json
|
|
1937
|
+
FROM trace_metric_buckets
|
|
1938
|
+
WHERE projection_name = ? AND projection_version = ? AND resolution_ms = ?
|
|
1939
|
+
AND bucket_start_ms >= ? AND bucket_start_ms < ?
|
|
1940
|
+
ORDER BY bucket_start_ms ${query.order === "desc" ? "DESC" : "ASC"}, dimension_key
|
|
1941
|
+
`).all(query.metric, version, resolutionMs, query.fromMs, query.toMs);
|
|
1942
|
+
const buckets = [];
|
|
1943
|
+
for (const row of rows) {
|
|
1944
|
+
const dimensions = JSON.parse(row.dimensions_json);
|
|
1945
|
+
if (!matchesWhere(dimensions, query.where)) continue;
|
|
1946
|
+
const values = JSON.parse(row.values_json);
|
|
1947
|
+
buckets.push(Object.freeze({
|
|
1948
|
+
startMs: row.bucket_start_ms,
|
|
1949
|
+
endMs: row.bucket_start_ms + resolutionMs,
|
|
1950
|
+
dimensions,
|
|
1951
|
+
values: selectMeasurements(query.measurements, row.count, values)
|
|
1952
|
+
}));
|
|
1953
|
+
if (buckets.length >= query.limit) break;
|
|
1954
|
+
}
|
|
1955
|
+
return buckets;
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1958
|
+
function isMeasurement(measurement) {
|
|
1959
|
+
if (measurement === "count") return true;
|
|
1960
|
+
const dot = measurement.lastIndexOf(".");
|
|
1961
|
+
return dot > 0 && AGGREGATES.has(measurement.slice(dot + 1));
|
|
1962
|
+
}
|
|
1963
|
+
function selectMeasurements(measurements, bucketCount, values) {
|
|
1964
|
+
const selected = {};
|
|
1965
|
+
for (const measurement of measurements) {
|
|
1966
|
+
if (measurement === "count") {
|
|
1967
|
+
selected.count = bucketCount;
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
const dot = measurement.lastIndexOf(".");
|
|
1971
|
+
const field = measurement.slice(0, dot);
|
|
1972
|
+
const aggregate = measurement.slice(dot + 1);
|
|
1973
|
+
const state = values[field];
|
|
1974
|
+
if (state === void 0) continue;
|
|
1975
|
+
const value = aggregate === "avg" ? state.count > 0 ? state.sum / state.count : 0 : state[aggregate];
|
|
1976
|
+
if (!Number.isFinite(value)) continue;
|
|
1977
|
+
const existing = selected[field];
|
|
1978
|
+
selected[field] = { ...typeof existing === "object" ? existing : {}, [aggregate]: value };
|
|
1979
|
+
}
|
|
1980
|
+
return Object.freeze(selected);
|
|
1981
|
+
}
|
|
1982
|
+
function matchesWhere(dimensions, where) {
|
|
1983
|
+
for (const [key, filter] of Object.entries(where)) {
|
|
1984
|
+
const actual = dimensions[key];
|
|
1985
|
+
if (actual === void 0) return false;
|
|
1986
|
+
const accepted = Array.isArray(filter) ? filter : [filter];
|
|
1987
|
+
if (!accepted.some((candidate) => String(candidate) === actual)) return false;
|
|
1988
|
+
}
|
|
1989
|
+
return true;
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1813
1992
|
// src/cli/offline_sqlite_session.ts
|
|
1814
1993
|
var import_node_crypto2 = require("crypto");
|
|
1815
|
-
var
|
|
1994
|
+
var import_node_fs4 = require("fs");
|
|
1995
|
+
var import_node_path4 = require("path");
|
|
1816
1996
|
var import_node_url = require("url");
|
|
1817
1997
|
var import_proper = require("@noego/proper");
|
|
1818
|
-
var
|
|
1998
|
+
var import_better_sqlite32 = __toESM(require("better-sqlite3"), 1);
|
|
1819
1999
|
var import_adapters = require("sqlstack/adapters");
|
|
1820
2000
|
|
|
1821
2001
|
// src/storage/sqlite_ownership_lease.ts
|
|
1822
|
-
var
|
|
1823
|
-
var
|
|
2002
|
+
var import_node_fs3 = require("fs");
|
|
2003
|
+
var import_node_path3 = require("path");
|
|
1824
2004
|
var SqliteOwnershipLease = class {
|
|
1825
2005
|
constructor(options) {
|
|
1826
2006
|
this.options = options;
|
|
1827
|
-
(0,
|
|
1828
|
-
const canonicalDirectory = (0,
|
|
1829
|
-
const databaseName = (0,
|
|
1830
|
-
this.canonicalDatabasePath = (0,
|
|
2007
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(options.databasePath), { recursive: true });
|
|
2008
|
+
const canonicalDirectory = (0, import_node_fs3.realpathSync)((0, import_node_path3.dirname)((0, import_node_path3.resolve)(options.databasePath)));
|
|
2009
|
+
const databaseName = (0, import_node_path3.resolve)(options.databasePath).slice((0, import_node_path3.dirname)((0, import_node_path3.resolve)(options.databasePath)).length + 1);
|
|
2010
|
+
this.canonicalDatabasePath = (0, import_node_path3.resolve)(canonicalDirectory, databaseName);
|
|
1831
2011
|
this.leasePath = `${this.canonicalDatabasePath}.telemetry-owner.json`;
|
|
1832
2012
|
}
|
|
1833
2013
|
options;
|
|
@@ -1870,14 +2050,14 @@ var SqliteOwnershipLease = class {
|
|
|
1870
2050
|
});
|
|
1871
2051
|
}
|
|
1872
2052
|
release() {
|
|
1873
|
-
if (!this.held || !(0,
|
|
2053
|
+
if (!this.held || !(0, import_node_fs3.existsSync)(this.leasePath)) {
|
|
1874
2054
|
this.held = false;
|
|
1875
2055
|
return;
|
|
1876
2056
|
}
|
|
1877
2057
|
const current = this.readRecord();
|
|
1878
2058
|
if (current.ok && current.value.processToken === this.options.processToken) {
|
|
1879
2059
|
try {
|
|
1880
|
-
(0,
|
|
2060
|
+
(0, import_node_fs3.rmSync)(this.leasePath, { force: true });
|
|
1881
2061
|
} catch {
|
|
1882
2062
|
}
|
|
1883
2063
|
}
|
|
@@ -1906,14 +2086,14 @@ var SqliteOwnershipLease = class {
|
|
|
1906
2086
|
writeExclusive(record) {
|
|
1907
2087
|
let descriptor = null;
|
|
1908
2088
|
try {
|
|
1909
|
-
descriptor = (0,
|
|
1910
|
-
(0,
|
|
1911
|
-
(0,
|
|
2089
|
+
descriptor = (0, import_node_fs3.openSync)(this.leasePath, "wx", 384);
|
|
2090
|
+
(0, import_node_fs3.writeFileSync)(descriptor, JSON.stringify(record), { encoding: "utf8" });
|
|
2091
|
+
(0, import_node_fs3.closeSync)(descriptor);
|
|
1912
2092
|
return { ok: true, value: void 0 };
|
|
1913
2093
|
} catch (cause) {
|
|
1914
2094
|
if (descriptor !== null) {
|
|
1915
2095
|
try {
|
|
1916
|
-
(0,
|
|
2096
|
+
(0, import_node_fs3.closeSync)(descriptor);
|
|
1917
2097
|
} catch {
|
|
1918
2098
|
}
|
|
1919
2099
|
}
|
|
@@ -1923,12 +2103,12 @@ var SqliteOwnershipLease = class {
|
|
|
1923
2103
|
writeReplace(record) {
|
|
1924
2104
|
const temporaryPath = `${this.leasePath}.${this.options.processToken}.tmp`;
|
|
1925
2105
|
try {
|
|
1926
|
-
(0,
|
|
1927
|
-
(0,
|
|
2106
|
+
(0, import_node_fs3.writeFileSync)(temporaryPath, JSON.stringify(record), { encoding: "utf8", mode: 384 });
|
|
2107
|
+
(0, import_node_fs3.renameSync)(temporaryPath, this.leasePath);
|
|
1928
2108
|
return { ok: true, value: void 0 };
|
|
1929
2109
|
} catch (cause) {
|
|
1930
2110
|
try {
|
|
1931
|
-
(0,
|
|
2111
|
+
(0, import_node_fs3.rmSync)(temporaryPath, { force: true });
|
|
1932
2112
|
} catch {
|
|
1933
2113
|
}
|
|
1934
2114
|
return { ok: false, error: this.errorMessage(cause) };
|
|
@@ -1936,7 +2116,7 @@ var SqliteOwnershipLease = class {
|
|
|
1936
2116
|
}
|
|
1937
2117
|
readRecord() {
|
|
1938
2118
|
try {
|
|
1939
|
-
const parsed = JSON.parse((0,
|
|
2119
|
+
const parsed = JSON.parse((0, import_node_fs3.readFileSync)(this.leasePath, "utf8"));
|
|
1940
2120
|
if (!this.isLeaseRecord(parsed)) {
|
|
1941
2121
|
return { ok: false, error: `Invalid telemetry ownership lease at ${this.leasePath}` };
|
|
1942
2122
|
}
|
|
@@ -1960,8 +2140,8 @@ var SqliteOwnershipLease = class {
|
|
|
1960
2140
|
}
|
|
1961
2141
|
try {
|
|
1962
2142
|
const stalePath = `${this.leasePath}.stale.${current.value.processToken}`;
|
|
1963
|
-
(0,
|
|
1964
|
-
(0,
|
|
2143
|
+
(0, import_node_fs3.renameSync)(this.leasePath, stalePath);
|
|
2144
|
+
(0, import_node_fs3.rmSync)(stalePath, { force: true });
|
|
1965
2145
|
return { ok: true, value: void 0 };
|
|
1966
2146
|
} catch (cause) {
|
|
1967
2147
|
return { ok: false, error: this.errorMessage(cause) };
|
|
@@ -4356,7 +4536,7 @@ var ReadOnlySqlGuard = class {
|
|
|
4356
4536
|
|
|
4357
4537
|
// src/cli/offline_sqlite_session.ts
|
|
4358
4538
|
var import_meta = {};
|
|
4359
|
-
var MIGRATIONS_DIRECTORY = typeof __dirname === "string" ? (0,
|
|
4539
|
+
var MIGRATIONS_DIRECTORY = typeof __dirname === "string" ? (0, import_node_path4.resolve)(__dirname, "../../migrations") : (0, import_node_url.fileURLToPath)(new URL("../../migrations/", import_meta.url));
|
|
4360
4540
|
var OfflineSqliteTelemetrySession = class _OfflineSqliteTelemetrySession {
|
|
4361
4541
|
constructor(target, mode, definitions, now, sqlGuard) {
|
|
4362
4542
|
this.target = target;
|
|
@@ -4498,6 +4678,87 @@ var OfflineSqliteTelemetrySession = class _OfflineSqliteTelemetrySession {
|
|
|
4498
4678
|
this.verifySchema();
|
|
4499
4679
|
return this.migrationStatus();
|
|
4500
4680
|
}
|
|
4681
|
+
async compact() {
|
|
4682
|
+
this.requireMaintenanceMode();
|
|
4683
|
+
const connection = this.requireConnection();
|
|
4684
|
+
const maintenanceTable = this.requireTableMap().physical("maintenanceRuns");
|
|
4685
|
+
const runId = (0, import_node_crypto2.randomUUID)();
|
|
4686
|
+
const startedAtMs = this.now();
|
|
4687
|
+
connection.prepare(
|
|
4688
|
+
`INSERT INTO "${maintenanceTable}" (
|
|
4689
|
+
target_id, run_id, state, stage, started_at_ms,
|
|
4690
|
+
completed_at_ms, affected_rows, affected_pages, detail
|
|
4691
|
+
) VALUES (?, ?, 'started', 'compaction', ?, NULL, 0, 0, ?)`
|
|
4692
|
+
).run(this.target.targetId, runId, startedAtMs, "offline-cli; outcome=started");
|
|
4693
|
+
try {
|
|
4694
|
+
const before = this.compactionSnapshot();
|
|
4695
|
+
connection.pragma("wal_checkpoint(TRUNCATE)");
|
|
4696
|
+
const checkpointed = this.compactionSnapshot();
|
|
4697
|
+
this.assertCompactionSpace(checkpointed);
|
|
4698
|
+
connection.pragma("auto_vacuum = INCREMENTAL");
|
|
4699
|
+
connection.exec("VACUUM");
|
|
4700
|
+
connection.pragma("wal_checkpoint(TRUNCATE)");
|
|
4701
|
+
const after = this.compactionSnapshot();
|
|
4702
|
+
if (after.autoVacuum !== 2) {
|
|
4703
|
+
throw new Error("Telemetry compaction did not enable auto_vacuum=INCREMENTAL");
|
|
4704
|
+
}
|
|
4705
|
+
const completedAtMs = this.now();
|
|
4706
|
+
const result = Object.freeze({
|
|
4707
|
+
targetId: this.target.targetId,
|
|
4708
|
+
autoVacuum: "incremental",
|
|
4709
|
+
pageSize: checkpointed.pageSize,
|
|
4710
|
+
pagesBefore: checkpointed.pageCount,
|
|
4711
|
+
pagesAfter: after.pageCount,
|
|
4712
|
+
freePagesBefore: checkpointed.freePages,
|
|
4713
|
+
freePagesAfter: after.freePages,
|
|
4714
|
+
reclaimedPages: Math.max(0, checkpointed.pageCount - after.pageCount),
|
|
4715
|
+
databaseBytesBefore: before.databaseBytes,
|
|
4716
|
+
databaseBytesAfter: after.databaseBytes,
|
|
4717
|
+
walBytesBefore: before.walBytes,
|
|
4718
|
+
walBytesAfter: after.walBytes,
|
|
4719
|
+
reclaimedBytes: Math.max(
|
|
4720
|
+
0,
|
|
4721
|
+
before.databaseBytes + before.walBytes - after.databaseBytes - after.walBytes
|
|
4722
|
+
),
|
|
4723
|
+
elapsedMs: completedAtMs - startedAtMs
|
|
4724
|
+
});
|
|
4725
|
+
connection.prepare(
|
|
4726
|
+
`UPDATE "${maintenanceTable}"
|
|
4727
|
+
SET state = 'completed', completed_at_ms = ?, affected_pages = ?, detail = ?
|
|
4728
|
+
WHERE target_id = ? AND run_id = ? AND stage = 'compaction'`
|
|
4729
|
+
).run(
|
|
4730
|
+
completedAtMs,
|
|
4731
|
+
result.reclaimedPages,
|
|
4732
|
+
JSON.stringify({
|
|
4733
|
+
mode: "offline-cli",
|
|
4734
|
+
outcome: "completed",
|
|
4735
|
+
reclaimedBytes: result.reclaimedBytes,
|
|
4736
|
+
walBytesBefore: result.walBytesBefore,
|
|
4737
|
+
walBytesAfter: result.walBytesAfter,
|
|
4738
|
+
autoVacuum: result.autoVacuum
|
|
4739
|
+
}),
|
|
4740
|
+
this.target.targetId,
|
|
4741
|
+
runId
|
|
4742
|
+
);
|
|
4743
|
+
return result;
|
|
4744
|
+
} catch (cause) {
|
|
4745
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
4746
|
+
try {
|
|
4747
|
+
connection.prepare(
|
|
4748
|
+
`UPDATE "${maintenanceTable}"
|
|
4749
|
+
SET state = 'failed', completed_at_ms = ?, detail = ?
|
|
4750
|
+
WHERE target_id = ? AND run_id = ? AND stage = 'compaction'`
|
|
4751
|
+
).run(
|
|
4752
|
+
this.now(),
|
|
4753
|
+
JSON.stringify({ mode: "offline-cli", outcome: "failed", error: message }),
|
|
4754
|
+
this.target.targetId,
|
|
4755
|
+
runId
|
|
4756
|
+
);
|
|
4757
|
+
} catch {
|
|
4758
|
+
}
|
|
4759
|
+
throw cause;
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4501
4762
|
async close() {
|
|
4502
4763
|
try {
|
|
4503
4764
|
if (this.connection?.open) {
|
|
@@ -4531,7 +4792,7 @@ var OfflineSqliteTelemetrySession = class _OfflineSqliteTelemetrySession {
|
|
|
4531
4792
|
throw new Error(acquired.error);
|
|
4532
4793
|
}
|
|
4533
4794
|
}
|
|
4534
|
-
const connection = new
|
|
4795
|
+
const connection = new import_better_sqlite32.default(this.target.path, {
|
|
4535
4796
|
readonly: this.mode === "query",
|
|
4536
4797
|
fileMustExist: true
|
|
4537
4798
|
});
|
|
@@ -4558,6 +4819,32 @@ var OfflineSqliteTelemetrySession = class _OfflineSqliteTelemetrySession {
|
|
|
4558
4819
|
}
|
|
4559
4820
|
}
|
|
4560
4821
|
}
|
|
4822
|
+
compactionSnapshot() {
|
|
4823
|
+
const connection = this.requireConnection();
|
|
4824
|
+
return Object.freeze({
|
|
4825
|
+
pageSize: Number(connection.pragma("page_size", { simple: true })),
|
|
4826
|
+
pageCount: Number(connection.pragma("page_count", { simple: true })),
|
|
4827
|
+
freePages: Number(connection.pragma("freelist_count", { simple: true })),
|
|
4828
|
+
autoVacuum: Number(connection.pragma("auto_vacuum", { simple: true })),
|
|
4829
|
+
databaseBytes: this.fileBytes(this.target.path),
|
|
4830
|
+
walBytes: this.fileBytes(`${this.target.path}-wal`)
|
|
4831
|
+
});
|
|
4832
|
+
}
|
|
4833
|
+
assertCompactionSpace(snapshot) {
|
|
4834
|
+
const liveBytes = Math.max(0, snapshot.pageCount - snapshot.freePages) * snapshot.pageSize;
|
|
4835
|
+
const reserveBytes = Math.max(64 * 1024 * 1024, Math.ceil(liveBytes * 0.05));
|
|
4836
|
+
const filesystem = (0, import_node_fs4.statfsSync)((0, import_node_path4.dirname)(this.target.path));
|
|
4837
|
+
const availableBytes = Number(filesystem.bavail) * Number(filesystem.bsize);
|
|
4838
|
+
const requiredBytes = liveBytes + reserveBytes;
|
|
4839
|
+
if (availableBytes < requiredBytes) {
|
|
4840
|
+
throw new Error(
|
|
4841
|
+
`Telemetry compaction requires at least ${requiredBytes} free bytes; only ${availableBytes} are available`
|
|
4842
|
+
);
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4845
|
+
fileBytes(path) {
|
|
4846
|
+
return (0, import_node_fs4.existsSync)(path) ? (0, import_node_fs4.statSync)(path).size : 0;
|
|
4847
|
+
}
|
|
4561
4848
|
verifyManifest() {
|
|
4562
4849
|
const rows = this.requireConnection().prepare(
|
|
4563
4850
|
`SELECT target_id, format_version, mapping_json, mapping_checksum,
|
|
@@ -4742,7 +5029,8 @@ var ALLOWED_OPTIONS = Object.freeze({
|
|
|
4742
5029
|
"metric",
|
|
4743
5030
|
"where",
|
|
4744
5031
|
"measurement",
|
|
4745
|
-
"resolution"
|
|
5032
|
+
"resolution",
|
|
5033
|
+
"source"
|
|
4746
5034
|
]),
|
|
4747
5035
|
"metrics-graph": /* @__PURE__ */ new Set([
|
|
4748
5036
|
...COMMON_QUERY_OPTIONS,
|
|
@@ -4750,6 +5038,7 @@ var ALLOWED_OPTIONS = Object.freeze({
|
|
|
4750
5038
|
"where",
|
|
4751
5039
|
"measurement",
|
|
4752
5040
|
"resolution",
|
|
5041
|
+
"source",
|
|
4753
5042
|
"type",
|
|
4754
5043
|
"output",
|
|
4755
5044
|
"width",
|
|
@@ -4767,6 +5056,7 @@ var ALLOWED_OPTIONS = Object.freeze({
|
|
|
4767
5056
|
"limit",
|
|
4768
5057
|
"max-duration"
|
|
4769
5058
|
]),
|
|
5059
|
+
"sqlite-compact": /* @__PURE__ */ new Set(["config", "target", "storage-base", "format"]),
|
|
4770
5060
|
"sqlite-migration-status": /* @__PURE__ */ new Set(["config", "target", "storage-base", "format"]),
|
|
4771
5061
|
"sqlite-migration-up": /* @__PURE__ */ new Set(["config", "target", "storage-base", "format"])
|
|
4772
5062
|
});
|
|
@@ -4776,6 +5066,7 @@ var COMMANDS = Object.freeze([
|
|
|
4776
5066
|
{ tokens: ["metrics", "query"], kind: "metrics-query" },
|
|
4777
5067
|
{ tokens: ["metrics", "graph"], kind: "metrics-graph" },
|
|
4778
5068
|
{ tokens: ["sqlite", "query"], kind: "sqlite-query" },
|
|
5069
|
+
{ tokens: ["sqlite", "compact"], kind: "sqlite-compact" },
|
|
4779
5070
|
{ tokens: ["sqlite", "migration", "status"], kind: "sqlite-migration-status" },
|
|
4780
5071
|
{ tokens: ["sqlite", "migration", "up"], kind: "sqlite-migration-up" }
|
|
4781
5072
|
]);
|
|
@@ -5054,6 +5345,22 @@ var TelemetryCliFormatter = class {
|
|
|
5054
5345
|
return `${lines.join("\n")}
|
|
5055
5346
|
`;
|
|
5056
5347
|
}
|
|
5348
|
+
compaction(result, format) {
|
|
5349
|
+
if (format === "json") {
|
|
5350
|
+
return this.json(result);
|
|
5351
|
+
}
|
|
5352
|
+
return [
|
|
5353
|
+
`Target: ${result.targetId}`,
|
|
5354
|
+
`Auto-vacuum: ${result.autoVacuum}`,
|
|
5355
|
+
`Pages: ${result.pagesBefore} -> ${result.pagesAfter} (${result.reclaimedPages} reclaimed)`,
|
|
5356
|
+
`Freelist: ${result.freePagesBefore} -> ${result.freePagesAfter}`,
|
|
5357
|
+
`Database bytes: ${result.databaseBytesBefore} -> ${result.databaseBytesAfter}`,
|
|
5358
|
+
`WAL bytes: ${result.walBytesBefore} -> ${result.walBytesAfter}`,
|
|
5359
|
+
`Filesystem bytes reclaimed: ${result.reclaimedBytes}`,
|
|
5360
|
+
`Elapsed ms: ${result.elapsedMs}`,
|
|
5361
|
+
""
|
|
5362
|
+
].join("\n");
|
|
5363
|
+
}
|
|
5057
5364
|
migrations(result, format) {
|
|
5058
5365
|
if (format === "json") {
|
|
5059
5366
|
return this.json(result);
|
|
@@ -5130,7 +5437,7 @@ var TelemetryCli = class {
|
|
|
5130
5437
|
this.configurationParser = options.configurationParser ?? new NodeTelemetryConfigurationParser();
|
|
5131
5438
|
this.stdout = options.stdout ?? ((text) => process.stdout.write(text));
|
|
5132
5439
|
this.stderr = options.stderr ?? ((text) => process.stderr.write(text));
|
|
5133
|
-
this.writeOutputFile = options.writeOutputFile ?? ((path, content) => (0,
|
|
5440
|
+
this.writeOutputFile = options.writeOutputFile ?? ((path, content) => (0, import_node_fs5.writeFileSync)(path, content, "utf8"));
|
|
5134
5441
|
this.now = options.now ?? (() => Date.now());
|
|
5135
5442
|
this.createSession = options.createSession ?? ((configuration, target, mode) => OfflineSqliteTelemetrySession.create(target, mode, configuration.metrics, this.now));
|
|
5136
5443
|
}
|
|
@@ -5161,6 +5468,10 @@ var TelemetryCli = class {
|
|
|
5161
5468
|
storageBaseDirectory === null ? {} : { storageBaseDirectory }
|
|
5162
5469
|
);
|
|
5163
5470
|
const target = this.resolveTarget(configuration, this.optionalOption(command, "target"));
|
|
5471
|
+
if (this.metricSource(command, target) === "history") {
|
|
5472
|
+
await this.executeHistoryMetrics(command, target, configuration);
|
|
5473
|
+
return 0;
|
|
5474
|
+
}
|
|
5164
5475
|
const mode = this.sessionMode(command);
|
|
5165
5476
|
const session = await this.createSession(configuration, target, mode);
|
|
5166
5477
|
this.activeSession = session;
|
|
@@ -5174,23 +5485,61 @@ var TelemetryCli = class {
|
|
|
5174
5485
|
}
|
|
5175
5486
|
}
|
|
5176
5487
|
}
|
|
5488
|
+
/**
|
|
5489
|
+
* Which store answers a metric command. `history` is the projector's
|
|
5490
|
+
* pre-aggregated buckets (history.db, current whenever the app runs with
|
|
5491
|
+
* ingest-time extraction off); `legacy` is the collector's own rollups in
|
|
5492
|
+
* telemetry.db. `auto` prefers history.db when it exists beside the target —
|
|
5493
|
+
* since ingestion moved to the projector the legacy rollups stopped
|
|
5494
|
+
* advancing, so answering from them silently reports stale gaps.
|
|
5495
|
+
*/
|
|
5496
|
+
metricSource(command, target) {
|
|
5497
|
+
if (command.kind !== "metrics-query" && command.kind !== "metrics-graph") return "legacy";
|
|
5498
|
+
const requested = this.optionalOption(command, "source") ?? "auto";
|
|
5499
|
+
if (requested === "history" || requested === "legacy") return requested;
|
|
5500
|
+
if (requested !== "auto") {
|
|
5501
|
+
throw new TelemetryCliUsageError("--source must be auto, history, or legacy");
|
|
5502
|
+
}
|
|
5503
|
+
return HistoryMetricReader.exists(historyDatabasePath(target)) ? "history" : "legacy";
|
|
5504
|
+
}
|
|
5505
|
+
async executeHistoryMetrics(command, target, configuration) {
|
|
5506
|
+
const databasePath = historyDatabasePath(target);
|
|
5507
|
+
if (!HistoryMetricReader.exists(databasePath)) {
|
|
5508
|
+
throw new TelemetryCliUsageError(`No projector history store at ${databasePath}`);
|
|
5509
|
+
}
|
|
5510
|
+
const reader = HistoryMetricReader.open(databasePath, configuration.metrics);
|
|
5511
|
+
try {
|
|
5512
|
+
const run = async (query) => reader.metrics(query, target.targetId);
|
|
5513
|
+
if (command.kind === "metrics-graph") {
|
|
5514
|
+
await this.metricGraph(command, target, run);
|
|
5515
|
+
} else {
|
|
5516
|
+
await this.metricQuery(command, target, run);
|
|
5517
|
+
}
|
|
5518
|
+
} finally {
|
|
5519
|
+
reader.close();
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5177
5522
|
async executeCommand(command, target, session) {
|
|
5178
5523
|
if (command.kind === "timeline-query") {
|
|
5179
5524
|
await this.timelineQuery(command, target, session);
|
|
5180
5525
|
return;
|
|
5181
5526
|
}
|
|
5182
5527
|
if (command.kind === "metrics-query") {
|
|
5183
|
-
await this.metricQuery(command, target, session);
|
|
5528
|
+
await this.metricQuery(command, target, (query) => session.metrics(query));
|
|
5184
5529
|
return;
|
|
5185
5530
|
}
|
|
5186
5531
|
if (command.kind === "metrics-graph") {
|
|
5187
|
-
await this.metricGraph(command, target, session);
|
|
5532
|
+
await this.metricGraph(command, target, (query) => session.metrics(query));
|
|
5188
5533
|
return;
|
|
5189
5534
|
}
|
|
5190
5535
|
if (command.kind === "sqlite-query") {
|
|
5191
5536
|
await this.sqliteQuery(command, target, session);
|
|
5192
5537
|
return;
|
|
5193
5538
|
}
|
|
5539
|
+
if (command.kind === "sqlite-compact") {
|
|
5540
|
+
await this.sqliteCompact(command, target, session);
|
|
5541
|
+
return;
|
|
5542
|
+
}
|
|
5194
5543
|
if (command.kind === "sqlite-migration-status" || command.kind === "sqlite-migration-up") {
|
|
5195
5544
|
await this.sqliteMigration(command, target, session);
|
|
5196
5545
|
return;
|
|
@@ -5220,8 +5569,8 @@ var TelemetryCli = class {
|
|
|
5220
5569
|
const output = this.formatter.timeline(result.value, format, this.timezoneOption(command));
|
|
5221
5570
|
this.writeBounded(output, target.query.maxResponseBytes);
|
|
5222
5571
|
}
|
|
5223
|
-
async metricQuery(command, target,
|
|
5224
|
-
const result = await
|
|
5572
|
+
async metricQuery(command, target, runMetrics) {
|
|
5573
|
+
const result = await runMetrics(this.metricQueryInput(command, target));
|
|
5225
5574
|
if (!result.ok) {
|
|
5226
5575
|
throw new Error(`${result.error.kind}: ${result.error.message}`);
|
|
5227
5576
|
}
|
|
@@ -5232,12 +5581,12 @@ var TelemetryCli = class {
|
|
|
5232
5581
|
);
|
|
5233
5582
|
this.writeBounded(output, target.query.maxResponseBytes);
|
|
5234
5583
|
}
|
|
5235
|
-
async metricGraph(command, target,
|
|
5584
|
+
async metricGraph(command, target, runMetrics) {
|
|
5236
5585
|
const measurements = this.options(command, "measurement");
|
|
5237
5586
|
if (measurements.length !== 1) {
|
|
5238
5587
|
throw new TelemetryCliUsageError("metrics graph requires exactly one --measurement");
|
|
5239
5588
|
}
|
|
5240
|
-
const result = await
|
|
5589
|
+
const result = await runMetrics(this.metricQueryInput(command, target));
|
|
5241
5590
|
if (!result.ok) {
|
|
5242
5591
|
throw new Error(`${result.error.kind}: ${result.error.message}`);
|
|
5243
5592
|
}
|
|
@@ -5288,6 +5637,14 @@ var TelemetryCli = class {
|
|
|
5288
5637
|
}
|
|
5289
5638
|
this.writeBounded(this.formatter.migrations(result, format), target.query.maxResponseBytes);
|
|
5290
5639
|
}
|
|
5640
|
+
async sqliteCompact(command, target, session) {
|
|
5641
|
+
const format = this.optionalOption(command, "format") ?? "table";
|
|
5642
|
+
if (format !== "table" && format !== "json") {
|
|
5643
|
+
throw new TelemetryCliUsageError("Compaction format must be table or json");
|
|
5644
|
+
}
|
|
5645
|
+
const result = await session.compact();
|
|
5646
|
+
this.writeBounded(this.formatter.compaction(result, format), target.query.maxResponseBytes);
|
|
5647
|
+
}
|
|
5291
5648
|
metricQueryInput(command, target) {
|
|
5292
5649
|
const measurements = this.options(command, "measurement");
|
|
5293
5650
|
if (measurements.length === 0) {
|
|
@@ -5322,7 +5679,7 @@ var TelemetryCli = class {
|
|
|
5322
5679
|
return target;
|
|
5323
5680
|
}
|
|
5324
5681
|
sessionMode(command) {
|
|
5325
|
-
return command.kind === "sqlite-migration-status" || command.kind === "sqlite-migration-up" ? "maintenance" : "query";
|
|
5682
|
+
return command.kind === "sqlite-compact" || command.kind === "sqlite-migration-status" || command.kind === "sqlite-migration-up" ? "maintenance" : "query";
|
|
5326
5683
|
}
|
|
5327
5684
|
structuredFormat(command) {
|
|
5328
5685
|
const value = this.optionalOption(command, "format") ?? "table";
|
|
@@ -5489,9 +5846,10 @@ var TelemetryCli = class {
|
|
|
5489
5846
|
help() {
|
|
5490
5847
|
return `Usage:
|
|
5491
5848
|
telemetry timeline query --config FILE --from TIME --to TIME [filters] [--format table|json|jsonl|csv]
|
|
5492
|
-
telemetry metrics query --config FILE --metric NAME --measurement NAME --resolution DURATION|auto --from TIME --to TIME [--where KEY=VALUE]
|
|
5493
|
-
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]
|
|
5849
|
+
telemetry metrics query --config FILE --metric NAME --measurement NAME --resolution DURATION|auto --from TIME --to TIME [--where KEY=VALUE] [--source auto|history|legacy]
|
|
5850
|
+
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]
|
|
5494
5851
|
telemetry sqlite query --config FILE --sql SQL [--param NAME=VALUE] [--format table|json|jsonl|csv]
|
|
5852
|
+
telemetry sqlite compact --config FILE [--format table|json]
|
|
5495
5853
|
telemetry sqlite migration status --config FILE [--format table|json]
|
|
5496
5854
|
telemetry sqlite migration up --config FILE [--format table|json]
|
|
5497
5855
|
|