@checkstack/healthcheck-grpc-backend 0.1.13 → 0.1.14
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/CHANGELOG.md +30 -0
- package/package.json +6 -6
- package/src/health-collector.test.ts +5 -3
- package/src/health-collector.ts +40 -27
- package/src/strategy.test.ts +5 -3
- package/src/strategy.ts +59 -44
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @checkstack/healthcheck-grpc-backend
|
|
2
2
|
|
|
3
|
+
## 0.1.14
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 48c2080: Migrate aggregation from batch to incremental (`mergeResult`)
|
|
8
|
+
|
|
9
|
+
### Breaking Changes (Internal)
|
|
10
|
+
|
|
11
|
+
- Replaced `aggregateResult(runs[])` with `mergeResult(existing, run)` interface across all HealthCheckStrategy and CollectorStrategy implementations
|
|
12
|
+
|
|
13
|
+
### New Features
|
|
14
|
+
|
|
15
|
+
- Added incremental aggregation utilities in `@checkstack/backend-api`:
|
|
16
|
+
- `mergeCounter()` - track occurrences
|
|
17
|
+
- `mergeAverage()` - track sum/count, compute avg
|
|
18
|
+
- `mergeRate()` - track success/total, compute %
|
|
19
|
+
- `mergeMinMax()` - track min/max values
|
|
20
|
+
- Exported Zod schemas for internal state: `averageStateSchema`, `rateStateSchema`, `minMaxStateSchema`, `counterStateSchema`
|
|
21
|
+
|
|
22
|
+
### Improvements
|
|
23
|
+
|
|
24
|
+
- Enables O(1) storage overhead by maintaining incremental aggregation state
|
|
25
|
+
- Prepares for real-time hourly aggregation without batch accumulation
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [f676e11]
|
|
28
|
+
- Updated dependencies [48c2080]
|
|
29
|
+
- @checkstack/common@0.6.2
|
|
30
|
+
- @checkstack/backend-api@0.6.0
|
|
31
|
+
- @checkstack/healthcheck-common@0.8.2
|
|
32
|
+
|
|
3
33
|
## 0.1.13
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/healthcheck-grpc-backend",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
"lint:code": "eslint . --max-warnings 0"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@checkstack/backend-api": "0.5.
|
|
13
|
-
"@checkstack/common": "0.6.
|
|
14
|
-
"@checkstack/healthcheck-common": "0.
|
|
12
|
+
"@checkstack/backend-api": "0.5.2",
|
|
13
|
+
"@checkstack/common": "0.6.1",
|
|
14
|
+
"@checkstack/healthcheck-common": "0.8.1",
|
|
15
15
|
"@grpc/grpc-js": "^1.9.0"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@types/bun": "^1.0.0",
|
|
19
19
|
"typescript": "^5.0.0",
|
|
20
|
-
"@checkstack/tsconfig": "0.0.
|
|
21
|
-
"@checkstack/scripts": "0.1.
|
|
20
|
+
"@checkstack/tsconfig": "0.0.3",
|
|
21
|
+
"@checkstack/scripts": "0.1.1"
|
|
22
22
|
}
|
|
23
23
|
}
|
|
@@ -65,7 +65,7 @@ describe("HealthCollector", () => {
|
|
|
65
65
|
});
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
describe("
|
|
68
|
+
describe("mergeResult", () => {
|
|
69
69
|
it("should calculate average response time and serving rate", () => {
|
|
70
70
|
const collector = new HealthCollector();
|
|
71
71
|
const runs = [
|
|
@@ -87,7 +87,8 @@ describe("HealthCollector", () => {
|
|
|
87
87
|
},
|
|
88
88
|
];
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
91
|
+
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
91
92
|
|
|
92
93
|
expect(aggregated.avgResponseTimeMs).toBe(75);
|
|
93
94
|
expect(aggregated.servingRate).toBe(100);
|
|
@@ -118,7 +119,8 @@ describe("HealthCollector", () => {
|
|
|
118
119
|
},
|
|
119
120
|
];
|
|
120
121
|
|
|
121
|
-
|
|
122
|
+
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
123
|
+
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
122
124
|
|
|
123
125
|
expect(aggregated.servingRate).toBe(50);
|
|
124
126
|
});
|
package/src/health-collector.ts
CHANGED
|
@@ -4,6 +4,12 @@ import {
|
|
|
4
4
|
type HealthCheckRunForAggregation,
|
|
5
5
|
type CollectorResult,
|
|
6
6
|
type CollectorStrategy,
|
|
7
|
+
mergeAverage,
|
|
8
|
+
averageStateSchema,
|
|
9
|
+
mergeRate,
|
|
10
|
+
rateStateSchema,
|
|
11
|
+
type AverageState,
|
|
12
|
+
type RateState,
|
|
7
13
|
} from "@checkstack/backend-api";
|
|
8
14
|
import {
|
|
9
15
|
healthResultNumber,
|
|
@@ -49,7 +55,7 @@ const grpcHealthResultSchema = healthResultSchema({
|
|
|
49
55
|
|
|
50
56
|
export type HealthResult = z.infer<typeof grpcHealthResultSchema>;
|
|
51
57
|
|
|
52
|
-
const
|
|
58
|
+
const healthAggregatedDisplaySchema = healthResultSchema({
|
|
53
59
|
avgResponseTimeMs: healthResultNumber({
|
|
54
60
|
"x-chart-type": "line",
|
|
55
61
|
"x-chart-label": "Avg Response Time",
|
|
@@ -62,6 +68,17 @@ const healthAggregatedSchema = healthResultSchema({
|
|
|
62
68
|
}),
|
|
63
69
|
});
|
|
64
70
|
|
|
71
|
+
const healthAggregatedInternalSchema = z.object({
|
|
72
|
+
_responseTime: averageStateSchema
|
|
73
|
+
.optional(),
|
|
74
|
+
_serving: rateStateSchema
|
|
75
|
+
.optional(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const healthAggregatedSchema = healthAggregatedDisplaySchema.merge(
|
|
79
|
+
healthAggregatedInternalSchema,
|
|
80
|
+
);
|
|
81
|
+
|
|
65
82
|
export type HealthAggregatedResult = z.infer<typeof healthAggregatedSchema>;
|
|
66
83
|
|
|
67
84
|
// ============================================================================
|
|
@@ -72,15 +89,12 @@ export type HealthAggregatedResult = z.infer<typeof healthAggregatedSchema>;
|
|
|
72
89
|
* Built-in gRPC health collector.
|
|
73
90
|
* Checks gRPC health status using the standard Health Checking Protocol.
|
|
74
91
|
*/
|
|
75
|
-
export class HealthCollector
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
HealthAggregatedResult
|
|
82
|
-
>
|
|
83
|
-
{
|
|
92
|
+
export class HealthCollector implements CollectorStrategy<
|
|
93
|
+
GrpcTransportClient,
|
|
94
|
+
HealthConfig,
|
|
95
|
+
HealthResult,
|
|
96
|
+
HealthAggregatedResult
|
|
97
|
+
> {
|
|
84
98
|
id = "health";
|
|
85
99
|
displayName = "gRPC Health Check";
|
|
86
100
|
description = "Check gRPC service health status";
|
|
@@ -124,28 +138,27 @@ export class HealthCollector
|
|
|
124
138
|
};
|
|
125
139
|
}
|
|
126
140
|
|
|
127
|
-
|
|
128
|
-
|
|
141
|
+
mergeResult(
|
|
142
|
+
existing: HealthAggregatedResult | undefined,
|
|
143
|
+
run: HealthCheckRunForAggregation<HealthResult>,
|
|
129
144
|
): HealthAggregatedResult {
|
|
130
|
-
const
|
|
131
|
-
.map((r) => r.metadata?.responseTimeMs)
|
|
132
|
-
.filter((v): v is number => typeof v === "number");
|
|
145
|
+
const metadata = run.metadata;
|
|
133
146
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
147
|
+
const responseTimeState = mergeAverage(
|
|
148
|
+
existing?._responseTime as AverageState | undefined,
|
|
149
|
+
metadata?.responseTimeMs,
|
|
150
|
+
);
|
|
137
151
|
|
|
138
|
-
const
|
|
152
|
+
const servingState = mergeRate(
|
|
153
|
+
existing?._serving as RateState | undefined,
|
|
154
|
+
metadata?.serving,
|
|
155
|
+
);
|
|
139
156
|
|
|
140
157
|
return {
|
|
141
|
-
avgResponseTimeMs:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
servingRate:
|
|
146
|
-
servingResults.length > 0
|
|
147
|
-
? Math.round((servingCount / servingResults.length) * 100)
|
|
148
|
-
: 0,
|
|
158
|
+
avgResponseTimeMs: responseTimeState.avg,
|
|
159
|
+
servingRate: servingState.rate,
|
|
160
|
+
_responseTime: responseTimeState,
|
|
161
|
+
_serving: servingState,
|
|
149
162
|
};
|
|
150
163
|
}
|
|
151
164
|
}
|
package/src/strategy.test.ts
CHANGED
|
@@ -111,7 +111,7 @@ describe("GrpcHealthCheckStrategy", () => {
|
|
|
111
111
|
});
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
-
describe("
|
|
114
|
+
describe("mergeResult", () => {
|
|
115
115
|
it("should calculate averages correctly", () => {
|
|
116
116
|
const strategy = new GrpcHealthCheckStrategy();
|
|
117
117
|
const runs = [
|
|
@@ -141,7 +141,8 @@ describe("GrpcHealthCheckStrategy", () => {
|
|
|
141
141
|
},
|
|
142
142
|
];
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
145
|
+
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
145
146
|
|
|
146
147
|
expect(aggregated.avgResponseTime).toBe(10);
|
|
147
148
|
expect(aggregated.successRate).toBe(100);
|
|
@@ -179,7 +180,8 @@ describe("GrpcHealthCheckStrategy", () => {
|
|
|
179
180
|
},
|
|
180
181
|
];
|
|
181
182
|
|
|
182
|
-
|
|
183
|
+
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
184
|
+
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
183
185
|
|
|
184
186
|
expect(aggregated.errorCount).toBe(1);
|
|
185
187
|
expect(aggregated.servingCount).toBe(0);
|
package/src/strategy.ts
CHANGED
|
@@ -5,6 +5,15 @@ import {
|
|
|
5
5
|
Versioned,
|
|
6
6
|
z,
|
|
7
7
|
type ConnectedClient,
|
|
8
|
+
mergeAverage,
|
|
9
|
+
averageStateSchema,
|
|
10
|
+
mergeRate,
|
|
11
|
+
rateStateSchema,
|
|
12
|
+
mergeCounter,
|
|
13
|
+
counterStateSchema,
|
|
14
|
+
type AverageState,
|
|
15
|
+
type RateState,
|
|
16
|
+
type CounterState,
|
|
8
17
|
} from "@checkstack/backend-api";
|
|
9
18
|
import {
|
|
10
19
|
healthResultBoolean,
|
|
@@ -83,7 +92,7 @@ type GrpcResult = z.infer<typeof grpcResultSchema>;
|
|
|
83
92
|
/**
|
|
84
93
|
* Aggregated metadata for buckets.
|
|
85
94
|
*/
|
|
86
|
-
const
|
|
95
|
+
const grpcAggregatedDisplaySchema = healthResultSchema({
|
|
87
96
|
avgResponseTime: healthResultNumber({
|
|
88
97
|
"x-chart-type": "line",
|
|
89
98
|
"x-chart-label": "Avg Response Time",
|
|
@@ -104,6 +113,19 @@ const grpcAggregatedSchema = healthResultSchema({
|
|
|
104
113
|
}),
|
|
105
114
|
});
|
|
106
115
|
|
|
116
|
+
const grpcAggregatedInternalSchema = z.object({
|
|
117
|
+
_responseTime: averageStateSchema
|
|
118
|
+
.optional(),
|
|
119
|
+
_success: rateStateSchema
|
|
120
|
+
.optional(),
|
|
121
|
+
_errors: counterStateSchema.optional(),
|
|
122
|
+
_serving: counterStateSchema.optional(),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const grpcAggregatedSchema = grpcAggregatedDisplaySchema.merge(
|
|
126
|
+
grpcAggregatedInternalSchema,
|
|
127
|
+
);
|
|
128
|
+
|
|
107
129
|
type GrpcAggregatedResult = z.infer<typeof grpcAggregatedSchema>;
|
|
108
130
|
|
|
109
131
|
// ============================================================================
|
|
@@ -176,7 +198,7 @@ const defaultGrpcClient: GrpcHealthClient = {
|
|
|
176
198
|
resolve({
|
|
177
199
|
status: statusMap[response?.status ?? 0] ?? "UNKNOWN",
|
|
178
200
|
});
|
|
179
|
-
}
|
|
201
|
+
},
|
|
180
202
|
);
|
|
181
203
|
});
|
|
182
204
|
},
|
|
@@ -186,15 +208,12 @@ const defaultGrpcClient: GrpcHealthClient = {
|
|
|
186
208
|
// STRATEGY
|
|
187
209
|
// ============================================================================
|
|
188
210
|
|
|
189
|
-
export class GrpcHealthCheckStrategy
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
GrpcAggregatedResult
|
|
196
|
-
>
|
|
197
|
-
{
|
|
211
|
+
export class GrpcHealthCheckStrategy implements HealthCheckStrategy<
|
|
212
|
+
GrpcConfig,
|
|
213
|
+
GrpcTransportClient,
|
|
214
|
+
GrpcResult,
|
|
215
|
+
GrpcAggregatedResult
|
|
216
|
+
> {
|
|
198
217
|
id = "grpc";
|
|
199
218
|
displayName = "gRPC Health Check";
|
|
200
219
|
description =
|
|
@@ -237,50 +256,46 @@ export class GrpcHealthCheckStrategy
|
|
|
237
256
|
schema: grpcAggregatedSchema,
|
|
238
257
|
});
|
|
239
258
|
|
|
240
|
-
|
|
241
|
-
|
|
259
|
+
mergeResult(
|
|
260
|
+
existing: GrpcAggregatedResult | undefined,
|
|
261
|
+
run: HealthCheckRunForAggregation<GrpcResult>,
|
|
242
262
|
): GrpcAggregatedResult {
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
if (validRuns.length === 0) {
|
|
246
|
-
return {
|
|
247
|
-
avgResponseTime: 0,
|
|
248
|
-
successRate: 0,
|
|
249
|
-
errorCount: 0,
|
|
250
|
-
servingCount: 0,
|
|
251
|
-
};
|
|
252
|
-
}
|
|
263
|
+
const metadata = run.metadata;
|
|
253
264
|
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
265
|
+
const responseTimeState = mergeAverage(
|
|
266
|
+
existing?._responseTime as AverageState | undefined,
|
|
267
|
+
metadata?.responseTimeMs,
|
|
268
|
+
);
|
|
257
269
|
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
)
|
|
263
|
-
: 0;
|
|
270
|
+
const successState = mergeRate(
|
|
271
|
+
existing?._success as RateState | undefined,
|
|
272
|
+
metadata?.status === "SERVING",
|
|
273
|
+
);
|
|
264
274
|
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
275
|
+
const errorState = mergeCounter(
|
|
276
|
+
existing?._errors as CounterState | undefined,
|
|
277
|
+
metadata?.error !== undefined,
|
|
278
|
+
);
|
|
269
279
|
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
280
|
+
const servingState = mergeCounter(
|
|
281
|
+
existing?._serving as CounterState | undefined,
|
|
282
|
+
metadata?.status === "SERVING",
|
|
283
|
+
);
|
|
273
284
|
|
|
274
285
|
return {
|
|
275
|
-
avgResponseTime,
|
|
276
|
-
successRate,
|
|
277
|
-
errorCount,
|
|
278
|
-
servingCount,
|
|
286
|
+
avgResponseTime: responseTimeState.avg,
|
|
287
|
+
successRate: successState.rate,
|
|
288
|
+
errorCount: errorState.count,
|
|
289
|
+
servingCount: servingState.count,
|
|
290
|
+
_responseTime: responseTimeState,
|
|
291
|
+
_success: successState,
|
|
292
|
+
_errors: errorState,
|
|
293
|
+
_serving: servingState,
|
|
279
294
|
};
|
|
280
295
|
}
|
|
281
296
|
|
|
282
297
|
async createClient(
|
|
283
|
-
config: GrpcConfigInput
|
|
298
|
+
config: GrpcConfigInput,
|
|
284
299
|
): Promise<ConnectedClient<GrpcTransportClient>> {
|
|
285
300
|
const validatedConfig = this.config.validate(config);
|
|
286
301
|
|