@ogygia/devtools 0.11.4 → 0.13.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 +1 -1
- package/dist/app.d.ts +3 -0
- package/dist/app.js +9 -0
- package/dist/devtools/BoundaryOverlay.svelte +10 -6
- package/dist/devtools/Devtools.svelte +24 -18
- package/dist/devtools/IslandDetail.svelte +7 -5
- package/dist/devtools/PageTab.svelte +4 -2
- package/dist/devtools/ProfilerTab.svelte +8 -2
- package/dist/devtools/SessionTab.svelte +21 -8
- package/dist/devtools/highlight.js +14 -7
- package/dist/devtools/ledger-dom.js +6 -9
- package/dist/devtools/names.js +18 -17
- package/dist/devtools/page.d.ts +2 -0
- package/dist/devtools/page.js +37 -17
- package/dist/devtools/profile-store.d.ts +4 -9
- package/dist/devtools/profile-store.js +26 -21
- package/dist/devtools/session.d.ts +2 -0
- package/dist/devtools/session.js +48 -42
- package/dist/devtools/testing-engine.js +10 -1
- package/dist/devtools/ui-app.d.ts +3 -1
- package/dist/devtools/ui-app.js +9 -3
- package/dist/devtools/ui.js +83 -40
- package/dist/messages/build.d.ts +21 -0
- package/dist/messages/build.js +56 -0
- package/dist/messages/index.d.ts +3 -0
- package/dist/messages/index.js +3 -0
- package/dist/messages/runtime.d.ts +63 -0
- package/dist/messages/runtime.js +122 -0
- package/dist/profiler/async-io.d.ts +12 -7
- package/dist/profiler/async-io.js +94 -82
- package/dist/profiler/beacon-standalone.js +15 -6
- package/dist/profiler/busy.d.ts +13 -0
- package/dist/profiler/busy.js +45 -0
- package/dist/profiler/client.js +5 -24
- package/dist/profiler/embedded-maps.js +9 -5
- package/dist/profiler/gc.d.ts +5 -0
- package/dist/profiler/gc.js +75 -11
- package/dist/profiler/index.d.ts +23 -2
- package/dist/profiler/index.js +648 -373
- package/dist/profiler/net.d.ts +14 -6
- package/dist/profiler/net.js +61 -45
- package/dist/profiler/patterns.js +29 -1
- package/dist/profiler/source-scan.js +24 -12
- package/dist/profiler/standalone.js +12 -4
- package/dist/profiler/storage/index.d.ts +14 -3
- package/dist/profiler/storage/index.js +41 -41
- package/dist/profiler/storage/postgres.d.ts +18 -5
- package/dist/profiler/storage/postgres.js +124 -85
- package/dist/profiler/storage/redis.d.ts +18 -5
- package/dist/profiler/storage/redis.js +160 -131
- package/dist/profiler/storage/sqlite.d.ts +15 -2
- package/dist/profiler/storage/sqlite.js +54 -48
- package/dist/profiler/ui/ComponentsTable.svelte +2 -2
- package/dist/profiler/ui/Dashboard.svelte +2 -3
- package/dist/profiler/ui/DownloadHtml.svelte +1 -0
- package/dist/profiler/ui/ExportButton.svelte +1 -0
- package/dist/profiler/ui/Flame.svelte +10 -4
- package/dist/profiler/ui/Flame.svelte.d.ts +0 -5
- package/dist/profiler/ui/FrameDetails.svelte +13 -2
- package/dist/profiler/ui/FrameDetails.svelte.d.ts +0 -6
- package/dist/profiler/ui/FunctionsTable.svelte +2 -2
- package/dist/profiler/ui/IslandsTable.svelte +2 -2
- package/dist/profiler/ui/OnThisPage.svelte +12 -4
- package/dist/profiler/ui/RunView.svelte +31 -27
- package/dist/profiler/ui/ShareLink.svelte +6 -1
- package/dist/profiler/ui/Sidebar.svelte +14 -9
- package/dist/profiler/ui/Treemap.svelte +11 -4
- package/dist/profiler/ui/Treemap.svelte.d.ts +0 -6
- package/dist/profiler/ui/WaitingTable.svelte +2 -2
- package/dist/profiler/ui/fake-progress.svelte.d.ts +7 -12
- package/dist/profiler/ui/fake-progress.svelte.js +57 -36
- package/dist/profiler/ui/press.js +4 -2
- package/dist/profiler/ui/row-anchor.svelte.d.ts +0 -6
- package/dist/profiler/ui/row-anchor.svelte.js +13 -8
- package/dist/profiler/ui/sort.svelte.d.ts +9 -6
- package/dist/profiler/ui/sort.svelte.js +34 -28
- package/dist/profiler/visit.d.ts +0 -1
- package/dist/profiler/visit.js +1 -2
- package/dist/server.js +27 -10
- package/dist/vite.js +3 -2
- package/package.json +18 -9
|
@@ -1,94 +1,133 @@
|
|
|
1
1
|
import { MAX_VISITS_PER_KEY } from "./visits.js";
|
|
2
|
+
import { STALE, flights } from "@ogygia/machine";
|
|
2
3
|
// src/profiler/storage/postgres.ts
|
|
3
4
|
async function resolve(client) {
|
|
4
5
|
if (typeof client !== "string") return client;
|
|
5
6
|
const pg = await import("pg");
|
|
6
7
|
const Pool = pg.Pool ?? pg.default?.Pool;
|
|
7
|
-
if (!Pool) throw new Error("ogygia/profiler:
|
|
8
|
+
if (!Pool) throw new Error("ogygia/profiler: PostgresStore needs the 'pg' package.");
|
|
8
9
|
return new Pool({ connectionString: client });
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
11
|
+
var PostgresStore = class {
|
|
12
|
+
kind = "postgres";
|
|
13
|
+
#client;
|
|
14
|
+
#schema;
|
|
15
|
+
#base;
|
|
16
|
+
#R;
|
|
17
|
+
#V;
|
|
18
|
+
/** the client, made once: two first calls share it, and a failure to make it is not kept */
|
|
19
|
+
#connect;
|
|
20
|
+
/** the client made, for `close` */
|
|
21
|
+
#made = null;
|
|
22
|
+
/** the tables, made once: a setup that failed (a connection blip) is made again by the next call */
|
|
23
|
+
#setup;
|
|
24
|
+
constructor(client, opts = {}) {
|
|
25
|
+
const base = opts.table ?? "ogygia";
|
|
26
|
+
const q = (n) => opts.schema ? `"${opts.schema}"."${base}_${n}"` : `"${base}_${n}"`;
|
|
27
|
+
this.#client = client;
|
|
28
|
+
this.#schema = opts.schema;
|
|
29
|
+
this.#base = base;
|
|
30
|
+
this.#R = q("reports");
|
|
31
|
+
this.#V = q("visits");
|
|
32
|
+
this.#connect = this.#connector();
|
|
33
|
+
this.#setup = flights({
|
|
34
|
+
name: "profiler postgres tables",
|
|
35
|
+
run: () => this.#create_tables()
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async init() {
|
|
39
|
+
await this.#need();
|
|
40
|
+
}
|
|
41
|
+
async putReport(rec) {
|
|
42
|
+
await (await this.#need()).query(`INSERT INTO ${this.#R} (id,created,page,trigger,label,median,dump) VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb)
|
|
43
|
+
ON CONFLICT (id) DO UPDATE SET created=EXCLUDED.created,page=EXCLUDED.page,trigger=EXCLUDED.trigger,label=EXCLUDED.label,median=EXCLUDED.median,dump=EXCLUDED.dump`, [
|
|
44
|
+
rec.id,
|
|
45
|
+
rec.created,
|
|
46
|
+
rec.page,
|
|
47
|
+
rec.trigger,
|
|
48
|
+
rec.label,
|
|
49
|
+
rec.median,
|
|
50
|
+
rec.dump
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
async getReport(id) {
|
|
54
|
+
const { rows } = await (await this.#need()).query(`SELECT dump FROM ${this.#R} WHERE id = $1`, [id]);
|
|
55
|
+
if (!rows.length) return null;
|
|
56
|
+
const d = rows[0].dump;
|
|
57
|
+
return typeof d === "string" ? d : JSON.stringify(d);
|
|
58
|
+
}
|
|
59
|
+
async listReports(limit = 30) {
|
|
60
|
+
const { rows } = await (await this.#need()).query(`SELECT id,created,page,trigger,label,median FROM ${this.#R} ORDER BY created DESC LIMIT $1`, [limit]);
|
|
61
|
+
return rows.map((r) => ({
|
|
62
|
+
id: r.id,
|
|
63
|
+
created: Number(r.created),
|
|
64
|
+
page: r.page ?? null,
|
|
65
|
+
trigger: r.trigger,
|
|
66
|
+
label: r.label,
|
|
67
|
+
median: r.median === null ? null : Number(r.median)
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
async deleteReport(id) {
|
|
71
|
+
await (await this.#need()).query(`DELETE FROM ${this.#R} WHERE id = $1`, [id]);
|
|
72
|
+
}
|
|
73
|
+
async prune(keep) {
|
|
74
|
+
const R = this.#R;
|
|
75
|
+
await (await this.#need()).query(`DELETE FROM ${R} WHERE id NOT IN (SELECT id FROM ${R} ORDER BY created DESC LIMIT $1)`, [keep]);
|
|
76
|
+
}
|
|
77
|
+
async putVisit(rec) {
|
|
78
|
+
const V = this.#V;
|
|
79
|
+
const db = await this.#need();
|
|
80
|
+
await db.query(`INSERT INTO ${V} (key,page,at,visit) VALUES ($1,$2,$3,$4::jsonb) ON CONFLICT (key) DO UPDATE SET at=EXCLUDED.at,visit=EXCLUDED.visit`, [
|
|
81
|
+
rec.key,
|
|
82
|
+
rec.page,
|
|
83
|
+
rec.at,
|
|
84
|
+
rec.visit
|
|
85
|
+
]);
|
|
86
|
+
await db.query(`DELETE FROM ${V} WHERE page = $1 AND key NOT IN (SELECT key FROM ${V} WHERE page = $1 ORDER BY at DESC LIMIT $2)`, [rec.page, MAX_VISITS_PER_KEY]);
|
|
87
|
+
}
|
|
88
|
+
async listVisits(page, limit = 10) {
|
|
89
|
+
const { rows } = await (await this.#need()).query(`SELECT key,page,at,visit FROM ${this.#V} WHERE page = $1 ORDER BY at DESC LIMIT $2`, [page, limit]);
|
|
90
|
+
return rows.map((r) => ({
|
|
91
|
+
key: r.key,
|
|
92
|
+
page: r.page,
|
|
93
|
+
at: Number(r.at),
|
|
94
|
+
visit: typeof r.visit === "string" ? r.visit : JSON.stringify(r.visit)
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
async close() {
|
|
98
|
+
const made = this.#made;
|
|
99
|
+
this.#made = null;
|
|
100
|
+
this.#connect = this.#connector();
|
|
101
|
+
await made?.end?.();
|
|
102
|
+
}
|
|
103
|
+
#connector() {
|
|
104
|
+
return flights({
|
|
105
|
+
name: "profiler postgres client",
|
|
106
|
+
run: async () => this.#made = await resolve(this.#client)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/** The client (nothing drops a connector, so it is never STALE; were it, the current one is
|
|
110
|
+
* asked). */
|
|
111
|
+
async #client_made() {
|
|
112
|
+
const c = await this.#connect.get();
|
|
113
|
+
return c === STALE ? this.#client_made() : c;
|
|
114
|
+
}
|
|
115
|
+
async #need() {
|
|
116
|
+
const c = await this.#client_made();
|
|
117
|
+
await this.#setup.get();
|
|
27
118
|
return c;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
rec.dump
|
|
44
|
-
]);
|
|
45
|
-
},
|
|
46
|
-
async getReport(id) {
|
|
47
|
-
const { rows } = await (await need()).query(`SELECT dump FROM ${R} WHERE id = $1`, [id]);
|
|
48
|
-
if (!rows.length) return null;
|
|
49
|
-
const d = rows[0].dump;
|
|
50
|
-
return typeof d === "string" ? d : JSON.stringify(d);
|
|
51
|
-
},
|
|
52
|
-
async listReports(limit = 30) {
|
|
53
|
-
const { rows } = await (await need()).query(`SELECT id,created,page,trigger,label,median FROM ${R} ORDER BY created DESC LIMIT $1`, [limit]);
|
|
54
|
-
return rows.map((r) => ({
|
|
55
|
-
id: r.id,
|
|
56
|
-
created: Number(r.created),
|
|
57
|
-
page: r.page ?? null,
|
|
58
|
-
trigger: r.trigger,
|
|
59
|
-
label: r.label,
|
|
60
|
-
median: r.median === null ? null : Number(r.median)
|
|
61
|
-
}));
|
|
62
|
-
},
|
|
63
|
-
async deleteReport(id) {
|
|
64
|
-
await (await need()).query(`DELETE FROM ${R} WHERE id = $1`, [id]);
|
|
65
|
-
},
|
|
66
|
-
async prune(keep) {
|
|
67
|
-
await (await need()).query(`DELETE FROM ${R} WHERE id NOT IN (SELECT id FROM ${R} ORDER BY created DESC LIMIT $1)`, [keep]);
|
|
68
|
-
},
|
|
69
|
-
async putVisit(rec) {
|
|
70
|
-
const db = await need();
|
|
71
|
-
await db.query(`INSERT INTO ${V} (key,page,at,visit) VALUES ($1,$2,$3,$4::jsonb) ON CONFLICT (key) DO UPDATE SET at=EXCLUDED.at,visit=EXCLUDED.visit`, [
|
|
72
|
-
rec.key,
|
|
73
|
-
rec.page,
|
|
74
|
-
rec.at,
|
|
75
|
-
rec.visit
|
|
76
|
-
]);
|
|
77
|
-
await db.query(`DELETE FROM ${V} WHERE page = $1 AND key NOT IN (SELECT key FROM ${V} WHERE page = $1 ORDER BY at DESC LIMIT $2)`, [rec.page, MAX_VISITS_PER_KEY]);
|
|
78
|
-
},
|
|
79
|
-
async listVisits(page, limit = 10) {
|
|
80
|
-
const { rows } = await (await need()).query(`SELECT key,page,at,visit FROM ${V} WHERE page = $1 ORDER BY at DESC LIMIT $2`, [page, limit]);
|
|
81
|
-
return rows.map((r) => ({
|
|
82
|
-
key: r.key,
|
|
83
|
-
page: r.page,
|
|
84
|
-
at: Number(r.at),
|
|
85
|
-
visit: typeof r.visit === "string" ? r.visit : JSON.stringify(r.visit)
|
|
86
|
-
}));
|
|
87
|
-
},
|
|
88
|
-
async close() {
|
|
89
|
-
await c?.end?.();
|
|
90
|
-
c = null;
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
export { postgresStore };
|
|
119
|
+
}
|
|
120
|
+
async #create_tables() {
|
|
121
|
+
const c = await this.#client_made();
|
|
122
|
+
const R = this.#R;
|
|
123
|
+
const V = this.#V;
|
|
124
|
+
const base = this.#base;
|
|
125
|
+
const schema = this.#schema;
|
|
126
|
+
if (schema) await c.query(`CREATE SCHEMA IF NOT EXISTS "${schema}"`);
|
|
127
|
+
await c.query(`CREATE TABLE IF NOT EXISTS ${R} (id TEXT PRIMARY KEY, created BIGINT NOT NULL, page TEXT, trigger TEXT, label TEXT, median DOUBLE PRECISION, dump JSONB NOT NULL)`);
|
|
128
|
+
await c.query(`CREATE INDEX IF NOT EXISTS ${base}_reports_created ON ${R} (created DESC)`);
|
|
129
|
+
await c.query(`CREATE TABLE IF NOT EXISTS ${V} (key TEXT PRIMARY KEY, page TEXT NOT NULL, at BIGINT NOT NULL, visit JSONB NOT NULL)`);
|
|
130
|
+
await c.query(`CREATE INDEX IF NOT EXISTS ${base}_visits_page ON ${V} (page, at DESC)`);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
export { PostgresStore };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProfilerStore } from "./index.js";
|
|
1
|
+
import { ProfilerRecord, ProfilerStore, ProfilerSummary, ProfilerVisitRecord } from "./index.js";
|
|
2
2
|
/** The subset of a Redis client this store uses — node-redis and ioredis both satisfy it (their
|
|
3
3
|
* method names differ in case, and a few calls take options objects on one and arguments on the
|
|
4
4
|
* other; the flavour is read once, below). */
|
|
@@ -12,8 +12,21 @@ interface RedisLike {
|
|
|
12
12
|
zcard?(key: string): Promise<number>;
|
|
13
13
|
connect?(): Promise<void>;
|
|
14
14
|
}
|
|
15
|
-
export declare
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
export declare class RedisStore implements ProfilerStore {
|
|
16
|
+
#private;
|
|
17
|
+
readonly kind: string;
|
|
18
|
+
constructor(client: RedisLike | string, opts?: {
|
|
19
|
+
ttlSeconds?: number;
|
|
20
|
+
prefix?: string;
|
|
21
|
+
});
|
|
22
|
+
init(): Promise<void>;
|
|
23
|
+
putReport(rec: ProfilerRecord): Promise<void>;
|
|
24
|
+
getReport(id: string): Promise<string | null>;
|
|
25
|
+
listReports(limit?: number): Promise<ProfilerSummary[]>;
|
|
26
|
+
deleteReport(id: string): Promise<void>;
|
|
27
|
+
prune(keep: number): Promise<void>;
|
|
28
|
+
putVisit(rec: ProfilerVisitRecord): Promise<void>;
|
|
29
|
+
listVisits(page: string, limit?: number): Promise<ProfilerVisitRecord[]>;
|
|
30
|
+
close(): Promise<void>;
|
|
31
|
+
}
|
|
19
32
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MAX_VISITS_PER_KEY } from "./visits.js";
|
|
2
|
+
import { STALE, flights } from "@ogygia/machine";
|
|
2
3
|
// src/profiler/storage/redis.ts
|
|
3
4
|
/** reports kept at most, whatever the host prunes to (a store used on its own still has a cap) */
|
|
4
5
|
const MAX_REPORTS = 1e3;
|
|
@@ -6,143 +7,171 @@ async function resolve(client) {
|
|
|
6
7
|
if (typeof client !== "string") return client;
|
|
7
8
|
const nodeRedis = "redis";
|
|
8
9
|
const ioRedis = "ioredis";
|
|
10
|
+
let node_redis = null;
|
|
9
11
|
try {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
node_redis = await import(nodeRedis);
|
|
13
|
+
} catch {}
|
|
14
|
+
if (node_redis) {
|
|
15
|
+
const c = node_redis.createClient({ url: client });
|
|
12
16
|
await c.connect?.();
|
|
13
17
|
return c;
|
|
14
|
-
}
|
|
18
|
+
}
|
|
15
19
|
return new (await (import(ioRedis))).default(client);
|
|
16
20
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
21
|
+
const node = (r) => typeof r.zAdd === "function" ? r : null;
|
|
22
|
+
const zadd = (r, key, score, member) => {
|
|
23
|
+
const n = node(r);
|
|
24
|
+
return n ? n.zAdd(key, {
|
|
25
|
+
score,
|
|
26
|
+
value: member
|
|
27
|
+
}) : r.zadd(key, score, member);
|
|
28
|
+
};
|
|
29
|
+
const zrem = (r, key, members) => {
|
|
30
|
+
if (!members.length) return Promise.resolve();
|
|
31
|
+
const n = node(r);
|
|
32
|
+
return n ? n.zRem(key, members) : r.zrem(key, ...members);
|
|
33
|
+
};
|
|
34
|
+
const zrange = (r, key, a, b, rev = false) => {
|
|
35
|
+
const n = node(r);
|
|
36
|
+
if (n) return rev ? n.zRange(key, a, b, { REV: true }) : n.zRange(key, a, b);
|
|
37
|
+
return rev ? r.zrange(key, a, b, "REV") : r.zrange(key, a, b);
|
|
38
|
+
};
|
|
39
|
+
const zcard = (r, key) => {
|
|
40
|
+
const n = node(r);
|
|
41
|
+
return n ? n.zCard(key) : r.zcard(key);
|
|
42
|
+
};
|
|
43
|
+
/** drop every member but the newest `keep`, with the keys they name */
|
|
44
|
+
const trim = async (r, key, item, keep) => {
|
|
45
|
+
const stale = await zrange(r, key, 0, -(keep + 1));
|
|
46
|
+
if (!stale.length) return;
|
|
47
|
+
await r.del(stale.map((m) => item + m));
|
|
48
|
+
await zrem(r, key, stale);
|
|
49
|
+
};
|
|
50
|
+
var RedisStore = class {
|
|
51
|
+
kind = "redis";
|
|
52
|
+
#client;
|
|
53
|
+
#ttl;
|
|
54
|
+
#R;
|
|
55
|
+
#RZ;
|
|
56
|
+
#V;
|
|
57
|
+
/** the client, made once: two first calls share it, and a failure to make it is not kept */
|
|
58
|
+
#connect;
|
|
59
|
+
/** the client made, for `close` */
|
|
60
|
+
#made = null;
|
|
61
|
+
constructor(client, opts = {}) {
|
|
62
|
+
const prefix = opts.prefix ?? "og:";
|
|
63
|
+
this.#client = client;
|
|
64
|
+
this.#ttl = opts.ttlSeconds;
|
|
65
|
+
this.#R = `${prefix}report:`;
|
|
66
|
+
this.#RZ = `${prefix}reports`;
|
|
67
|
+
this.#V = `${prefix}visit:`;
|
|
68
|
+
this.#connect = this.#connector();
|
|
69
|
+
}
|
|
70
|
+
async init() {
|
|
71
|
+
await this.#need();
|
|
72
|
+
}
|
|
73
|
+
async putReport(rec) {
|
|
74
|
+
const r = await this.#need();
|
|
75
|
+
const summary = {
|
|
76
|
+
id: rec.id,
|
|
77
|
+
created: rec.created,
|
|
78
|
+
page: rec.page,
|
|
79
|
+
trigger: rec.trigger,
|
|
80
|
+
label: rec.label,
|
|
81
|
+
median: rec.median
|
|
82
|
+
};
|
|
83
|
+
await this.#set_str(r, this.#R + rec.id, JSON.stringify({
|
|
84
|
+
summary,
|
|
85
|
+
dump: rec.dump
|
|
86
|
+
}));
|
|
87
|
+
await zadd(r, this.#RZ, rec.created, rec.id);
|
|
88
|
+
if (await zcard(r, this.#RZ) > MAX_REPORTS) await trim(r, this.#RZ, this.#R, MAX_REPORTS);
|
|
89
|
+
}
|
|
90
|
+
async getReport(id) {
|
|
91
|
+
const raw = await (await this.#need()).get(this.#R + id);
|
|
92
|
+
if (!raw) return null;
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(raw).dump;
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async listReports(limit = 30) {
|
|
100
|
+
const r = await this.#need();
|
|
101
|
+
const ids = await zrange(r, this.#RZ, 0, limit - 1, true);
|
|
102
|
+
const out = [];
|
|
103
|
+
const gone = [];
|
|
104
|
+
for (const id of ids) {
|
|
105
|
+
const raw = await r.get(this.#R + id);
|
|
106
|
+
if (!raw) {
|
|
107
|
+
gone.push(id);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
out.push(JSON.parse(raw).summary);
|
|
112
|
+
} catch {}
|
|
113
|
+
}
|
|
114
|
+
await zrem(r, this.#RZ, gone);
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
async deleteReport(id) {
|
|
118
|
+
const r = await this.#need();
|
|
119
|
+
await r.del(this.#R + id);
|
|
120
|
+
await zrem(r, this.#RZ, [id]);
|
|
121
|
+
}
|
|
122
|
+
async prune(keep) {
|
|
123
|
+
await trim(await this.#need(), this.#RZ, this.#R, keep);
|
|
124
|
+
}
|
|
125
|
+
async putVisit(rec) {
|
|
126
|
+
const r = await this.#need();
|
|
127
|
+
const vz = this.#VZ(rec.page);
|
|
128
|
+
await this.#set_str(r, this.#V + rec.key, JSON.stringify(rec));
|
|
129
|
+
await zadd(r, vz, rec.at, rec.key);
|
|
130
|
+
if (await zcard(r, vz) > 2e3) await trim(r, vz, this.#V, MAX_VISITS_PER_KEY);
|
|
131
|
+
}
|
|
132
|
+
async listVisits(page, limit = 10) {
|
|
133
|
+
const r = await this.#need();
|
|
134
|
+
const keys = await zrange(r, this.#VZ(page), 0, limit - 1, true);
|
|
135
|
+
const out = [];
|
|
136
|
+
const gone = [];
|
|
137
|
+
for (const k of keys) {
|
|
138
|
+
const raw = await r.get(this.#V + k);
|
|
139
|
+
if (!raw) {
|
|
140
|
+
gone.push(k);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
out.push(JSON.parse(raw));
|
|
145
|
+
} catch {}
|
|
146
|
+
}
|
|
147
|
+
await zrem(r, this.#VZ(page), gone);
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
async close() {
|
|
151
|
+
const made = this.#made;
|
|
152
|
+
this.#made = null;
|
|
153
|
+
this.#connect = this.#connector();
|
|
154
|
+
await made?.quit?.().catch(() => {});
|
|
155
|
+
}
|
|
156
|
+
#connector() {
|
|
157
|
+
return flights({
|
|
158
|
+
name: "profiler redis client",
|
|
159
|
+
run: async () => this.#made = await resolve(this.#client)
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
async #need() {
|
|
163
|
+
const c = await this.#connect.get();
|
|
164
|
+
return c === STALE ? this.#need() : c;
|
|
165
|
+
}
|
|
166
|
+
#VZ(page) {
|
|
167
|
+
return `${this.#V}z:${page}`;
|
|
168
|
+
}
|
|
169
|
+
async #set_str(r, key, val) {
|
|
28
170
|
const n = node(r);
|
|
171
|
+
const ttl = this.#ttl;
|
|
29
172
|
if (!ttl) await r.set(key, val);
|
|
30
173
|
else if (n) await n.set(key, val, { EX: ttl });
|
|
31
174
|
else await r.set(key, val, "EX", ttl);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return n ? n.zAdd(key, {
|
|
36
|
-
score,
|
|
37
|
-
value: member
|
|
38
|
-
}) : r.zadd(key, score, member);
|
|
39
|
-
};
|
|
40
|
-
const zrem = (r, key, members) => {
|
|
41
|
-
if (!members.length) return Promise.resolve();
|
|
42
|
-
const n = node(r);
|
|
43
|
-
return n ? n.zRem(key, members) : r.zrem(key, ...members);
|
|
44
|
-
};
|
|
45
|
-
const zrange = (r, key, a, b, rev = false) => {
|
|
46
|
-
const n = node(r);
|
|
47
|
-
if (n) return rev ? n.zRange(key, a, b, { REV: true }) : n.zRange(key, a, b);
|
|
48
|
-
return rev ? r.zrange(key, a, b, "REV") : r.zrange(key, a, b);
|
|
49
|
-
};
|
|
50
|
-
const zcard = (r, key) => {
|
|
51
|
-
const n = node(r);
|
|
52
|
-
return n ? n.zCard(key) : r.zcard(key);
|
|
53
|
-
};
|
|
54
|
-
/** drop every member but the newest `keep`, with the keys they name */
|
|
55
|
-
const trim = async (r, key, item, keep) => {
|
|
56
|
-
const stale = await zrange(r, key, 0, -(keep + 1));
|
|
57
|
-
if (!stale.length) return;
|
|
58
|
-
await r.del(stale.map((m) => item + m));
|
|
59
|
-
await zrem(r, key, stale);
|
|
60
|
-
};
|
|
61
|
-
return {
|
|
62
|
-
kind: "redis",
|
|
63
|
-
async init() {
|
|
64
|
-
await need();
|
|
65
|
-
},
|
|
66
|
-
async putReport(rec) {
|
|
67
|
-
const r = await need();
|
|
68
|
-
const summary = {
|
|
69
|
-
id: rec.id,
|
|
70
|
-
created: rec.created,
|
|
71
|
-
page: rec.page,
|
|
72
|
-
trigger: rec.trigger,
|
|
73
|
-
label: rec.label,
|
|
74
|
-
median: rec.median
|
|
75
|
-
};
|
|
76
|
-
await setStr(r, R + rec.id, JSON.stringify({
|
|
77
|
-
summary,
|
|
78
|
-
dump: rec.dump
|
|
79
|
-
}));
|
|
80
|
-
await zadd(r, RZ, rec.created, rec.id);
|
|
81
|
-
if (await zcard(r, RZ) > MAX_REPORTS) await trim(r, RZ, R, MAX_REPORTS);
|
|
82
|
-
},
|
|
83
|
-
async getReport(id) {
|
|
84
|
-
const raw = await (await need()).get(R + id);
|
|
85
|
-
if (!raw) return null;
|
|
86
|
-
try {
|
|
87
|
-
return JSON.parse(raw).dump;
|
|
88
|
-
} catch {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
async listReports(limit = 30) {
|
|
93
|
-
const r = await need();
|
|
94
|
-
const ids = await zrange(r, RZ, 0, limit - 1, true);
|
|
95
|
-
const out = [];
|
|
96
|
-
const gone = [];
|
|
97
|
-
for (const id of ids) {
|
|
98
|
-
const raw = await r.get(R + id);
|
|
99
|
-
if (!raw) {
|
|
100
|
-
gone.push(id);
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
try {
|
|
104
|
-
out.push(JSON.parse(raw).summary);
|
|
105
|
-
} catch {}
|
|
106
|
-
}
|
|
107
|
-
await zrem(r, RZ, gone);
|
|
108
|
-
return out;
|
|
109
|
-
},
|
|
110
|
-
async deleteReport(id) {
|
|
111
|
-
const r = await need();
|
|
112
|
-
await r.del(R + id);
|
|
113
|
-
await zrem(r, RZ, [id]);
|
|
114
|
-
},
|
|
115
|
-
async prune(keep) {
|
|
116
|
-
await trim(await need(), RZ, R, keep);
|
|
117
|
-
},
|
|
118
|
-
async putVisit(rec) {
|
|
119
|
-
const r = await need();
|
|
120
|
-
await setStr(r, V + rec.key, JSON.stringify(rec));
|
|
121
|
-
await zadd(r, VZ(rec.page), rec.at, rec.key);
|
|
122
|
-
if (await zcard(r, VZ(rec.page)) > 2e3) await trim(r, VZ(rec.page), V, MAX_VISITS_PER_KEY);
|
|
123
|
-
},
|
|
124
|
-
async listVisits(page, limit = 10) {
|
|
125
|
-
const r = await need();
|
|
126
|
-
const keys = await zrange(r, VZ(page), 0, limit - 1, true);
|
|
127
|
-
const out = [];
|
|
128
|
-
const gone = [];
|
|
129
|
-
for (const k of keys) {
|
|
130
|
-
const raw = await r.get(V + k);
|
|
131
|
-
if (!raw) {
|
|
132
|
-
gone.push(k);
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
try {
|
|
136
|
-
out.push(JSON.parse(raw));
|
|
137
|
-
} catch {}
|
|
138
|
-
}
|
|
139
|
-
await zrem(r, VZ(page), gone);
|
|
140
|
-
return out;
|
|
141
|
-
},
|
|
142
|
-
async close() {
|
|
143
|
-
await c?.quit?.().catch(() => {});
|
|
144
|
-
c = null;
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
export { redisStore };
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
export { RedisStore };
|
|
@@ -1,2 +1,15 @@
|
|
|
1
|
-
import { ProfilerStore } from "./index.js";
|
|
2
|
-
export declare
|
|
1
|
+
import { ProfilerRecord, ProfilerStore, ProfilerSummary, ProfilerVisitRecord } from "./index.js";
|
|
2
|
+
export declare class SqliteStore implements ProfilerStore {
|
|
3
|
+
#private;
|
|
4
|
+
readonly kind: string;
|
|
5
|
+
constructor(filename?: string);
|
|
6
|
+
init(): void;
|
|
7
|
+
putReport(rec: ProfilerRecord): void;
|
|
8
|
+
getReport(id: string): string | null;
|
|
9
|
+
listReports(limit?: number): ProfilerSummary[];
|
|
10
|
+
deleteReport(id: string): void;
|
|
11
|
+
prune(keep: number): void;
|
|
12
|
+
putVisit(rec: ProfilerVisitRecord): void;
|
|
13
|
+
listVisits(page: string, limit?: number): ProfilerVisitRecord[];
|
|
14
|
+
close(): void;
|
|
15
|
+
}
|