Skip to content

Commit ef71fc3

Browse files
committed
changes to apply an oop paradigm in metrics
1 parent cb88904 commit ef71fc3

15 files changed

Lines changed: 391 additions & 205 deletions

File tree

lib/core/http.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,25 @@ import * as https from "https";
33
import { Client } from "../node/client";
44
import { RequestType } from "../transport/types";
55

6-
type HttpRequestOptions = {
7-
callback?: (stream: http.IncomingMessage) => void;
8-
onError?: (error: Error) => void;
6+
type RequestOptionsType = {
7+
body: {};
8+
onError: (error: Error) => void;
99
};
1010
export class HttpModule {
1111
host: string;
1212
url: URL;
13-
body: string;
1413
method: RequestType;
1514

16-
constructor(url: string, body?: {}, method: RequestType = "POST") {
15+
constructor(url: string, method: RequestType = "POST") {
1716
this.host = Client.config.url;
1817

1918
this.url = new URL(url, this.host);
20-
this.body = JSON.stringify(body);
2119
this.method = method;
2220
}
2321

24-
// TODO: implement callbacks from options later
25-
public request(_options?: HttpRequestOptions) {
22+
public request(options: RequestOptionsType) {
23+
const { body, onError } = options;
24+
2625
const requestOptions = {
2726
...this.requestHeaders(),
2827
...this.requestOptions(),
@@ -31,16 +30,16 @@ export class HttpModule {
3130
const httpModule = this.requestModule();
3231

3332
const request = httpModule.request(requestOptions);
34-
request.on("error", () => {});
33+
request.on("error", () => onError);
3534

36-
this.requestWriteBody(request);
35+
this.requestWriteBody(request, body);
3736

3837
request.end();
3938
}
4039

41-
private requestWriteBody(request: http.ClientRequest) {
40+
private requestWriteBody(request: http.ClientRequest, body: {}) {
4241
if (this.method === "POST") {
43-
request.write(this.body);
42+
request.write(JSON.stringify(body));
4443
}
4544
}
4645

@@ -73,7 +72,6 @@ export class HttpModule {
7372
return {
7473
headers: {
7574
"Content-Type": "application/json",
76-
"Content-Length": this.body.length,
7775
},
7876
};
7977
}

lib/core/interfaces/IMetrics.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface IMetrics<T> {
2+
collect(): T;
3+
}

lib/node/client.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
import { isEmpty } from "../core/is";
22
import { TraceoOptions } from "../transport/options";
33
import { Logger } from "./logger";
4-
import { metrics } from "./metrics";
5-
import { loadRuntimeMetrics } from "./metrics/runtime-data";
4+
import { MetricsProbe } from "./metrics";
5+
import { RuntimeData } from "./metrics/runtime-data";
66

77
export class Client {
8-
options: TraceoOptions;
8+
private options: TraceoOptions;
9+
10+
private metricsProbe: MetricsProbe;
11+
private runtimeData: RuntimeData;
12+
913
readonly logger: Logger;
1014

1115
constructor(options: TraceoOptions) {
1216
this.configGlobalClient();
1317

1418
this.options = options;
19+
this.metricsProbe = new MetricsProbe(this.options);
20+
1521
this.logger = new Logger();
22+
this.runtimeData = new RuntimeData();
1623

1724
if (!this.isOffline) {
1825
this.initSDK();
@@ -44,11 +51,11 @@ export class Client {
4451
}
4552

4653
private initSDK(): void {
54+
this.runtimeData.collect();
55+
4756
if (this.options.metrics.collect) {
48-
metrics.collectMetrics(this.options);
57+
this.metricsProbe.register();
4958
}
50-
51-
loadRuntimeMetrics();
5259
}
5360

5461
private configGlobalClient(): void {

lib/node/exceptions/handler.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,16 @@ export const catchException = async (error: any, catchOptions?: Catch) => {
4949

5050
const handleException = async (error: TraceoError) => {
5151
const event: Incident = await prepareException(error);
52-
const httpModule = new HttpModule("/api/worker/incident", event);
53-
httpModule.request();
52+
const httpModule = new HttpModule("/api/worker/incident");
53+
httpModule.request({
54+
body: event,
55+
onError: (error: Error) => {
56+
console.error(
57+
`Traceo Error. Something went wrong while sending new Incident to Traceo. Please report this issue.`
58+
);
59+
console.error(`Caused by: ${error.message}`);
60+
},
61+
});
5462
};
5563

5664
const prepareException = async (error: TraceoError): Promise<Incident> => {

lib/node/logger.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { format } from "util";
22
import { HttpModule } from "../core/http";
3-
import { LogLevel, TraceoLog } from "../transport/logger";
3+
import { LogLevel } from "../transport/logger";
44

55
export class Logger {
6-
constructor() {}
6+
private readonly http: HttpModule;
7+
8+
constructor() {
9+
this.http = new HttpModule("/api/worker/log");
10+
}
711

812
public log(...args: any[]): void {
913
return this.printMessage(this.getEntryFromArgs(args), LogLevel.Log);
@@ -46,8 +50,15 @@ export class Logger {
4650
resources: this.resources,
4751
};
4852

49-
const httpModule = new HttpModule("/api/worker/log", requestPayload);
50-
httpModule.request();
53+
this.http.request({
54+
body: requestPayload,
55+
onError: (error: Error) => {
56+
console.error(
57+
`Traceo Error. Something went wrong while sending new Log to Traceo. Please report this issue.`
58+
);
59+
console.error(`Caused by: ${error.message}`);
60+
},
61+
});
5162
}
5263

5364
private get timestamp(): string {

lib/node/metrics.ts

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,70 @@
11
import { HttpModule } from "../core/http";
22
import { Metrics } from "../transport/metrics";
33
import { TraceoOptions } from "../transport/options";
4-
import { cpu } from "./metrics/cpu-usage";
5-
import * as os from "node:os";
6-
import { memory } from "./metrics/memory-usage";
7-
import { heap } from "./metrics/heap";
8-
import { eventLoop } from "./metrics/event-loop";
94
import { toDecimalNumber } from "./helpers";
5+
import { CpuUsageMetrics } from "./metrics/cpu-usage";
6+
import { EventLoopMetrics } from "./metrics/event-loop";
7+
import { HeapMetrics } from "./metrics/heap";
8+
import { MemoryUsageMetrics } from "./metrics/memory-usage";
9+
import * as os from "os";
1010

1111
const DEFAULT_INTERVAL = 30; //seconds
1212

13-
const collectMetrics = (options: TraceoOptions) => {
14-
const { metrics } = options;
13+
export class MetricsProbe {
14+
private readonly interval: number;
1515

16-
const INTERVAL =
17-
(metrics?.interval < 15 ? DEFAULT_INTERVAL : metrics?.interval) * 1000;
16+
private readonly http: HttpModule;
1817

19-
setInterval(() => {
20-
const metrics: Metrics = {
21-
cpuUsage: cpu.usage(),
22-
memory: memory.usage(),
23-
loadAvg: toDecimalNumber(os.loadavg()[0]),
18+
private readonly cpuUsage: CpuUsageMetrics;
19+
private readonly eventLoop: EventLoopMetrics;
20+
private readonly heap: HeapMetrics;
21+
private readonly memoryUsage: MemoryUsageMetrics;
22+
23+
constructor(options: TraceoOptions) {
24+
if (!options.metrics.collect) {
25+
return;
26+
}
27+
28+
this.interval = options.metrics.interval || DEFAULT_INTERVAL;
29+
30+
this.http = new HttpModule("/api/worker/metrics");
31+
32+
this.cpuUsage = new CpuUsageMetrics();
33+
this.eventLoop = new EventLoopMetrics();
34+
this.heap = new HeapMetrics();
35+
this.memoryUsage = new MemoryUsageMetrics();
36+
}
37+
38+
public register() {
39+
setInterval(() => this.collectMetrics(), this.interval * 1000);
40+
}
41+
42+
private collectMetrics() {
43+
const cpuUsage = this.cpuUsage.collect();
44+
const eventLoop = this.eventLoop.collect();
45+
const heap = this.heap.collect();
46+
const memory = this.memoryUsage.collect();
47+
48+
const metrics: Partial<Metrics> = {
49+
cpuUsage,
50+
eventLoopLag: eventLoop,
2451
heap,
25-
eventLoopLag: eventLoop.collect(),
52+
memory,
53+
loadAvg: this.loadAvg,
2654
};
2755

28-
const httpModule = new HttpModule("/api/worker/metrics", metrics);
29-
httpModule.request();
30-
}, INTERVAL);
31-
};
56+
this.http.request({
57+
body: metrics,
58+
onError: (error: Error) => {
59+
console.error(
60+
`Traceo Error. Something went wrong while sending new Log to Traceo. Please report this issue.`
61+
);
62+
console.error(`Caused by: ${error.message}`);
63+
},
64+
});
65+
}
3266

33-
export const metrics = {
34-
collectMetrics,
35-
};
67+
private get loadAvg() {
68+
return toDecimalNumber(os.loadavg()[0]);
69+
}
70+
}

lib/node/metrics/cpu-usage.ts

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,44 @@
11
import * as os from "node:os";
2+
import { IMetrics } from "../../core/interfaces/IMetrics";
23

3-
const averageCpu = (): { idle: number; total: number } => {
4-
let totalIdle = 0;
5-
let totalTick = 0;
6-
const cpus = os.cpus();
7-
8-
for (let i = 0, len = cpus.length; i < len; i++) {
9-
const cpu = cpus[i];
10-
for (const type in cpu.times) {
11-
totalTick += cpu.times[type];
12-
}
4+
type AverageCpuMetricType = {
5+
idle: number;
6+
total: number;
7+
};
8+
export class CpuUsageMetrics implements IMetrics<number> {
9+
measureStart: AverageCpuMetricType;
1310

14-
totalIdle += cpu.times.idle;
11+
constructor() {
12+
this.measureStart = this.calculateAverageCpuUsage();
1513
}
1614

17-
return { idle: totalIdle / cpus.length, total: totalTick / cpus.length };
18-
};
15+
collect(): number {
16+
const endMeasure = this.calculateAverageCpuUsage();
1917

20-
const startMeasure = averageCpu();
18+
const idleDifference = endMeasure.idle - this.measureStart.idle;
19+
const totalDifference = endMeasure.total - this.measureStart.total;
2120

22-
const getCpuUsage = () => {
23-
const endMeasure = averageCpu();
21+
const cpuUsage =
22+
Math.round((100 - (100 * idleDifference) / totalDifference) * 100) / 100;
2423

25-
const idleDifference = endMeasure.idle - startMeasure.idle;
26-
const totalDifference = endMeasure.total - startMeasure.total;
24+
return cpuUsage;
25+
}
2726

28-
const cpuUsage =
29-
Math.round((100 - (100 * idleDifference) / totalDifference) * 100) / 100;
27+
private calculateAverageCpuUsage(): AverageCpuMetricType {
28+
const cpus = os.cpus();
3029

31-
return cpuUsage;
32-
};
30+
let totalIdle: number = 0;
31+
let totalTick: number = 0;
3332

34-
export const cpu = {
35-
usage: getCpuUsage,
36-
};
33+
for (let i = 0, len = cpus.length; i < len; i++) {
34+
const cpu = cpus[i];
35+
for (const type in cpu.times) {
36+
totalTick += cpu.times[type];
37+
}
38+
39+
totalIdle += cpu.times.idle;
40+
}
41+
42+
return { idle: totalIdle / cpus.length, total: totalTick / cpus.length };
43+
}
44+
}

lib/node/metrics/event-loop.ts

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,35 @@
11
import * as perf_hooks from "perf_hooks";
2-
import { Metrics } from "../../transport/metrics";
2+
import { IMetrics } from "../../core/interfaces/IMetrics";
33
import { toDecimalNumber } from "../helpers";
44

5-
const histogram = perf_hooks.monitorEventLoopDelay({
6-
resolution: 10,
7-
});
8-
histogram.enable();
5+
type EventLoopMetricType = {
6+
min: number;
7+
max: number;
8+
mean: number;
9+
stddev: number;
10+
};
911

10-
const collect = () => {
11-
const data: Metrics["eventLoopLag"] = {
12-
min: toDecimalNumber(histogram.min / 1e6),
13-
max: toDecimalNumber(histogram.max / 1e6),
14-
mean: toDecimalNumber(histogram.mean / 1e6),
15-
stddev: toDecimalNumber(histogram.stddev / 1e6),
16-
p50: toDecimalNumber(histogram.percentile(50) / 1e6),
17-
p90: toDecimalNumber(histogram.percentile(90) / 1e6),
18-
p99: toDecimalNumber(histogram.percentile(99) / 1e6),
19-
};
20-
histogram.reset();
12+
export class EventLoopMetrics implements IMetrics<EventLoopMetricType> {
13+
//TODO: should be type here, but in different versions there are a different type,
14+
//eq. perf_hooks.IntervalHistogram
15+
histogram: any;
2116

22-
return data;
23-
};
17+
constructor() {
18+
this.histogram = perf_hooks.monitorEventLoopDelay({
19+
resolution: 10,
20+
});
21+
this.histogram.enable();
22+
}
23+
24+
collect(): EventLoopMetricType {
25+
const data: EventLoopMetricType = {
26+
min: toDecimalNumber(this.histogram.min / 1e6),
27+
max: toDecimalNumber(this.histogram.max / 1e6),
28+
mean: toDecimalNumber(this.histogram.mean / 1e6),
29+
stddev: toDecimalNumber(this.histogram.stddev / 1e6),
30+
};
31+
this.histogram.reset();
2432

25-
export const eventLoop = { collect };
33+
return data;
34+
}
35+
}

0 commit comments

Comments
 (0)