@andersbakken/fisk 3.5.7 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/VM_runtime.js +4040 -0
- package/dist/VM_runtime.js.map +1 -0
- package/dist/fisk-builder.js +55239 -0
- package/dist/fisk-builder.js.map +1 -0
- package/dist/fisk-daemon.js +4223 -0
- package/dist/fisk-daemon.js.map +1 -0
- package/dist/fisk-scheduler.js +53883 -0
- package/dist/fisk-scheduler.js.map +1 -0
- package/package.json +68 -34
- package/builder/VM.js +0 -156
- package/builder/VM_runtime.js +0 -171
- package/builder/client.js +0 -181
- package/builder/compile.js +0 -269
- package/builder/fisk-builder.js +0 -899
- package/builder/load.js +0 -85
- package/builder/objectcache.js +0 -301
- package/builder/quit-on-error.js +0 -9
- package/builder/server.js +0 -214
- package/common/index.js +0 -45
- package/daemon/client.js +0 -100
- package/daemon/clientbuffer.js +0 -73
- package/daemon/compile.js +0 -140
- package/daemon/constants.js +0 -13
- package/daemon/fisk-daemon.js +0 -166
- package/daemon/server.js +0 -81
- package/daemon/slots.js +0 -69
- package/monitor/fisk-monitor.js +0 -767
- package/proxy/fisk-proxy.js +0 -150
- package/scheduler/database.js +0 -144
- package/scheduler/environments.js +0 -386
- package/scheduler/fisk-scheduler.js +0 -1411
- package/scheduler/objectcachemanager.js +0 -306
- package/scheduler/peak.js +0 -112
- package/scheduler/public/index.html +0 -37
- package/scheduler/server.js +0 -432
- package/scheduler/usertester.js +0 -60
|
@@ -1,1411 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const path = require("path");
|
|
4
|
-
const os = require("os");
|
|
5
|
-
const option = require("@jhanssen/options")({ prefix: "fisk/scheduler",
|
|
6
|
-
applicationPath: false,
|
|
7
|
-
additionalFiles: [ "fisk/scheduler.conf.override" ] });
|
|
8
|
-
const posix = require("posix");
|
|
9
|
-
const Server = require("./server");
|
|
10
|
-
const common = require("../common")(option);
|
|
11
|
-
const Environments = require("./environments");
|
|
12
|
-
const server = new Server(option, common.Version);
|
|
13
|
-
const fs = require("fs-extra");
|
|
14
|
-
const bytes = require("bytes");
|
|
15
|
-
const crypto = require("crypto");
|
|
16
|
-
const Database = require("./database");
|
|
17
|
-
const Peak = require("./peak");
|
|
18
|
-
const ObjectCacheManager = require("./objectcachemanager");
|
|
19
|
-
const compareVersions = require("compare-versions");
|
|
20
|
-
const humanizeDuration = require("humanize-duration");
|
|
21
|
-
const wol = require("wake_on_lan");
|
|
22
|
-
let wolBuilders = {};
|
|
23
|
-
(option("wake-on-lan-builders") || []).forEach(builder => {
|
|
24
|
-
if (!builder.name || !builder.mac) {
|
|
25
|
-
console.error("Bad wol, missing name or mac address");
|
|
26
|
-
} else if (builder.name in wolBuilders) {
|
|
27
|
-
console.error(`Duplicate name for wol-builder ${JSON.stringify(builder)}`);
|
|
28
|
-
} else {
|
|
29
|
-
wolBuilders[builder.name] = { mac: builder.mac, connected: false, address: builder.address || "255.255.255.255" };
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const clientMinimumVersion = "3.4.96";
|
|
34
|
-
const serverStartTime = Date.now();
|
|
35
|
-
process.on("unhandledRejection", (reason, p) => {
|
|
36
|
-
console.log("Unhandled Rejection at: Promise", p, "reason:", reason.stack);
|
|
37
|
-
addLogFile({ source: "no source file", ip: "self", contents: `reason: ${reason.stack} p: ${p}\n` }, () => {
|
|
38
|
-
process.exit();
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
process.on("uncaughtException", err => {
|
|
43
|
-
console.error("Uncaught exception", err);
|
|
44
|
-
addLogFile({ source: "no source file", ip: "self", contents: err.toString() + err.stack + "\n" }, () => {
|
|
45
|
-
process.exit();
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const monitorsLog = option("monitor-log");
|
|
50
|
-
|
|
51
|
-
server.on("error", error => {
|
|
52
|
-
throw new error;
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
let schedulerNpmVersion;
|
|
56
|
-
try {
|
|
57
|
-
schedulerNpmVersion = JSON.parse(fs.readFileSync(path.join(__dirname, "../package.json"))).version;
|
|
58
|
-
} catch (err) {
|
|
59
|
-
console.log("Couldn't parse package json", err);
|
|
60
|
-
process.exit();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const builders = {};
|
|
64
|
-
let lastWol = 0;
|
|
65
|
-
function sendWols()
|
|
66
|
-
{
|
|
67
|
-
const now = Date.now();
|
|
68
|
-
// console.log("sendWols", now, lastWol, now - lastWol);
|
|
69
|
-
if (now - lastWol < 60000)
|
|
70
|
-
return;
|
|
71
|
-
|
|
72
|
-
lastWol = now;
|
|
73
|
-
let byName;
|
|
74
|
-
for (let name in wolBuilders) {
|
|
75
|
-
const wolBuilder = wolBuilders[name];
|
|
76
|
-
if (wolBuilder.connected)
|
|
77
|
-
continue;
|
|
78
|
-
if (!byName) {
|
|
79
|
-
byName = {};
|
|
80
|
-
for (let key in builders) {
|
|
81
|
-
const builder = builders[key];
|
|
82
|
-
if (builder.name)
|
|
83
|
-
byName[builder.name] = builder;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (!(name in byName)) {
|
|
87
|
-
wol.wake(wolBuilder.mac, error => {
|
|
88
|
-
console.log("sending wol", JSON.stringify(wolBuilder));
|
|
89
|
-
if (error) {
|
|
90
|
-
console.error(`Failed to wol builder: ${JSON.stringify(wolBuilder)}: ${error}`);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
} else {
|
|
94
|
-
console.log(wolBuilder, "is already connected");
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const monitors = [];
|
|
100
|
-
let builderCount = 0;
|
|
101
|
-
let activeJobs = 0;
|
|
102
|
-
let capacity = 0;
|
|
103
|
-
let jobsFailed = 0;
|
|
104
|
-
let jobsStarted = 0;
|
|
105
|
-
let jobsScheduled = 0;
|
|
106
|
-
let jobsFinished = 0;
|
|
107
|
-
let jobId = 0;
|
|
108
|
-
const db = new Database(path.join(common.cacheDir(), "db.json"));
|
|
109
|
-
let objectCache;
|
|
110
|
-
const logFileDir = path.join(common.cacheDir(), "logs");
|
|
111
|
-
try {
|
|
112
|
-
fs.mkdirSync(logFileDir);
|
|
113
|
-
} catch (err) {
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const peaks = [
|
|
117
|
-
new Peak(60 * 60 * 1000, "Last hour"),
|
|
118
|
-
new Peak(24 * 60 * 60 * 1000, "Last 24 hours"),
|
|
119
|
-
new Peak(7 * 24 * 60 * 60 * 1000, "Last 7 days"),
|
|
120
|
-
new Peak(30 * 24 * 60 * 60 * 1000, "Last 30 days"),
|
|
121
|
-
new Peak(undefined, "Forever")
|
|
122
|
-
];
|
|
123
|
-
|
|
124
|
-
function peakData()
|
|
125
|
-
{
|
|
126
|
-
let ret = {};
|
|
127
|
-
peaks.forEach(peak => ret[peak.name] = peak.toObject());
|
|
128
|
-
return ret;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function statsMessage()
|
|
132
|
-
{
|
|
133
|
-
let info = peakData();
|
|
134
|
-
info.type = "stats";
|
|
135
|
-
const jobs = jobsFailed + jobsFinished + (objectCache ? objectCache.hits : 0);
|
|
136
|
-
info.jobs = jobs;
|
|
137
|
-
info.jobsFailed = jobsFailed;
|
|
138
|
-
info.jobsScheduled = jobsScheduled;
|
|
139
|
-
info.jobsFinished = jobsFinished;
|
|
140
|
-
info.jobsStarted = jobsStarted;
|
|
141
|
-
info.cacheHits = objectCache ? objectCache.hits : 0;
|
|
142
|
-
return info;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const pendingUsers = {};
|
|
146
|
-
|
|
147
|
-
function nextJobId()
|
|
148
|
-
{
|
|
149
|
-
let id = ++jobId;
|
|
150
|
-
if (id == 2147483647)
|
|
151
|
-
id = 1;
|
|
152
|
-
return id;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function jobStartedOrScheduled(type, job)
|
|
156
|
-
{
|
|
157
|
-
if (monitors.length) {
|
|
158
|
-
// console.log("GOT STUFF", job);
|
|
159
|
-
let info = {
|
|
160
|
-
type: type,
|
|
161
|
-
client: {
|
|
162
|
-
hostname: job.client.hostname,
|
|
163
|
-
ip: job.client.ip,
|
|
164
|
-
name: job.client.name,
|
|
165
|
-
user: job.client.user,
|
|
166
|
-
labels: job.client.labels
|
|
167
|
-
},
|
|
168
|
-
sourceFile: job.sourceFile,
|
|
169
|
-
builder: {
|
|
170
|
-
ip: job.builder.ip,
|
|
171
|
-
name: job.builder.name,
|
|
172
|
-
port: job.builder.port,
|
|
173
|
-
labels: job.builder.labels
|
|
174
|
-
},
|
|
175
|
-
id: job.id
|
|
176
|
-
};
|
|
177
|
-
if (job.builder.hostname)
|
|
178
|
-
info.builder.hostname = job.builder.hostname;
|
|
179
|
-
|
|
180
|
-
if (monitorsLog)
|
|
181
|
-
console.log("send to monitors", info);
|
|
182
|
-
monitors.forEach(monitor => monitor.send(info));
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function cacheHit(builder, job)
|
|
187
|
-
{
|
|
188
|
-
if (objectCache)
|
|
189
|
-
objectCache.hit(job.sha1);
|
|
190
|
-
if (monitors.length) {
|
|
191
|
-
let info = {
|
|
192
|
-
type: "cacheHit",
|
|
193
|
-
client: {
|
|
194
|
-
hostname: job.client.hostname,
|
|
195
|
-
ip: job.client.ip,
|
|
196
|
-
name: job.client.name,
|
|
197
|
-
user: job.client.user,
|
|
198
|
-
labels: job.client.labels
|
|
199
|
-
},
|
|
200
|
-
sourceFile: job.sourceFile,
|
|
201
|
-
builder: {
|
|
202
|
-
ip: builder.ip,
|
|
203
|
-
name: builder.name,
|
|
204
|
-
port: builder.port,
|
|
205
|
-
labels: builder.labels
|
|
206
|
-
},
|
|
207
|
-
id: job.id,
|
|
208
|
-
jobs: (objectCache ? objectCache.hits : 0) + jobsFailed + jobsFinished,
|
|
209
|
-
jobsFailed: jobsFailed,
|
|
210
|
-
jobsStarted: jobsStarted,
|
|
211
|
-
jobsFinished: jobsFinished,
|
|
212
|
-
jobsScheduled: jobsScheduled,
|
|
213
|
-
cacheHits: objectCache ? objectCache.hits : 0
|
|
214
|
-
};
|
|
215
|
-
if (builder.hostname)
|
|
216
|
-
info.builder.hostname = builder.hostname;
|
|
217
|
-
if (monitorsLog)
|
|
218
|
-
console.log("send to monitors", info);
|
|
219
|
-
// console.log("sending info", info);
|
|
220
|
-
monitors.forEach(monitor => monitor.send(info));
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function jobFinished(builder, job)
|
|
225
|
-
{
|
|
226
|
-
++jobsFinished;
|
|
227
|
-
++builder.jobsPerformed;
|
|
228
|
-
builder.totalCompileSpeed += job.compileSpeed;
|
|
229
|
-
builder.totalUploadSpeed += job.uploadSpeed;
|
|
230
|
-
// console.log(`builder: ${builder.ip}:${builder.port} performed a job`, job);
|
|
231
|
-
if (monitors.length) {
|
|
232
|
-
const jobs = jobsFailed + jobsFinished + (objectCache ? objectCache.hits : 0);
|
|
233
|
-
const info = {
|
|
234
|
-
type: "jobFinished",
|
|
235
|
-
id: job.id,
|
|
236
|
-
cppSize: job.cppSize,
|
|
237
|
-
compileDuration: job.compileDuration,
|
|
238
|
-
uploadDuration: job.uploadDuration,
|
|
239
|
-
jobs: jobs,
|
|
240
|
-
jobsStarted: jobsStarted,
|
|
241
|
-
jobsFailed: jobsFailed,
|
|
242
|
-
jobsFinished: jobsFinished,
|
|
243
|
-
jobsScheduled: jobsScheduled,
|
|
244
|
-
cacheHits: objectCache ? objectCache.hits : 0
|
|
245
|
-
};
|
|
246
|
-
if (monitorsLog)
|
|
247
|
-
console.log("send to monitors", info);
|
|
248
|
-
monitors.forEach(monitor => monitor.send(info));
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function builderKey() {
|
|
253
|
-
if (arguments.length == 1) {
|
|
254
|
-
return arguments[0].ip + " " + arguments[0].port;
|
|
255
|
-
} else {
|
|
256
|
-
return arguments[0] + " " + arguments[1];
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function builderToMonitorInfo(builder, type)
|
|
261
|
-
{
|
|
262
|
-
return {
|
|
263
|
-
type: type,
|
|
264
|
-
ip: builder.ip,
|
|
265
|
-
name: builder.name,
|
|
266
|
-
hostname: builder.hostname,
|
|
267
|
-
slots: builder.slots,
|
|
268
|
-
port: builder.port,
|
|
269
|
-
jobsPerformed: builder.jobsPerformed,
|
|
270
|
-
compileSpeed: builder.jobsPerformed / builder.totalCompileSpeed || 0,
|
|
271
|
-
uploadSpeed: builder.jobsPerformed / builder.totalUploadSpeed || 0,
|
|
272
|
-
system: builder.system,
|
|
273
|
-
created: builder.created,
|
|
274
|
-
npmVersion: builder.npmVersion,
|
|
275
|
-
environments: Object.keys(builder.environments),
|
|
276
|
-
labels: builder.labels
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function insertBuilder(builder) {
|
|
281
|
-
builders[builderKey(builder)] = builder;
|
|
282
|
-
if (builder.name && builder.name in wolBuilders) {
|
|
283
|
-
wolBuilders[builder.name].connected = true;
|
|
284
|
-
}
|
|
285
|
-
++builderCount;
|
|
286
|
-
capacity += builder.slots;
|
|
287
|
-
if (monitors.length) {
|
|
288
|
-
const info = builderToMonitorInfo(builder, "builderAdded");
|
|
289
|
-
if (monitorsLog)
|
|
290
|
-
console.log("send to monitors", info);
|
|
291
|
-
monitors.forEach(monitor => {
|
|
292
|
-
monitor.send(info);
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function forEachBuilder(cb) {
|
|
298
|
-
for (let key in builders) {
|
|
299
|
-
cb(builders[key]);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function onObjectCacheCleared()
|
|
304
|
-
{
|
|
305
|
-
jobsFailed = 0;
|
|
306
|
-
jobsStarted = 0;
|
|
307
|
-
jobsScheduled = 0;
|
|
308
|
-
jobsFinished = 0;
|
|
309
|
-
const msg = { type: "clearObjectCache" };
|
|
310
|
-
forEachBuilder(builder => builder.send(msg));
|
|
311
|
-
let info = statsMessage();
|
|
312
|
-
monitors.forEach(monitor => monitor.send(info));
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function setObjectCacheEnabled(on)
|
|
316
|
-
{
|
|
317
|
-
if (on && !objectCache) {
|
|
318
|
-
objectCache = new ObjectCacheManager(option);
|
|
319
|
-
objectCache.on("cleared", onObjectCacheCleared);
|
|
320
|
-
server.objectCache = true;
|
|
321
|
-
} else if (!on && objectCache) {
|
|
322
|
-
objectCache.removeAllListeners();
|
|
323
|
-
objectCache = undefined;
|
|
324
|
-
server.objectCache = false;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (option("object-cache")) {
|
|
329
|
-
setObjectCacheEnabled(true);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function removeBuilder(builder) {
|
|
333
|
-
--builderCount;
|
|
334
|
-
capacity -= builder.slots;
|
|
335
|
-
delete builders[builderKey(builder)];
|
|
336
|
-
if (builder.name && builder.name in wolBuilders) {
|
|
337
|
-
lastWol = 0; // lets recussitate him right away!
|
|
338
|
-
wolBuilders[builder.name].connected = false;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (monitors.length) {
|
|
342
|
-
const info = builderToMonitorInfo(builder, "builderRemoved");
|
|
343
|
-
if (monitorsLog)
|
|
344
|
-
console.log("send to monitors", info);
|
|
345
|
-
monitors.forEach(monitor => {
|
|
346
|
-
monitor.send(info);
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function purgeEnvironmentsToMaxSize()
|
|
353
|
-
{
|
|
354
|
-
return new Promise((resolve, reject) => {
|
|
355
|
-
let maxSize = bytes.parse(option("max-environment-size"));
|
|
356
|
-
if (!maxSize) {
|
|
357
|
-
resolve(false);
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
const p = Environments._path;
|
|
361
|
-
try {
|
|
362
|
-
let purged = false;
|
|
363
|
-
fs.readdirSync(p).map(file => {
|
|
364
|
-
// console.log("got file", file);
|
|
365
|
-
const abs = path.join(p, file);
|
|
366
|
-
if (file.length != 47 || file.indexOf(".tar.gz", 40) != 40) {
|
|
367
|
-
try {
|
|
368
|
-
console.log("Removing unexpected file", abs);
|
|
369
|
-
fs.removeSync(abs);
|
|
370
|
-
} catch (err) {
|
|
371
|
-
console.error("Failed to remove file", abs, err);
|
|
372
|
-
}
|
|
373
|
-
return undefined;
|
|
374
|
-
}
|
|
375
|
-
let stat;
|
|
376
|
-
try {
|
|
377
|
-
stat = fs.statSync(abs);
|
|
378
|
-
} catch (err) {
|
|
379
|
-
return undefined;
|
|
380
|
-
}
|
|
381
|
-
return {
|
|
382
|
-
path: abs,
|
|
383
|
-
hash: file.substr(0, 40),
|
|
384
|
-
size: stat.size,
|
|
385
|
-
created: stat.birthtimeMs
|
|
386
|
-
};
|
|
387
|
-
}).sort((a, b) => {
|
|
388
|
-
// console.log(`comparing ${a.path} ${a.created} to ${b.path} ${b.created}`);
|
|
389
|
-
return b.created - a.created;
|
|
390
|
-
}).forEach(env => {
|
|
391
|
-
if (!env)
|
|
392
|
-
return;
|
|
393
|
-
if (maxSize >= env.size) {
|
|
394
|
-
maxSize -= env.size;
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
purged = true;
|
|
398
|
-
Environments.remove(env.hash);
|
|
399
|
-
console.log("Should purge env", env.hash, maxSize, env.size);
|
|
400
|
-
});
|
|
401
|
-
resolve(purged);
|
|
402
|
-
} catch (err) {
|
|
403
|
-
resolve(false);
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function syncEnvironments(builder)
|
|
410
|
-
{
|
|
411
|
-
if (!builder) {
|
|
412
|
-
forEachBuilder(syncEnvironments);
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
let needs = [];
|
|
416
|
-
let unwanted = [];
|
|
417
|
-
console.log("scheduler has", Object.keys(Environments.environments).sort());
|
|
418
|
-
console.log("builder has", builder.ip, Object.keys(builder.environments).sort());
|
|
419
|
-
for (let env in Environments.environments) {
|
|
420
|
-
if (env in builder.environments) {
|
|
421
|
-
builder.environments[env] = -1;
|
|
422
|
-
} else if (Environments.environments[env].canRun(builder.system)) {
|
|
423
|
-
needs.push(env);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
for (let env in builder.environments) {
|
|
427
|
-
if (builder.environments[env] != -1) {
|
|
428
|
-
unwanted.push(env);
|
|
429
|
-
delete builder.environments[env];
|
|
430
|
-
} else {
|
|
431
|
-
builder.environments[env] = true;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
console.log("unwanted", unwanted);
|
|
435
|
-
console.log("needs", needs);
|
|
436
|
-
if (unwanted.length) {
|
|
437
|
-
builder.send({ type: "dropEnvironments", environments: unwanted });
|
|
438
|
-
}
|
|
439
|
-
if (needs.length) {
|
|
440
|
-
builder.send({ type: "getEnvironments", environments: needs });
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
function environmentsInfo()
|
|
445
|
-
{
|
|
446
|
-
let ret = Object.assign({}, Environments.environments);
|
|
447
|
-
ret.maxSize = option("max-environment-size") || 0;
|
|
448
|
-
ret.maxSizeBytes = bytes.parse(option("max-environment-size")) || 0;
|
|
449
|
-
ret.usedSizeBytes = 0;
|
|
450
|
-
for (let hash in Environments.environments) {
|
|
451
|
-
let env = Environments.environments[hash];
|
|
452
|
-
if (env.size)
|
|
453
|
-
ret.usedSizeBytes += env.size;
|
|
454
|
-
}
|
|
455
|
-
ret.usedSize = bytes.format(ret.usedSizeBytes);
|
|
456
|
-
ret.links = Environments.linksInfo();
|
|
457
|
-
return ret;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
server.on("listen", app => {
|
|
461
|
-
app.get("/environments", (req, res, next) => {
|
|
462
|
-
const pretty = req.query && req.query.unpretty ? undefined : 4;
|
|
463
|
-
res.send(JSON.stringify(environmentsInfo(), null, pretty) + "\n");
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
app.get("/clear-log-files", (req, res, next) => {
|
|
467
|
-
clearLogFiles();
|
|
468
|
-
res.sendStatus(200);
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
app.get("/builders", (req, res, next) => {
|
|
472
|
-
let ret = [];
|
|
473
|
-
const now = Date.now();
|
|
474
|
-
for (let builderKey in builders) {
|
|
475
|
-
let s = builders[builderKey];
|
|
476
|
-
ret.push({
|
|
477
|
-
ip: s.ip,
|
|
478
|
-
name: s.name,
|
|
479
|
-
labels: s.labels,
|
|
480
|
-
slots: s.slots,
|
|
481
|
-
port: s.port,
|
|
482
|
-
activeClients: s.activeClients,
|
|
483
|
-
jobsScheduled: s.jobsScheduled,
|
|
484
|
-
lastJob: s.lastJob ? new Date(s.lastJob).toString() : "",
|
|
485
|
-
jobsPerformed: s.jobsPerformed,
|
|
486
|
-
compileSpeed: s.jobsPerformed / s.totalCompileSpeed || 0,
|
|
487
|
-
uploadSpeed: s.jobsPerformed / s.totalUploadSpeed || 0,
|
|
488
|
-
hostname: s.hostname,
|
|
489
|
-
system: s.system,
|
|
490
|
-
name: s.name,
|
|
491
|
-
created: s.created,
|
|
492
|
-
load: s.load,
|
|
493
|
-
uptime: now - s.created.valueOf(),
|
|
494
|
-
npmVersion: s.npmVersion,
|
|
495
|
-
environments: Object.keys(s.environments),
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
const pretty = req.query && req.query.unpretty ? undefined : 4;
|
|
499
|
-
res.send(JSON.stringify(ret, null, pretty) + "\n");
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
app.get("/info", (req, res, next) => {
|
|
503
|
-
const now = Date.now();
|
|
504
|
-
const jobs = jobsFailed + jobsStarted + (objectCache ? objectCache.hits : 0);
|
|
505
|
-
function percentage(count)
|
|
506
|
-
{
|
|
507
|
-
return { count: count, percentage: (count ? count * 100 / jobs : 0).toFixed(1) + "%" };
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
let obj = {
|
|
511
|
-
builderCount: Object.keys(builders).length,
|
|
512
|
-
npmVersion: schedulerNpmVersion,
|
|
513
|
-
environments: environmentsInfo(),
|
|
514
|
-
configVersion: common.Version,
|
|
515
|
-
capacity: capacity,
|
|
516
|
-
activeJobs: activeJobs,
|
|
517
|
-
peaks: peakData(),
|
|
518
|
-
jobsFailed: percentage(jobsFailed),
|
|
519
|
-
jobsStarted: jobsStarted,
|
|
520
|
-
jobs: jobs,
|
|
521
|
-
jobsScheduled: jobsScheduled,
|
|
522
|
-
jobsFinished: percentage(jobsFinished),
|
|
523
|
-
cacheHits: percentage(objectCache ? objectCache.hits : 0),
|
|
524
|
-
uptimeMS: now - serverStartTime,
|
|
525
|
-
uptime: humanizeDuration(now - serverStartTime),
|
|
526
|
-
serverStartTime: new Date(serverStartTime).toString(),
|
|
527
|
-
wolBuilders: wolBuilders
|
|
528
|
-
};
|
|
529
|
-
const pretty = req.query && req.query.unpretty ? undefined : 4;
|
|
530
|
-
res.send(JSON.stringify(obj, null, pretty) + "\n");
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
app.get("/objectcache", (req, res) => {
|
|
534
|
-
if ("on" in req.query) {
|
|
535
|
-
if (option("object-cache")) {
|
|
536
|
-
setObjectCacheEnabled(true);
|
|
537
|
-
res.sendStatus(200);
|
|
538
|
-
} else {
|
|
539
|
-
res.sendStatus(400);
|
|
540
|
-
}
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if ("off" in req.query) {
|
|
545
|
-
setObjectCacheEnabled(false);
|
|
546
|
-
res.sendStatus(200);
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
if (!objectCache) {
|
|
551
|
-
res.sendStatus(404);
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
if (req.query && "clear" in req.query) {
|
|
556
|
-
objectCache.clear();
|
|
557
|
-
res.sendStatus(200);
|
|
558
|
-
} else if (req.query && "distribute" in req.query) {
|
|
559
|
-
objectCache.distribute(req.query, res);
|
|
560
|
-
res.sendStatus(200);
|
|
561
|
-
} else {
|
|
562
|
-
const pretty = req.query && req.query.unpretty ? undefined : 4;
|
|
563
|
-
res.send(JSON.stringify(objectCache.dump(req.query || {}), null, pretty) + "\n");
|
|
564
|
-
}
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
app.get("/quit-builders", (req, res) => {
|
|
568
|
-
res.sendStatus(200);
|
|
569
|
-
const msg = {
|
|
570
|
-
type: "quit",
|
|
571
|
-
code: req.query.code || 0,
|
|
572
|
-
purgeEnvironments: "purge_environments" in req.query
|
|
573
|
-
};
|
|
574
|
-
console.log("Sending quit message to builders", msg, Object.keys(builders));
|
|
575
|
-
for (let ip in builders) {
|
|
576
|
-
builders[ip].send(msg);
|
|
577
|
-
}
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
app.get("/environment/*", function(req, res) {
|
|
581
|
-
const hash = req.path.substr(13);
|
|
582
|
-
const env = Environments.environment(hash);
|
|
583
|
-
console.log("got env request", hash, env);
|
|
584
|
-
if (!env) {
|
|
585
|
-
res.sendStatus(404);
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
const rstream = fs.createReadStream(env.path);
|
|
590
|
-
rstream.on("error", err => {
|
|
591
|
-
console.error("Got read stream error for", env.path, err);
|
|
592
|
-
rstream.close();
|
|
593
|
-
});
|
|
594
|
-
rstream.pipe(res);
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
app.get("/quit", (req, res) => {
|
|
598
|
-
console.log("quitting", req.query);
|
|
599
|
-
if ("purge_environments" in req.query) {
|
|
600
|
-
try {
|
|
601
|
-
fs.removeSync(path.join(common.cacheDir(), "environments"));
|
|
602
|
-
} catch (err) {
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
res.sendStatus(200);
|
|
606
|
-
setTimeout(() => process.exit(), 100);
|
|
607
|
-
});
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
function updateLogFilesToMonitors()
|
|
611
|
-
{
|
|
612
|
-
if (monitors.length) {
|
|
613
|
-
fs.readdir(logFileDir, (err, files) => {
|
|
614
|
-
if (files)
|
|
615
|
-
files = files.reverse();
|
|
616
|
-
const msg = { type: "logFiles", files: files || [] };
|
|
617
|
-
// console.log("sending files", msg);
|
|
618
|
-
monitors.forEach(monitor => monitor.send(msg));
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
function clearLogFiles()
|
|
624
|
-
{
|
|
625
|
-
fs.readdir(logFileDir, (err, files) => {
|
|
626
|
-
if (err) {
|
|
627
|
-
console.log("Got error removing log files", err);
|
|
628
|
-
return;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
for (const file of files) {
|
|
632
|
-
fs.unlink(path.join(logFileDir, file), err => {
|
|
633
|
-
if (err)
|
|
634
|
-
console.log("failed to remove file", path.join(logFileDir, file), err);
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
updateLogFilesToMonitors();
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
try {
|
|
642
|
-
fs.watch(logFileDir, (type, filename) => {
|
|
643
|
-
if (type == "rename") {
|
|
644
|
-
updateLogFilesToMonitors();
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
} catch (err) {
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
function formatDate(date)
|
|
651
|
-
{
|
|
652
|
-
let year = date.getFullYear(),
|
|
653
|
-
month = date.getMonth() + 1, // months are zero indexed
|
|
654
|
-
day = date.getDate(),
|
|
655
|
-
hour = date.getHours(),
|
|
656
|
-
minute = date.getMinutes(),
|
|
657
|
-
second = date.getSeconds();
|
|
658
|
-
|
|
659
|
-
if (month < 10)
|
|
660
|
-
month = "0" + month;
|
|
661
|
-
if (day < 10)
|
|
662
|
-
day = "0" + day;
|
|
663
|
-
if (hour < 10)
|
|
664
|
-
hour = "0" + hour;
|
|
665
|
-
if (minute < 10)
|
|
666
|
-
minute = "0" + minute;
|
|
667
|
-
if (second < 10)
|
|
668
|
-
second = "0" + second;
|
|
669
|
-
return `${year}_${month}_${day}_${hour}:${minute}:${second}`;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
function addLogFile(log, cb) {
|
|
673
|
-
try {
|
|
674
|
-
fs.writeFileSync(path.join(logFileDir, `${formatDate(new Date())} ${log.source} ${log.ip}`), log.contents, cb);
|
|
675
|
-
} catch (err) {
|
|
676
|
-
console.error(`Failed to write log file from ${log.ip}`, err);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
server.on("builder", builder => {
|
|
681
|
-
if (compareVersions(schedulerNpmVersion, builder.npmVersion) >= 1) {
|
|
682
|
-
console.log(`builder ${builder.ip} has bad npm version: ${builder.npmVersion} should have been at least: ${schedulerNpmVersion}`);
|
|
683
|
-
builder.send({ type: "version_mismatch", required_version: schedulerNpmVersion, code: 1 });
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
builder.activeClients = 0;
|
|
687
|
-
insertBuilder(builder);
|
|
688
|
-
console.log("builder connected", builder.npmVersion, builder.ip, builder.name || "", builder.hostname || "", Object.keys(builder.environments), "builderCount is", builderCount);
|
|
689
|
-
syncEnvironments(builder);
|
|
690
|
-
|
|
691
|
-
builder.on("environments", message => {
|
|
692
|
-
builder.environments = {};
|
|
693
|
-
message.environments.forEach(env => builder.environments[env] = true);
|
|
694
|
-
syncEnvironments(builder);
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
builder.on("log", event => {
|
|
698
|
-
addLogFile({ source: "builder", ip: builder.ip, contents: event.message });
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
builder.on("error", msg => {
|
|
702
|
-
console.error(`builder error '${msg}' from ${builder.ip}`);
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
builder.on("objectCache", msg => {
|
|
706
|
-
if (objectCache) {
|
|
707
|
-
objectCache.addNode(builder, msg);
|
|
708
|
-
}
|
|
709
|
-
});
|
|
710
|
-
|
|
711
|
-
builder.on("objectCacheAdded", msg => {
|
|
712
|
-
if (objectCache) {
|
|
713
|
-
objectCache.insert(msg, builder);
|
|
714
|
-
}
|
|
715
|
-
});
|
|
716
|
-
|
|
717
|
-
builder.on("objectCacheRemoved", msg => {
|
|
718
|
-
if (objectCache) {
|
|
719
|
-
objectCache.remove(msg, builder);
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
builder.on("close", () => {
|
|
724
|
-
removeBuilder(builder);
|
|
725
|
-
if (objectCache)
|
|
726
|
-
objectCache.removeNode(builder);
|
|
727
|
-
console.log(`builder disconnected ${builder.ip}:${builder.port} ${builder.name} ${builder.hostname} builderCount is ${builderCount}`);
|
|
728
|
-
builder.removeAllListeners();
|
|
729
|
-
});
|
|
730
|
-
|
|
731
|
-
builder.on("load", message => {
|
|
732
|
-
builder.load = message.measure;
|
|
733
|
-
// console.log(message);
|
|
734
|
-
});
|
|
735
|
-
|
|
736
|
-
builder.on("jobStarted", job => {
|
|
737
|
-
++jobsStarted;
|
|
738
|
-
jobStartedOrScheduled("jobStarted", job);
|
|
739
|
-
});
|
|
740
|
-
builder.on("jobFinished", job => jobFinished(builder, job));
|
|
741
|
-
builder.on("cacheHit", job => cacheHit(builder, job));
|
|
742
|
-
|
|
743
|
-
builder.on("jobAborted", job => {
|
|
744
|
-
console.log(`builder: ${builder.ip}:${builder.port} aborted a job`, job);
|
|
745
|
-
if (monitors.length) {
|
|
746
|
-
const info = {
|
|
747
|
-
type: "jobAborted",
|
|
748
|
-
id: job.id
|
|
749
|
-
};
|
|
750
|
-
|
|
751
|
-
monitors.forEach(monitor => monitor.send(info));
|
|
752
|
-
}
|
|
753
|
-
});
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
let pendingEnvironments = {};
|
|
757
|
-
function requestEnvironment(compile)
|
|
758
|
-
{
|
|
759
|
-
if (compile.environment in pendingEnvironments)
|
|
760
|
-
return false;
|
|
761
|
-
pendingEnvironments[compile.environment] = true;
|
|
762
|
-
|
|
763
|
-
console.log(`Asking ${compile.name} ${compile.ip} to upload ${compile.environment}`);
|
|
764
|
-
compile.send({ type: "needsEnvironment" });
|
|
765
|
-
|
|
766
|
-
let file;
|
|
767
|
-
let gotLast = false;
|
|
768
|
-
compile.on("uploadEnvironment", environment => {
|
|
769
|
-
file = Environments.prepare(environment);
|
|
770
|
-
console.log("Got environment message", environment, typeof file);
|
|
771
|
-
if (!file) {
|
|
772
|
-
// we already have this environment
|
|
773
|
-
console.error("already got environment", environment.message);
|
|
774
|
-
compile.send({ error: "already got environment" });
|
|
775
|
-
compile.close();
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
let hash = environment.hash;
|
|
779
|
-
compile.on("uploadEnvironmentData", environment => {
|
|
780
|
-
if (!file) {
|
|
781
|
-
console.error("no pending file");
|
|
782
|
-
compile.send({ error: "no pending file" });
|
|
783
|
-
compile.close();
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
if (environment.last) {
|
|
787
|
-
gotLast = true;
|
|
788
|
-
console.log("Got environmentdata message", environment.data.length, environment.last);
|
|
789
|
-
}
|
|
790
|
-
file.save(environment.data).then(() => {
|
|
791
|
-
if (environment.last) {
|
|
792
|
-
file.close();
|
|
793
|
-
compile.close();
|
|
794
|
-
return Environments.complete(file);
|
|
795
|
-
}
|
|
796
|
-
return undefined;
|
|
797
|
-
}).then(() => {
|
|
798
|
-
if (environment.last) {
|
|
799
|
-
file = undefined;
|
|
800
|
-
// send any new environments to builders
|
|
801
|
-
delete pendingEnvironments[hash];
|
|
802
|
-
return purgeEnvironmentsToMaxSize();
|
|
803
|
-
}
|
|
804
|
-
return undefined;
|
|
805
|
-
}).then(() => {
|
|
806
|
-
if (environment.last) {
|
|
807
|
-
syncEnvironments();
|
|
808
|
-
}
|
|
809
|
-
}).catch(error => {
|
|
810
|
-
console.error("Got some error here", error);
|
|
811
|
-
file = undefined;
|
|
812
|
-
});
|
|
813
|
-
});
|
|
814
|
-
});
|
|
815
|
-
compile.on("error", msg => {
|
|
816
|
-
console.error(`upload error '${msg}' from ${compile.ip}`);
|
|
817
|
-
if (file) {
|
|
818
|
-
file.discard();
|
|
819
|
-
file = undefined;
|
|
820
|
-
}
|
|
821
|
-
delete pendingEnvironments[compile.environment];
|
|
822
|
-
});
|
|
823
|
-
compile.once("close", () => {
|
|
824
|
-
if (file && !gotLast) {
|
|
825
|
-
console.log("compile with upload closed", compile.environment, "discarding");
|
|
826
|
-
file.discard();
|
|
827
|
-
file = undefined;
|
|
828
|
-
}
|
|
829
|
-
delete pendingEnvironments[compile.environment];
|
|
830
|
-
});
|
|
831
|
-
return true;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
server.on("clientVerify", clientVerify => {
|
|
835
|
-
if (compareVersions(clientMinimumVersion, clientVerify.npmVersion) >= 1) {
|
|
836
|
-
clientVerify.send("version_mismatch", { minimum_version: `${clientMinimumVersion}` });
|
|
837
|
-
} else {
|
|
838
|
-
clientVerify.send("version_verified", { minimum_version: `${clientMinimumVersion}` });
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
server.on("compile", compile => {
|
|
843
|
-
sendWols();
|
|
844
|
-
compile.on("log", event => {
|
|
845
|
-
addLogFile({ source: "client", ip: compile.ip, contents: event.message });
|
|
846
|
-
});
|
|
847
|
-
|
|
848
|
-
if (compareVersions(clientMinimumVersion, compile.npmVersion) >= 1) {
|
|
849
|
-
++jobsFailed;
|
|
850
|
-
compile.send("version_mismatch", { minimum_version: `${clientMinimumVersion}` });
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
// console.log("request", compile.hostname, compile.ip, compile.environment);
|
|
855
|
-
const usableEnvs = Environments.compatibleEnvironments(compile.environment);
|
|
856
|
-
if (!Environments.hasEnvironment(compile.environment) && requestEnvironment(compile)) {
|
|
857
|
-
++jobsFailed;
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
// console.log("compatible environments", usableEnvs);
|
|
861
|
-
|
|
862
|
-
if (!usableEnvs.length) {
|
|
863
|
-
console.log(`We're already waiting for ${compile.environment} and we don't have any compatible ones`);
|
|
864
|
-
compile.send("builder", {});
|
|
865
|
-
++jobsFailed;
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
function score(s) {
|
|
870
|
-
let available = Math.min(4, s.slots - s.activeClients);
|
|
871
|
-
return available * (1 - s.load);
|
|
872
|
-
}
|
|
873
|
-
let file;
|
|
874
|
-
let builder;
|
|
875
|
-
let bestScore;
|
|
876
|
-
let env;
|
|
877
|
-
let extraArgs;
|
|
878
|
-
let blacklistedArgs;
|
|
879
|
-
// console.log("got usableEnvs", usableEnvs);
|
|
880
|
-
// ### should have a function match(s) that checks for env, score and compile.builder etc
|
|
881
|
-
let foundInCache = false;
|
|
882
|
-
|
|
883
|
-
function filterBuilder(s)
|
|
884
|
-
{
|
|
885
|
-
if (compile.builder && compile.builder != s.ip && compile.builder != s.name)
|
|
886
|
-
return false;
|
|
887
|
-
|
|
888
|
-
if (compile.labels) {
|
|
889
|
-
for (let i=0; i<compile.labels.length; ++i) {
|
|
890
|
-
if (!s.labels || s.labels.indexOf(compile.labels[i]) === -1) {
|
|
891
|
-
return false;
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
return true;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
if (objectCache) {
|
|
899
|
-
let data = objectCache.get(compile.sha1);
|
|
900
|
-
if (data) {
|
|
901
|
-
data.nodes.forEach(s => {
|
|
902
|
-
if (!filterBuilder(s))
|
|
903
|
-
return;
|
|
904
|
-
const builderScore = score(s);
|
|
905
|
-
if (!builder || builderScore > bestScore || (builderScore == bestScore && builder.lastJob < s.lastJob)) {
|
|
906
|
-
bestScore = builderScore;
|
|
907
|
-
builder = s;
|
|
908
|
-
foundInCache = true;
|
|
909
|
-
}
|
|
910
|
-
});
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
if (!builder) {
|
|
914
|
-
forEachBuilder(s => {
|
|
915
|
-
if (!filterBuilder(s)) {
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
for (let i=0; i<usableEnvs.length; ++i) {
|
|
920
|
-
// console.log("checking builder", s.name, s.environments);
|
|
921
|
-
if (usableEnvs[i] in s.environments) {
|
|
922
|
-
const builderScore = score(s);
|
|
923
|
-
// console.log("comparing", builderScore, bestScore);
|
|
924
|
-
if (!builder || builderScore > bestScore || (builderScore == bestScore && s.lastJob < builder.lastJob)) {
|
|
925
|
-
bestScore = builderScore;
|
|
926
|
-
builder = s;
|
|
927
|
-
env = usableEnvs[i];
|
|
928
|
-
break;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
});
|
|
933
|
-
}
|
|
934
|
-
if (!builder) {
|
|
935
|
-
if (compile.builder) {
|
|
936
|
-
++jobsFailed;
|
|
937
|
-
console.log(`Specific builder "${compile.builder}" was requested and we couldn't find a builder with that ${compile.environment}`);
|
|
938
|
-
compile.send("builder", {});
|
|
939
|
-
return;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
if (compile.labels) {
|
|
943
|
-
++jobsFailed;
|
|
944
|
-
console.log(`Specific labels "${compile.labels}" were specified we couldn't match ${compile.environment} with any builder with those labels`);
|
|
945
|
-
compile.send("builder", {});
|
|
946
|
-
return;
|
|
947
|
-
}
|
|
948
|
-
++jobsFailed;
|
|
949
|
-
console.log("No builder for you", compile.ip);
|
|
950
|
-
compile.send("builder", {});
|
|
951
|
-
return;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
let data = {};
|
|
955
|
-
if (env != compile.environment) {
|
|
956
|
-
data.environment = env;
|
|
957
|
-
data.extraArgs = Environments.extraArgs(compile.environment, env);
|
|
958
|
-
}
|
|
959
|
-
++activeJobs;
|
|
960
|
-
let utilization = (activeJobs / capacity);
|
|
961
|
-
let peakInfo = false;
|
|
962
|
-
const now = Date.now();
|
|
963
|
-
peaks.forEach(peak => {
|
|
964
|
-
if (peak.record(now, activeJobs, utilization))
|
|
965
|
-
peakInfo = true;
|
|
966
|
-
});
|
|
967
|
-
if (peakInfo && monitors.length) {
|
|
968
|
-
let info = statsMessage();
|
|
969
|
-
monitors.forEach(monitor => monitor.send(info));
|
|
970
|
-
}
|
|
971
|
-
let sendTime = Date.now();
|
|
972
|
-
++builder.activeClients;
|
|
973
|
-
++builder.jobsScheduled;
|
|
974
|
-
console.log(`${compile.name} ${compile.ip} ${compile.sourceFile} was assigned to builder ${builder.ip} ${builder.port} ${builder.name} score: ${bestScore} objectCache: ${foundInCache}. `
|
|
975
|
-
+ `Builder has ${builder.activeClients} and performed ${builder.jobsScheduled} jobs. Total active jobs is ${activeJobs}`);
|
|
976
|
-
builder.lastJob = Date.now();
|
|
977
|
-
let id = nextJobId();
|
|
978
|
-
data.id = id;
|
|
979
|
-
data.ip = builder.ip;
|
|
980
|
-
data.hostname = builder.hostname;
|
|
981
|
-
data.port = builder.port;
|
|
982
|
-
compile.send("builder", data);
|
|
983
|
-
jobStartedOrScheduled("jobScheduled", { client: compile, builder: builder, id: id, sourceFile: compile.sourceFile });
|
|
984
|
-
++jobsScheduled;
|
|
985
|
-
compile.on("error", msg => {
|
|
986
|
-
if (builder) {
|
|
987
|
-
--builder.activeClients;
|
|
988
|
-
--activeJobs;
|
|
989
|
-
builder = undefined;
|
|
990
|
-
}
|
|
991
|
-
console.error(`compile error '${msg}' from ${compile.ip}`);
|
|
992
|
-
});
|
|
993
|
-
compile.on("close", event => {
|
|
994
|
-
// console.log("Client disappeared");
|
|
995
|
-
compile.removeAllListeners();
|
|
996
|
-
if (builder) {
|
|
997
|
-
--builder.activeClients;
|
|
998
|
-
--activeJobs;
|
|
999
|
-
builder = undefined;
|
|
1000
|
-
}
|
|
1001
|
-
});
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
function writeConfiguration(change)
|
|
1005
|
-
{
|
|
1006
|
-
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
function hash(password, salt)
|
|
1010
|
-
{
|
|
1011
|
-
return new Promise((resolve, reject) => {
|
|
1012
|
-
crypto.pbkdf2(password, salt, 12000, 256, "sha512", (err, hash) => {
|
|
1013
|
-
if (err) {
|
|
1014
|
-
reject(err);
|
|
1015
|
-
} else {
|
|
1016
|
-
resolve(hash);
|
|
1017
|
-
}
|
|
1018
|
-
});
|
|
1019
|
-
});
|
|
1020
|
-
};
|
|
1021
|
-
|
|
1022
|
-
function randomBytes(bytes)
|
|
1023
|
-
{
|
|
1024
|
-
return new Promise((resolve, reject) => {
|
|
1025
|
-
crypto.randomBytes(bytes, (err, result) => {
|
|
1026
|
-
if (err) {
|
|
1027
|
-
reject(`Failed to random bytes ${err}`);
|
|
1028
|
-
} else {
|
|
1029
|
-
resolve(result);
|
|
1030
|
-
}
|
|
1031
|
-
});
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
function sendInfoToClient(client)
|
|
1036
|
-
{
|
|
1037
|
-
forEachBuilder(builder => {
|
|
1038
|
-
const info = builderToMonitorInfo(builder, "builderAdded");
|
|
1039
|
-
if (monitorsLog)
|
|
1040
|
-
console.log("sending to monitor", info);
|
|
1041
|
-
client.send(info);
|
|
1042
|
-
});
|
|
1043
|
-
let info = statsMessage();
|
|
1044
|
-
if (monitorsLog)
|
|
1045
|
-
console.log("sending info to monitor", info);
|
|
1046
|
-
|
|
1047
|
-
client.send(info);
|
|
1048
|
-
|
|
1049
|
-
let scheduler = { version: schedulerNpmVersion, type: "schedulerInfo" };
|
|
1050
|
-
client.send(scheduler);
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
server.on("monitor", client => {
|
|
1054
|
-
if (monitorsLog)
|
|
1055
|
-
console.log("Got monitor", client.ip, client.hostname);
|
|
1056
|
-
monitors.push(client);
|
|
1057
|
-
function remove()
|
|
1058
|
-
{
|
|
1059
|
-
let idx = monitors.indexOf(client);
|
|
1060
|
-
if (idx != -1) {
|
|
1061
|
-
monitors.splice(idx, 1);
|
|
1062
|
-
}
|
|
1063
|
-
client.removeAllListeners();
|
|
1064
|
-
}
|
|
1065
|
-
let user;
|
|
1066
|
-
client.on("message", messageText => {
|
|
1067
|
-
if (monitorsLog)
|
|
1068
|
-
console.log("Got message from monitor", client.ip, client.hostname, messageText);
|
|
1069
|
-
let message;
|
|
1070
|
-
try {
|
|
1071
|
-
message = JSON.parse(messageText);
|
|
1072
|
-
} catch (err) {
|
|
1073
|
-
console.error(`Bad json message from monitor ${err.message}`);
|
|
1074
|
-
client.send({ success: false, error: `Bad message won't parse as JSON: ${err}` });
|
|
1075
|
-
client.close();
|
|
1076
|
-
return;
|
|
1077
|
-
}
|
|
1078
|
-
switch (message.type) {
|
|
1079
|
-
case "sendInfo":
|
|
1080
|
-
sendInfoToClient(client);
|
|
1081
|
-
break;
|
|
1082
|
-
case "clearLogFiles":
|
|
1083
|
-
clearLogFiles();
|
|
1084
|
-
updateLogFilesToMonitors();
|
|
1085
|
-
break;
|
|
1086
|
-
case "logFiles":
|
|
1087
|
-
// console.log("logFiles:", message);
|
|
1088
|
-
fs.readdir(logFileDir, (err, files) => {
|
|
1089
|
-
if (files)
|
|
1090
|
-
files = files.reverse();
|
|
1091
|
-
console.log("sending files", files);
|
|
1092
|
-
client.send({ type: "logFiles", files: files || [] });
|
|
1093
|
-
});
|
|
1094
|
-
break;
|
|
1095
|
-
case "logFile":
|
|
1096
|
-
// console.log("logFile:", message);
|
|
1097
|
-
if (message.file.indexOf("../") != -1 || message.file.indexOf("/..") != -1) {
|
|
1098
|
-
client.close();
|
|
1099
|
-
return;
|
|
1100
|
-
}
|
|
1101
|
-
const f = path.join(logFileDir, message.file);
|
|
1102
|
-
fs.readFile(f, "utf8", (err, contents) => {
|
|
1103
|
-
// console.log("sending file", f, contents.length);
|
|
1104
|
-
client.send({ type: "logFile", file: f, contents: contents || ""});
|
|
1105
|
-
});
|
|
1106
|
-
break;
|
|
1107
|
-
case "readConfiguration":
|
|
1108
|
-
break;
|
|
1109
|
-
case "writeConfiguration":
|
|
1110
|
-
if (!user) {
|
|
1111
|
-
client.send({ type: "writeConfiguration", success: false, "error": `Unauthenticated message: ${message.type}` });
|
|
1112
|
-
return;
|
|
1113
|
-
}
|
|
1114
|
-
writeConfiguration(message);
|
|
1115
|
-
break;
|
|
1116
|
-
case "listEnvironments":
|
|
1117
|
-
client.send({ type: "listEnvironments", environments: environmentsInfo() });
|
|
1118
|
-
break;
|
|
1119
|
-
case "linkEnvironments":
|
|
1120
|
-
Environments.link(message.srcHash, message.targetHash, message.arguments, message.blacklist).then(() => {
|
|
1121
|
-
const info = { type: "listEnvironments", environments: environmentsInfo() };
|
|
1122
|
-
monitors.forEach(monitor => monitor.send(info));
|
|
1123
|
-
});
|
|
1124
|
-
break;
|
|
1125
|
-
case "unlinkEnvironments":
|
|
1126
|
-
Environments.unlink(message.srcHash, message.targetHash).then(() => {
|
|
1127
|
-
const info = { type: "listEnvironments", environments: environmentsInfo() };
|
|
1128
|
-
monitors.forEach(monitor => monitor.send(info));
|
|
1129
|
-
});
|
|
1130
|
-
break;
|
|
1131
|
-
case "listUsers": {
|
|
1132
|
-
if (!user) {
|
|
1133
|
-
client.send({ type: "listUsers", success: false, "error": `Unauthenticated message: ${message.type}` });
|
|
1134
|
-
return;
|
|
1135
|
-
}
|
|
1136
|
-
db.get("users").then(users => {
|
|
1137
|
-
if (!users)
|
|
1138
|
-
users = {};
|
|
1139
|
-
client.send({ type: "listUsers", success: true, users: Object.keys(users) });
|
|
1140
|
-
}).catch(err => {
|
|
1141
|
-
console.error(`Something went wrong ${message.type} ${err.toString()} ${err.stack}`);
|
|
1142
|
-
client.send({ type: "listUsers", success: false, error: err.toString() });
|
|
1143
|
-
});
|
|
1144
|
-
break; }
|
|
1145
|
-
case "removeUser": {
|
|
1146
|
-
if (!user) {
|
|
1147
|
-
client.send({ type: "removeUser", success: false, "error": `Unauthenticated message: ${message.type}` });
|
|
1148
|
-
return;
|
|
1149
|
-
}
|
|
1150
|
-
if (!message.user) {
|
|
1151
|
-
client.send({ type: "removeUser", success: false, error: "Bad removeUser message" });
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
if (pendingUsers[message.user]) {
|
|
1156
|
-
client.send({ type: "removeUser", success: false, error: "Someone's here already" });
|
|
1157
|
-
return;
|
|
1158
|
-
}
|
|
1159
|
-
pendingUsers[message.user] = true;
|
|
1160
|
-
let users;
|
|
1161
|
-
db.get("users").then(users => {
|
|
1162
|
-
if (!users || !users[message.user]) {
|
|
1163
|
-
throw new Error(`user ${message.user} doesn't exist`);
|
|
1164
|
-
}
|
|
1165
|
-
delete users[message.user];
|
|
1166
|
-
return db.set("users", users);
|
|
1167
|
-
}).then(() => {
|
|
1168
|
-
client.send({ type: "removeUser", success: true, user: message.user });
|
|
1169
|
-
}).catch(err => {
|
|
1170
|
-
console.error(`Something went wrong ${message.type} ${err.toString()} ${err.stack}`);
|
|
1171
|
-
client.send({ type: "removeUser", success: false, error: err.toString() });
|
|
1172
|
-
}).finally(() => {
|
|
1173
|
-
delete pendingUsers[message.user];
|
|
1174
|
-
});
|
|
1175
|
-
|
|
1176
|
-
// console.log("gotta remove user", message);
|
|
1177
|
-
break; }
|
|
1178
|
-
case "login": {
|
|
1179
|
-
user = undefined;
|
|
1180
|
-
if (!message.user || (!message.password && !message.hmac)) {
|
|
1181
|
-
client.send({ type: "login", success: false, error: "Bad login message" });
|
|
1182
|
-
return;
|
|
1183
|
-
}
|
|
1184
|
-
let users;
|
|
1185
|
-
db.get("users").then(u => {
|
|
1186
|
-
users = u || {};
|
|
1187
|
-
if (!users[message.user]) {
|
|
1188
|
-
throw new Error(`User: ${message.user} does not seem to exist`);
|
|
1189
|
-
}
|
|
1190
|
-
if (message.hmac) {
|
|
1191
|
-
if (!users[message.user].cookie) {
|
|
1192
|
-
throw new Error("No cookie");
|
|
1193
|
-
} else if (users[message.user].cookieIp != client.ip) {
|
|
1194
|
-
throw new Error("Wrong ip address");
|
|
1195
|
-
} else if (users[message.user].cookieExpiration <= Date.now()) {
|
|
1196
|
-
throw new Error("Cookie expired");
|
|
1197
|
-
} else {
|
|
1198
|
-
const hmac = crypto.createHmac("sha512", Buffer.from(users[message.user].cookie, "base64"));
|
|
1199
|
-
hmac.write(client.nonce);
|
|
1200
|
-
hmac.end();
|
|
1201
|
-
const hmacString = hmac.read().toString("base64");
|
|
1202
|
-
if (hmacString != message.hmac) {
|
|
1203
|
-
throw new Error(`Wrong password ${message.user}`);
|
|
1204
|
-
};
|
|
1205
|
-
return undefined;
|
|
1206
|
-
}
|
|
1207
|
-
} else {
|
|
1208
|
-
return hash(message.password, Buffer.from(users[message.user].salt, "base64")).then(hash => {
|
|
1209
|
-
if (users[message.user].hash != hash.toString("base64")) {
|
|
1210
|
-
throw new Error(`Wrong password ${message.user}`);
|
|
1211
|
-
}
|
|
1212
|
-
});
|
|
1213
|
-
}
|
|
1214
|
-
}).then(() => {
|
|
1215
|
-
return randomBytes(256);
|
|
1216
|
-
}).then(cookie => {
|
|
1217
|
-
user = message.user;
|
|
1218
|
-
const expiration = new Date(Date.now() + 12096e5);
|
|
1219
|
-
users[message.user].cookie = cookie.toString("base64");
|
|
1220
|
-
users[message.user].cookieIp = client.ip;
|
|
1221
|
-
users[message.user].cookieExpiration = expiration.valueOf();
|
|
1222
|
-
return db.set("users", users);
|
|
1223
|
-
}).then(() => {
|
|
1224
|
-
client.send({ type: "login", success: true, user: message.user, cookie: users[message.user].cookie });
|
|
1225
|
-
}).catch(err => {
|
|
1226
|
-
console.error(`Something went wrong ${message.type} ${err.toString()} ${err.stack}`);
|
|
1227
|
-
client.send({ type: "login", success: false, error: err.toString() });
|
|
1228
|
-
});
|
|
1229
|
-
break; }
|
|
1230
|
-
case "addUser": {
|
|
1231
|
-
if (!message.user || !message.password) {
|
|
1232
|
-
client.send({ type: "addUser", success: false, error: "Bad addUser message" });
|
|
1233
|
-
return;
|
|
1234
|
-
}
|
|
1235
|
-
if (pendingUsers[message.user]) {
|
|
1236
|
-
client.send({ type: "addUser", success: false, error: "Someone's here already" });
|
|
1237
|
-
return;
|
|
1238
|
-
}
|
|
1239
|
-
pendingUsers[message.user] = true;
|
|
1240
|
-
let users;
|
|
1241
|
-
db.get("users").then(u => {
|
|
1242
|
-
users = u || {};
|
|
1243
|
-
if (users[message.user]) {
|
|
1244
|
-
throw new Error(`user ${message.user} already exists`);
|
|
1245
|
-
}
|
|
1246
|
-
return randomBytes(256);
|
|
1247
|
-
}).then(salt => {
|
|
1248
|
-
users[message.user] = { salt: salt.toString("base64") };
|
|
1249
|
-
return hash(message.password, salt);
|
|
1250
|
-
}).then(hash => {
|
|
1251
|
-
users[message.user].hash = hash.toString("base64");
|
|
1252
|
-
return randomBytes(256);
|
|
1253
|
-
}).then(cookie => {
|
|
1254
|
-
users[message.user].cookie = cookie.toString("base64");
|
|
1255
|
-
users[message.user].cookieExpiration = (Date.now() + 12096e5);
|
|
1256
|
-
users[message.user].cookieIp = client.ip;
|
|
1257
|
-
return db.set("users", users);
|
|
1258
|
-
}).then(() => {
|
|
1259
|
-
// console.log("here", values);
|
|
1260
|
-
// values = [1,2];
|
|
1261
|
-
client.send({ type: "addUser",
|
|
1262
|
-
success: true,
|
|
1263
|
-
user: message.user,
|
|
1264
|
-
cookie: users[message.user].cookie });
|
|
1265
|
-
}).catch(err => {
|
|
1266
|
-
console.error(`Something went wrong ${message.type} ${err.toString()} ${err.stack}`);
|
|
1267
|
-
client.send({ type: "addUser", success: false, error: err.toString() });
|
|
1268
|
-
}).finally(() => {
|
|
1269
|
-
delete pendingUsers[message.user];
|
|
1270
|
-
});
|
|
1271
|
-
|
|
1272
|
-
// console.log("gotta add user", message);
|
|
1273
|
-
break; }
|
|
1274
|
-
}
|
|
1275
|
-
});
|
|
1276
|
-
client.on("close", remove);
|
|
1277
|
-
client.on("error", remove);
|
|
1278
|
-
});
|
|
1279
|
-
|
|
1280
|
-
server.on("error", err => {
|
|
1281
|
-
console.error(`error '${err.message}' from ${err.ip}`);
|
|
1282
|
-
});
|
|
1283
|
-
|
|
1284
|
-
function simulate(count)
|
|
1285
|
-
{
|
|
1286
|
-
let usedIps = {};
|
|
1287
|
-
function randomIp(transient)
|
|
1288
|
-
{
|
|
1289
|
-
let ip;
|
|
1290
|
-
do {
|
|
1291
|
-
ip = [ parseInt(Math.random() * 256), parseInt(Math.random() * 256), parseInt(Math.random() * 256), parseInt(Math.random() * 256) ].join(".");
|
|
1292
|
-
} while (ip in usedIps);
|
|
1293
|
-
if (!transient)
|
|
1294
|
-
usedIps[ip] = true;
|
|
1295
|
-
return ip;
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
function randomWord()
|
|
1299
|
-
{
|
|
1300
|
-
if (!words) {
|
|
1301
|
-
words = fs.readFileSync("/etc/dictionaries-common/words", "utf8").split("\n").filter(x => x);
|
|
1302
|
-
}
|
|
1303
|
-
const idx = Math.floor(Math.random() * words.length);
|
|
1304
|
-
return words[idx];
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
let fakeBuilders = [];
|
|
1308
|
-
let jobs = [];
|
|
1309
|
-
for (let i=0; i<count; ++i) {
|
|
1310
|
-
const ip = randomIp();
|
|
1311
|
-
const fakeBuilder = {
|
|
1312
|
-
ip: ip,
|
|
1313
|
-
name: randomWord(),
|
|
1314
|
-
hostname: randomWord(),
|
|
1315
|
-
slots: [4, 16, 32][parseInt(Math.random() * 3)],
|
|
1316
|
-
port: 8097,
|
|
1317
|
-
jobsPerformed: 0,
|
|
1318
|
-
compileSpeed: 0,
|
|
1319
|
-
uploadSpeed: 0,
|
|
1320
|
-
system: "Linux x86_64",
|
|
1321
|
-
created: new Date(),
|
|
1322
|
-
npmVersion: schedulerNpmVersion,
|
|
1323
|
-
environments: Object.keys(Environments.environments)
|
|
1324
|
-
};
|
|
1325
|
-
for (let j=0; j<fakeBuilder.slots; ++j) {
|
|
1326
|
-
jobs.push({ builder: fakeBuilder });
|
|
1327
|
-
}
|
|
1328
|
-
fakeBuilders.push(fakeBuilder);
|
|
1329
|
-
insertBuilder(fakeBuilder);
|
|
1330
|
-
}
|
|
1331
|
-
const clients = [];
|
|
1332
|
-
const clientCount = count / 2 || 1;
|
|
1333
|
-
for (let i=0; i<clientCount; ++i) {
|
|
1334
|
-
clients[i] = { hostname: randomWord(), ip: randomIp(true), name: randomWord() };
|
|
1335
|
-
}
|
|
1336
|
-
function tick()
|
|
1337
|
-
{
|
|
1338
|
-
for (let i=0; i<jobs.length; ++i) {
|
|
1339
|
-
const percentage = Math.random() * 100;
|
|
1340
|
-
if (jobs[i].builder.gone) {
|
|
1341
|
-
if (percentage <= 10) {
|
|
1342
|
-
jobs[i].builder.gone = false;
|
|
1343
|
-
insertBuilder(jobs[i].builder);
|
|
1344
|
-
} else {
|
|
1345
|
-
continue;
|
|
1346
|
-
}
|
|
1347
|
-
} else if (percentage <= 1) {
|
|
1348
|
-
jobs[i].builder.gone = true;
|
|
1349
|
-
removeBuilder(jobs[i].builder);
|
|
1350
|
-
while (jobs[i + 1] && jobs[i + 1].builder == jobs[i].builder) {
|
|
1351
|
-
++i;
|
|
1352
|
-
}
|
|
1353
|
-
continue;
|
|
1354
|
-
}
|
|
1355
|
-
if (percentage <= 30) {
|
|
1356
|
-
if (!jobs[i].client) {
|
|
1357
|
-
jobs[i].client = clients[parseInt(Math.random() * clientCount)];
|
|
1358
|
-
jobs[i].id = nextJobId();
|
|
1359
|
-
jobStartedOrScheduled("jobScheduled", { client: jobs[i].client, builder: jobs[i].builder, id: jobs[i].id, sourceFile: randomWord() + ".cpp" });
|
|
1360
|
-
jobStartedOrScheduled("jobStarted", { client: jobs[i].client, builder: jobs[i].builder, id: jobs[i].id, sourceFile: randomWord() + ".cpp" });
|
|
1361
|
-
} else {
|
|
1362
|
-
// const client = jobs[i].client;
|
|
1363
|
-
// const id = jobs[i].id;
|
|
1364
|
-
jobFinished(jobs[i].builder, { id: jobs[i].id,
|
|
1365
|
-
cppSize: parseInt(Math.random() * 1024 * 1024 * 4),
|
|
1366
|
-
compileDuration: parseInt(Math.random() * 5000),
|
|
1367
|
-
uploadDuration: parseInt(Math.random() * 500) });
|
|
1368
|
-
delete jobs[i].client;
|
|
1369
|
-
delete jobs[i].id;
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
setTimeout(tick, Math.random() * 2000);
|
|
1374
|
-
}
|
|
1375
|
-
tick();
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
Environments.load(db, option("env-dir", path.join(common.cacheDir(), "environments")))
|
|
1379
|
-
.then(() => {
|
|
1380
|
-
const limit = option.int("max-file-descriptors");
|
|
1381
|
-
if (limit) {
|
|
1382
|
-
console.log("setting limit", limit);
|
|
1383
|
-
posix.setrlimit("nofile", { soft: limit });
|
|
1384
|
-
}
|
|
1385
|
-
})
|
|
1386
|
-
.then(purgeEnvironmentsToMaxSize)
|
|
1387
|
-
// .then(() => {
|
|
1388
|
-
// return db.get("users");
|
|
1389
|
-
// }).then(u => {
|
|
1390
|
-
// console.log("got users", u);
|
|
1391
|
-
// users = u || {};
|
|
1392
|
-
// })
|
|
1393
|
-
.then(() => server.listen())
|
|
1394
|
-
.then(() => {
|
|
1395
|
-
const simulateCount = option("simulate");
|
|
1396
|
-
if (simulateCount) {
|
|
1397
|
-
simulate(parseInt(simulateCount) || 64);
|
|
1398
|
-
} else {
|
|
1399
|
-
setInterval(() => {
|
|
1400
|
-
// console.log("sending pings");
|
|
1401
|
-
for (let key in builders) {
|
|
1402
|
-
const builder = builders[key];
|
|
1403
|
-
builder.ping();
|
|
1404
|
-
}
|
|
1405
|
-
}, option.int("ping-interval", 20000));
|
|
1406
|
-
|
|
1407
|
-
}
|
|
1408
|
-
}).catch(e => {
|
|
1409
|
-
console.error(e);
|
|
1410
|
-
process.exit();
|
|
1411
|
-
});
|