From 472ac8a356aed9dfd900032b922fd23c7a480fee Mon Sep 17 00:00:00 2001 From: Daz DeBoer Date: Sat, 30 Oct 2021 12:17:41 -0600 Subject: [PATCH] Report sizes of cache entries Using the patched version of @actions/cache, we now report the total size of cache entries restored/saved, as well as details of each one. --- __tests__/cache-base.test.ts | 12 +++++----- src/cache-base.ts | 21 +++++++++++------- src/cache-gradle-user-home.ts | 14 +++++++----- src/caches.ts | 42 ++++++++++++++++++++++++++++++----- 4 files changed, 63 insertions(+), 26 deletions(-) diff --git a/__tests__/cache-base.test.ts b/__tests__/cache-base.test.ts index 0b5fb52..b0f88db 100644 --- a/__tests__/cache-base.test.ts +++ b/__tests__/cache-base.test.ts @@ -5,7 +5,7 @@ describe('caching report', () => { it('with one requested entry report', async () => { const report = new CacheListener() report.entry('foo').markRequested('1', ['2']) - report.entry('bar').markRequested('3').markRestored('4') + report.entry('bar').markRequested('3').markRestored('4', 500) expect(report.fullyRestored).toBe(false) }) }) @@ -22,13 +22,13 @@ describe('caching report', () => { }) it('with restored entry report', async () => { const report = new CacheListener() - report.entry('bar').markRequested('3').markRestored('4') + report.entry('bar').markRequested('3').markRestored('4', 300) expect(report.fullyRestored).toBe(true) }) it('with multiple restored entry reportss', async () => { const report = new CacheListener() - report.entry('foo').markRestored('4') - report.entry('bar').markRequested('3').markRestored('4') + report.entry('foo').markRestored('4', 3300) + report.entry('bar').markRequested('3').markRestored('4', 333) expect(report.fullyRestored).toBe(true) }) }) @@ -64,7 +64,7 @@ describe('caching report', () => { const report = new CacheListener() const entryReport = report.entry('foo') entryReport.markRequested('1', ['2', '3']) - entryReport.markSaved('4') + entryReport.markSaved('4', 100) const stringRep = report.stringify() const reportClone: CacheListener = CacheListener.rehydrate(stringRep) @@ -85,7 +85,7 @@ describe('caching report', () => { // Check type and call method on rehydrated entry report expect(entryClone).toBeInstanceOf(CacheEntryListener) - entryClone.markSaved('4') + entryClone.markSaved('4', 100) expect(entryClone.requestedKey).toBe('1') expect(entryClone.requestedRestoreKeys).toEqual(['2', '3']) diff --git a/src/cache-base.ts b/src/cache-base.ts index 8a220eb..e23db92 100644 --- a/src/cache-base.ts +++ b/src/cache-base.ts @@ -98,13 +98,15 @@ export class CacheEntryListener { return this } - markRestored(key: string): CacheEntryListener { + markRestored(key: string, size: number | undefined): CacheEntryListener { this.restoredKey = key + this.restoredSize = size return this } - markSaved(key: string): CacheEntryListener { + markSaved(key: string, size: number | undefined): CacheEntryListener { this.savedKey = key + this.savedSize = size return this } } @@ -149,7 +151,7 @@ export abstract class AbstractCache { } core.saveState(this.cacheResultStateKey, cacheResult) - entryReport.markRestored(cacheResult) + entryReport.markRestored(cacheResult.key, cacheResult.size) core.info(`Restored ${this.cacheDescription} from cache key: ${cacheResult}`) try { @@ -170,7 +172,7 @@ export abstract class AbstractCache { cachePath: string[], cacheKey: string, cacheRestoreKeys: string[] = [] - ): Promise { + ): Promise { try { return await cache.restoreCache(cachePath, cacheKey, cacheRestoreKeys) } catch (error) { @@ -214,18 +216,20 @@ export abstract class AbstractCache { core.info(`Caching ${this.cacheDescription} with cache key: ${cacheKey}`) const cachePath = this.getCachePath() - await this.saveCache(cachePath, cacheKey) + const savedEntry = await this.saveCache(cachePath, cacheKey) - listener.entry(this.cacheDescription).markSaved(cacheKey) + if (savedEntry) { + listener.entry(this.cacheDescription).markSaved(savedEntry.key, savedEntry.size) + } return } protected async beforeSave(_listener: CacheListener): Promise {} - protected async saveCache(cachePath: string[], cacheKey: string): Promise { + protected async saveCache(cachePath: string[], cacheKey: string): Promise { try { - await cache.saveCache(cachePath, cacheKey) + return await cache.saveCache(cachePath, cacheKey) } catch (error) { if (error instanceof cache.ValidationError) { // Validation errors should fail the build action @@ -238,6 +242,7 @@ export abstract class AbstractCache { core.warning(String(error)) } } + return undefined } protected debug(message: string): void { diff --git a/src/cache-gradle-user-home.ts b/src/cache-gradle-user-home.ts index fe68dfb..3cd65ff 100644 --- a/src/cache-gradle-user-home.ts +++ b/src/cache-gradle-user-home.ts @@ -67,10 +67,10 @@ export class GradleUserHomeCache extends AbstractCache { const cacheKey = fs.readFileSync(bundleMetaFile, 'utf-8').trim() listener.markRequested(cacheKey) - const restoredKey = await this.restoreCache([bundlePattern], cacheKey) - if (restoredKey) { + const restoredEntry = await this.restoreCache([bundlePattern], cacheKey) + if (restoredEntry) { core.info(`Restored ${bundle} with key ${cacheKey} to ${bundlePattern}`) - listener.markRestored(restoredKey) + listener.markRestored(restoredEntry.key, restoredEntry.size) } else { core.info(`Did not restore ${bundle} with key ${cacheKey} to ${bundlePattern}`) tryDelete(bundleMetaFile) @@ -154,9 +154,11 @@ export class GradleUserHomeCache extends AbstractCache { this.debug(`No change to previously restored ${bundle}. Not caching.`) } else { core.info(`Caching ${bundle} with cache key: ${cacheKey}`) - await this.saveCache([artifactPath], cacheKey) - this.writeBundleMetaFile(bundleMetaFile, cacheKey) - listener.markSaved(cacheKey) + const savedEntry = await this.saveCache([artifactPath], cacheKey) + if (savedEntry !== undefined) { + this.writeBundleMetaFile(bundleMetaFile, cacheKey) + listener.markSaved(savedEntry.key, savedEntry.size) + } } for (const file of bundleFiles) { diff --git a/src/caches.ts b/src/caches.ts index 9f8a57f..eb256ce 100644 --- a/src/caches.ts +++ b/src/caches.ts @@ -2,7 +2,7 @@ import {GradleUserHomeCache} from './cache-gradle-user-home' import {ProjectDotGradleCache} from './cache-project-dot-gradle' import * as core from '@actions/core' import {isCacheDisabled, isCacheReadOnly} from './cache-utils' -import {CacheListener} from './cache-base' +import {CacheEntryListener, CacheListener} from './cache-base' const BUILD_ROOT_DIR = 'BUILD_ROOT_DIR' const CACHE_LISTENER = 'CACHE_LISTENER' @@ -54,11 +54,41 @@ export async function save(): Promise { } function logCachingReport(listener: CacheListener): void { - core.info('---------- CACHING REPORT -------------') + core.info(`---------- Caching Summary ------------- +Restored Entries Count: ${getCount(listener.cacheEntries, e => e.restoredSize)} + Size: ${getSum(listener.cacheEntries, e => e.restoredSize)} +Saved Entries Count: ${getCount(listener.cacheEntries, e => e.savedSize)} + Size: ${getSum(listener.cacheEntries, e => e.savedSize)}`) + + core.startGroup('Cache Entry details') for (const entry of listener.cacheEntries) { - core.info(`${entry.entryName} - Requested Key: ${entry.requestedKey ?? ''} - Restored Key : ${entry.restoredKey ?? ''} - Saved Key : ${entry.savedKey ?? ''}`) + core.info(`Entry: ${entry.entryName} + Requested Key : ${entry.requestedKey ?? ''} + Restored Key : ${entry.restoredKey ?? ''} + Size: ${formatSize(entry.restoredSize)} + Saved Key : ${entry.savedKey ?? ''} + Size: ${formatSize(entry.savedSize)}`) } + core.endGroup() +} + +function getCount( + cacheEntries: CacheEntryListener[], + predicate: (value: CacheEntryListener) => number | undefined +): number { + return cacheEntries.filter(e => predicate(e) !== undefined).length +} + +function getSum( + cacheEntries: CacheEntryListener[], + predicate: (value: CacheEntryListener) => number | undefined +): string { + return formatSize(cacheEntries.map(e => predicate(e) ?? 0).reduce((p, v) => p + v, 0)) +} + +function formatSize(bytes: number | undefined): string { + if (bytes === undefined || bytes === 0) { + return '' + } + return `${Math.round(bytes / (1024 * 1024))} MB (${bytes} B)` }