-
Notifications
You must be signed in to change notification settings - Fork 226
Expand file tree
/
Copy pathquery-history-scrubber.ts
More file actions
185 lines (171 loc) · 5.42 KB
/
query-history-scrubber.ts
File metadata and controls
185 lines (171 loc) · 5.42 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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import { pathExists, remove, readFile } from "fs-extra";
import { EOL } from "os";
import { join } from "path";
import type { Disposable, ExtensionContext } from "vscode";
import { extLogger } from "../common/logging/vscode";
import { readDirFullPaths } from "../common/files";
import type { QueryHistoryDirs } from "./query-history-dirs";
import type { QueryHistoryManager } from "./query-history-manager";
import { getErrorMessage } from "../common/helpers-pure";
const LAST_SCRUB_TIME_KEY = "lastScrubTime";
/**
* Registers an interval timer that will periodically check for queries old enought
* to be deleted.
*
* Note that this scrubber will clean all queries from all workspaces. It should not
* run too often and it should only run from one workspace at a time.
*
* Generally, `wakeInterval` should be significantly shorter than `throttleTime`.
*
* @param wakeInterval How often to check to see if the job should run.
* @param throttleTime How often to actually run the job.
* @param maxQueryTime The maximum age of a query before is ready for deletion.
* @param queryHistoryDirs The directories containing all query history information.
* @param ctx The extension context.
*/
export function registerQueryHistoryScrubber(
wakeInterval: number,
throttleTime: number,
maxQueryTime: number,
queryHistoryDirs: QueryHistoryDirs,
qhm: QueryHistoryManager,
ctx: ExtensionContext,
// optional callback to keep track of how many times the scrubber has run
onScrubberRun?: () => void,
): Disposable {
const deregister = setInterval(
scrubQueries,
wakeInterval,
throttleTime,
maxQueryTime,
queryHistoryDirs,
qhm,
ctx,
onScrubberRun,
);
return {
dispose: () => {
clearInterval(deregister);
},
};
}
async function scrubQueries(
throttleTime: number,
maxQueryTime: number,
queryHistoryDirs: QueryHistoryDirs,
qhm: QueryHistoryManager,
ctx: ExtensionContext,
onScrubberRun?: () => void,
) {
const lastScrubTime = ctx.globalState.get<number>(LAST_SCRUB_TIME_KEY);
const now = Date.now();
// If we have never scrubbed before, or if the last scrub was more than `throttleTime` ago,
// then scrub again.
if (lastScrubTime === undefined || now - lastScrubTime >= throttleTime) {
await ctx.globalState.update(LAST_SCRUB_TIME_KEY, now);
let scrubCount = 0; // total number of directories deleted
try {
onScrubberRun?.();
void extLogger.log(
"Cleaning up query history directories. Removing old entries.",
);
if (!(await pathExists(queryHistoryDirs.localQueriesDirPath))) {
void extLogger.log(
`Cannot clean up query history directories. Local queries directory does not exist: ${queryHistoryDirs.localQueriesDirPath}`,
);
return;
}
if (!(await pathExists(queryHistoryDirs.variantAnalysesDirPath))) {
void extLogger.log(
`Cannot clean up query history directories. Variant analyses directory does not exist: ${queryHistoryDirs.variantAnalysesDirPath}`,
);
return;
}
const localQueryDirPaths = await readDirFullPaths(
queryHistoryDirs.localQueriesDirPath,
);
const variantAnalysisDirPaths = await readDirFullPaths(
queryHistoryDirs.variantAnalysesDirPath,
);
const allDirPaths = [...localQueryDirPaths, ...variantAnalysisDirPaths];
const errors: string[] = [];
for (const dir of allDirPaths) {
const scrubResult = await scrubDirectory(dir, now, maxQueryTime);
if (scrubResult.errorMsg) {
errors.push(scrubResult.errorMsg);
}
if (scrubResult.deleted) {
scrubCount++;
}
}
if (errors.length) {
throw new Error(EOL + errors.join(EOL));
}
} catch (e) {
void extLogger.log(
`Error while scrubbing queries: ${getErrorMessage(e)}`,
);
} finally {
void extLogger.log(`Scrubbed ${scrubCount} old queries.`);
}
await qhm.removeDeletedQueries();
}
}
async function scrubDirectory(
dir: string,
now: number,
maxQueryTime: number,
): Promise<{
errorMsg?: string;
deleted: boolean;
}> {
try {
if (await shouldScrubDirectory(dir, now, maxQueryTime)) {
await remove(dir);
return { deleted: true };
} else {
return { deleted: false };
}
} catch (err) {
return {
errorMsg: ` Could not delete '${dir}': ${getErrorMessage(err)}`,
deleted: false,
};
}
}
async function shouldScrubDirectory(
dir: string,
now: number,
maxQueryTime: number,
): Promise<boolean> {
const timestamp = await getTimestamp(join(dir, "timestamp"));
if (timestamp === undefined || Number.isNaN(timestamp)) {
void extLogger.log(` ${dir} timestamp is missing or invalid. Deleting.`);
return true;
} else if (now - timestamp > maxQueryTime) {
void extLogger.log(
` ${dir} is older than ${maxQueryTime / 1000} seconds. Deleting.`,
);
return true;
} else {
void extLogger.log(
` ${dir} is not older than ${maxQueryTime / 1000} seconds. Keeping.`,
);
return false;
}
}
async function getTimestamp(
timestampFile: string,
): Promise<number | undefined> {
try {
const timestampText = await readFile(timestampFile, "utf8");
return parseInt(timestampText, 10);
} catch (err) {
void extLogger.log(
` Could not read timestamp file '${timestampFile}': ${getErrorMessage(
err,
)}`,
);
return undefined;
}
}