Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: Create utility for stats collection and reporting (#27919)
- Loading branch information
Showing
7 changed files
with
194 additions
and
81 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import { logger } from '../../test/util'; | ||
import * as memCache from './cache/memory'; | ||
import { LookupStats, makeTimingReport } from './stats'; | ||
|
||
describe('util/stats', () => { | ||
beforeEach(() => { | ||
memCache.init(); | ||
}); | ||
|
||
describe('makeTimingReport', () => { | ||
it('supports empty data', () => { | ||
const res = makeTimingReport([]); | ||
expect(res).toEqual({ | ||
avgMs: 0, | ||
count: 0, | ||
maxMs: 0, | ||
medianMs: 0, | ||
totalMs: 0, | ||
}); | ||
}); | ||
|
||
it('supports single data point', () => { | ||
const res = makeTimingReport([100]); | ||
expect(res).toEqual({ | ||
avgMs: 100, | ||
count: 1, | ||
maxMs: 100, | ||
medianMs: 100, | ||
totalMs: 100, | ||
}); | ||
}); | ||
|
||
it('supports multiple data points', () => { | ||
const res = makeTimingReport([100, 200, 400]); | ||
expect(res).toEqual({ | ||
avgMs: 233, | ||
count: 3, | ||
maxMs: 400, | ||
medianMs: 200, | ||
totalMs: 700, | ||
}); | ||
}); | ||
}); | ||
|
||
describe('LookupStats', () => { | ||
beforeEach(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
it('returns empty report', () => { | ||
const res = LookupStats.getReport(); | ||
expect(res).toEqual({}); | ||
}); | ||
|
||
it('writes data points', () => { | ||
LookupStats.write('npm', 100); | ||
LookupStats.write('npm', 200); | ||
LookupStats.write('npm', 400); | ||
LookupStats.write('docker', 1000); | ||
|
||
const res = LookupStats.getReport(); | ||
|
||
expect(res).toEqual({ | ||
docker: { | ||
avgMs: 1000, | ||
count: 1, | ||
maxMs: 1000, | ||
medianMs: 1000, | ||
totalMs: 1000, | ||
}, | ||
npm: { | ||
avgMs: 233, | ||
count: 3, | ||
maxMs: 400, | ||
medianMs: 200, | ||
totalMs: 700, | ||
}, | ||
}); | ||
}); | ||
|
||
it('wraps a function', async () => { | ||
const res = await LookupStats.wrap('npm', () => { | ||
jest.advanceTimersByTime(100); | ||
return Promise.resolve('foo'); | ||
}); | ||
|
||
expect(res).toBe('foo'); | ||
expect(LookupStats.getReport()).toEqual({ | ||
npm: { | ||
avgMs: 100, | ||
count: 1, | ||
maxMs: 100, | ||
medianMs: 100, | ||
totalMs: 100, | ||
}, | ||
}); | ||
}); | ||
|
||
it('logs report', () => { | ||
LookupStats.write('npm', 100); | ||
LookupStats.write('npm', 200); | ||
LookupStats.write('npm', 400); | ||
LookupStats.write('docker', 1000); | ||
|
||
LookupStats.report(); | ||
|
||
expect(logger.logger.debug).toHaveBeenCalledTimes(1); | ||
const [data, msg] = logger.logger.debug.mock.calls[0]; | ||
expect(msg).toBe('Lookup statistics'); | ||
expect(data).toEqual({ | ||
docker: { | ||
avgMs: 1000, | ||
count: 1, | ||
maxMs: 1000, | ||
medianMs: 1000, | ||
totalMs: 1000, | ||
}, | ||
npm: { | ||
avgMs: 233, | ||
count: 3, | ||
maxMs: 400, | ||
medianMs: 200, | ||
totalMs: 700, | ||
}, | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { logger } from '../logger'; | ||
import * as memCache from './cache/memory'; | ||
|
||
type LookupStatsData = Record<string, number[]>; | ||
|
||
interface TimingStatsReport { | ||
count: number; | ||
avgMs: number; | ||
medianMs: number; | ||
maxMs: number; | ||
totalMs: number; | ||
} | ||
|
||
export function makeTimingReport(data: number[]): TimingStatsReport { | ||
const count = data.length; | ||
const totalMs = data.reduce((a, c) => a + c, 0); | ||
const avgMs = count ? Math.round(totalMs / count) : 0; | ||
const maxMs = Math.max(0, ...data); | ||
const sorted = data.sort((a, b) => a - b); | ||
const medianMs = count ? sorted[Math.floor(count / 2)] : 0; | ||
return { count, avgMs, medianMs, maxMs, totalMs }; | ||
} | ||
|
||
export class LookupStats { | ||
static write(datasource: string, duration: number): void { | ||
const data = memCache.get<LookupStatsData>('lookup-stats') ?? {}; | ||
data[datasource] ??= []; | ||
data[datasource].push(duration); | ||
memCache.set('lookup-stats', data); | ||
} | ||
|
||
static async wrap<T>( | ||
datasource: string, | ||
callback: () => Promise<T>, | ||
): Promise<T> { | ||
const start = Date.now(); | ||
const result = await callback(); | ||
const duration = Date.now() - start; | ||
LookupStats.write(datasource, duration); | ||
return result; | ||
} | ||
|
||
static getReport(): Record<string, TimingStatsReport> { | ||
const report: Record<string, TimingStatsReport> = {}; | ||
const data = memCache.get<LookupStatsData>('lookup-stats') ?? {}; | ||
for (const [datasource, durations] of Object.entries(data)) { | ||
report[datasource] = makeTimingReport(durations); | ||
} | ||
return report; | ||
} | ||
|
||
static report(): void { | ||
const report = LookupStats.getReport(); | ||
logger.debug(report, 'Lookup statistics'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters