feat: add stats endpoint with cache stats

This commit is contained in:
WebStreamr 2025-09-03 13:00:07 +00:00
parent 5ea34f66dd
commit fd0900e053
No known key found for this signature in database
7 changed files with 57 additions and 1 deletions

View file

@ -54,4 +54,11 @@ describe('ExtractorRegistry', () => {
const urlResults = await extractorRegistry.handle(ctx, new URL('https://dropload.io/mocked-blocked-2.html'), CountryCode.de, 'a title!');
expect(urlResults).toMatchSnapshot();
});
test('stats returns something', async () => {
const stats = extractorRegistry.stats();
expect(stats).toHaveProperty('urlResultCache');
expect(stats.urlResultCache).toBeTruthy();
});
});

View file

@ -18,9 +18,16 @@ export class ExtractorRegistry {
this.urlResultCache = new Cacheable({
primary: new Keyv({ store: new CacheableMemory({ lruSize: 4096 }) }),
secondary: new Keyv(new KeyvSqlite(`sqlite://${getCacheDir()}/webstreamr-extractor-cache.sqlite`)),
stats: true,
});
}
public stats() {
return {
urlResultCache: this.urlResultCache.stats,
};
};
public async handle(ctx: Context, url: URL, countryCode: CountryCode, title?: string | undefined): Promise<UrlResult[]> {
const extractor = this.extractors.find(extractor => !isExtractorDisabled(ctx.config, extractor) && extractor.supports(ctx, url));
if (!extractor) {

View file

@ -139,6 +139,14 @@ addon.get('/live', async (req: Request, res: Response) => {
}
});
addon.get('/stats', async (_req: Request, res: Response) => {
res.json({
extractorRegistry: extractorRegistry.stats(),
fetcher: fetcher.stats(),
sources: Source.stats(),
});
});
const port = parseInt(envGet('PORT') || '51546');
addon.listen(port, () => {
logger.info(`Add-on Repository URL: http://127.0.0.1:${port}/manifest.json`);

10
src/source/Source.test.ts Normal file
View file

@ -0,0 +1,10 @@
import { Source } from './Source';
describe('Source', () => {
test('stats returns something', async () => {
const stats = Source.stats();
expect(stats).toHaveProperty('sourceResultCache');
expect(stats.sourceResultCache).toBeTruthy();
});
});

View file

@ -28,10 +28,17 @@ export abstract class Source {
private static readonly sourceResultCache = new Cacheable({
primary: new Keyv({ store: new CacheableMemory({ lruSize: 1024 }) }),
secondary: new Keyv(new KeyvSqlite(`sqlite://${getCacheDir()}/webstreamr-source-cache.sqlite`)),
stats: true,
});
protected abstract handleInternal(ctx: Context, type: ContentType, id: Id): Promise<(SourceResult[])>;
public static stats() {
return {
sourceResultCache: Source.sourceResultCache.stats,
};
};
public async handle(ctx: Context, type: ContentType, id: Id): Promise<(SourceResult[])> {
const cacheKey = `${this.id}_${id.toString()}`;

View file

@ -258,4 +258,11 @@ describe('fetch', () => {
await expect(fetcher.text(ctx, new URL('https://too-many-recent-timeouts-url.test/'), { timeout: 10, timeoutsCountThrow: 2 })).rejects.toBeInstanceOf(TimeoutError);
await expect(fetcher.text(ctx, new URL('https://too-many-recent-timeouts-url.test/'), { timeout: 10, timeoutsCountThrow: 2 })).rejects.toBeInstanceOf(TooManyTimeoutsError);
});
test('stats returns something', async () => {
const stats = fetcher.stats();
expect(stats).toHaveProperty('httpCache');
expect(stats.httpCache).toBeTruthy();
});
});

View file

@ -63,7 +63,11 @@ export class Fetcher {
private readonly logger: winston.Logger;
private readonly httpCache = new Cacheable({ primary: new Keyv({ store: new CacheableMemory({ lruSize: 2048 }) }) });
private readonly httpCache = new Cacheable({
primary: new Keyv({ store: new CacheableMemory({ lruSize: 2048 }) }),
stats: true,
});
private readonly rateLimitedCache = new Cacheable({ primary: new Keyv({ store: new CacheableMemory({ lruSize: 1024 }) }) });
private readonly semaphores = new Map<string, SemaphoreInterface>();
private readonly hostUserAgentMap = new Map<string, string>();
@ -76,6 +80,12 @@ export class Fetcher {
this.logger = logger;
}
public stats() {
return {
httpCache: this.httpCache.stats,
};
};
public async text(ctx: Context, url: URL, init?: CustomRequestInit): Promise<string> {
return (await this.cachedFetch(ctx, url, init)).body;
};