265 lines
7.2 KiB
TypeScript
265 lines
7.2 KiB
TypeScript
import * as client from "prom-client";
|
|
import * as yaml from "yaml";
|
|
import { readFileSync, watchFile } from "fs";
|
|
import * as net from "net";
|
|
import * as ping from "ping";
|
|
import * as Traceroute from "nodejs-traceroute";
|
|
import * as argparse from "argparse";
|
|
|
|
import { strict as assert } from "assert";
|
|
|
|
const argp = new argparse.ArgumentParser();
|
|
|
|
argp.addArgument(["-c", "--config"], {
|
|
help: "Path of the config file",
|
|
type: String,
|
|
required: true,
|
|
dest: "config",
|
|
});
|
|
|
|
argp.addArgument(["-p", "--port"], {
|
|
help: "The port to listen at",
|
|
type: Number,
|
|
defaultValue: 9285,
|
|
dest: "port",
|
|
});
|
|
|
|
const args: {
|
|
config: string;
|
|
port: number;
|
|
} = argp.parseArgs();
|
|
|
|
async function testPing(host: string) {
|
|
let probe = await ping.promise.probe(host, {
|
|
timeout: 5,
|
|
});
|
|
|
|
if (!probe.alive) return -1;
|
|
|
|
return Number(probe.avg);
|
|
}
|
|
|
|
async function testTraceroute(host: string) {
|
|
const tracer = new Traceroute();
|
|
return new Promise((yes, no) => {
|
|
console.log("Tracepath to:", host);
|
|
tracer
|
|
.on("pid", (pid) => {
|
|
console.log(`pid: ${pid}`);
|
|
})
|
|
.on("destination", (destination) => {
|
|
console.log(`destination: ${destination}`);
|
|
})
|
|
.on("hop", (hop) => {
|
|
console.log(`hop: ${JSON.stringify(hop)}`);
|
|
})
|
|
.on("close", (code) => {
|
|
console.log(`close: code ${code}`);
|
|
})
|
|
.on("error", no);
|
|
|
|
tracer.trace("github.com");
|
|
});
|
|
}
|
|
|
|
async function testTCP(host: string, port: number) {
|
|
return new Promise((yes, no) => {
|
|
console.log("Checking TCP", host, port);
|
|
let socket = net.connect(port, host);
|
|
|
|
socket.on("error", () => {
|
|
yes(false);
|
|
socket.destroy();
|
|
});
|
|
socket.on("connect", () => {
|
|
yes(true);
|
|
socket.destroy();
|
|
});
|
|
});
|
|
}
|
|
|
|
let getMetrics: () => Promise<string>;
|
|
|
|
type IConfig = {
|
|
targets: {
|
|
host: string;
|
|
icmp: boolean;
|
|
traceroute: boolean;
|
|
tcp: (number | { port: number })[];
|
|
}[];
|
|
};
|
|
|
|
function load() {
|
|
let config: IConfig;
|
|
try {
|
|
console.log("Loading: ", args.config);
|
|
config = yaml.parse(readFileSync(args.config, "utf8"));
|
|
|
|
// Verify config
|
|
|
|
assert(typeof config === "object", "Config is invalid!");
|
|
assert(typeof config.targets !== "undefined", "Field targets required!");
|
|
assert(Array.isArray(config.targets), "Field targets must be an array");
|
|
config.targets.forEach((target, idx) => {
|
|
assert(typeof target === "object", "Each target must be an object!");
|
|
assert(
|
|
typeof target.host === "string",
|
|
`The host must be a string in the ${idx} target`
|
|
);
|
|
assert(
|
|
typeof target.icmp === "undefined" ||
|
|
typeof target.icmp === "boolean",
|
|
`Field icmp must be undefined or boolean in target ${target.host}`
|
|
);
|
|
assert(
|
|
typeof target.traceroute === "undefined" ||
|
|
typeof target.traceroute === "boolean",
|
|
`Field traceroute must be undefined or boolean in target ${target.host}`
|
|
);
|
|
|
|
assert(
|
|
typeof target.tcp === "undefined" || Array.isArray(target.tcp),
|
|
`Field tcp must be undefined or array in target ${target.host}`
|
|
);
|
|
|
|
if (target.tcp) {
|
|
target.tcp.forEach((tcp) => {
|
|
assert(
|
|
typeof tcp === "number" || typeof tcp === "object",
|
|
"Each entry in TCP has to be a number or an object"
|
|
);
|
|
|
|
if (typeof tcp === "object") {
|
|
assert(
|
|
typeof tcp.port === "number",
|
|
"The port in TCP is required!"
|
|
);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
} catch (err) {
|
|
console.log(err);
|
|
return;
|
|
}
|
|
console.log("Config verified. Putting in production");
|
|
// Enable config
|
|
|
|
const reg = new client.Registry();
|
|
|
|
const reachable = new client.Gauge({
|
|
name: "connectivity_ping_reachable",
|
|
help: "Checks if the target is pingable by this host",
|
|
labelNames: ["target"],
|
|
registers: [reg],
|
|
});
|
|
|
|
const pingTimes = new client.Gauge({
|
|
name: "connectivity_ping_time",
|
|
help: "Contains the current ping time from this host to target",
|
|
labelNames: ["target"],
|
|
registers: [reg],
|
|
});
|
|
|
|
const hopCount = new client.Gauge({
|
|
name: "connectivity_hops_count",
|
|
help: "The number of hops between this host and the target",
|
|
labelNames: ["target"],
|
|
registers: [reg],
|
|
});
|
|
|
|
const tcpCheck = new client.Gauge({
|
|
name: "connectivity_tcp_available",
|
|
help: "Checks if connectivity to specified tcp port is available",
|
|
labelNames: ["target", "port"],
|
|
registers: [reg],
|
|
});
|
|
|
|
getMetrics = async () => {
|
|
console.log("Running");
|
|
await Promise.all(
|
|
config.targets.map(async (target) => {
|
|
let jobs = [];
|
|
if (target.icmp) {
|
|
jobs.push(
|
|
testPing(target.host).then((ping) => {
|
|
reachable.set(
|
|
{
|
|
target: target.host,
|
|
},
|
|
ping >= 0 ? 1 : 0
|
|
);
|
|
|
|
pingTimes.set(
|
|
{
|
|
target: target.host,
|
|
},
|
|
ping as any
|
|
);
|
|
})
|
|
);
|
|
}
|
|
|
|
if (target.traceroute && false) {
|
|
//Currently not working
|
|
jobs.push(testTraceroute(target.host).then(() => {}));
|
|
}
|
|
|
|
if (target.tcp) {
|
|
jobs.push(
|
|
...target.tcp.map((check) =>
|
|
testTCP(
|
|
target.host,
|
|
typeof check === "number" ? check : check.port
|
|
).then((avail) => {
|
|
tcpCheck.set(
|
|
{
|
|
target: target.host,
|
|
port:
|
|
typeof check === "number" ? check : check.port,
|
|
},
|
|
avail ? 1 : 0
|
|
);
|
|
})
|
|
)
|
|
);
|
|
}
|
|
|
|
return Promise.all(jobs);
|
|
})
|
|
);
|
|
return reg.metrics();
|
|
};
|
|
}
|
|
|
|
process.on("SIGHUP", () => load());
|
|
watchFile(args.config, load);
|
|
load();
|
|
|
|
import * as http from "http";
|
|
import { URL } from "url";
|
|
|
|
http
|
|
.createServer(async (req, res) => {
|
|
const url = new URL(req.url, "http://127.0.0.1");
|
|
if (url.pathname == "/metrics") {
|
|
if (getMetrics)
|
|
getMetrics()
|
|
.then((data) => res.end(data))
|
|
.catch((err) => {
|
|
console.log("Error", err);
|
|
res.statusCode = 400;
|
|
res.end("Something went wrong!");
|
|
});
|
|
else {
|
|
res.statusCode = 500;
|
|
res.end("Not ready!");
|
|
}
|
|
} else {
|
|
res.end("<a href='/metrics'>Metrics</a>");
|
|
}
|
|
})
|
|
.listen(args.port, () => {
|
|
console.log("Listening at:", args.port);
|
|
});
|