Skip to content

Commit 5837896

Browse files
committed
add performance analyzer
1 parent 232f449 commit 5837896

3 files changed

Lines changed: 153 additions & 0 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
import { isEmpty } from 'lodash';
4+
5+
interface ConcurrentPerformanceRecord {
6+
[key: string]: [{ group?: string; diff?: number }];
7+
}
8+
let is_analysis = false;
9+
let performanceRecord: ConcurrentPerformanceRecord = {};
10+
let keyStatistics: Record<
11+
string,
12+
{
13+
min?: number;
14+
max?: number;
15+
avg?: number;
16+
median?: number;
17+
p90?: number;
18+
}
19+
> = {};
20+
export class PerformanceAnalysis {
21+
public static collect(
22+
key: string,
23+
start: number,
24+
end: number,
25+
group?: string
26+
) {
27+
if (!start || !end) {
28+
throw new Error(
29+
`should provide start and end time when doing performance analysis task "${key}"`
30+
);
31+
}
32+
if (!performanceRecord[key]) {
33+
performanceRecord[key] = [] as any;
34+
}
35+
const diff = end - start;
36+
performanceRecord[key].push({ group, diff });
37+
if (process.env['PRINT_COLLECTION']) {
38+
console.log(
39+
`${key}: collected, start: ${start}, end: ${end}, diff: ${diff}`
40+
);
41+
}
42+
}
43+
44+
public static count(): boolean {
45+
// sort by diff
46+
if (isEmpty(performanceRecord)) {
47+
console.log('performanceRecord is empty');
48+
return false;
49+
}
50+
Object.values(performanceRecord).map((records) => {
51+
records.sort((a, b) => {
52+
return <number>a.diff - <number>b.diff;
53+
});
54+
});
55+
// count statistics
56+
Object.entries(performanceRecord).map(([key, records]) => {
57+
const count = records.length;
58+
const min = records[0].diff;
59+
const max = records[count - 1].diff;
60+
const avg =
61+
records.reduce((acc, cur) => {
62+
return acc + <number>cur.diff;
63+
}, 0) / count;
64+
const median = records[Math.floor(count / 2)].diff;
65+
const p90 = records[Math.floor(count * 0.9)].diff;
66+
keyStatistics[key] = { min, max, avg, median, p90 };
67+
});
68+
return true;
69+
}
70+
71+
public static getStatistic(key: string): any {
72+
return keyStatistics[key];
73+
}
74+
75+
public static clean = () => {
76+
performanceRecord = {};
77+
keyStatistics = {};
78+
};
79+
80+
// write to txt file
81+
// write by key
82+
public static writePerformanceReport() {
83+
const filePath = path.join('./performanceRecord.txt');
84+
// write by key
85+
// print current date, time as humun readable format
86+
fs.appendFileSync(filePath, `------${new Date().toLocaleString()}\n`);
87+
for (const key of Object.keys(keyStatistics)) {
88+
fs.appendFileSync(filePath, `${key}\n`);
89+
let staticLine = '';
90+
if (keyStatistics[key]) {
91+
const statics = keyStatistics[key];
92+
Object.entries(statics).map(([k, v]) => {
93+
staticLine += `${k}: ${v}, `;
94+
});
95+
fs.appendFileSync(filePath, `${staticLine}\n`);
96+
}
97+
}
98+
fs.appendFileSync(filePath, `------\n`);
99+
}
100+
}
101+
102+
export function getAnalysis() {
103+
const counted = PerformanceAnalysis.count();
104+
if (counted && !is_analysis) {
105+
PerformanceAnalysis.writePerformanceReport();
106+
console.log('performance analysis finished');
107+
console.log('check the performanceRecord.txt file for details');
108+
is_analysis = true;
109+
}
110+
}

packages/core/src/lib/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from './module';
44
export * from './streams';
55
export * from './errors';
66
export * from './flattenElements';
7+
export * from './analyzer';
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { PerformanceAnalysis, getAnalysis } from '../src/lib/utils/analyzer';
2+
import * as fs from 'fs';
3+
4+
async function waitOneSec(): Promise<void> {
5+
return new Promise((resolve) => {
6+
setTimeout(() => {
7+
resolve();
8+
}, 1000);
9+
});
10+
}
11+
12+
async function collect(key: string): Promise<void> {
13+
const t1 = new Date().getTime();
14+
await waitOneSec();
15+
const t2 = new Date().getTime();
16+
PerformanceAnalysis.collect(key, t1, t2);
17+
}
18+
19+
describe('Performance Analysis', () => {
20+
beforeEach(() => {
21+
PerformanceAnalysis.clean();
22+
if (fs.existsSync('performanceRecord.txt')) {
23+
fs.unlinkSync('performanceRecord.txt');
24+
}
25+
});
26+
it('should collect performance data', async () => {
27+
await collect('waitOneSec');
28+
expect(PerformanceAnalysis.count()).toBeTruthy();
29+
});
30+
// expect performanceRecord.txt exists when getStatistics() is called
31+
it('should write performance data to file', async () => {
32+
await collect('waitOneSec');
33+
await collect('waitAnotherSec');
34+
PerformanceAnalysis.count();
35+
getAnalysis();
36+
expect(fs.existsSync('performanceRecord.txt')).toBeTruthy();
37+
// expect file have two lines
38+
const data = fs.readFileSync('performanceRecord.txt', 'utf8');
39+
const lines = data.split('\n').filter((line) => line !== '');
40+
expect(lines.length).toBe(4);
41+
});
42+
});

0 commit comments

Comments
 (0)