blob: 3dce77accb520ca2b92e44ff14ea2ec7abd2abd6 [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001{%
2'use strict';
3
4import * as fs from "fs";
5import { connect } from "ubus";
6import { cursor } from "uci";
7
8function debug(...s) {
9 if (global.debug)
10 warn("DEBUG: ", ...s, "\n");
11}
12
13function puts(...s) {
14 return uhttpd.send(...s, "\n");
15}
16
17function govalue(value) {
18 if (value == Infinity)
19 return "+Inf";
20 else if (value == -Infinity)
21 return "-Inf";
22 else if (value != value)
23 return "NaN";
24 else if (type(value) in [ "int", "double" ])
25 return value;
26 else if (type(value) in [ "bool", "string" ])
27 return +value;
28
29 return null;
30}
31
32function metric(name, mtype, help, skipdecl) {
33 let func;
34 let decl = skipdecl == true ? false : true;
35
36 let yield = function(labels, value) {
37 let v = govalue(value);
38
39 if (v == null) {
40 debug(`skipping metric: unsupported value '${value}' (${name})`);
41 return func;
42 }
43
44 let labels_str = "";
45 if (length(labels)) {
46 let sep = "";
47 let s;
48 labels_str = "{";
49 for (let l in labels) {
50 if (labels[l] == null)
51 s = "";
52 else if (type(labels[l]) == "string") {
53 s = labels[l];
54 s = replace(labels[l], "\\", "\\\\");
55 s = replace(s, "\"", "\\\"");
56 s = replace(s, "\n", "\\n");
57 } else {
58 s = govalue(labels[l]);
59
60 if (!s)
61 continue;
62 }
63
64 labels_str += sep + l + "=\"" + s + "\"";
65 sep = ",";
66 }
67 labels_str += "}";
68 }
69
70 if (decl) {
71 if (help)
72 puts("# HELP ", name, " ", help);
73 puts("# TYPE ", name, " ", mtype);
74 decl = false;
75 }
76
77 puts(name, labels_str, " ", v);
78 return func;
79 };
80
81 func = yield;
82 return func;
83}
84
85function counter(name, help, skipdecl) {
86 return metric(name, "counter", help, skipdecl);
87}
88
89function gauge(name, help, skipdecl) {
90 return metric(name, "gauge", help, skipdecl);
91}
92
93function httpstatus(status) {
94 puts("Status: ", status, "\nContent-Type: text/plain; version=0.0.4; charset=utf-8\n");
95}
96
97function clockdiff(t1, t2) {
98 return (t2[0] - t1[0]) * 1000000000 + t2[1] - t1[1];
99}
100
101let collectors = {};
102
103global.handle_request = function(env) {
104 let scope = {
105 config: null,
106 fs,
107 ubus: connect(),
108 counter,
109 gauge,
110 wsplit: function(line) {
111 return split(line, /\s+/);
112 },
113 nextline: function(f) {
114 return rtrim(f.read("line"), "\n");
115 },
116 oneline: function(fn) {
117 let f = fs.open(fn);
118
119 if (!f)
120 return null;
121
122 return nextline(f);
123 },
124 poneline: function(cmd) {
125 let f = fs.popen(cmd);
126
127 if (!f)
128 return null;
129
130 return nextline(f);
131 },
132 };
133
134 if (length(collectors) < 1) {
135 httpstatus("404 No Collectors found");
136 return;
137 }
138
139 let cols = [];
140 for (let q in split(env.QUERY_STRING, "&")) {
141 let s = split(q, "=", 2);
142 if (length(s) == 2 && s[0] == "collect") {
143 if (!(s[1] in collectors)) {
144 httpstatus(`404 Collector ${s[1]} not found`);
145 return;
146 }
147
148 push(cols, s[1]);
149 }
150 }
151
152 if (length(cols) > 0)
153 cols = uniq(cols);
154 else
155 cols = keys(collectors);
156
157 httpstatus("200 OK");
158
159 let duration = gauge("node_scrape_collector_duration_seconds");
160 let success = gauge("node_scrape_collector_success");
161
162 for (let col in cols) {
163 let ok = false;
164 let t1, t2;
165
166 scope["config"] = collectors[col].config;
167 t1 = clock(true);
168 try {
169 ok = call(collectors[col].func, null, scope) != false;
170 } catch(e) {
171 warn(`error running collector '${col}':\n${e.message}\n`);
172 }
173 t2 = clock(true);
174
175 duration({ collector: col }, clockdiff(t1, t2) / 1000000000.0);
176 success({ collector: col }, ok);
177 }
178};
179
180const lib = "/usr/share/ucode/node-exporter/lib";
181const opts = {
182 strict_declarations: true,
183 raw_mode: true,
184};
185
186let cols = fs.lsdir(lib, "*.uc");
187for (let col in cols) {
188 let func;
189 let uci = cursor();
190
191 try {
192 func = loadfile(lib + "/" + col, opts);
193 } catch(e) {
194 warn(`error compiling collector '${col}':\n${e.message}\n`);
195 continue;
196 }
197
198 let name = substr(col, 0, -3);
199 let config = uci.get_all("prometheus-node-exporter-ucode", name);
200 if (!config || config[".type"] != "collector")
201 config = {};
202 else {
203 delete config[".anonymous"];
204 delete config[".type"];
205 delete config[".name"];
206 }
207
208 collectors[name] = {
209 func,
210 config,
211 };
212}
213
214warn(`prometheus-node-exporter-ucode now serving requests with ${length(collectors)} collectors\n`);
215
216if (!("uhttpd" in global)) {
217 global.debug = true;
218
219 puts = function(...s) {
220 return print(...s, "\n");
221 };
222
223 handle_request({
224 QUERY_STRING: join("&", map(ARGV, v => "collect=" + v)),
225 });
226}
227%}