First commit
This commit is contained in:
commit
65068b7c4d
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
root=true
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 3
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yaml]
|
||||
indent_size = 2
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
lib/
|
||||
connectivity-exporter
|
14
example.config.yaml
Normal file
14
example.config.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
targets:
|
||||
- host: 127.0.0.1
|
||||
icmp: true
|
||||
tcp:
|
||||
- 1234
|
||||
- host: 1.1.1.1
|
||||
traceroute: true
|
||||
icmp: true
|
||||
tcp:
|
||||
- 53
|
||||
- host: 8.8.8.8
|
||||
icmp: true
|
||||
tcp:
|
||||
- 53
|
1983
package-lock.json
generated
Normal file
1983
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
package.json
Normal file
30
package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@hibas123/connectivity-exporter",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "lib/index.js",
|
||||
"bin": "lib/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc && pkg -t node12-linux .",
|
||||
"dev": "nodemon -e ts --exec ts-node src/index.ts -- --config example.config.yaml"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^13.13.4",
|
||||
"@types/ping": "^0.2.0",
|
||||
"nodemon": "^2.0.3",
|
||||
"pkg": "^4.4.8",
|
||||
"ts-node": "^8.9.1",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
"argparse": "^1.0.10",
|
||||
"nodejs-traceroute": "^1.2.0",
|
||||
"ping": "^0.2.3",
|
||||
"prom-client": "^12.0.0",
|
||||
"yaml": "^1.9.2"
|
||||
}
|
||||
}
|
264
src/index.ts
Normal file
264
src/index.ts
Normal file
@ -0,0 +1,264 @@
|
||||
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);
|
||||
});
|
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES2020",
|
||||
"outDir": "lib/"
|
||||
},
|
||||
"include": ["src/"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user