@runtimescope/collector 0.10.7 → 0.10.8
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.
|
@@ -76,6 +76,18 @@ var EventStore = class {
|
|
|
76
76
|
this.sqliteStore = store;
|
|
77
77
|
this.currentProject = project;
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Drop the SQLite pointer if it matches the given project. Called by the
|
|
81
|
+
* collector's idle-store eviction timer so the store doesn't try to write
|
|
82
|
+
* through a closed handle after eviction. The next `setSqliteStore` (from
|
|
83
|
+
* the next event for this project) restores the binding.
|
|
84
|
+
*/
|
|
85
|
+
clearSqliteStoreIfMatches(project) {
|
|
86
|
+
if (this.currentProject === project) {
|
|
87
|
+
this.sqliteStore = null;
|
|
88
|
+
this.currentProject = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
79
91
|
/**
|
|
80
92
|
* Pre-load recent events from a SqliteStore into the in-memory ring buffer.
|
|
81
93
|
* Used at collector startup to make MCP tools immediately useful after a
|
|
@@ -1674,6 +1686,13 @@ var CollectorServer = class {
|
|
|
1674
1686
|
pendingHandshakes = /* @__PURE__ */ new Set();
|
|
1675
1687
|
pendingCommands = /* @__PURE__ */ new Map();
|
|
1676
1688
|
sqliteStores = /* @__PURE__ */ new Map();
|
|
1689
|
+
// Per-project last-access timestamps for the SQLite stores. Used by the
|
|
1690
|
+
// LRU eviction timer to close handles for projects that haven't been read
|
|
1691
|
+
// from or written to recently — without this, every project ever seen
|
|
1692
|
+
// keeps its WAL handle + ~2-3MB page cache open forever, which on a
|
|
1693
|
+
// 40-project machine adds up to ~100MB of permanently-allocated baseline
|
|
1694
|
+
// RSS. ensureSqliteStore() updates this on every access.
|
|
1695
|
+
sqliteStoreLastAccess = /* @__PURE__ */ new Map();
|
|
1677
1696
|
wals = /* @__PURE__ */ new Map();
|
|
1678
1697
|
ready = false;
|
|
1679
1698
|
metrics = new MetricsRegistry();
|
|
@@ -1684,6 +1703,7 @@ var CollectorServer = class {
|
|
|
1684
1703
|
disconnectCallbacks = [];
|
|
1685
1704
|
pruneTimer = null;
|
|
1686
1705
|
heartbeatTimer = null;
|
|
1706
|
+
sqliteEvictTimer = null;
|
|
1687
1707
|
tlsConfig = null;
|
|
1688
1708
|
pmStore = null;
|
|
1689
1709
|
constructor(options = {}) {
|
|
@@ -1873,8 +1893,55 @@ var CollectorServer = class {
|
|
|
1873
1893
|
}
|
|
1874
1894
|
}
|
|
1875
1895
|
this.ready = true;
|
|
1896
|
+
this.startSqliteEvictionTimer();
|
|
1876
1897
|
return this.tryStart(port, host, maxRetries, retryDelayMs, tls);
|
|
1877
1898
|
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Close SQLite stores that haven't been accessed in IDLE_TIMEOUT_MS. Any
|
|
1901
|
+
* subsequent `ensureSqliteStore(name)` call will transparently re-open the
|
|
1902
|
+
* store from disk, so eviction is invisible to callers — they just see a
|
|
1903
|
+
* brief warm-up the next time they touch a project.
|
|
1904
|
+
*
|
|
1905
|
+
* We deliberately do NOT evict stores belonging to currently-connected
|
|
1906
|
+
* SDK clients, since those are guaranteed to write again soon and closing
|
|
1907
|
+
* + reopening on every event would be expensive.
|
|
1908
|
+
*/
|
|
1909
|
+
startSqliteEvictionTimer() {
|
|
1910
|
+
if (this.sqliteEvictTimer) return;
|
|
1911
|
+
const IDLE_TIMEOUT_MS = parseInt(
|
|
1912
|
+
process.env.RUNTIMESCOPE_SQLITE_IDLE_MS ?? String(5 * 60 * 1e3),
|
|
1913
|
+
10
|
|
1914
|
+
);
|
|
1915
|
+
const SWEEP_INTERVAL_MS = parseInt(
|
|
1916
|
+
process.env.RUNTIMESCOPE_SQLITE_SWEEP_MS ?? String(60 * 1e3),
|
|
1917
|
+
10
|
|
1918
|
+
);
|
|
1919
|
+
this.sqliteEvictTimer = setInterval(() => {
|
|
1920
|
+
const now = Date.now();
|
|
1921
|
+
const liveProjects = /* @__PURE__ */ new Set();
|
|
1922
|
+
for (const info of this.clients.values()) {
|
|
1923
|
+
liveProjects.add(info.projectName);
|
|
1924
|
+
}
|
|
1925
|
+
for (const [projectName, store] of this.sqliteStores) {
|
|
1926
|
+
if (liveProjects.has(projectName)) continue;
|
|
1927
|
+
const lastAccess = this.sqliteStoreLastAccess.get(projectName) ?? 0;
|
|
1928
|
+
if (now - lastAccess < IDLE_TIMEOUT_MS) continue;
|
|
1929
|
+
try {
|
|
1930
|
+
store.close();
|
|
1931
|
+
} catch (e) {
|
|
1932
|
+
console.error(
|
|
1933
|
+
`[RuntimeScope] Failed to close idle SQLite store "${projectName}":`,
|
|
1934
|
+
e.message
|
|
1935
|
+
);
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
this.sqliteStores.delete(projectName);
|
|
1939
|
+
this.sqliteStoreLastAccess.delete(projectName);
|
|
1940
|
+
this.store.clearSqliteStoreIfMatches(projectName);
|
|
1941
|
+
}
|
|
1942
|
+
}, SWEEP_INTERVAL_MS);
|
|
1943
|
+
this.sqliteEvictTimer.unref?.();
|
|
1944
|
+
}
|
|
1878
1945
|
/**
|
|
1879
1946
|
* On collector startup, for each known project:
|
|
1880
1947
|
* 1. Replay any sealed/active WAL files into SqliteStore (mirror of the
|
|
@@ -1979,6 +2046,7 @@ var CollectorServer = class {
|
|
|
1979
2046
|
ensureSqliteStore(projectName) {
|
|
1980
2047
|
if (!this.projectManager) return null;
|
|
1981
2048
|
if (!isSqliteAvailable()) return null;
|
|
2049
|
+
this.sqliteStoreLastAccess.set(projectName, Date.now());
|
|
1982
2050
|
let sqliteStore = this.sqliteStores.get(projectName);
|
|
1983
2051
|
if (!sqliteStore) {
|
|
1984
2052
|
try {
|
|
@@ -2361,6 +2429,10 @@ var CollectorServer = class {
|
|
|
2361
2429
|
clearInterval(this.pruneTimer);
|
|
2362
2430
|
this.pruneTimer = null;
|
|
2363
2431
|
}
|
|
2432
|
+
if (this.sqliteEvictTimer) {
|
|
2433
|
+
clearInterval(this.sqliteEvictTimer);
|
|
2434
|
+
this.sqliteEvictTimer = null;
|
|
2435
|
+
}
|
|
2364
2436
|
if (this.wss) {
|
|
2365
2437
|
for (const client of this.wss.clients) {
|
|
2366
2438
|
if (client.readyState === 1) {
|
|
@@ -7410,4 +7482,4 @@ export {
|
|
|
7410
7482
|
parseSessionJsonl,
|
|
7411
7483
|
ProjectDiscovery
|
|
7412
7484
|
};
|
|
7413
|
-
//# sourceMappingURL=chunk-
|
|
7485
|
+
//# sourceMappingURL=chunk-TT3VVKUE.js.map
|