@checkstack/collector-hardware-backend 0.1.13 → 0.1.15

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 CHANGED
@@ -1,5 +1,43 @@
1
1
  # @checkstack/collector-hardware-backend
2
2
 
3
+ ## 0.1.15
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [3dd1914]
8
+ - @checkstack/backend-api@0.7.0
9
+
10
+ ## 0.1.14
11
+
12
+ ### Patch Changes
13
+
14
+ - 48c2080: Migrate aggregation from batch to incremental (`mergeResult`)
15
+
16
+ ### Breaking Changes (Internal)
17
+
18
+ - Replaced `aggregateResult(runs[])` with `mergeResult(existing, run)` interface across all HealthCheckStrategy and CollectorStrategy implementations
19
+
20
+ ### New Features
21
+
22
+ - Added incremental aggregation utilities in `@checkstack/backend-api`:
23
+ - `mergeCounter()` - track occurrences
24
+ - `mergeAverage()` - track sum/count, compute avg
25
+ - `mergeRate()` - track success/total, compute %
26
+ - `mergeMinMax()` - track min/max values
27
+ - Exported Zod schemas for internal state: `averageStateSchema`, `rateStateSchema`, `minMaxStateSchema`, `counterStateSchema`
28
+
29
+ ### Improvements
30
+
31
+ - Enables O(1) storage overhead by maintaining incremental aggregation state
32
+ - Prepares for real-time hourly aggregation without batch accumulation
33
+
34
+ - Updated dependencies [f676e11]
35
+ - Updated dependencies [48c2080]
36
+ - @checkstack/common@0.6.2
37
+ - @checkstack/backend-api@0.6.0
38
+ - @checkstack/healthcheck-common@0.8.2
39
+ - @checkstack/healthcheck-ssh-common@0.1.8
40
+
3
41
  ## 0.1.13
4
42
 
5
43
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/collector-hardware-backend",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {
@@ -10,15 +10,15 @@
10
10
  "test": "bun test"
11
11
  },
12
12
  "dependencies": {
13
- "@checkstack/backend-api": "0.5.1",
14
- "@checkstack/common": "0.6.0",
15
- "@checkstack/healthcheck-common": "0.7.0",
16
- "@checkstack/healthcheck-ssh-common": "0.1.6"
13
+ "@checkstack/backend-api": "0.5.2",
14
+ "@checkstack/common": "0.6.1",
15
+ "@checkstack/healthcheck-common": "0.8.1",
16
+ "@checkstack/healthcheck-ssh-common": "0.1.7"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/bun": "^1.0.0",
20
20
  "typescript": "^5.0.0",
21
- "@checkstack/tsconfig": "0.0.2",
22
- "@checkstack/scripts": "0.1.0"
21
+ "@checkstack/tsconfig": "0.0.3",
22
+ "@checkstack/scripts": "0.1.1"
23
23
  }
24
24
  }
@@ -9,7 +9,7 @@ describe("CpuCollector", () => {
9
9
  stat2?: { stdout: string };
10
10
  loadavg?: { stdout: string };
11
11
  nproc?: { stdout: string };
12
- } = {}
12
+ } = {},
13
13
  ): SshTransportClient => {
14
14
  let callCount = 0;
15
15
  return {
@@ -96,7 +96,7 @@ describe("CpuCollector", () => {
96
96
  });
97
97
  });
98
98
 
99
- describe("aggregateResult", () => {
99
+ describe("mergeResult", () => {
100
100
  it("should calculate average and max CPU usage", () => {
101
101
  const collector = new CpuCollector();
102
102
  const runs = [
@@ -118,11 +118,13 @@ describe("CpuCollector", () => {
118
118
  },
119
119
  ];
120
120
 
121
- const aggregated = collector.aggregateResult(runs);
121
+ let aggregated = collector.mergeResult(undefined, runs[0]);
122
+ aggregated = collector.mergeResult(aggregated, runs[1]);
122
123
 
123
- expect(aggregated.avgUsagePercent).toBe(50);
124
- expect(aggregated.maxUsagePercent).toBe(75);
125
- expect(aggregated.avgLoadAvg1m).toBe(1.5);
124
+ expect(aggregated.avgUsagePercent.avg).toBe(50);
125
+ expect(aggregated.maxUsagePercent.max).toBe(75);
126
+ // (1.0 + 2.0) / 2 = 1.5
127
+ expect(aggregated.avgLoadAvg1m.avg).toBe(1.5);
126
128
  });
127
129
  });
128
130
 
@@ -4,6 +4,12 @@ import {
4
4
  type HealthCheckRunForAggregation,
5
5
  type CollectorResult,
6
6
  type CollectorStrategy,
7
+ mergeAverage,
8
+ mergeMinMax,
9
+ VersionedAggregated,
10
+ aggregatedAverage,
11
+ aggregatedMinMax,
12
+ type InferAggregatedResult,
7
13
  } from "@checkstack/backend-api";
8
14
  import { healthResultNumber } from "@checkstack/healthcheck-common";
9
15
  import {
@@ -58,38 +64,39 @@ const cpuResultSchema = z.object({
58
64
 
59
65
  export type CpuResult = z.infer<typeof cpuResultSchema>;
60
66
 
61
- const cpuAggregatedSchema = z.object({
62
- avgUsagePercent: healthResultNumber({
67
+ // Aggregated result fields definition
68
+ const cpuAggregatedFields = {
69
+ avgUsagePercent: aggregatedAverage({
63
70
  "x-chart-type": "line",
64
71
  "x-chart-label": "Avg CPU Usage",
65
72
  "x-chart-unit": "%",
66
73
  }),
67
- maxUsagePercent: healthResultNumber({
74
+ maxUsagePercent: aggregatedMinMax({
68
75
  "x-chart-type": "line",
69
76
  "x-chart-label": "Max CPU Usage",
70
77
  "x-chart-unit": "%",
71
78
  }),
72
- avgLoadAvg1m: healthResultNumber({
79
+ avgLoadAvg1m: aggregatedAverage({
73
80
  "x-chart-type": "line",
74
81
  "x-chart-label": "Avg Load (1m)",
75
82
  }),
76
- });
83
+ };
77
84
 
78
- export type CpuAggregatedResult = z.infer<typeof cpuAggregatedSchema>;
85
+ // Type inferred from field definitions
86
+ export type CpuAggregatedResult = InferAggregatedResult<
87
+ typeof cpuAggregatedFields
88
+ >;
79
89
 
80
90
  // ============================================================================
81
91
  // CPU COLLECTOR
82
92
  // ============================================================================
83
93
 
84
- export class CpuCollector
85
- implements
86
- CollectorStrategy<
87
- SshTransportClient,
88
- CpuConfig,
89
- CpuResult,
90
- CpuAggregatedResult
91
- >
92
- {
94
+ export class CpuCollector implements CollectorStrategy<
95
+ SshTransportClient,
96
+ CpuConfig,
97
+ CpuResult,
98
+ CpuAggregatedResult
99
+ > {
93
100
  id = "cpu";
94
101
  displayName = "CPU Metrics";
95
102
  description = "Collects CPU usage, load averages, and core count via SSH";
@@ -98,7 +105,10 @@ export class CpuCollector
98
105
 
99
106
  config = new Versioned({ version: 1, schema: cpuConfigSchema });
100
107
  result = new Versioned({ version: 1, schema: cpuResultSchema });
101
- aggregatedResult = new Versioned({ version: 1, schema: cpuAggregatedSchema });
108
+ aggregatedResult = new VersionedAggregated({
109
+ version: 1,
110
+ fields: cpuAggregatedFields,
111
+ });
102
112
 
103
113
  async execute({
104
114
  config,
@@ -135,21 +145,22 @@ export class CpuCollector
135
145
  return { result };
136
146
  }
137
147
 
138
- aggregateResult(
139
- runs: HealthCheckRunForAggregation<CpuResult>[]
148
+ mergeResult(
149
+ existing: CpuAggregatedResult | undefined,
150
+ run: HealthCheckRunForAggregation<CpuResult>,
140
151
  ): CpuAggregatedResult {
141
- const usages = runs
142
- .map((r) => r.metadata?.usagePercent)
143
- .filter((v): v is number => typeof v === "number");
144
-
145
- const loads = runs
146
- .map((r) => r.metadata?.loadAvg1m)
147
- .filter((v): v is number => typeof v === "number");
152
+ const metadata = run.metadata;
148
153
 
149
154
  return {
150
- avgUsagePercent: usages.length > 0 ? this.avg(usages) : 0,
151
- maxUsagePercent: usages.length > 0 ? Math.max(...usages) : 0,
152
- avgLoadAvg1m: loads.length > 0 ? this.avg(loads) : 0,
155
+ avgUsagePercent: mergeAverage(
156
+ existing?.avgUsagePercent,
157
+ metadata?.usagePercent,
158
+ ),
159
+ maxUsagePercent: mergeMinMax(
160
+ existing?.maxUsagePercent,
161
+ metadata?.usagePercent,
162
+ ),
163
+ avgLoadAvg1m: mergeAverage(existing?.avgLoadAvg1m, metadata?.loadAvg1m),
153
164
  };
154
165
  }
155
166
 
@@ -66,7 +66,7 @@ describe("DiskCollector", () => {
66
66
  });
67
67
  });
68
68
 
69
- describe("aggregateResult", () => {
69
+ describe("mergeResult", () => {
70
70
  it("should calculate average and max disk usage", () => {
71
71
  const collector = new DiskCollector();
72
72
  const runs = [
@@ -102,10 +102,11 @@ describe("DiskCollector", () => {
102
102
  },
103
103
  ];
104
104
 
105
- const aggregated = collector.aggregateResult(runs);
105
+ let aggregated = collector.mergeResult(undefined, runs[0]);
106
+ aggregated = collector.mergeResult(aggregated, runs[1]);
106
107
 
107
- expect(aggregated.avgUsedPercent).toBe(40);
108
- expect(aggregated.maxUsedPercent).toBe(50);
108
+ expect(aggregated.avgUsedPercent.avg).toBe(40);
109
+ expect(aggregated.maxUsedPercent.max).toBe(50);
109
110
  });
110
111
  });
111
112
 
@@ -4,6 +4,12 @@ import {
4
4
  type HealthCheckRunForAggregation,
5
5
  type CollectorResult,
6
6
  type CollectorStrategy,
7
+ mergeAverage,
8
+ mergeMinMax,
9
+ VersionedAggregated,
10
+ aggregatedAverage,
11
+ aggregatedMinMax,
12
+ type InferAggregatedResult,
7
13
  } from "@checkstack/backend-api";
8
14
  import {
9
15
  healthResultNumber,
@@ -64,34 +70,35 @@ const diskResultSchema = z.object({
64
70
 
65
71
  export type DiskResult = z.infer<typeof diskResultSchema>;
66
72
 
67
- const diskAggregatedSchema = z.object({
68
- avgUsedPercent: healthResultNumber({
73
+ // Aggregated result fields definition
74
+ const diskAggregatedFields = {
75
+ avgUsedPercent: aggregatedAverage({
69
76
  "x-chart-type": "line",
70
77
  "x-chart-label": "Avg Disk Usage",
71
78
  "x-chart-unit": "%",
72
79
  }),
73
- maxUsedPercent: healthResultNumber({
80
+ maxUsedPercent: aggregatedMinMax({
74
81
  "x-chart-type": "line",
75
82
  "x-chart-label": "Max Disk Usage",
76
83
  "x-chart-unit": "%",
77
84
  }),
78
- });
85
+ };
79
86
 
80
- export type DiskAggregatedResult = z.infer<typeof diskAggregatedSchema>;
87
+ // Type inferred from field definitions
88
+ export type DiskAggregatedResult = InferAggregatedResult<
89
+ typeof diskAggregatedFields
90
+ >;
81
91
 
82
92
  // ============================================================================
83
93
  // DISK COLLECTOR
84
94
  // ============================================================================
85
95
 
86
- export class DiskCollector
87
- implements
88
- CollectorStrategy<
89
- SshTransportClient,
90
- DiskConfig,
91
- DiskResult,
92
- DiskAggregatedResult
93
- >
94
- {
96
+ export class DiskCollector implements CollectorStrategy<
97
+ SshTransportClient,
98
+ DiskConfig,
99
+ DiskResult,
100
+ DiskAggregatedResult
101
+ > {
95
102
  id = "disk";
96
103
  displayName = "Disk Metrics";
97
104
  description = "Collects disk usage for a specific mount point via SSH";
@@ -100,9 +107,9 @@ export class DiskCollector
100
107
 
101
108
  config = new Versioned({ version: 1, schema: diskConfigSchema });
102
109
  result = new Versioned({ version: 1, schema: diskResultSchema });
103
- aggregatedResult = new Versioned({
110
+ aggregatedResult = new VersionedAggregated({
104
111
  version: 1,
105
- schema: diskAggregatedSchema,
112
+ fields: diskAggregatedFields,
106
113
  });
107
114
 
108
115
  async execute({
@@ -120,16 +127,21 @@ export class DiskCollector
120
127
  return { result: parsed };
121
128
  }
122
129
 
123
- aggregateResult(
124
- runs: HealthCheckRunForAggregation<DiskResult>[]
130
+ mergeResult(
131
+ existing: DiskAggregatedResult | undefined,
132
+ run: HealthCheckRunForAggregation<DiskResult>,
125
133
  ): DiskAggregatedResult {
126
- const usedPercents = runs
127
- .map((r) => r.metadata?.usedPercent)
128
- .filter((v): v is number => typeof v === "number");
134
+ const metadata = run.metadata;
129
135
 
130
136
  return {
131
- avgUsedPercent: usedPercents.length > 0 ? this.avg(usedPercents) : 0,
132
- maxUsedPercent: usedPercents.length > 0 ? Math.max(...usedPercents) : 0,
137
+ avgUsedPercent: mergeAverage(
138
+ existing?.avgUsedPercent,
139
+ metadata?.usedPercent,
140
+ ),
141
+ maxUsedPercent: mergeMinMax(
142
+ existing?.maxUsedPercent,
143
+ metadata?.usedPercent,
144
+ ),
133
145
  };
134
146
  }
135
147
 
@@ -76,7 +76,7 @@ Swap: 4096 512 3584`
76
76
  });
77
77
  });
78
78
 
79
- describe("aggregateResult", () => {
79
+ describe("mergeResult", () => {
80
80
  it("should calculate average and max memory usage", () => {
81
81
  const collector = new MemoryCollector();
82
82
  const runs = [
@@ -108,11 +108,12 @@ Swap: 4096 512 3584`
108
108
  },
109
109
  ];
110
110
 
111
- const aggregated = collector.aggregateResult(runs);
111
+ let aggregated = collector.mergeResult(undefined, runs[0]);
112
+ aggregated = collector.mergeResult(aggregated, runs[1]);
112
113
 
113
- expect(aggregated.avgUsedPercent).toBe(50);
114
- expect(aggregated.maxUsedPercent).toBe(75);
115
- expect(aggregated.avgUsedMb).toBe(8000);
114
+ expect(aggregated.avgUsedPercent.avg).toBe(50);
115
+ expect(aggregated.maxUsedPercent.max).toBe(75);
116
+ expect(aggregated.avgUsedMb.avg).toBe(8000);
116
117
  });
117
118
  });
118
119
 
@@ -4,6 +4,12 @@ import {
4
4
  type HealthCheckRunForAggregation,
5
5
  type CollectorResult,
6
6
  type CollectorStrategy,
7
+ mergeAverage,
8
+ mergeMinMax,
9
+ VersionedAggregated,
10
+ aggregatedAverage,
11
+ aggregatedMinMax,
12
+ type InferAggregatedResult,
7
13
  } from "@checkstack/backend-api";
8
14
  import { healthResultNumber } from "@checkstack/healthcheck-common";
9
15
  import {
@@ -67,39 +73,40 @@ const memoryResultSchema = z.object({
67
73
 
68
74
  export type MemoryResult = z.infer<typeof memoryResultSchema>;
69
75
 
70
- const memoryAggregatedSchema = z.object({
71
- avgUsedPercent: healthResultNumber({
76
+ // Aggregated result fields definition
77
+ const memoryAggregatedFields = {
78
+ avgUsedPercent: aggregatedAverage({
72
79
  "x-chart-type": "line",
73
80
  "x-chart-label": "Avg Memory Usage",
74
81
  "x-chart-unit": "%",
75
82
  }),
76
- maxUsedPercent: healthResultNumber({
83
+ maxUsedPercent: aggregatedMinMax({
77
84
  "x-chart-type": "line",
78
85
  "x-chart-label": "Max Memory Usage",
79
86
  "x-chart-unit": "%",
80
87
  }),
81
- avgUsedMb: healthResultNumber({
88
+ avgUsedMb: aggregatedAverage({
82
89
  "x-chart-type": "line",
83
90
  "x-chart-label": "Avg Memory Used",
84
91
  "x-chart-unit": "MB",
85
92
  }),
86
- });
93
+ };
87
94
 
88
- export type MemoryAggregatedResult = z.infer<typeof memoryAggregatedSchema>;
95
+ // Type inferred from field definitions
96
+ export type MemoryAggregatedResult = InferAggregatedResult<
97
+ typeof memoryAggregatedFields
98
+ >;
89
99
 
90
100
  // ============================================================================
91
101
  // MEMORY COLLECTOR
92
102
  // ============================================================================
93
103
 
94
- export class MemoryCollector
95
- implements
96
- CollectorStrategy<
97
- SshTransportClient,
98
- MemoryConfig,
99
- MemoryResult,
100
- MemoryAggregatedResult
101
- >
102
- {
104
+ export class MemoryCollector implements CollectorStrategy<
105
+ SshTransportClient,
106
+ MemoryConfig,
107
+ MemoryResult,
108
+ MemoryAggregatedResult
109
+ > {
103
110
  id = "memory";
104
111
  displayName = "Memory Metrics";
105
112
  description = "Collects RAM and swap usage via SSH";
@@ -108,9 +115,9 @@ export class MemoryCollector
108
115
 
109
116
  config = new Versioned({ version: 1, schema: memoryConfigSchema });
110
117
  result = new Versioned({ version: 1, schema: memoryResultSchema });
111
- aggregatedResult = new Versioned({
118
+ aggregatedResult = new VersionedAggregated({
112
119
  version: 1,
113
- schema: memoryAggregatedSchema,
120
+ fields: memoryAggregatedFields,
114
121
  });
115
122
 
116
123
  async execute({
@@ -140,21 +147,22 @@ export class MemoryCollector
140
147
  return { result };
141
148
  }
142
149
 
143
- aggregateResult(
144
- runs: HealthCheckRunForAggregation<MemoryResult>[]
150
+ mergeResult(
151
+ existing: MemoryAggregatedResult | undefined,
152
+ run: HealthCheckRunForAggregation<MemoryResult>,
145
153
  ): MemoryAggregatedResult {
146
- const usedPercents = runs
147
- .map((r) => r.metadata?.usedPercent)
148
- .filter((v): v is number => typeof v === "number");
149
-
150
- const usedMbs = runs
151
- .map((r) => r.metadata?.usedMb)
152
- .filter((v): v is number => typeof v === "number");
154
+ const metadata = run.metadata;
153
155
 
154
156
  return {
155
- avgUsedPercent: usedPercents.length > 0 ? this.avg(usedPercents) : 0,
156
- maxUsedPercent: usedPercents.length > 0 ? Math.max(...usedPercents) : 0,
157
- avgUsedMb: usedMbs.length > 0 ? this.avg(usedMbs) : 0,
157
+ avgUsedPercent: mergeAverage(
158
+ existing?.avgUsedPercent,
159
+ metadata?.usedPercent,
160
+ ),
161
+ maxUsedPercent: mergeMinMax(
162
+ existing?.maxUsedPercent,
163
+ metadata?.usedPercent,
164
+ ),
165
+ avgUsedMb: mergeAverage(existing?.avgUsedMb, metadata?.usedMb),
158
166
  };
159
167
  }
160
168