| {% |
| 'use strict'; |
| |
| import * as fs from "fs"; |
| import { connect } from "ubus"; |
| import { cursor } from "uci"; |
| |
| function debug(...s) { |
| if (global.debug) |
| warn("DEBUG: ", ...s, "\n"); |
| } |
| |
| function puts(...s) { |
| return uhttpd.send(...s, "\n"); |
| } |
| |
| function govalue(value) { |
| if (value == Infinity) |
| return "+Inf"; |
| else if (value == -Infinity) |
| return "-Inf"; |
| else if (value != value) |
| return "NaN"; |
| else if (type(value) in [ "int", "double" ]) |
| return value; |
| else if (type(value) in [ "bool", "string" ]) |
| return +value; |
| |
| return null; |
| } |
| |
| function metric(name, mtype, help, skipdecl) { |
| let func; |
| let decl = skipdecl == true ? false : true; |
| |
| let yield = function(labels, value) { |
| let v = govalue(value); |
| |
| if (v == null) { |
| debug(`skipping metric: unsupported value '${value}' (${name})`); |
| return func; |
| } |
| |
| let labels_str = ""; |
| if (length(labels)) { |
| let sep = ""; |
| let s; |
| labels_str = "{"; |
| for (let l in labels) { |
| if (labels[l] == null) |
| s = ""; |
| else if (type(labels[l]) == "string") { |
| s = labels[l]; |
| s = replace(labels[l], "\\", "\\\\"); |
| s = replace(s, "\"", "\\\""); |
| s = replace(s, "\n", "\\n"); |
| } else { |
| s = govalue(labels[l]); |
| |
| if (!s) |
| continue; |
| } |
| |
| labels_str += sep + l + "=\"" + s + "\""; |
| sep = ","; |
| } |
| labels_str += "}"; |
| } |
| |
| if (decl) { |
| if (help) |
| puts("# HELP ", name, " ", help); |
| puts("# TYPE ", name, " ", mtype); |
| decl = false; |
| } |
| |
| puts(name, labels_str, " ", v); |
| return func; |
| }; |
| |
| func = yield; |
| return func; |
| } |
| |
| function counter(name, help, skipdecl) { |
| return metric(name, "counter", help, skipdecl); |
| } |
| |
| function gauge(name, help, skipdecl) { |
| return metric(name, "gauge", help, skipdecl); |
| } |
| |
| function httpstatus(status) { |
| puts("Status: ", status, "\nContent-Type: text/plain; version=0.0.4; charset=utf-8\n"); |
| } |
| |
| function clockdiff(t1, t2) { |
| return (t2[0] - t1[0]) * 1000000000 + t2[1] - t1[1]; |
| } |
| |
| let collectors = {}; |
| |
| global.handle_request = function(env) { |
| let scope = { |
| config: null, |
| fs, |
| ubus: connect(), |
| counter, |
| gauge, |
| wsplit: function(line) { |
| return split(line, /\s+/); |
| }, |
| nextline: function(f) { |
| return rtrim(f.read("line"), "\n"); |
| }, |
| oneline: function(fn) { |
| let f = fs.open(fn); |
| |
| if (!f) |
| return null; |
| |
| return nextline(f); |
| }, |
| poneline: function(cmd) { |
| let f = fs.popen(cmd); |
| |
| if (!f) |
| return null; |
| |
| return nextline(f); |
| }, |
| }; |
| |
| if (length(collectors) < 1) { |
| httpstatus("404 No Collectors found"); |
| return; |
| } |
| |
| let cols = []; |
| for (let q in split(env.QUERY_STRING, "&")) { |
| let s = split(q, "=", 2); |
| if (length(s) == 2 && s[0] == "collect") { |
| if (!(s[1] in collectors)) { |
| httpstatus(`404 Collector ${s[1]} not found`); |
| return; |
| } |
| |
| push(cols, s[1]); |
| } |
| } |
| |
| if (length(cols) > 0) |
| cols = uniq(cols); |
| else |
| cols = keys(collectors); |
| |
| httpstatus("200 OK"); |
| |
| let duration = gauge("node_scrape_collector_duration_seconds"); |
| let success = gauge("node_scrape_collector_success"); |
| |
| for (let col in cols) { |
| let ok = false; |
| let t1, t2; |
| |
| scope["config"] = collectors[col].config; |
| t1 = clock(true); |
| try { |
| ok = call(collectors[col].func, null, scope) != false; |
| } catch(e) { |
| warn(`error running collector '${col}':\n${e.message}\n`); |
| } |
| t2 = clock(true); |
| |
| duration({ collector: col }, clockdiff(t1, t2) / 1000000000.0); |
| success({ collector: col }, ok); |
| } |
| }; |
| |
| const lib = "/usr/share/ucode/node-exporter/lib"; |
| const opts = { |
| strict_declarations: true, |
| raw_mode: true, |
| }; |
| |
| let cols = fs.lsdir(lib, "*.uc"); |
| for (let col in cols) { |
| let func; |
| let uci = cursor(); |
| |
| try { |
| func = loadfile(lib + "/" + col, opts); |
| } catch(e) { |
| warn(`error compiling collector '${col}':\n${e.message}\n`); |
| continue; |
| } |
| |
| let name = substr(col, 0, -3); |
| let config = uci.get_all("prometheus-node-exporter-ucode", name); |
| if (!config || config[".type"] != "collector") |
| config = {}; |
| else { |
| delete config[".anonymous"]; |
| delete config[".type"]; |
| delete config[".name"]; |
| } |
| |
| collectors[name] = { |
| func, |
| config, |
| }; |
| } |
| |
| warn(`prometheus-node-exporter-ucode now serving requests with ${length(collectors)} collectors\n`); |
| |
| if (!("uhttpd" in global)) { |
| global.debug = true; |
| |
| puts = function(...s) { |
| return print(...s, "\n"); |
| }; |
| |
| handle_request({ |
| QUERY_STRING: join("&", map(ARGV, v => "collect=" + v)), |
| }); |
| } |
| %} |