-
Notifications
You must be signed in to change notification settings - Fork 451
Expand file tree
/
Copy pathstatus.ts
More file actions
147 lines (133 loc) · 4.83 KB
/
status.ts
File metadata and controls
147 lines (133 loc) · 4.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
* We perform enablement checks for overlay analysis to avoid using it on runners that are too small
* to support it. However these checks cannot avoid every potential issue without being overly
* conservative. Therefore, if our enablement checks enable overlay analysis for a runner that is
* too small, we want to remember that, so that we will not try to use overlay analysis until
* something changes (e.g. a larger runner is provisioned, or a new CodeQL version is released).
*
* We use the Actions cache as a lightweight way of providing this functionality.
*/
import * as fs from "fs";
import * as path from "path";
import * as actionsCache from "@actions/cache";
import { getTemporaryDirectory } from "../actions-util";
import { type CodeQL } from "../codeql";
import { Logger } from "../logging";
import {
DiskUsage,
getErrorMessage,
waitForResultWithTimeLimit,
} from "../util";
/** The maximum time to wait for a cache operation to complete. */
const MAX_CACHE_OPERATION_MS = 30_000;
/** File name for the serialized overlay status. */
const STATUS_FILE_NAME = "overlay-status.json";
/** Status of an overlay analysis for a particular language. */
export interface OverlayStatus {
/** Whether the job successfully built an overlay base database. */
builtOverlayBaseDatabase: boolean;
}
/**
* Retrieve overlay status from the Actions cache, if available.
*
* @returns `undefined` if no status was found in the cache (e.g. first run with
* this cache key) or if the cache operation fails.
*/
export async function getOverlayStatus(
codeql: CodeQL,
language: string,
diskUsage: DiskUsage,
logger: Logger,
): Promise<OverlayStatus | undefined> {
const cacheKey = await getCacheKey(codeql, language, diskUsage);
const statusFile = path.join(
getTemporaryDirectory(),
"overlay-status",
language,
STATUS_FILE_NAME,
);
await fs.promises.mkdir(path.dirname(statusFile), { recursive: true });
try {
const foundKey = await waitForResultWithTimeLimit(
MAX_CACHE_OPERATION_MS,
actionsCache.restoreCache([statusFile], cacheKey),
() => {
logger.info("Timed out restoring overlay status from cache");
},
);
if (foundKey === undefined) {
logger.debug("No overlay status found in Actions cache");
return undefined;
}
if (!fs.existsSync(statusFile)) {
logger.debug(
"Overlay status cache entry found but status file is missing",
);
return undefined;
}
const contents = await fs.promises.readFile(statusFile, "utf-8");
return JSON.parse(contents) as OverlayStatus;
} catch (error) {
logger.warning(
`Failed to restore overlay status from cache: ${getErrorMessage(error)}`,
);
return undefined;
}
}
/**
* Save overlay status to the Actions cache.
*
* @returns `true` if the status was saved successfully, `false` otherwise.
*/
export async function saveOverlayStatus(
codeql: CodeQL,
language: string,
diskUsage: DiskUsage,
status: OverlayStatus,
logger: Logger,
): Promise<boolean> {
const cacheKey = await getCacheKey(codeql, language, diskUsage);
const statusFile = path.join(
getTemporaryDirectory(),
"overlay-status",
language,
STATUS_FILE_NAME,
);
await fs.promises.mkdir(path.dirname(statusFile), { recursive: true });
await fs.promises.writeFile(statusFile, JSON.stringify(status));
try {
const cacheId = await waitForResultWithTimeLimit(
MAX_CACHE_OPERATION_MS,
actionsCache.saveCache([statusFile], cacheKey),
() => {},
);
if (cacheId === undefined) {
logger.warning("Timed out saving overlay status to cache");
return false;
}
logger.info(`Saved overlay status to Actions cache with key ${cacheKey}`);
return true;
} catch (error) {
logger.warning(
`Failed to save overlay status to cache: ${getErrorMessage(error)}`,
);
return false;
}
}
export async function getCacheKey(
codeql: CodeQL,
language: string,
diskUsage: DiskUsage,
): Promise<string> {
// Total disk space, rounded to the nearest 10 GB. This is included in the cache key so that if a
// customer upgrades their runner, we will try again to use overlay analysis, even if the CodeQL
// version has not changed. We round to the nearest 10 GB to work around small differences in disk
// space.
//
// Limitation: this can still flip from "too small" to "large enough" and back again if the disk
// space fluctuates above and below a multiple of 10 GB.
const diskSpaceToNearest10Gb = `${10 * Math.floor(diskUsage.numTotalBytes / (10 * 1024 * 1024 * 1024))}GB`;
// Include the CodeQL version in the cache key so we will try again to use overlay analysis when
// new queries and libraries that may be more efficient are released.
return `codeql-overlay-status-${language}-${(await codeql.getVersion()).version}-runner-${diskSpaceToNearest10Gb}`;
}