mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-11 14:36:25 +00:00
feat: Stats for datasource cache (#30623)
This commit is contained in:
parent
2a74013dfc
commit
7229d962f7
4 changed files with 207 additions and 0 deletions
|
@ -9,6 +9,7 @@ import * as memCache from '../../util/cache/memory';
|
||||||
import * as packageCache from '../../util/cache/package';
|
import * as packageCache from '../../util/cache/package';
|
||||||
import { clone } from '../../util/clone';
|
import { clone } from '../../util/clone';
|
||||||
import { AsyncResult, Result } from '../../util/result';
|
import { AsyncResult, Result } from '../../util/result';
|
||||||
|
import { DatasourceCacheStats } from '../../util/stats';
|
||||||
import { trimTrailingSlash } from '../../util/url';
|
import { trimTrailingSlash } from '../../util/url';
|
||||||
import datasources from './api';
|
import datasources from './api';
|
||||||
import {
|
import {
|
||||||
|
@ -69,16 +70,22 @@ async function getRegistryReleases(
|
||||||
cacheNamespace,
|
cacheNamespace,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
// istanbul ignore if
|
// istanbul ignore if
|
||||||
if (cachedResult) {
|
if (cachedResult) {
|
||||||
logger.trace({ cacheKey }, 'Returning cached datasource response');
|
logger.trace({ cacheKey }, 'Returning cached datasource response');
|
||||||
|
DatasourceCacheStats.hit(datasource.id, registryUrl, config.packageName);
|
||||||
return cachedResult;
|
return cachedResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DatasourceCacheStats.miss(datasource.id, registryUrl, config.packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await datasource.getReleases({ ...config, registryUrl });
|
const res = await datasource.getReleases({ ...config, registryUrl });
|
||||||
if (res?.releases.length) {
|
if (res?.releases.length) {
|
||||||
res.registryUrl ??= registryUrl;
|
res.registryUrl ??= registryUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// cache non-null responses unless marked as private
|
// cache non-null responses unless marked as private
|
||||||
if (datasource.caching && res) {
|
if (datasource.caching && res) {
|
||||||
const cachePrivatePackages = GlobalConfig.get(
|
const cachePrivatePackages = GlobalConfig.get(
|
||||||
|
@ -89,8 +96,12 @@ async function getRegistryReleases(
|
||||||
logger.trace({ cacheKey }, 'Caching datasource response');
|
logger.trace({ cacheKey }, 'Caching datasource response');
|
||||||
const cacheMinutes = 15;
|
const cacheMinutes = 15;
|
||||||
await packageCache.set(cacheNamespace, cacheKey, res, cacheMinutes);
|
await packageCache.set(cacheNamespace, cacheKey, res, cacheMinutes);
|
||||||
|
DatasourceCacheStats.set(datasource.id, registryUrl, config.packageName);
|
||||||
|
} else {
|
||||||
|
DatasourceCacheStats.skip(datasource.id, registryUrl, config.packageName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { logger } from '../../test/util';
|
import { logger } from '../../test/util';
|
||||||
import * as memCache from './cache/memory';
|
import * as memCache from './cache/memory';
|
||||||
import {
|
import {
|
||||||
|
DatasourceCacheStats,
|
||||||
HttpCacheStats,
|
HttpCacheStats,
|
||||||
HttpStats,
|
HttpStats,
|
||||||
LookupStats,
|
LookupStats,
|
||||||
|
@ -230,6 +231,60 @@ describe('util/stats', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('DatasourceCacheStats', () => {
|
||||||
|
it('collects data points', () => {
|
||||||
|
DatasourceCacheStats.hit('crate', 'https://foo.example.com', 'foo');
|
||||||
|
DatasourceCacheStats.miss('maven', 'https://bar.example.com', 'bar');
|
||||||
|
DatasourceCacheStats.set('npm', 'https://baz.example.com', 'baz');
|
||||||
|
DatasourceCacheStats.skip('rubygems', 'https://qux.example.com', 'qux');
|
||||||
|
|
||||||
|
const report = DatasourceCacheStats.getReport();
|
||||||
|
|
||||||
|
expect(report).toEqual({
|
||||||
|
long: {
|
||||||
|
crate: {
|
||||||
|
'https://foo.example.com': { foo: { read: 'hit' } },
|
||||||
|
},
|
||||||
|
maven: {
|
||||||
|
'https://bar.example.com': { bar: { read: 'miss' } },
|
||||||
|
},
|
||||||
|
npm: {
|
||||||
|
'https://baz.example.com': { baz: { write: 'set' } },
|
||||||
|
},
|
||||||
|
rubygems: {
|
||||||
|
'https://qux.example.com': { qux: { write: 'skip' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
short: {
|
||||||
|
crate: {
|
||||||
|
'https://foo.example.com': { hit: 1, miss: 0, set: 0, skip: 0 },
|
||||||
|
},
|
||||||
|
maven: {
|
||||||
|
'https://bar.example.com': { hit: 0, miss: 1, set: 0, skip: 0 },
|
||||||
|
},
|
||||||
|
npm: {
|
||||||
|
'https://baz.example.com': { hit: 0, miss: 0, set: 1, skip: 0 },
|
||||||
|
},
|
||||||
|
rubygems: {
|
||||||
|
'https://qux.example.com': { hit: 0, miss: 0, set: 0, skip: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports', () => {
|
||||||
|
DatasourceCacheStats.hit('crate', 'https://foo.example.com', 'foo');
|
||||||
|
DatasourceCacheStats.miss('maven', 'https://bar.example.com', 'bar');
|
||||||
|
DatasourceCacheStats.set('npm', 'https://baz.example.com', 'baz');
|
||||||
|
DatasourceCacheStats.skip('rubygems', 'https://qux.example.com', 'qux');
|
||||||
|
|
||||||
|
DatasourceCacheStats.report();
|
||||||
|
|
||||||
|
expect(logger.logger.trace).toHaveBeenCalledTimes(1);
|
||||||
|
expect(logger.logger.debug).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('HttpStats', () => {
|
describe('HttpStats', () => {
|
||||||
it('returns empty report', () => {
|
it('returns empty report', () => {
|
||||||
const res = HttpStats.getReport();
|
const res = HttpStats.getReport();
|
||||||
|
|
|
@ -105,6 +105,145 @@ export class PackageCacheStats {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DatasourceCacheDataPoint {
|
||||||
|
datasource: string;
|
||||||
|
registryUrl: string;
|
||||||
|
packageName: string;
|
||||||
|
action: 'hit' | 'miss' | 'set' | 'skip';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatasourceCacheReport {
|
||||||
|
long: {
|
||||||
|
[datasource in string]: {
|
||||||
|
[registryUrl in string]: {
|
||||||
|
[packageName in string]: {
|
||||||
|
read?: 'hit' | 'miss';
|
||||||
|
write?: 'set' | 'skip';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
short: {
|
||||||
|
[datasource in string]: {
|
||||||
|
[registryUrl in string]: {
|
||||||
|
hit: number;
|
||||||
|
miss: number;
|
||||||
|
set: number;
|
||||||
|
skip: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DatasourceCacheStats {
|
||||||
|
private static getData(): DatasourceCacheDataPoint[] {
|
||||||
|
return (
|
||||||
|
memCache.get<DatasourceCacheDataPoint[]>('datasource-cache-stats') ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static setData(data: DatasourceCacheDataPoint[]): void {
|
||||||
|
memCache.set('datasource-cache-stats', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static hit(
|
||||||
|
datasource: string,
|
||||||
|
registryUrl: string,
|
||||||
|
packageName: string,
|
||||||
|
): void {
|
||||||
|
const data = this.getData();
|
||||||
|
data.push({ datasource, registryUrl, packageName, action: 'hit' });
|
||||||
|
this.setData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static miss(
|
||||||
|
datasource: string,
|
||||||
|
registryUrl: string,
|
||||||
|
packageName: string,
|
||||||
|
): void {
|
||||||
|
const data = this.getData();
|
||||||
|
data.push({ datasource, registryUrl, packageName, action: 'miss' });
|
||||||
|
this.setData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static set(
|
||||||
|
datasource: string,
|
||||||
|
registryUrl: string,
|
||||||
|
packageName: string,
|
||||||
|
): void {
|
||||||
|
const data = this.getData();
|
||||||
|
data.push({ datasource, registryUrl, packageName, action: 'set' });
|
||||||
|
this.setData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static skip(
|
||||||
|
datasource: string,
|
||||||
|
registryUrl: string,
|
||||||
|
packageName: string,
|
||||||
|
): void {
|
||||||
|
const data = this.getData();
|
||||||
|
data.push({ datasource, registryUrl, packageName, action: 'skip' });
|
||||||
|
this.setData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getReport(): DatasourceCacheReport {
|
||||||
|
const data = this.getData();
|
||||||
|
const result: DatasourceCacheReport = { long: {}, short: {} };
|
||||||
|
for (const { datasource, registryUrl, packageName, action } of data) {
|
||||||
|
result.long[datasource] ??= {};
|
||||||
|
result.long[datasource][registryUrl] ??= {};
|
||||||
|
result.long[datasource][registryUrl] ??= {};
|
||||||
|
result.long[datasource][registryUrl][packageName] ??= {};
|
||||||
|
|
||||||
|
result.short[datasource] ??= {};
|
||||||
|
result.short[datasource][registryUrl] ??= {
|
||||||
|
hit: 0,
|
||||||
|
miss: 0,
|
||||||
|
set: 0,
|
||||||
|
skip: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (action === 'hit') {
|
||||||
|
result.long[datasource][registryUrl][packageName].read = 'hit';
|
||||||
|
result.short[datasource][registryUrl].hit += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'miss') {
|
||||||
|
result.long[datasource][registryUrl][packageName].read = 'miss';
|
||||||
|
result.short[datasource][registryUrl].miss += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'set') {
|
||||||
|
result.long[datasource][registryUrl][packageName].write = 'set';
|
||||||
|
result.short[datasource][registryUrl].set += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'skip') {
|
||||||
|
result.long[datasource][registryUrl][packageName].write = 'skip';
|
||||||
|
result.short[datasource][registryUrl].skip += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static report(): void {
|
||||||
|
const { long, short } = this.getReport();
|
||||||
|
|
||||||
|
if (Object.keys(short).length > 0) {
|
||||||
|
logger.debug(short, 'Datasource cache statistics');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(long).length > 0) {
|
||||||
|
logger.trace(long, 'Datasource cache detailed statistics');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface HttpRequestStatsDataPoint {
|
export interface HttpRequestStatsDataPoint {
|
||||||
method: string;
|
method: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
|
|
@ -20,6 +20,7 @@ import * as queue from '../../util/http/queue';
|
||||||
import * as throttle from '../../util/http/throttle';
|
import * as throttle from '../../util/http/throttle';
|
||||||
import { addSplit, getSplits, splitInit } from '../../util/split';
|
import { addSplit, getSplits, splitInit } from '../../util/split';
|
||||||
import {
|
import {
|
||||||
|
DatasourceCacheStats,
|
||||||
HttpCacheStats,
|
HttpCacheStats,
|
||||||
HttpStats,
|
HttpStats,
|
||||||
LookupStats,
|
LookupStats,
|
||||||
|
@ -134,6 +135,7 @@ export async function renovateRepository(
|
||||||
const splits = getSplits();
|
const splits = getSplits();
|
||||||
logger.debug(splits, 'Repository timing splits (milliseconds)');
|
logger.debug(splits, 'Repository timing splits (milliseconds)');
|
||||||
PackageCacheStats.report();
|
PackageCacheStats.report();
|
||||||
|
DatasourceCacheStats.report();
|
||||||
HttpStats.report();
|
HttpStats.report();
|
||||||
HttpCacheStats.report();
|
HttpCacheStats.report();
|
||||||
LookupStats.report();
|
LookupStats.report();
|
||||||
|
|
Loading…
Reference in a new issue