-
Notifications
You must be signed in to change notification settings - Fork 226
Expand file tree
/
Copy pathquery-history-scrubber.ts
More file actions
177 lines (163 loc) · 5.34 KB
/
query-history-scrubber.ts
File metadata and controls
177 lines (163 loc) · 5.34 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
import { pathExists, stat, remove, readFile } from "fs-extra";
import { EOL } from "os";
import { join } from "path";
import { Disposable, ExtensionContext } from "vscode";
import { extLogger } from "../common";
import { readDirFullPaths } from "../pure/files";
import { QueryHistoryDirs } from "./query-history-dirs";
import { QueryHistoryManager } from "./query-history-manager";
const LAST_SCRUB_TIME_KEY = "lastScrubTime";
type Counter = {
increment: () => void;
};
/**
* 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 counter to keep track of how many times the scrubber has run
counter?: Counter,
): Disposable {
const deregister = setInterval(
scrubQueries,
wakeInterval,
throttleTime,
maxQueryTime,
queryHistoryDirs,
qhm,
ctx,
counter,
);
return {
dispose: () => {
clearInterval(deregister);
},
};
}
async function scrubQueries(
throttleTime: number,
maxQueryTime: number,
queryHistoryDirs: QueryHistoryDirs,
qhm: QueryHistoryManager,
ctx: ExtensionContext,
counter?: Counter,
) {
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 {
counter?.increment();
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: ${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;
}> {
const timestampFile = join(dir, "timestamp");
try {
let deleted = true;
if (!(await stat(dir)).isDirectory()) {
void extLogger.log(` ${dir} is not a directory. Deleting.`);
await remove(dir);
} else if (!(await pathExists(timestampFile))) {
void extLogger.log(` ${dir} has no timestamp file. Deleting.`);
await remove(dir);
} else if (!(await stat(timestampFile)).isFile()) {
void extLogger.log(` ${timestampFile} is not a file. Deleting.`);
await remove(dir);
} else {
const timestampText = await readFile(timestampFile, "utf8");
const timestamp = parseInt(timestampText, 10);
if (Number.isNaN(timestamp)) {
void extLogger.log(
` ${dir} has invalid timestamp '${timestampText}'. Deleting.`,
);
await remove(dir);
} else if (now - timestamp > maxQueryTime) {
void extLogger.log(
` ${dir} is older than ${maxQueryTime / 1000} seconds. Deleting.`,
);
await remove(dir);
} else {
void extLogger.log(
` ${dir} is not older than ${maxQueryTime / 1000} seconds. Keeping.`,
);
deleted = false;
}
}
return {
deleted,
};
} catch (err) {
return {
errorMsg: ` Could not delete '${dir}': ${err}`,
deleted: false,
};
}
}