Merge pull request #4331 from simonswine/feature-prometheus-exporter
[feed/packages.git] / utils / prometheus-node-exporter-lua / files / usr / bin / prometheus-node-exporter-lua
1 #!/usr/bin/lua
2
3 -- Metrics web server
4
5 -- Copyright (c) 2016 Jeff Schornick <jeff@schornick.org>
6 -- Copyright (c) 2015 Kevin Lyda
7 -- Licensed under the Apache License, Version 2.0
8
9 socket = require("socket")
10
11 -- Allow us to call unpack under both lua5.1 and lua5.2+
12 local unpack = unpack or table.unpack
13
14 -- This table defines the scrapers to run.
15 -- Each corresponds directly to a scraper_<name> function.
16 scrapers = { "cpu", "load_averages", "memory", "file_handles", "network",
17 "network_devices", "time", "uname", "nat", "wifi"}
18
19 -- Parsing
20
21 function space_split(s)
22 elements = {}
23 for element in s:gmatch("%S+") do
24 table.insert(elements, element)
25 end
26 return elements
27 end
28
29 function line_split(s)
30 elements = {}
31 for element in s:gmatch("[^\n]+") do
32 table.insert(elements, element)
33 end
34 return elements
35 end
36
37 function get_contents(filename)
38 local f = io.open(filename, "rb")
39 local contents = ""
40 if f then
41 contents = f:read "*a"
42 f:close()
43 end
44
45 return contents
46 end
47
48 -- Metric printing
49
50 function print_metric(metric, labels, value)
51 local label_string = ""
52 if labels then
53 for label,value in pairs(labels) do
54 label_string = label_string .. label .. '="' .. value .. '",'
55 end
56 label_string = "{" .. string.sub(label_string, 1, -2) .. "}"
57 end
58 output(string.format("%s%s %s", metric, label_string, value))
59 end
60
61 function metric(name, mtype, labels, value)
62 output("# TYPE " .. name .. " " .. mtype)
63 local outputter = function(labels, value)
64 print_metric(name, labels, value)
65 end
66 if value then
67 outputter(labels, value)
68 end
69 return outputter
70 end
71
72 function scraper_wifi()
73 local rv = { }
74 local ntm = require "luci.model.network".init()
75
76 local metric_wifi_network_up = metric("wifi_network_up","gauge")
77 local metric_wifi_network_quality = metric("wifi_network_quality","gauge")
78 local metric_wifi_network_bitrate = metric("wifi_network_bitrate","gauge")
79 local metric_wifi_network_noise = metric("wifi_network_noise","gauge")
80 local metric_wifi_network_signal = metric("wifi_network_signal","gauge")
81
82 local metric_wifi_station_signal = metric("wifi_station_signal","gauge")
83 local metric_wifi_station_tx_packets = metric("wifi_station_tx_packets","gauge")
84 local metric_wifi_station_rx_packets = metric("wifi_station_rx_packets","gauge")
85
86 local dev
87 for _, dev in ipairs(ntm:get_wifidevs()) do
88 local rd = {
89 up = dev:is_up(),
90 device = dev:name(),
91 name = dev:get_i18n(),
92 networks = { }
93 }
94
95 local net
96 for _, net in ipairs(dev:get_wifinets()) do
97 local labels = {
98 channel = net:channel(),
99 ssid = net:active_ssid(),
100 bssid = net:active_bssid(),
101 mode = net:active_mode(),
102 ifname = net:ifname(),
103 country = net:country(),
104 frequency = net:frequency(),
105 }
106 if net:is_up() then
107 metric_wifi_network_up(labels, 1)
108 local signal = net:signal_percent()
109 if signal ~= 0 then
110 metric_wifi_network_quality(labels, net:signal_percent())
111 end
112 metric_wifi_network_noise(labels, net:noise())
113 local bitrate = net:bitrate()
114 if bitrate then
115 metric_wifi_network_bitrate(labels, bitrate)
116 end
117
118 local assoclist = net:assoclist()
119 for mac, station in pairs(assoclist) do
120 local labels = {
121 ifname = net:ifname(),
122 mac = mac,
123 }
124 metric_wifi_station_signal(labels, station.signal)
125 metric_wifi_station_tx_packets(labels, station.tx_packets)
126 metric_wifi_station_rx_packets(labels, station.rx_packets)
127 end
128 else
129 metric_wifi_network_up(labels, 0)
130 end
131 end
132 rv[#rv+1] = rd
133 end
134 end
135
136 function scraper_cpu()
137 local stat = get_contents("/proc/stat")
138
139 -- system boot time, seconds since epoch
140 metric("node_boot_time", "gauge", nil, string.match(stat, "btime ([0-9]+)"))
141
142 -- context switches since boot (all CPUs)
143 metric("node_context_switches", "counter", nil, string.match(stat, "ctxt ([0-9]+)"))
144
145 -- cpu times, per CPU, per mode
146 local cpu_mode = {"user", "nice", "system", "idle", "iowait", "irq",
147 "softirq", "steal", "guest", "guest_nice"}
148 local i = 0
149 local cpu_metric = metric("node_cpu", "counter")
150 while string.match(stat, string.format("cpu%d ", i)) do
151 local cpu = space_split(string.match(stat, string.format("cpu%d ([0-9 ]+)", i)))
152 local labels = {cpu = "cpu" .. i}
153 for ii, mode in ipairs(cpu_mode) do
154 labels['mode'] = mode
155 cpu_metric(labels, cpu[ii] / 100)
156 end
157 i = i + 1
158 end
159
160 -- interrupts served
161 metric("node_intr", "counter", nil, string.match(stat, "intr ([0-9]+)"))
162
163 -- processes forked
164 metric("node_forks", "counter", nil, string.match(stat, "processes ([0-9]+)"))
165
166 -- processes running
167 metric("node_procs_running", "gauge", nil, string.match(stat, "procs_running ([0-9]+)"))
168
169 -- processes blocked for I/O
170 metric("node_procs_blocked", "gauge", nil, string.match(stat, "procs_blocked ([0-9]+)"))
171 end
172
173 function scraper_load_averages()
174 local loadavg = space_split(get_contents("/proc/loadavg"))
175
176 metric("node_load1", "gauge", nil, loadavg[1])
177 metric("node_load5", "gauge", nil, loadavg[2])
178 metric("node_load15", "gauge", nil, loadavg[3])
179 end
180
181 function scraper_memory()
182 local meminfo = line_split(get_contents("/proc/meminfo"):gsub("[):]", ""):gsub("[(]", "_"))
183
184 for i, mi in ipairs(meminfo) do
185 local name, size, unit = unpack(space_split(mi))
186 if unit == 'kB' then
187 size = size * 1024
188 end
189 metric("node_memory_" .. name, "gauge", nil, size)
190 end
191 end
192
193 function scraper_file_handles()
194 local file_nr = space_split(get_contents("/proc/sys/fs/file-nr"))
195
196 metric("node_filefd_allocated", "gauge", nil, file_nr[1])
197 metric("node_filefd_maximum", "gauge", nil, file_nr[3])
198 end
199
200 function scraper_network()
201 -- NOTE: Both of these are missing in OpenWRT kernels.
202 -- See: https://dev.openwrt.org/ticket/15781
203 local netstat = get_contents("/proc/net/netstat") .. get_contents("/proc/net/snmp")
204
205 -- all devices
206 local netsubstat = {"IcmpMsg", "Icmp", "IpExt", "Ip", "TcpExt", "Tcp", "UdpLite", "Udp"}
207 for i, nss in ipairs(netsubstat) do
208 local substat_s = string.match(netstat, nss .. ": ([A-Z][A-Za-z0-9 ]+)")
209 if substat_s then
210 local substat = space_split(substat_s)
211 local substatv = space_split(string.match(netstat, nss .. ": ([0-9 -]+)"))
212 for ii, ss in ipairs(substat) do
213 metric("node_netstat_" .. nss .. "_" .. ss, "gauge", nil, substatv[ii])
214 end
215 end
216 end
217 end
218
219 function scraper_network_devices()
220 local netdevstat = line_split(get_contents("/proc/net/dev"))
221 local netdevsubstat = {"receive_bytes", "receive_packets", "receive_errs",
222 "receive_drop", "receive_fifo", "receive_frame", "receive_compressed",
223 "receive_multicast", "transmit_bytes", "transmit_packets",
224 "transmit_errs", "transmit_drop", "transmit_fifo", "transmit_colls",
225 "transmit_carrier", "transmit_compressed"}
226 for i, line in ipairs(netdevstat) do
227 netdevstat[i] = string.match(netdevstat[i], "%S.*")
228 end
229 local nds_table = {}
230 local devs = {}
231 for i, nds in ipairs(netdevstat) do
232 local dev, stat_s = string.match(netdevstat[i], "([^:]+): (.*)")
233 if dev then
234 nds_table[dev] = space_split(stat_s)
235 table.insert(devs, dev)
236 end
237 end
238 for i, ndss in ipairs(netdevsubstat) do
239 netdev_metric = metric("node_network_" .. ndss, "gauge")
240 for ii, d in ipairs(devs) do
241 netdev_metric({device=d}, nds_table[d][i])
242 end
243 end
244 end
245
246 function scraper_time()
247 -- current time
248 metric("node_time", "counter", nil, os.time())
249 end
250
251 function scraper_uname()
252 -- version can have spaces, so grab it directly
253 local version = string.sub(io.popen("uname -v"):read("*a"), 1, -2)
254 -- avoid individual popen calls for the rest of the values
255 local uname_string = io.popen("uname -a"):read("*a")
256 local sysname, nodename, release = unpack(space_split(uname_string))
257 local labels = {domainname = "(none)", nodename = nodename, release = release,
258 sysname = sysname, version = version}
259
260 -- The machine hardware name is immediately after the version string, so add
261 -- up the values we know and add in the 4 spaces to find the offset...
262 machine_offset = string.len(sysname .. nodename .. release .. version) + 4
263 labels['machine'] = string.match(string.sub(uname_string, machine_offset), "(%S+)" )
264 metric("node_uname_info", "gauge", labels, 1)
265 end
266
267 function scraper_nat()
268 -- documetation about nf_conntrack:
269 -- https://www.frozentux.net/iptables-tutorial/chunkyhtml/x1309.html
270 -- local natstat = line_split(get_contents("/proc/net/nf_conntrack"))
271 local natstat = line_split(get_contents("nf_conntrack"))
272
273 nat_metric = metric("node_nat_traffic", "gauge" )
274 for i, e in ipairs(natstat) do
275 -- output(string.format("%s\n",e ))
276 local fields = space_split(e)
277 local src, dest, bytes;
278 bytes = 0;
279 for ii, field in ipairs(fields) do
280 if src == nil and string.match(field, '^src') then
281 src = string.match(field,"src=([^ ]+)");
282 elseif dest == nil and string.match(field, '^dst') then
283 dest = string.match(field,"dst=([^ ]+)");
284 elseif string.match(field, '^bytes') then
285 local b = string.match(field, "bytes=([^ ]+)");
286 bytes = bytes + b;
287 -- output(string.format("\t%d %s",ii,field ));
288 end
289
290 end
291 -- local src, dest, bytes = string.match(natstat[i], "src=([^ ]+) dst=([^ ]+) .- bytes=([^ ]+)");
292 -- local src, dest, bytes = string.match(natstat[i], "src=([^ ]+) dst=([^ ]+) sport=[^ ]+ dport=[^ ]+ packets=[^ ]+ bytes=([^ ]+)")
293
294 local labels = { src = src, dest = dest }
295 -- output(string.format("src=|%s| dest=|%s| bytes=|%s|", src, dest, bytes ))
296 nat_metric(labels, bytes )
297 end
298 end
299
300 function timed_scrape(scraper)
301 local start_time = socket.gettime()
302 -- build the function name and call it from global variable table
303 _G["scraper_"..scraper]()
304 local duration = socket.gettime() - start_time
305 return duration
306 end
307
308 function run_all_scrapers()
309 times = {}
310 for i,scraper in ipairs(scrapers) do
311 runtime = timed_scrape(scraper)
312 times[scraper] = runtime
313 scrape_time_sums[scraper] = scrape_time_sums[scraper] + runtime
314 scrape_counts[scraper] = scrape_counts[scraper] + 1
315 end
316
317 local name = "node_exporter_scrape_duration_seconds"
318 local duration_metric = metric(name, "summary")
319 for i,scraper in ipairs(scrapers) do
320 local labels = {collector=scraper, result="success"}
321 duration_metric(labels, times[scraper])
322 print_metric(name.."_sum", labels, scrape_time_sums[scraper])
323 print_metric(name.."_count", labels, scrape_counts[scraper])
324 end
325 end
326
327 -- Web server-specific functions
328
329 function http_ok_header()
330 output("HTTP/1.1 200 OK\r")
331 output("Server: lua-metrics\r")
332 output("Content-Type: text/plain; version=0.0.4\r")
333 output("\r")
334 end
335
336 function http_not_found()
337 output("HTTP/1.1 404 Not Found\r")
338 output("Server: lua-metrics\r")
339 output("Content-Type: text/plain\r")
340 output("\r")
341 output("ERROR: File Not Found.")
342 end
343
344 function serve(request)
345 if not string.match(request, "GET /metrics.*") then
346 http_not_found()
347 else
348 http_ok_header()
349 run_all_scrapers()
350 end
351 client:close()
352 return true
353 end
354
355 -- Main program
356
357 for k,v in ipairs(arg) do
358 if (v == "-p") or (v == "--port") then
359 port = arg[k+1]
360 end
361 if (v == "-b") or (v == "--bind") then
362 bind = arg[k+1]
363 end
364 end
365
366 scrape_counts = {}
367 scrape_time_sums = {}
368 for i,scraper in ipairs(scrapers) do
369 scrape_counts[scraper] = 0
370 scrape_time_sums[scraper] = 0
371 end
372
373 if port then
374 server = assert(socket.bind(bind, port))
375
376 while 1 do
377 client = server:accept()
378 client:settimeout(60)
379 local request, err = client:receive()
380
381 if not err then
382 output = function (str) client:send(str.."\n") end
383 if not serve(request) then
384 break
385 end
386 end
387 end
388 else
389 output = print
390 run_all_scrapers()
391 end