Skip to content

Commit a9b9ccd

Browse files
ericallamclaude
andcommitted
fix(webapp): don't emit zeroed engine metrics when $metrics read fails
A failed $metrics.json() previously zero-filled every engine-derived value, so the batch observer reported spurious zeros — including for cumulative counters (db.client.queries.total, db.datasource.queries.total, db.pool.connections.opened/closed.total), which reads as a counter reset and corrupts rate math. Restore the old fail-quiet behavior per client: the collector now signals engineMetricsAvailable and leaves engine-derived fields (counters/gauges/histograms, and quaint pool figures) undefined on failure, and the tracer skips those instruments for that client. Adapter clients still emit live pg.Pool figures because those don't depend on $metrics. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KHjfL7qXHia5DTnxi1RePS
1 parent 2ec585b commit a9b9ccd

3 files changed

Lines changed: 54 additions & 42 deletions

File tree

apps/webapp/app/utils/databaseMetrics.server.test.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ describe("normalizeDatabaseMetrics", () => {
4747

4848
expect(result.driver).toBe("quaint");
4949
expect(result.clientType).toBe("writer");
50+
expect(result.engineMetricsAvailable).toBe(true);
5051
expect(result.pool).toEqual({
5152
open: 9,
5253
busy: 4,
@@ -72,6 +73,7 @@ describe("normalizeDatabaseMetrics", () => {
7273
const result = normalizeDatabaseMetrics(source, quaintJson());
7374

7475
expect(result.driver).toBe("pg-adapter");
76+
expect(result.engineMetricsAvailable).toBe(true);
7577
expect(result.pool).toEqual({
7678
open: 8,
7779
busy: 5,
@@ -94,10 +96,10 @@ describe("normalizeDatabaseMetrics", () => {
9496

9597
const result = normalizeDatabaseMetrics(source, quaintJson());
9698

97-
expect(result.pool.busy).toBe(0);
99+
expect(result.pool?.busy).toBe(0);
98100
});
99101

100-
it("falls back to zeroed query/pool metrics when $metrics is unavailable for a quaint client", () => {
102+
it("omits engine-derived metrics and pool when $metrics is unavailable for a quaint client", () => {
101103
const source: DatabaseMetricsSource = {
102104
clientType: "writer",
103105
usesDriverAdapter: false,
@@ -106,19 +108,14 @@ describe("normalizeDatabaseMetrics", () => {
106108

107109
const result = normalizeDatabaseMetrics(source, undefined);
108110

109-
expect(result.pool).toEqual({
110-
open: 0,
111-
busy: 0,
112-
idle: 0,
113-
waiting: 0,
114-
openedTotal: 0,
115-
closedTotal: 0,
116-
});
117-
expect(result.counters).toEqual({ queriesTotal: 0, datasourceQueriesTotal: 0 });
111+
expect(result.engineMetricsAvailable).toBe(false);
112+
expect(result.pool).toBeUndefined();
113+
expect(result.counters).toBeUndefined();
114+
expect(result.gauges).toBeUndefined();
118115
expect(result.histograms.queriesDuration).toBeUndefined();
119116
});
120117

121-
it("keeps adapter pool figures even when $metrics is unavailable", () => {
118+
it("keeps pg.Pool figures but omits engine metrics when $metrics is unavailable for an adapter client", () => {
122119
const source: DatabaseMetricsSource = {
123120
clientType: "control-plane-writer",
124121
usesDriverAdapter: true,
@@ -129,6 +126,7 @@ describe("normalizeDatabaseMetrics", () => {
129126

130127
const result = normalizeDatabaseMetrics(source, undefined);
131128

129+
expect(result.engineMetricsAvailable).toBe(false);
132130
expect(result.pool).toEqual({
133131
open: 7,
134132
busy: 5,
@@ -137,6 +135,7 @@ describe("normalizeDatabaseMetrics", () => {
137135
openedTotal: 9,
138136
closedTotal: 2,
139137
});
140-
expect(result.counters).toEqual({ queriesTotal: 0, datasourceQueriesTotal: 0 });
138+
expect(result.counters).toBeUndefined();
139+
expect(result.gauges).toBeUndefined();
141140
});
142141
});

apps/webapp/app/utils/databaseMetrics.server.ts

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ export type NormalizedPoolMetrics = {
4242
export type NormalizedDatabaseMetrics = {
4343
clientType: string;
4444
driver: "pg-adapter" | "quaint";
45-
pool: NormalizedPoolMetrics;
46-
counters: { queriesTotal: number; datasourceQueriesTotal: number };
47-
gauges: { queriesActive: number; queriesWait: number };
45+
engineMetricsAvailable: boolean;
46+
pool?: NormalizedPoolMetrics;
47+
counters?: { queriesTotal: number; datasourceQueriesTotal: number };
48+
gauges?: { queriesActive: number; queriesWait: number };
4849
histograms: {
4950
queriesWait?: MetricHistogramValue;
5051
queriesDuration?: MetricHistogramValue;
@@ -78,16 +79,11 @@ export function normalizeDatabaseMetrics(
7879
source: DatabaseMetricsSource,
7980
json: PrismaMetricsJson | undefined
8081
): NormalizedDatabaseMetrics {
81-
const counters = json ? indexByKey(json.counters) : {};
82-
const gauges = json ? indexByKey(json.gauges) : {};
83-
const histograms: Record<string, MetricHistogramValue> = {};
84-
if (json) {
85-
for (const histogram of json.histograms) {
86-
histograms[histogram.key] = histogram.value;
87-
}
88-
}
82+
const driver = source.usesDriverAdapter ? ("pg-adapter" as const) : ("quaint" as const);
83+
const counters = json ? indexByKey(json.counters) : undefined;
84+
const gauges = json ? indexByKey(json.gauges) : undefined;
8985

90-
let pool: NormalizedPoolMetrics;
86+
let pool: NormalizedPoolMetrics | undefined;
9187
if (source.usesDriverAdapter && source.pool) {
9288
const total = source.pool.totalCount;
9389
const idle = source.pool.idleCount;
@@ -99,7 +95,7 @@ export function normalizeDatabaseMetrics(
9995
openedTotal: source.poolCounters?.opened() ?? 0,
10096
closedTotal: source.poolCounters?.closed() ?? 0,
10197
};
102-
} else {
98+
} else if (counters && gauges) {
10399
pool = {
104100
open: gauges["prisma_pool_connections_open"] ?? 0,
105101
busy: gauges["prisma_pool_connections_busy"] ?? 0,
@@ -110,24 +106,35 @@ export function normalizeDatabaseMetrics(
110106
};
111107
}
112108

113-
return {
109+
const result: NormalizedDatabaseMetrics = {
114110
clientType: source.clientType,
115-
driver: source.usesDriverAdapter ? "pg-adapter" : "quaint",
111+
driver,
112+
engineMetricsAvailable: json !== undefined,
116113
pool,
117-
counters: {
114+
histograms: {},
115+
};
116+
117+
if (json && counters && gauges) {
118+
const histograms: Record<string, MetricHistogramValue> = {};
119+
for (const histogram of json.histograms) {
120+
histograms[histogram.key] = histogram.value;
121+
}
122+
result.counters = {
118123
queriesTotal: counters["prisma_client_queries_total"] ?? 0,
119124
datasourceQueriesTotal: counters["prisma_datasource_queries_total"] ?? 0,
120-
},
121-
gauges: {
125+
};
126+
result.gauges = {
122127
queriesActive: gauges["prisma_client_queries_active"] ?? 0,
123128
queriesWait: gauges["prisma_client_queries_wait"] ?? 0,
124-
},
125-
histograms: {
129+
};
130+
result.histograms = {
126131
queriesWait: histograms["prisma_client_queries_wait_histogram_ms"],
127132
queriesDuration: histograms["prisma_client_queries_duration_histogram_ms"],
128133
datasourceQueriesDuration: histograms["prisma_datasource_queries_duration_histogram_ms"],
129-
},
130-
};
134+
};
135+
}
136+
137+
return result;
131138
}
132139

133140
export async function collectDatabaseClientMetrics(): Promise<NormalizedDatabaseMetrics[]> {

apps/webapp/app/v3/tracer.server.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -521,17 +521,23 @@ function configurePrismaMetrics({ meter }: { meter: Meter }) {
521521
const attributes = { db_client: client.clientType, db_driver: client.driver };
522522
const { pool, counters, gauges, histograms } = client;
523523

524+
if (pool) {
525+
res.observe(connectionsOpenedTotal, pool.openedTotal, attributes);
526+
res.observe(connectionsClosedTotal, pool.closedTotal, attributes);
527+
res.observe(totalGauge, pool.open, attributes);
528+
res.observe(busyGauge, pool.busy, attributes);
529+
res.observe(freeGauge, pool.idle, attributes);
530+
res.observe(waitingGauge, pool.waiting, attributes);
531+
}
532+
533+
if (!counters || !gauges) {
534+
continue;
535+
}
536+
524537
res.observe(queriesTotal, counters.queriesTotal, attributes);
525538
res.observe(datasourceQueriesTotal, counters.datasourceQueriesTotal, attributes);
526-
res.observe(connectionsOpenedTotal, pool.openedTotal, attributes);
527-
res.observe(connectionsClosedTotal, pool.closedTotal, attributes);
528-
529539
res.observe(queriesActive, gauges.queriesActive, attributes);
530540
res.observe(queriesWait, gauges.queriesWait, attributes);
531-
res.observe(totalGauge, pool.open, attributes);
532-
res.observe(busyGauge, pool.busy, attributes);
533-
res.observe(freeGauge, pool.idle, attributes);
534-
res.observe(waitingGauge, pool.waiting, attributes);
535541

536542
if (histograms.queriesWait) {
537543
res.observe(queriesWaitTimeCount, histograms.queriesWait.count, attributes);

0 commit comments

Comments
 (0)