@checkstack/healthcheck-tcp-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/banner-collector.test.ts +3 -2
- package/src/banner-collector.ts +40 -27
- package/src/strategy.test.ts +5 -3
- package/src/strategy.ts +55 -36
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @checkstack/healthcheck-tcp-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-tcp-backend",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -9,14 +9,14 @@
|
|
|
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
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/bun": "^1.0.0",
|
|
18
18
|
"typescript": "^5.0.0",
|
|
19
|
-
"@checkstack/tsconfig": "0.0.
|
|
20
|
-
"@checkstack/scripts": "0.1.
|
|
19
|
+
"@checkstack/tsconfig": "0.0.3",
|
|
20
|
+
"@checkstack/scripts": "0.1.1"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -65,7 +65,7 @@ describe("BannerCollector", () => {
|
|
|
65
65
|
});
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
describe("
|
|
68
|
+
describe("mergeResult", () => {
|
|
69
69
|
it("should calculate average read time and banner rate", () => {
|
|
70
70
|
const collector = new BannerCollector();
|
|
71
71
|
const runs = [
|
|
@@ -87,7 +87,8 @@ describe("BannerCollector", () => {
|
|
|
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.avgReadTimeMs).toBe(75);
|
|
93
94
|
expect(aggregated.bannerRate).toBe(50);
|
package/src/banner-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,
|
|
@@ -50,7 +56,7 @@ const bannerResultSchema = healthResultSchema({
|
|
|
50
56
|
|
|
51
57
|
export type BannerResult = z.infer<typeof bannerResultSchema>;
|
|
52
58
|
|
|
53
|
-
const
|
|
59
|
+
const bannerAggregatedDisplaySchema = healthResultSchema({
|
|
54
60
|
avgReadTimeMs: healthResultNumber({
|
|
55
61
|
"x-chart-type": "line",
|
|
56
62
|
"x-chart-label": "Avg Read Time",
|
|
@@ -63,6 +69,17 @@ const bannerAggregatedSchema = healthResultSchema({
|
|
|
63
69
|
}),
|
|
64
70
|
});
|
|
65
71
|
|
|
72
|
+
const bannerAggregatedInternalSchema = z.object({
|
|
73
|
+
_readTime: averageStateSchema
|
|
74
|
+
.optional(),
|
|
75
|
+
_banner: rateStateSchema
|
|
76
|
+
.optional(),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const bannerAggregatedSchema = bannerAggregatedDisplaySchema.merge(
|
|
80
|
+
bannerAggregatedInternalSchema,
|
|
81
|
+
);
|
|
82
|
+
|
|
66
83
|
export type BannerAggregatedResult = z.infer<typeof bannerAggregatedSchema>;
|
|
67
84
|
|
|
68
85
|
// ============================================================================
|
|
@@ -73,15 +90,12 @@ export type BannerAggregatedResult = z.infer<typeof bannerAggregatedSchema>;
|
|
|
73
90
|
* Built-in TCP banner collector.
|
|
74
91
|
* Reads the initial banner/greeting from a TCP server.
|
|
75
92
|
*/
|
|
76
|
-
export class BannerCollector
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
BannerAggregatedResult
|
|
83
|
-
>
|
|
84
|
-
{
|
|
93
|
+
export class BannerCollector implements CollectorStrategy<
|
|
94
|
+
TcpTransportClient,
|
|
95
|
+
BannerConfig,
|
|
96
|
+
BannerResult,
|
|
97
|
+
BannerAggregatedResult
|
|
98
|
+
> {
|
|
85
99
|
id = "banner";
|
|
86
100
|
displayName = "TCP Banner";
|
|
87
101
|
description = "Read the initial banner/greeting from the server";
|
|
@@ -123,28 +137,27 @@ export class BannerCollector
|
|
|
123
137
|
};
|
|
124
138
|
}
|
|
125
139
|
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
mergeResult(
|
|
141
|
+
existing: BannerAggregatedResult | undefined,
|
|
142
|
+
run: HealthCheckRunForAggregation<BannerResult>,
|
|
128
143
|
): BannerAggregatedResult {
|
|
129
|
-
const
|
|
130
|
-
.map((r) => r.metadata?.readTimeMs)
|
|
131
|
-
.filter((v): v is number => typeof v === "number");
|
|
144
|
+
const metadata = run.metadata;
|
|
132
145
|
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
146
|
+
const readTimeState = mergeAverage(
|
|
147
|
+
existing?._readTime as AverageState | undefined,
|
|
148
|
+
metadata?.readTimeMs,
|
|
149
|
+
);
|
|
136
150
|
|
|
137
|
-
const
|
|
151
|
+
const bannerState = mergeRate(
|
|
152
|
+
existing?._banner as RateState | undefined,
|
|
153
|
+
metadata?.hasBanner,
|
|
154
|
+
);
|
|
138
155
|
|
|
139
156
|
return {
|
|
140
|
-
avgReadTimeMs:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
bannerRate:
|
|
145
|
-
hasBanners.length > 0
|
|
146
|
-
? Math.round((bannerCount / hasBanners.length) * 100)
|
|
147
|
-
: 0,
|
|
157
|
+
avgReadTimeMs: readTimeState.avg,
|
|
158
|
+
bannerRate: bannerState.rate,
|
|
159
|
+
_readTime: readTimeState,
|
|
160
|
+
_banner: bannerState,
|
|
148
161
|
};
|
|
149
162
|
}
|
|
150
163
|
}
|
package/src/strategy.test.ts
CHANGED
|
@@ -90,7 +90,7 @@ describe("TcpHealthCheckStrategy", () => {
|
|
|
90
90
|
});
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
describe("
|
|
93
|
+
describe("mergeResult", () => {
|
|
94
94
|
it("should calculate averages correctly", () => {
|
|
95
95
|
const strategy = new TcpHealthCheckStrategy();
|
|
96
96
|
const runs = [
|
|
@@ -118,7 +118,8 @@ describe("TcpHealthCheckStrategy", () => {
|
|
|
118
118
|
},
|
|
119
119
|
];
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
122
|
+
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
122
123
|
|
|
123
124
|
expect(aggregated.avgConnectionTime).toBe(15);
|
|
124
125
|
expect(aggregated.successRate).toBe(100);
|
|
@@ -153,7 +154,8 @@ describe("TcpHealthCheckStrategy", () => {
|
|
|
153
154
|
},
|
|
154
155
|
];
|
|
155
156
|
|
|
156
|
-
|
|
157
|
+
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
158
|
+
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
157
159
|
|
|
158
160
|
expect(aggregated.successRate).toBe(50);
|
|
159
161
|
expect(aggregated.errorCount).toBe(1);
|
package/src/strategy.ts
CHANGED
|
@@ -4,6 +4,15 @@ import {
|
|
|
4
4
|
Versioned,
|
|
5
5
|
z,
|
|
6
6
|
type ConnectedClient,
|
|
7
|
+
mergeAverage,
|
|
8
|
+
averageStateSchema,
|
|
9
|
+
mergeRate,
|
|
10
|
+
rateStateSchema,
|
|
11
|
+
mergeCounter,
|
|
12
|
+
counterStateSchema,
|
|
13
|
+
type AverageState,
|
|
14
|
+
type RateState,
|
|
15
|
+
type CounterState,
|
|
7
16
|
} from "@checkstack/backend-api";
|
|
8
17
|
import {
|
|
9
18
|
healthResultBoolean,
|
|
@@ -73,7 +82,8 @@ type TcpResult = z.infer<typeof tcpResultSchema>;
|
|
|
73
82
|
/**
|
|
74
83
|
* Aggregated metadata for buckets.
|
|
75
84
|
*/
|
|
76
|
-
|
|
85
|
+
// UI-visible aggregated fields
|
|
86
|
+
const tcpAggregatedDisplaySchema = healthResultSchema({
|
|
77
87
|
avgConnectionTime: healthResultNumber({
|
|
78
88
|
"x-chart-type": "line",
|
|
79
89
|
"x-chart-label": "Avg Connection Time",
|
|
@@ -90,6 +100,19 @@ const tcpAggregatedSchema = healthResultSchema({
|
|
|
90
100
|
}),
|
|
91
101
|
});
|
|
92
102
|
|
|
103
|
+
// Internal state for incremental aggregation
|
|
104
|
+
const tcpAggregatedInternalSchema = z.object({
|
|
105
|
+
_connectionTime: averageStateSchema
|
|
106
|
+
.optional(),
|
|
107
|
+
_success: rateStateSchema
|
|
108
|
+
.optional(),
|
|
109
|
+
_errors: counterStateSchema.optional(),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const tcpAggregatedSchema = tcpAggregatedDisplaySchema.merge(
|
|
113
|
+
tcpAggregatedInternalSchema,
|
|
114
|
+
);
|
|
115
|
+
|
|
93
116
|
type TcpAggregatedResult = z.infer<typeof tcpAggregatedSchema>;
|
|
94
117
|
|
|
95
118
|
// ============================================================================
|
|
@@ -159,15 +182,12 @@ const defaultSocketFactory: SocketFactory = () => {
|
|
|
159
182
|
// STRATEGY
|
|
160
183
|
// ============================================================================
|
|
161
184
|
|
|
162
|
-
export class TcpHealthCheckStrategy
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
TcpAggregatedResult
|
|
169
|
-
>
|
|
170
|
-
{
|
|
185
|
+
export class TcpHealthCheckStrategy implements HealthCheckStrategy<
|
|
186
|
+
TcpConfig,
|
|
187
|
+
TcpTransportClient,
|
|
188
|
+
TcpResult,
|
|
189
|
+
TcpAggregatedResult
|
|
190
|
+
> {
|
|
171
191
|
id = "tcp";
|
|
172
192
|
displayName = "TCP Health Check";
|
|
173
193
|
description = "TCP port connectivity check with optional banner grab";
|
|
@@ -213,40 +233,39 @@ export class TcpHealthCheckStrategy
|
|
|
213
233
|
schema: tcpAggregatedSchema,
|
|
214
234
|
});
|
|
215
235
|
|
|
216
|
-
|
|
217
|
-
|
|
236
|
+
mergeResult(
|
|
237
|
+
existing: TcpAggregatedResult | undefined,
|
|
238
|
+
run: HealthCheckRunForAggregation<TcpResult>,
|
|
218
239
|
): TcpAggregatedResult {
|
|
219
|
-
const
|
|
240
|
+
const metadata = run.metadata;
|
|
220
241
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
242
|
+
const connectionTimeState = mergeAverage(
|
|
243
|
+
existing?._connectionTime as AverageState | undefined,
|
|
244
|
+
metadata?.connectionTimeMs,
|
|
245
|
+
);
|
|
224
246
|
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
247
|
+
const successState = mergeRate(
|
|
248
|
+
existing?._success as RateState | undefined,
|
|
249
|
+
metadata?.connected,
|
|
250
|
+
);
|
|
228
251
|
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
)
|
|
234
|
-
: 0;
|
|
252
|
+
const errorState = mergeCounter(
|
|
253
|
+
existing?._errors as CounterState | undefined,
|
|
254
|
+
metadata?.error !== undefined,
|
|
255
|
+
);
|
|
235
256
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
return { avgConnectionTime, successRate, errorCount };
|
|
257
|
+
return {
|
|
258
|
+
avgConnectionTime: connectionTimeState.avg,
|
|
259
|
+
successRate: successState.rate,
|
|
260
|
+
errorCount: errorState.count,
|
|
261
|
+
_connectionTime: connectionTimeState,
|
|
262
|
+
_success: successState,
|
|
263
|
+
_errors: errorState,
|
|
264
|
+
};
|
|
246
265
|
}
|
|
247
266
|
|
|
248
267
|
async createClient(
|
|
249
|
-
config: TcpConfig
|
|
268
|
+
config: TcpConfig,
|
|
250
269
|
): Promise<ConnectedClient<TcpTransportClient>> {
|
|
251
270
|
const validatedConfig = this.config.validate(config);
|
|
252
271
|
const socket = this.socketFactory();
|