mirror of
				https://github.com/gradle/gradle-build-action.git
				synced 2025-10-31 23:28:55 +08:00 
			
		
		
		
	Monitor cache saves and add basic caching report
- Restore `CachingReport` instance in 'post' action - Record keys for any entries saved - Report caching activity as JSON in post action
This commit is contained in:
		| @@ -1,5 +1,4 @@ | |||||||
| import * as cacheUtils from '../src/cache-utils' | import * as cacheUtils from '../src/cache-utils' | ||||||
| import * as path from 'path' |  | ||||||
|  |  | ||||||
| describe('cacheUtils-utils', () => { | describe('cacheUtils-utils', () => { | ||||||
|     describe('can hash', () => { |     describe('can hash', () => { | ||||||
| @@ -18,4 +17,97 @@ describe('cacheUtils-utils', () => { | |||||||
|             expect(posixHash).toBe(windowsHash) |             expect(posixHash).toBe(windowsHash) | ||||||
|         }) |         }) | ||||||
|     }) |     }) | ||||||
|  |     describe('caching report', () => { | ||||||
|  |         describe('reports not fully restored', () => { | ||||||
|  |             it('with one requested entry report', async () => { | ||||||
|  |                 const report = new cacheUtils.CachingReport() | ||||||
|  |                 report.entryReport('foo').markRequested('1', ['2']) | ||||||
|  |                 report.entryReport('bar').markRequested('3').markRestored('4') | ||||||
|  |                 expect(report.fullyRestored).toBe(false) | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |         describe('reports fully restored', () => { | ||||||
|  |             it('when empty', async () => { | ||||||
|  |                 const report = new cacheUtils.CachingReport() | ||||||
|  |                 expect(report.fullyRestored).toBe(true) | ||||||
|  |             }) | ||||||
|  |             it('with empty entry reports', async () => { | ||||||
|  |                 const report = new cacheUtils.CachingReport() | ||||||
|  |                 report.entryReport('foo') | ||||||
|  |                 report.entryReport('bar') | ||||||
|  |                 expect(report.fullyRestored).toBe(true) | ||||||
|  |             }) | ||||||
|  |             it('with restored entry report', async () => { | ||||||
|  |                 const report = new cacheUtils.CachingReport() | ||||||
|  |                 report.entryReport('bar').markRequested('3').markRestored('4') | ||||||
|  |                 expect(report.fullyRestored).toBe(true) | ||||||
|  |             }) | ||||||
|  |             it('with multiple restored entry reportss', async () => { | ||||||
|  |                 const report = new cacheUtils.CachingReport() | ||||||
|  |                 report.entryReport('foo').markRestored('4') | ||||||
|  |                 report.entryReport('bar').markRequested('3').markRestored('4') | ||||||
|  |                 expect(report.fullyRestored).toBe(true) | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |         describe('can be stringified and rehydrated', () => { | ||||||
|  |             it('when empty', async () => { | ||||||
|  |                 const report = new cacheUtils.CachingReport() | ||||||
|  |  | ||||||
|  |                 const stringRep = report.stringify() | ||||||
|  |                 const reportClone: cacheUtils.CachingReport = cacheUtils.CachingReport.rehydrate(stringRep) | ||||||
|  |  | ||||||
|  |                 expect(reportClone.cacheEntryReports).toEqual([]) | ||||||
|  |  | ||||||
|  |                 // Can call methods on rehydrated | ||||||
|  |                 expect(reportClone.entryReport('foo')).toBeInstanceOf(cacheUtils.CacheEntryReport) | ||||||
|  |             }) | ||||||
|  |             it('with entry reports', async () => { | ||||||
|  |                 const report = new cacheUtils.CachingReport() | ||||||
|  |                 report.entryReport('foo') | ||||||
|  |                 report.entryReport('bar') | ||||||
|  |                 report.entryReport('baz') | ||||||
|  |  | ||||||
|  |                 const stringRep = report.stringify() | ||||||
|  |                 const reportClone: cacheUtils.CachingReport = cacheUtils.CachingReport.rehydrate(stringRep) | ||||||
|  |  | ||||||
|  |                 expect(reportClone.cacheEntryReports.length).toBe(3) | ||||||
|  |                 expect(reportClone.cacheEntryReports[0].entryName).toBe('foo') | ||||||
|  |                 expect(reportClone.cacheEntryReports[1].entryName).toBe('bar') | ||||||
|  |                 expect(reportClone.cacheEntryReports[2].entryName).toBe('baz') | ||||||
|  |  | ||||||
|  |                 expect(reportClone.entryReport('foo')).toBe(reportClone.cacheEntryReports[0]) | ||||||
|  |             }) | ||||||
|  |             it('with rehydrated entry report', async () => { | ||||||
|  |                 const report = new cacheUtils.CachingReport() | ||||||
|  |                 const entryReport = report.entryReport('foo') | ||||||
|  |                 entryReport.markRequested('1', ['2', '3']) | ||||||
|  |                 entryReport.markSaved('4') | ||||||
|  |  | ||||||
|  |                 const stringRep = report.stringify() | ||||||
|  |                 const reportClone: cacheUtils.CachingReport = cacheUtils.CachingReport.rehydrate(stringRep) | ||||||
|  |                 const entryClone = reportClone.entryReport('foo') | ||||||
|  |  | ||||||
|  |                 expect(entryClone.requestedKey).toBe('1') | ||||||
|  |                 expect(entryClone.requestedRestoreKeys).toEqual(['2', '3']) | ||||||
|  |                 expect(entryClone.savedKey).toBe('4') | ||||||
|  |             }) | ||||||
|  |             it('with live entry report', async () => { | ||||||
|  |                 const report = new cacheUtils.CachingReport() | ||||||
|  |                 const entryReport = report.entryReport('foo') | ||||||
|  |                 entryReport.markRequested('1', ['2', '3']) | ||||||
|  |  | ||||||
|  |                 const stringRep = report.stringify() | ||||||
|  |                 const reportClone: cacheUtils.CachingReport = cacheUtils.CachingReport.rehydrate(stringRep) | ||||||
|  |                 const entryClone = reportClone.entryReport('foo') | ||||||
|  |  | ||||||
|  |                 // Check type and call method on rehydrated entry report | ||||||
|  |                 expect(entryClone).toBeInstanceOf(cacheUtils.CacheEntryReport) | ||||||
|  |                 entryClone.markSaved('4') | ||||||
|  |  | ||||||
|  |                 expect(entryClone.requestedKey).toBe('1') | ||||||
|  |                 expect(entryClone.requestedRestoreKeys).toEqual(['2', '3']) | ||||||
|  |                 expect(entryClone.savedKey).toBe('4') | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |     }) | ||||||
| }) | }) | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ export class GradleUserHomeCache extends AbstractCache { | |||||||
|         // Iterate over all bundle meta files and try to restore |         // Iterate over all bundle meta files and try to restore | ||||||
|         for (const bundleMetaFile of bundleMetaFiles) { |         for (const bundleMetaFile of bundleMetaFiles) { | ||||||
|             const bundle = path.basename(bundleMetaFile, '.cache') |             const bundle = path.basename(bundleMetaFile, '.cache') | ||||||
|             const bundleEntryReport = report.addEntryReport(bundle) |             const bundleEntryReport = report.entryReport(bundle) | ||||||
|             const bundlePattern = bundlePatterns.get(bundle) |             const bundlePattern = bundlePatterns.get(bundle) | ||||||
|  |  | ||||||
|             // Handle case where the 'artifactBundlePatterns' have been changed |             // Handle case where the 'artifactBundlePatterns' have been changed | ||||||
| @@ -94,10 +94,10 @@ export class GradleUserHomeCache extends AbstractCache { | |||||||
|         return bundleFiles |         return bundleFiles | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async beforeSave(): Promise<void> { |     async beforeSave(report: CachingReport): Promise<void> { | ||||||
|         await this.reportGradleUserHomeSize('before saving common artifacts') |         await this.reportGradleUserHomeSize('before saving common artifacts') | ||||||
|         this.removeExcludedPaths() |         this.removeExcludedPaths() | ||||||
|         await this.saveArtifactBundles() |         await this.saveArtifactBundles(report) | ||||||
|         await this.reportGradleUserHomeSize( |         await this.reportGradleUserHomeSize( | ||||||
|             "after saving common artifacts (only 'caches' and 'notifications' will be stored)" |             "after saving common artifacts (only 'caches' and 'notifications' will be stored)" | ||||||
|         ) |         ) | ||||||
| @@ -113,10 +113,12 @@ export class GradleUserHomeCache extends AbstractCache { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async saveArtifactBundles(): Promise<void> { |     private async saveArtifactBundles(report: CachingReport): Promise<void> { | ||||||
|         const processes: Promise<void>[] = [] |         const processes: Promise<void>[] = [] | ||||||
|         for (const [bundle, pattern] of this.getArtifactBundles()) { |         for (const [bundle, pattern] of this.getArtifactBundles()) { | ||||||
|             const p = this.saveArtifactBundle(bundle, pattern) |             const bundleEntryReport = report.entryReport(bundle) | ||||||
|  |  | ||||||
|  |             const p = this.saveArtifactBundle(bundle, pattern, bundleEntryReport) | ||||||
|             // Run sequentially when debugging enabled |             // Run sequentially when debugging enabled | ||||||
|             if (this.cacheDebuggingEnabled) { |             if (this.cacheDebuggingEnabled) { | ||||||
|                 await p |                 await p | ||||||
| @@ -127,7 +129,7 @@ export class GradleUserHomeCache extends AbstractCache { | |||||||
|         await Promise.all(processes) |         await Promise.all(processes) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async saveArtifactBundle(bundle: string, artifactPath: string): Promise<void> { |     private async saveArtifactBundle(bundle: string, artifactPath: string, report: CacheEntryReport): Promise<void> { | ||||||
|         const bundleMetaFile = this.getBundleMetaFile(bundle) |         const bundleMetaFile = this.getBundleMetaFile(bundle) | ||||||
|  |  | ||||||
|         const globber = await glob.create(artifactPath, { |         const globber = await glob.create(artifactPath, { | ||||||
| @@ -156,6 +158,7 @@ export class GradleUserHomeCache extends AbstractCache { | |||||||
|             core.info(`Caching ${bundle} with cache key: ${cacheKey}`) |             core.info(`Caching ${bundle} with cache key: ${cacheKey}`) | ||||||
|             await this.saveCache([artifactPath], cacheKey) |             await this.saveCache([artifactPath], cacheKey) | ||||||
|             this.writeBundleMetaFile(bundleMetaFile, cacheKey) |             this.writeBundleMetaFile(bundleMetaFile, cacheKey) | ||||||
|  |             report.markSaved(cacheKey) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         for (const file of bundleFiles) { |         for (const file of bundleFiles) { | ||||||
|   | |||||||
| @@ -112,11 +112,31 @@ export class CachingReport { | |||||||
|         return this.cacheEntryReports.every(x => !x.wasRequestedButNotRestored()) |         return this.cacheEntryReports.every(x => !x.wasRequestedButNotRestored()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     addEntryReport(name: string): CacheEntryReport { |     entryReport(name: string): CacheEntryReport { | ||||||
|         const report = new CacheEntryReport(name) |         for (const report of this.cacheEntryReports) { | ||||||
|         this.cacheEntryReports.push(report) |             if (report.entryName === name) { | ||||||
|                 return report |                 return report | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const newReport = new CacheEntryReport(name) | ||||||
|  |         this.cacheEntryReports.push(newReport) | ||||||
|  |         return newReport | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     stringify(): string { | ||||||
|  |         return JSON.stringify(this) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static rehydrate(stringRep: string): CachingReport { | ||||||
|  |         const rehydrated: CachingReport = Object.assign(new CachingReport(), JSON.parse(stringRep)) | ||||||
|  |         const entryReports = rehydrated.cacheEntryReports | ||||||
|  |         for (let index = 0; index < entryReports.length; index++) { | ||||||
|  |             const rawEntryReport = entryReports[index] | ||||||
|  |             entryReports[index] = Object.assign(new CacheEntryReport(rawEntryReport.entryName), rawEntryReport) | ||||||
|  |         } | ||||||
|  |         return rehydrated | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class CacheEntryReport { | export class CacheEntryReport { | ||||||
| @@ -137,13 +157,20 @@ export class CacheEntryReport { | |||||||
|         return this.requestedKey !== undefined && this.restoredKey === undefined |         return this.requestedKey !== undefined && this.restoredKey === undefined | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     markRequested(key: string, restoreKeys: string[] = []): void { |     markRequested(key: string, restoreKeys: string[] = []): CacheEntryReport { | ||||||
|         this.requestedKey = key |         this.requestedKey = key | ||||||
|         this.requestedRestoreKeys = restoreKeys |         this.requestedRestoreKeys = restoreKeys | ||||||
|  |         return this | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     markRestored(key: string): void { |     markRestored(key: string): CacheEntryReport { | ||||||
|         this.restoredKey = key |         this.restoredKey = key | ||||||
|  |         return this | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     markSaved(key: string): CacheEntryReport { | ||||||
|  |         this.savedKey = key | ||||||
|  |         return this | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -170,7 +197,7 @@ export abstract class AbstractCache { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         const cacheKey = this.prepareCacheKey() |         const cacheKey = this.prepareCacheKey() | ||||||
|         const entryReport = report.addEntryReport(this.cacheName) |         const entryReport = report.entryReport(this.cacheName) | ||||||
|         entryReport.markRequested(cacheKey.key, cacheKey.restoreKeys) |         entryReport.markRequested(cacheKey.key, cacheKey.restoreKeys) | ||||||
|  |  | ||||||
|         this.debug( |         this.debug( | ||||||
| @@ -224,7 +251,7 @@ export abstract class AbstractCache { | |||||||
|  |  | ||||||
|     protected async afterRestore(_report: CachingReport): Promise<void> {} |     protected async afterRestore(_report: CachingReport): Promise<void> {} | ||||||
|  |  | ||||||
|     async save(): Promise<void> { |     async save(report: CachingReport): Promise<void> { | ||||||
|         if (!this.cacheOutputExists()) { |         if (!this.cacheOutputExists()) { | ||||||
|             this.debug(`No ${this.cacheDescription} to cache.`) |             this.debug(`No ${this.cacheDescription} to cache.`) | ||||||
|             return |             return | ||||||
| @@ -244,7 +271,7 @@ export abstract class AbstractCache { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|             await this.beforeSave() |             await this.beforeSave(report) | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|             core.warning(`Save ${this.cacheDescription} failed in 'beforeSave': ${error}`) |             core.warning(`Save ${this.cacheDescription} failed in 'beforeSave': ${error}`) | ||||||
|             return |             return | ||||||
| @@ -254,10 +281,12 @@ export abstract class AbstractCache { | |||||||
|         const cachePath = this.getCachePath() |         const cachePath = this.getCachePath() | ||||||
|         await this.saveCache(cachePath, cacheKey) |         await this.saveCache(cachePath, cacheKey) | ||||||
|  |  | ||||||
|  |         report.entryReport(this.cacheName).markSaved(cacheKey) | ||||||
|  |  | ||||||
|         return |         return | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected async beforeSave(): Promise<void> {} |     protected async beforeSave(_report: CachingReport): Promise<void> {} | ||||||
|  |  | ||||||
|     protected async saveCache(cachePath: string[], cacheKey: string): Promise<void> { |     protected async saveCache(cachePath: string[], cacheKey: string): Promise<void> { | ||||||
|         try { |         try { | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import * as core from '@actions/core' | |||||||
| import {CachingReport, isCacheDisabled, isCacheReadOnly} from './cache-utils' | import {CachingReport, isCacheDisabled, isCacheReadOnly} from './cache-utils' | ||||||
|  |  | ||||||
| const BUILD_ROOT_DIR = 'BUILD_ROOT_DIR' | const BUILD_ROOT_DIR = 'BUILD_ROOT_DIR' | ||||||
|  | const CACHING_REPORT = 'CACHING_REPORT' | ||||||
|  |  | ||||||
| export async function restore(buildRootDirectory: string): Promise<void> { | export async function restore(buildRootDirectory: string): Promise<void> { | ||||||
|     if (isCacheDisabled()) { |     if (isCacheDisabled()) { | ||||||
| @@ -15,7 +16,6 @@ export async function restore(buildRootDirectory: string): Promise<void> { | |||||||
|         core.saveState(BUILD_ROOT_DIR, buildRootDirectory) |         core.saveState(BUILD_ROOT_DIR, buildRootDirectory) | ||||||
|  |  | ||||||
|         const cachingReport = new CachingReport() |         const cachingReport = new CachingReport() | ||||||
|  |  | ||||||
|         await new GradleUserHomeCache(buildRootDirectory).restore(cachingReport) |         await new GradleUserHomeCache(buildRootDirectory).restore(cachingReport) | ||||||
|  |  | ||||||
|         const projectDotGradleCache = new ProjectDotGradleCache(buildRootDirectory) |         const projectDotGradleCache = new ProjectDotGradleCache(buildRootDirectory) | ||||||
| @@ -24,8 +24,11 @@ export async function restore(buildRootDirectory: string): Promise<void> { | |||||||
|             await projectDotGradleCache.restore(cachingReport) |             await projectDotGradleCache.restore(cachingReport) | ||||||
|         } else { |         } else { | ||||||
|             // Otherwise, prepare the cache key for later save() |             // Otherwise, prepare the cache key for later save() | ||||||
|  |             core.info('Gradle Home cache not fully restored: not restoring configuration-cache state') | ||||||
|             projectDotGradleCache.prepareCacheKey() |             projectDotGradleCache.prepareCacheKey() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         core.saveState(CACHING_REPORT, cachingReport.stringify()) | ||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -35,11 +38,19 @@ export async function save(): Promise<void> { | |||||||
|         return |         return | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     const cachingReport: CachingReport = CachingReport.rehydrate(core.getState(CACHING_REPORT)) | ||||||
|  |  | ||||||
|     await core.group('Caching Gradle state', async () => { |     await core.group('Caching Gradle state', async () => { | ||||||
|         const buildRootDirectory = core.getState(BUILD_ROOT_DIR) |         const buildRootDirectory = core.getState(BUILD_ROOT_DIR) | ||||||
|         return Promise.all([ |         return Promise.all([ | ||||||
|             new GradleUserHomeCache(buildRootDirectory).save(), |             new GradleUserHomeCache(buildRootDirectory).save(cachingReport), | ||||||
|             new ProjectDotGradleCache(buildRootDirectory).save() |             new ProjectDotGradleCache(buildRootDirectory).save(cachingReport) | ||||||
|         ]) |         ]) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  |     logCachingReport(cachingReport) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function logCachingReport(report: CachingReport): void { | ||||||
|  |     core.info(JSON.stringify(report, null, 2)) | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user