Skip to content

Commit a02dd07

Browse files
zharinovrarkinsHonkingGoose
authored
feat: Abandoned packages logging (renovatebot#36090)
Co-authored-by: Rhys Arkins <rhys@arkins.net> Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>
1 parent 08b4ebd commit a02dd07

File tree

7 files changed

+163
-0
lines changed

7 files changed

+163
-0
lines changed

lib/modules/manager/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ export interface PackageDependency<T = Record<string, any>>
170170
* override data source's default strategy.
171171
*/
172172
registryStrategy?: RegistryStrategy;
173+
174+
mostRecentTimestamp?: Timestamp;
175+
isAbandoned?: boolean;
173176
}
174177

175178
export interface Upgrade<T = Record<string, any>> extends PackageDependency<T> {

lib/util/stats.spec.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as memCache from './cache/memory';
22
import {
3+
AbandonedPackageStats,
34
DatasourceCacheStats,
45
HttpCacheStats,
56
HttpStats,
@@ -574,4 +575,95 @@ describe('util/stats', () => {
574575
});
575576
});
576577
});
578+
579+
describe('AbandonmentStats', () => {
580+
beforeEach(() => {
581+
memCache.init();
582+
});
583+
584+
it('returns empty report', () => {
585+
const res = AbandonedPackageStats.getReport();
586+
expect(res).toEqual({});
587+
});
588+
589+
it('writes data points', () => {
590+
AbandonedPackageStats.write(
591+
'npm',
592+
'package1',
593+
'2023-01-01T00:00:00.000Z',
594+
);
595+
AbandonedPackageStats.write(
596+
'npm',
597+
'package2',
598+
'2023-02-01T00:00:00.000Z',
599+
);
600+
AbandonedPackageStats.write(
601+
'docker',
602+
'image1',
603+
'2023-03-01T00:00:00.000Z',
604+
);
605+
606+
const data = AbandonedPackageStats.getData();
607+
expect(data).toEqual([
608+
{
609+
datasource: 'npm',
610+
packageName: 'package1',
611+
mostRecentTimestamp: '2023-01-01T00:00:00.000Z',
612+
},
613+
{
614+
datasource: 'npm',
615+
packageName: 'package2',
616+
mostRecentTimestamp: '2023-02-01T00:00:00.000Z',
617+
},
618+
{
619+
datasource: 'docker',
620+
packageName: 'image1',
621+
mostRecentTimestamp: '2023-03-01T00:00:00.000Z',
622+
},
623+
]);
624+
625+
const report = AbandonedPackageStats.getReport();
626+
expect(report).toEqual({
627+
npm: {
628+
package1: '2023-01-01T00:00:00.000Z',
629+
package2: '2023-02-01T00:00:00.000Z',
630+
},
631+
docker: {
632+
image1: '2023-03-01T00:00:00.000Z',
633+
},
634+
});
635+
});
636+
637+
it('logs report', () => {
638+
AbandonedPackageStats.write(
639+
'npm',
640+
'package1',
641+
'2023-01-01T00:00:00.000Z',
642+
);
643+
AbandonedPackageStats.write(
644+
'docker',
645+
'image1',
646+
'2023-03-01T00:00:00.000Z',
647+
);
648+
649+
AbandonedPackageStats.report();
650+
651+
expect(logger.logger.debug).toHaveBeenCalledTimes(1);
652+
const [data, msg] = logger.logger.debug.mock.calls[0];
653+
expect(msg).toBe('Abandoned package statistics');
654+
expect(data).toEqual({
655+
npm: {
656+
package1: '2023-01-01T00:00:00.000Z',
657+
},
658+
docker: {
659+
image1: '2023-03-01T00:00:00.000Z',
660+
},
661+
});
662+
});
663+
664+
it('does not log report when no data', () => {
665+
AbandonedPackageStats.report();
666+
expect(logger.logger.debug).not.toHaveBeenCalled();
667+
});
668+
});
577669
});

lib/util/stats.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,3 +520,58 @@ export class ObsoleteCacheHitLogger {
520520
}
521521
}
522522
/* v8 ignore stop: temporary code */
523+
524+
interface AbandonedPackage {
525+
datasource: string;
526+
packageName: string;
527+
mostRecentTimestamp: string;
528+
}
529+
530+
type AbandonedPackageReport = Record<string, Record<string, string>>;
531+
532+
export class AbandonedPackageStats {
533+
static getData(): AbandonedPackage[] {
534+
return memCache.get<AbandonedPackage[]>('abandonment-stats') ?? [];
535+
}
536+
537+
private static setData(data: AbandonedPackage[]): void {
538+
memCache.set('abandonment-stats', data);
539+
}
540+
541+
static write(
542+
datasource: string,
543+
packageName: string,
544+
mostRecentTimestamp: string,
545+
): void {
546+
const data = this.getData();
547+
data.push({ datasource, packageName, mostRecentTimestamp });
548+
this.setData(data);
549+
}
550+
551+
static getReport(): AbandonedPackageReport {
552+
const data = this.getData();
553+
const result: AbandonedPackageReport = {};
554+
555+
for (const { datasource, packageName, mostRecentTimestamp } of data) {
556+
result[datasource] ??= {};
557+
result[datasource][packageName] = mostRecentTimestamp;
558+
}
559+
560+
const sortedResult: AbandonedPackageReport = {};
561+
for (const datasource of Object.keys(result).sort()) {
562+
sortedResult[datasource] = {};
563+
for (const packageName of Object.keys(result[datasource]).sort()) {
564+
sortedResult[datasource][packageName] = result[datasource][packageName];
565+
}
566+
}
567+
568+
return sortedResult;
569+
}
570+
571+
static report(): void {
572+
const report = this.getReport();
573+
if (Object.keys(report).length > 0) {
574+
logger.debug(report, 'Abandoned package statistics');
575+
}
576+
}
577+
}

lib/workers/repository/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import * as queue from '../../util/http/queue';
2020
import * as throttle from '../../util/http/throttle';
2121
import { addSplit, getSplits, splitInit } from '../../util/split';
2222
import {
23+
AbandonedPackageStats,
2324
DatasourceCacheStats,
2425
HttpCacheStats,
2526
HttpStats,
@@ -149,6 +150,7 @@ export async function renovateRepository(
149150
HttpCacheStats.report();
150151
LookupStats.report();
151152
ObsoleteCacheHitLogger.report();
153+
AbandonedPackageStats.report();
152154
const cloned = isCloned();
153155
logger.info({ cloned, durationMs: splits.total }, 'Repository finished');
154156
resetRepositoryLogLevelRemaps();

lib/workers/repository/process/lookup/abandonment.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DateTime } from 'luxon';
22
import { logger } from '../../../../logger';
33
import type { ReleaseResult } from '../../../../modules/datasource/types';
44
import { toMs } from '../../../../util/pretty-time';
5+
import { AbandonedPackageStats } from '../../../../util/stats';
56
import type { LookupUpdateConfig } from './types';
67

78
export function calculateAbandonment(
@@ -63,5 +64,10 @@ export function calculateAbandonment(
6364
'Calculated abandonment status',
6465
);
6566

67+
if (isAbandoned) {
68+
const { datasource, packageName } = config;
69+
AbandonedPackageStats.write(datasource, packageName, mostRecentTimestamp);
70+
}
71+
6672
return releaseResult;
6773
}

lib/workers/repository/process/lookup/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ export async function lookupUpdates(
203203
'dependencyUrl',
204204
'lookupName',
205205
'packageScope',
206+
'mostRecentTimestamp',
207+
'isAbandoned',
206208
]);
207209

208210
const latestVersion = dependency.tags?.latest;

lib/workers/repository/process/lookup/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
} from '../../../../modules/manager/types';
99
import type { SkipReason } from '../../../../types';
1010
import type { MergeConfidence } from '../../../../util/merge-confidence/types';
11+
import type { Timestamp } from '../../../../util/timestamp';
1112

1213
export interface FilterConfig {
1314
allowedVersions?: string;
@@ -75,4 +76,6 @@ export interface UpdateResult {
7576
currentVersionTimestamp?: string;
7677
vulnerabilityFixVersion?: string;
7778
vulnerabilityFixStrategy?: string;
79+
mostRecentTimestamp?: Timestamp | null;
80+
isAbandoned?: boolean;
7881
}

0 commit comments

Comments
 (0)