diff --git a/lib/init-action.js b/lib/init-action.js index b19951d8e..91c00e45f 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -88137,7 +88137,7 @@ async function makeGlobber(patterns) { return glob.create(patterns.join("\n")); } async function downloadDependencyCaches(languages, logger, minimizeJavaJars) { - const restoredCaches = []; + const status = {}; for (const language of languages) { const cacheConfig = getDefaultCacheConfig()[language]; if (cacheConfig === void 0) { @@ -88148,6 +88148,7 @@ async function downloadDependencyCaches(languages, logger, minimizeJavaJars) { } const globber = await makeGlobber(cacheConfig.hash); if ((await globber.glob()).length === 0) { + status[language] = { hit: "no-hash" /* NoHash */ }; logger.info( `Skipping download of dependency cache for ${language} as we cannot calculate a hash for the cache key.` ); @@ -88162,19 +88163,26 @@ async function downloadDependencyCaches(languages, logger, minimizeJavaJars) { ", " )}` ); + const start = performance.now(); const hitKey = await actionsCache3.restoreCache( cacheConfig.paths, primaryKey, restoreKeys ); + const download_duration_ms = Math.round(performance.now() - start); + const download_size_bytes = Math.round( + await getTotalCacheSize(cacheConfig.paths, logger) + ); if (hitKey !== void 0) { logger.info(`Cache hit on key ${hitKey} for ${language}.`); - restoredCaches.push(language); + const hit = hitKey === primaryKey ? "exact" /* Exact */ : "partial" /* Partial */; + status[language] = { hit, download_duration_ms, download_size_bytes }; } else { + status[language] = { hit: "miss" /* Miss */ }; logger.info(`No suitable cache found for ${language}.`); } } - return restoredCaches; + return status; } async function cacheKey2(language, cacheConfig, minimizeJavaJars = false) { const hash = await glob.hashFiles(cacheConfig.hash.join("\n")); @@ -90278,7 +90286,7 @@ async function sendStatusReport(statusReport) { ); } } -async function createInitWithConfigStatusReport(config, initStatusReport, configFile, totalCacheSize, overlayBaseDatabaseStats) { +async function createInitWithConfigStatusReport(config, initStatusReport, configFile, totalCacheSize, overlayBaseDatabaseStats, dependencyCachingResults) { const languages = config.languages.join(","); const paths = (config.originalUserInput.paths || []).join(","); const pathsIgnore = (config.originalUserInput["paths-ignore"] || []).join( @@ -90315,6 +90323,9 @@ async function createInitWithConfigStatusReport(config, initStatusReport, config trap_cache_download_duration_ms: Math.round(config.trapCacheDownloadTime), overlay_base_database_download_size_bytes: overlayBaseDatabaseStats?.databaseSizeBytes, overlay_base_database_download_duration_ms: overlayBaseDatabaseStats?.databaseDownloadDurationMs, + dependency_caching_restore_results: JSON.stringify( + dependencyCachingResults ?? {} + ), query_filters: JSON.stringify( config.originalUserInput["query-filters"] ?? [] ), @@ -90497,7 +90508,7 @@ async function getWorkflowAbsolutePath(logger) { } // src/init-action.ts -async function sendCompletedStatusReport(startedAt, config, configFile, toolsDownloadStatusReport, toolsFeatureFlagsValid, toolsSource, toolsVersion, overlayBaseDatabaseStats, logger, error2) { +async function sendCompletedStatusReport(startedAt, config, configFile, toolsDownloadStatusReport, toolsFeatureFlagsValid, toolsSource, toolsVersion, overlayBaseDatabaseStats, dependencyCachingResults, logger, error2) { const statusReportBase = await createStatusReportBase( "init" /* Init */, getActionsStatus(error2), @@ -90534,7 +90545,8 @@ async function sendCompletedStatusReport(startedAt, config, configFile, toolsDow Math.round( await getTotalCacheSize(Object.values(config.trapCaches), logger) ), - overlayBaseDatabaseStats + overlayBaseDatabaseStats, + dependencyCachingResults ); await sendStatusReport({ ...initWithConfigStatusReport, @@ -90698,6 +90710,7 @@ async function run() { return; } let overlayBaseDatabaseStats; + let dependencyCachingResults; try { if (config.overlayDatabaseMode === "overlay" /* Overlay */ && config.useOverlayDatabaseCaching) { overlayBaseDatabaseStats = await downloadOverlayBaseDatabaseFromCache( @@ -90842,7 +90855,7 @@ exec ${goBinaryPath} "$@"` codeql ); if (shouldRestoreCache(config.dependencyCachingEnabled)) { - await downloadDependencyCaches( + dependencyCachingResults = await downloadDependencyCaches( config.languages, logger, minimizeJavaJars @@ -90947,6 +90960,7 @@ exec ${goBinaryPath} "$@"` toolsSource, toolsVersion, overlayBaseDatabaseStats, + dependencyCachingResults, logger, error2 ); @@ -90964,6 +90978,7 @@ exec ${goBinaryPath} "$@"` toolsSource, toolsVersion, overlayBaseDatabaseStats, + dependencyCachingResults, logger ); } diff --git a/src/dependency-caching.ts b/src/dependency-caching.ts index 6289ca2f6..773e4de9e 100644 --- a/src/dependency-caching.ts +++ b/src/dependency-caching.ts @@ -84,20 +84,44 @@ async function makeGlobber(patterns: string[]): Promise { return glob.create(patterns.join("\n")); } +/** Enumerates possible outcomes for cache hits. */ +export enum CacheHitResult { + /** We were unable to calculate a hash for the key. */ + NoHash = "no-hash", + /** No cache was found. */ + Miss = "miss", + /** The primary cache key matched. */ + Exact = "exact", + /** A restore key matched. */ + Partial = "partial", +} + +/** Represents results of trying to restore a dependency cache for a language. */ +export interface DependencyCacheRestoreStatus { + hit: CacheHitResult; + download_size_bytes?: number; + download_duration_ms?: number; +} + +/** A partial mapping from languages to the results of restoring dependency caches for them. */ +export type DependencyCacheRestoreStatusReport = Partial< + Record +>; + /** * Attempts to restore dependency caches for the languages being analyzed. * * @param languages The languages being analyzed. * @param logger A logger to record some informational messages to. * @param minimizeJavaJars Whether the Java extractor should rewrite downloaded JARs to minimize their size. - * @returns A list of languages for which dependency caches were restored. + * @returns A partial mapping of languages to results of restoring dependency caches for them. */ export async function downloadDependencyCaches( languages: Language[], logger: Logger, minimizeJavaJars: boolean, -): Promise { - const restoredCaches: Language[] = []; +): Promise { + const status: DependencyCacheRestoreStatusReport = {}; for (const language of languages) { const cacheConfig = getDefaultCacheConfig()[language]; @@ -114,6 +138,7 @@ export async function downloadDependencyCaches( const globber = await makeGlobber(cacheConfig.hash); if ((await globber.glob()).length === 0) { + status[language] = { hit: CacheHitResult.NoHash }; logger.info( `Skipping download of dependency cache for ${language} as we cannot calculate a hash for the cache key.`, ); @@ -131,21 +156,29 @@ export async function downloadDependencyCaches( )}`, ); + const start = performance.now(); const hitKey = await actionsCache.restoreCache( cacheConfig.paths, primaryKey, restoreKeys, ); + const download_duration_ms = Math.round(performance.now() - start); + const download_size_bytes = Math.round( + await getTotalCacheSize(cacheConfig.paths, logger), + ); if (hitKey !== undefined) { logger.info(`Cache hit on key ${hitKey} for ${language}.`); - restoredCaches.push(language); + const hit = + hitKey === primaryKey ? CacheHitResult.Exact : CacheHitResult.Partial; + status[language] = { hit, download_duration_ms, download_size_bytes }; } else { + status[language] = { hit: CacheHitResult.Miss }; logger.info(`No suitable cache found for ${language}.`); } } - return restoredCaches; + return status; } /** diff --git a/src/init-action.ts b/src/init-action.ts index 2b4dba3fc..114ad6cab 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -23,7 +23,10 @@ import { } from "./caching-utils"; import { CodeQL } from "./codeql"; import * as configUtils from "./config-utils"; -import { downloadDependencyCaches } from "./dependency-caching"; +import { + DependencyCacheRestoreStatusReport, + downloadDependencyCaches, +} from "./dependency-caching"; import { addDiagnostic, flushDiagnostics, @@ -102,6 +105,7 @@ async function sendCompletedStatusReport( toolsSource: ToolsSource, toolsVersion: string, overlayBaseDatabaseStats: OverlayBaseDatabaseDownloadStats | undefined, + dependencyCachingResults: DependencyCacheRestoreStatusReport | undefined, logger: Logger, error?: Error, ) { @@ -151,6 +155,7 @@ async function sendCompletedStatusReport( await getTotalCacheSize(Object.values(config.trapCaches), logger), ), overlayBaseDatabaseStats, + dependencyCachingResults, ); await sendStatusReport({ ...initWithConfigStatusReport, @@ -351,6 +356,7 @@ async function run() { } let overlayBaseDatabaseStats: OverlayBaseDatabaseDownloadStats | undefined; + let dependencyCachingResults: DependencyCacheRestoreStatusReport | undefined; try { if ( config.overlayDatabaseMode === OverlayDatabaseMode.Overlay && @@ -562,7 +568,7 @@ async function run() { codeql, ); if (shouldRestoreCache(config.dependencyCachingEnabled)) { - await downloadDependencyCaches( + dependencyCachingResults = await downloadDependencyCaches( config.languages, logger, minimizeJavaJars, @@ -714,6 +720,7 @@ async function run() { toolsSource, toolsVersion, overlayBaseDatabaseStats, + dependencyCachingResults, logger, error, ); @@ -736,6 +743,7 @@ async function run() { toolsSource, toolsVersion, overlayBaseDatabaseStats, + dependencyCachingResults, logger, ); } diff --git a/src/status-report.test.ts b/src/status-report.test.ts index 0d3292637..b535ef212 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -286,6 +286,7 @@ const testCreateInitWithConfigStatusReport = test.macro({ undefined, 1024, undefined, + undefined, ); if (t.truthy(initWithConfigStatusReport)) { diff --git a/src/status-report.ts b/src/status-report.ts index 75f400221..41f63a20a 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -13,6 +13,7 @@ import { } from "./actions-util"; import { getAnalysisKey, getApiClient } from "./api-client"; import { parseRegistriesWithoutCredentials, type Config } from "./config-utils"; +import { DependencyCacheRestoreStatusReport } from "./dependency-caching"; import { DocUrl } from "./doc-url"; import { EnvVar } from "./environment"; import { getRef } from "./git-utils"; @@ -497,6 +498,8 @@ export interface InitWithConfigStatusReport extends InitStatusReport { overlay_base_database_download_size_bytes?: number; /** Time taken to download the overlay-base database, in milliseconds. */ overlay_base_database_download_duration_ms?: number; + /** Stringified JSON object representing information about the results of restoring dependency caches. */ + dependency_caching_restore_results: string; /** Stringified JSON array of registry configuration objects, from the 'registries' config field or workflow input. **/ registries: string; @@ -522,6 +525,7 @@ export async function createInitWithConfigStatusReport( configFile: string | undefined, totalCacheSize: number, overlayBaseDatabaseStats: OverlayBaseDatabaseDownloadStats | undefined, + dependencyCachingResults: DependencyCacheRestoreStatusReport | undefined, ): Promise { const languages = config.languages.join(","); const paths = (config.originalUserInput.paths || []).join(","); @@ -570,6 +574,9 @@ export async function createInitWithConfigStatusReport( overlayBaseDatabaseStats?.databaseSizeBytes, overlay_base_database_download_duration_ms: overlayBaseDatabaseStats?.databaseDownloadDurationMs, + dependency_caching_restore_results: JSON.stringify( + dependencyCachingResults ?? {}, + ), query_filters: JSON.stringify( config.originalUserInput["query-filters"] ?? [], ),