93 lines
2.9 KiB
TypeScript
93 lines
2.9 KiB
TypeScript
|
|
import { UptimeRunnerApiClient } from './api-client.ts';
|
||
|
|
import { CheckExecutor } from './check-executor.ts';
|
||
|
|
import type {
|
||
|
|
IRunOnceResult,
|
||
|
|
IUptimeCheckResult,
|
||
|
|
IUptimeRunnerConfig,
|
||
|
|
TCheckJob,
|
||
|
|
} from './interfaces.ts';
|
||
|
|
import denoConfig from '../deno.json' with { type: 'json' };
|
||
|
|
|
||
|
|
export class UptimeRunner {
|
||
|
|
private readonly apiClient: UptimeRunnerApiClient;
|
||
|
|
private readonly checkExecutor: CheckExecutor;
|
||
|
|
private running = false;
|
||
|
|
|
||
|
|
constructor(private readonly config: IUptimeRunnerConfig, apiClientArg?: UptimeRunnerApiClient) {
|
||
|
|
this.apiClient = apiClientArg ?? new UptimeRunnerApiClient(config);
|
||
|
|
this.checkExecutor = new CheckExecutor(config.runnerId);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async run(): Promise<void> {
|
||
|
|
this.running = true;
|
||
|
|
console.log(`uptimerunner ${this.config.runnerId} connected to ${this.config.instanceUrl}`);
|
||
|
|
|
||
|
|
while (this.running) {
|
||
|
|
const started = Date.now();
|
||
|
|
try {
|
||
|
|
await this.heartbeat().catch((error) => {
|
||
|
|
console.warn(
|
||
|
|
`heartbeat failed: ${error instanceof Error ? error.message : String(error)}`,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
const result = await this.runOnce();
|
||
|
|
if (result.results.length > 0) {
|
||
|
|
console.log(`reported ${result.results.length} check result(s)`);
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error(
|
||
|
|
`runner iteration failed: ${error instanceof Error ? error.message : String(error)}`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const elapsed = Date.now() - started;
|
||
|
|
const delayMs = Math.max((this.config.pollIntervalMs ?? 30000) - elapsed, 1000);
|
||
|
|
await delayFor(delayMs);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public stop(): void {
|
||
|
|
this.running = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async runOnce(): Promise<IRunOnceResult> {
|
||
|
|
const checks = await this.apiClient.fetchAssignedChecks(
|
||
|
|
this.config.runnerId,
|
||
|
|
this.config.labels ?? [],
|
||
|
|
);
|
||
|
|
const results = await this.executeChecks(checks);
|
||
|
|
await this.apiClient.submitResults(this.config.runnerId, results);
|
||
|
|
return { checks, results };
|
||
|
|
}
|
||
|
|
|
||
|
|
private async heartbeat(): Promise<void> {
|
||
|
|
await this.apiClient.heartbeat({
|
||
|
|
runnerId: this.config.runnerId,
|
||
|
|
labels: this.config.labels,
|
||
|
|
version: denoConfig.version,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
private async executeChecks(checksArg: TCheckJob[]): Promise<IUptimeCheckResult[]> {
|
||
|
|
const maxConcurrentChecks = this.config.maxConcurrentChecks ?? 8;
|
||
|
|
const results: IUptimeCheckResult[] = [];
|
||
|
|
let nextIndex = 0;
|
||
|
|
|
||
|
|
const worker = async () => {
|
||
|
|
while (nextIndex < checksArg.length) {
|
||
|
|
const currentIndex = nextIndex++;
|
||
|
|
results[currentIndex] = await this.checkExecutor.execute(checksArg[currentIndex]);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
await Promise.all(
|
||
|
|
Array.from({ length: Math.min(maxConcurrentChecks, checksArg.length) }, () => worker()),
|
||
|
|
);
|
||
|
|
return results;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function delayFor(millisecondsArg: number): Promise<void> {
|
||
|
|
await new Promise((resolve) => setTimeout(resolve, millisecondsArg));
|
||
|
|
}
|