-
-
Notifications
You must be signed in to change notification settings - Fork 197
/
Copy pathperformance-service.ts
132 lines (118 loc) · 3.24 KB
/
performance-service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import { TrackActionNames } from "../constants";
const EOL = require("os").EOL;
import { getFixedLengthDateString } from "../common/helpers";
import * as semver from "semver";
import * as _ from "lodash";
import { IPerformanceService, IOptions } from "../declarations";
import { IFileSystem, IAnalyticsService } from "../common/declarations";
import { injector } from "../common/yok";
export class PerformanceService implements IPerformanceService {
public static LOG_MESSAGE_TEMPLATE = `Execution of method "%s" took %s ms.`;
public static FAIL_LOG_MESSAGE_TEMPLATE = `Failed to log pefromance data in file for method %s.`;
private static MIN_NODE_PERFORMANCE_MODULE_VERSION = "8.5.0";
private performance: { now(): number } = null;
constructor(
private $options: IOptions,
private $fs: IFileSystem,
private $logger: ILogger,
private $analyticsService: IAnalyticsService
) {
if (this.isPerformanceModuleSupported()) {
this.performance = require("perf_hooks").performance;
}
}
public processExecutionData(
methodInfo: string,
startTime: number,
endTime: number,
args: any[]
): void {
const executionTime = Math.floor(endTime - startTime);
this.trackAnalyticsData(methodInfo, executionTime);
if (typeof this.$options.performance === "string") {
this.logDataToFile(
this.$options.performance,
methodInfo,
executionTime,
args
);
} else if (this.$options.performance) {
this.$logger.info(
PerformanceService.LOG_MESSAGE_TEMPLATE,
methodInfo,
executionTime
);
}
}
public now(): number {
if (this.isPerformanceModuleSupported()) {
return this.performance.now();
} else {
return new Date().getTime();
}
}
private isPerformanceModuleSupported(): boolean {
return semver.gte(
process.version,
PerformanceService.MIN_NODE_PERFORMANCE_MODULE_VERSION
);
}
private trackAnalyticsData(methodInfo: string, executionTime: number): void {
this.$analyticsService
.trackEventActionInGoogleAnalytics({
action: TrackActionNames.Performance,
additionalData: methodInfo,
value: executionTime,
})
.catch((err) => {
throw err;
});
}
private logDataToFile(
filePath: string,
methodInfo: string,
executionTime: number,
args: any[]
) {
let methodArgs;
try {
methodArgs = JSON.stringify(args, this.getJsonSanitizer());
} catch (e) {
methodArgs = "cyclic args";
}
const info = {
methodInfo,
executionTime,
timestamp: getFixedLengthDateString(),
methodArgs: JSON.parse(methodArgs),
};
try {
this.$fs.appendFile(filePath, `${JSON.stringify(info)}${EOL}`);
} catch (e) {
this.$logger.trace(
PerformanceService.FAIL_LOG_MESSAGE_TEMPLATE,
methodInfo
);
this.$logger.info(
PerformanceService.LOG_MESSAGE_TEMPLATE,
methodInfo,
executionTime
);
}
}
//removes any injected members of the arguments and excludes the options object even if it was renamed
private getJsonSanitizer() {
const seen = new WeakSet();
seen.add(this.$options);
return (key: any, value: any) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value) || _.startsWith(key, "$")) {
return;
}
seen.add(value);
}
return value;
};
}
}
injector.register("performanceService", PerformanceService);