aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/apt-panopticommon.lua
diff options
context:
space:
mode:
authoronefang2019-12-10 15:45:11 +1000
committeronefang2019-12-10 15:45:11 +1000
commit4bb51520521d9ab612d50dd2bcf904d70cf15735 (patch)
tree8d9fde355e1ecc1b06f400f99b2285ca6492023d /apt-panopticommon.lua
parentAdd freedom -1. (diff)
downloadapt-panopticon-4bb51520521d9ab612d50dd2bcf904d70cf15735.zip
apt-panopticon-4bb51520521d9ab612d50dd2bcf904d70cf15735.tar.gz
apt-panopticon-4bb51520521d9ab612d50dd2bcf904d70cf15735.tar.bz2
apt-panopticon-4bb51520521d9ab612d50dd2bcf904d70cf15735.tar.xz
Move common code to it's own module.
Some minor clean ups and test tweaks likely came for the ride.
Diffstat (limited to '')
-rw-r--r--apt-panopticommon.lua563
1 files changed, 563 insertions, 0 deletions
diff --git a/apt-panopticommon.lua b/apt-panopticommon.lua
new file mode 100644
index 0000000..b38595c
--- /dev/null
+++ b/apt-panopticommon.lua
@@ -0,0 +1,563 @@
1local APT = {}
2
3-- https://oss.oetiker.ch/rrdtool/prog/rrdlua.en.html
4APT.rrd = require 'rrd'
5
6verbosity = -1
7APT.origin = false
8APT.keep = false
9-- TODO - Should actually implement this.
10APT.fork = true
11
12APT.options =
13{
14 referenceSite =
15 {
16 typ = "string",
17 help = "",
18 value = "pkgmaster.devuan.org",
19 },
20 roundRobin =
21 {
22 typ = "string",
23 help = "",
24 value = "deb.devuan.org",
25 },
26 tests =
27 {
28 typ = "table",
29 help = "",
30 value =
31 {
32 "IPv4",
33 "IPv6",
34-- "ftp",
35 "http",
36 "https",
37-- "rsync",
38 "DNSRR",
39 "Protocol",
40 "URLSanity",
41 "Integrity",
42 "Updated",
43 },
44 },
45 maxtime =
46 {
47 typ = "number",
48 help = "",
49 value = 300,
50 },
51 timeout =
52 {
53 typ = "number",
54 help = "",
55 value = 15,
56 },
57 reports =
58 {
59 typ = "table",
60 help = "",
61 value =
62 {
63 "RRD", -- RRD has to be before web, coz web creates a graph from the RRD data.
64 "email-web",
65-- "Nagios",
66-- "Prometheus",
67 },
68 },
69}
70
71APT.parseArgs = function(args)
72 local arg = {}
73 local sendArgs = ""
74 if 0 ~= #(args) then
75 local option = ""
76 for i, a in pairs(args) do
77 if ("--help" == a) or ("-h" == a) then
78 print("I should write some docs, huh? Read README.md for instructions.")
79 elseif "--version" == a then
80 print("apt-panopticon version 0.1 WIP development version")
81 elseif "-v" == a then
82 verbosity = verbosity + 1
83 sendArgs = sendArgs .. a .. " "
84 elseif "-q" == a then
85 verbosity = -1
86 sendArgs = sendArgs .. a .. " "
87 elseif "-k" == a then
88 APT.keep = true
89 elseif "-n" == a then
90 APT.fork = false
91 elseif "-o" == a then
92 APT.origin = true
93 elseif "--" == a:sub(1, 2) then
94 local s, e = a:find("=")
95 if nil == s then e = -1 end
96 option = a:sub(3, e - 1)
97 local o = APT.options[option]
98 if nil == o then
99 print("Unknown option --" .. option)
100 option = ""
101 else
102 option = a
103 sendArgs = sendArgs .. a .. " "
104 local s, e = a:find("=")
105 if nil == s then e = 0 end
106 option = a:sub(3, e - 1)
107 if "table" == APT.options[option].typ then
108 local result = {}
109 for t in (a:sub(e + 1) .. ","):gmatch("([+%-]?%w*),") do
110 local f = t:sub(1, 1)
111 local n = t:sub(2, -1)
112 if ("+" ~= f) and ("-" ~= f) then
113 table.insert(result, t)
114 end
115 end
116 if 0 ~= #result then
117 APT.options[option].value = result
118 else
119 for t in (a:sub(e + 1) .. ","):gmatch("([+%-]?%w*),") do
120 local f = t:sub(1, 1)
121 local n = t:sub(2, -1)
122 if "+" == f then
123 table.insert(APT.options[option].value, n)
124 elseif "-" == f then
125 local r = {}
126 for i, k in pairs(APT.options[option].value) do
127 if k ~= n then table.insert(r, k) end
128 end
129 APT.options[option].value = r
130 end
131 end
132 end
133 else
134 APT.options[option].value = a:sub(e + 1, -1)
135 end
136 option = ""
137 end
138 elseif "-" == a:sub(1, 1) then
139 print("Unknown option " .. a)
140 else
141 table.insert(arg, a)
142 end
143 end
144 end
145 return arg, sendArgs
146end
147
148--print(APT.dumpTable(APT.options, "", "options"))
149
150
151
152--[[ Ordered table iterator, allow to iterate on the natural order of the keys of a table.
153 From http://lua-users.org/wiki/SortedIteration
154 ]]
155function __genOrderedIndex( t )
156 local orderedIndex = {}
157 for key in pairs(t) do
158 table.insert( orderedIndex, key )
159 end
160 table.sort( orderedIndex )
161 return orderedIndex
162end
163function orderedNext(t, state)
164 -- Equivalent of the next function, but returns the keys in the alphabetic
165 -- order. We use a temporary ordered key table that is stored in the
166 -- table being iterated.
167
168 local key = nil
169 --print("orderedNext: state = "..tostring(state) )
170 if state == nil then
171 -- the first time, generate the index
172 t.__orderedIndex = __genOrderedIndex( t )
173 key = t.__orderedIndex[1]
174 else
175 -- fetch the next value
176 for i = 1,table.getn(t.__orderedIndex) do
177 if t.__orderedIndex[i] == state then
178 key = t.__orderedIndex[i+1]
179 end
180 end
181 end
182
183 if key then
184 return key, t[key]
185 end
186
187 -- no more value to return, cleanup
188 t.__orderedIndex = nil
189 return
190end
191function APT.orderedPairs(t)
192 -- Equivalent of the pairs() function on tables. Allows to iterate
193 -- in order
194 return orderedNext, t, nil
195end
196
197-- Use this to dump a table to a string, with HTML.
198APT.dumpTableHTML = function (table, space, name)
199 local r = name .. "\n"
200 r = r .. dumpTableHTMLSub(table, space .. " ")
201 r = r .. space .. ""
202 return r
203end
204dumpTableHTMLSub = function (table, space)
205 local r = ""
206 for k, v in APT.orderedPairs(table) do
207 if type(v) == "table" then
208 if " " == space then
209 r = r .. space .. APT.dumpTableHTML(v, space, k .. "<ul>") .. "</ul>\n"
210 else
211 r = r .. "<li>" .. space .. APT.dumpTableHTML(v, space, k .. "<ul>") .. "</ul></li>\n"
212 end
213 else
214 r = r .. space .. "<li>" .. k .. "</li>\n"
215 end
216 end
217 return r
218end
219
220-- Use this to dump a table to a string.
221APT.dumpTable = function (table, space, name)
222 local r = ""
223 if "" == space then r = r .. space .. name .. " =\n" else r = r .. space .. "[" .. name .. "] =\n" end
224 r = r .. space .. "{\n"
225 r = r .. dumpTableSub(table, space .. " ")
226 if "" == space then r = r .. space .. "}\n" else r = r .. space .. "},\n" end
227 return r
228end
229dumpTableSub = function (table, space)
230 local r = ""
231 for k, v in pairs(table) do
232 if type(k) == "string" then k = '"' .. k .. '"' end
233 if type(v) == "table" then
234 r = r .. APT.dumpTable(v, space, k)
235 elseif type(v) == "string" then
236 r = r .. space .. "[" .. k .. "] = '" .. v .. "';\n"
237 elseif type(v) == "function" then
238 r = r .. space .. "[" .. k .. "] = function ();\n"
239 elseif type(v) == "userdata" then
240 r = r .. space .. "userdata " .. "[" .. k .. "];\n"
241 elseif type(v) == "boolean" then
242 if (v) then
243 r = r .. space .. "[" .. k .. "] = true;\n"
244 else
245 r = r .. space .. "[" .. k .. "] = false;\n"
246 end
247 else
248 r = r .. space .. "[" .. k .. "] = " .. v .. ";\n"
249 end
250 end
251 return r
252end
253
254APT.results = {}
255APT.logFile = nil
256APT.html = false
257
258APT.logPre = function()
259 if nil ~= APT.logFile then
260 APT.logFile:write("<html><head>\n")
261 APT.logFile:write("</head><body bgcolor='black' text='white' alink='red' link='blue' vlink='purple'>\n")
262 end
263end
264APT.logPost = function()
265 if nil ~= APT.logFile then
266 APT.logFile:write("</body></html> \n")
267 end
268end
269
270local log = function(v, t, s, prot, test, host)
271 local x = ""
272 if nil == prot then prot = "" end
273 if nil == test then test = "" end
274 x = x .. prot
275 if "" ~= test then
276 if #x > 0 then x = x .. " " end
277 x = x .. test
278 end
279 if nil ~= host then
280 if #x > 0 then x = x .. " " end
281 x = x .. host
282 end
283 if #x > 0 then
284 t = t .. "(" .. x .. ")"
285 if "" ~= prot then
286 if "" == test then
287 if nil == APT.results[prot] then APT.results[prot] = {errors = 0; warnings = 0} end
288 if v == 0 then APT.results[prot].errors = APT.results[prot].errors + 1 end
289 if v == 1 then APT.results[prot].warnings = APT.results[prot].warnings + 1 end
290 else
291 if nil == APT.results[prot] then APT.results[prot] = {errors = 0; warnings = 0} end
292 if nil == APT.results[prot][test] then APT.results[prot][test] = {errors = 0; warnings = 0} end
293 if v == 0 then APT.results[prot][test].errors = APT.results[prot][test].errors + 1 end
294 if v == 1 then APT.results[prot][test].warnings = APT.results[prot][test].warnings + 1 end
295 end
296 end
297 end
298 if v <= verbosity then
299 if 3 <= verbosity then t = os.date() .. " " .. t end
300 print(t .. ": " .. s)
301 end
302 if nil ~= APT.logFile then
303 if APT.html then
304 local colour = "white"
305 if -1 == v then colour = "fuchsia" end -- CRITICAL
306 if 0 == v then colour = "red " end -- ERROR
307 if 1 == v then colour = "yellow " end -- WARNING
308 if 2 == v then colour = "white " end -- INFO
309 if 3 == v then colour = "gray " end -- DEBUG
310 APT.logFile:write(os.date() .. " <font color='" .. colour .. "'><b>" .. t .. "</b></font>: " .. s .. "</br>\n")
311 else
312 APT.logFile:write(os.date() .. " " .. t .. ": " .. s .. "\n")
313 end
314 APT.logFile:flush()
315 end
316end
317APT.D = function(s) log(3, "DEBUG ", s) end
318APT.I = function(s) log(2, "INFO ", s) end
319APT.W = function(s, p, t, h) log(1, "WARNING ", s, p, t, h) end
320APT.E = function(s, p, t, h) log(0, "ERROR ", s, p, t, h) end
321APT.C = function(s) log(-1, "CRITICAL", s) end
322local D = APT.D
323local I = APT.I
324local W = APT.W
325local E = APT.E
326local C = APT.C
327
328
329APT.mirrors = {}
330
331APT.testing = function(t, host)
332 for i, v in pairs(APT.options.tests.value) do
333 if t == v then
334 local h = APT.mirrors[host]
335 if nil == h then return true end
336 if true == h["Protocols"][t] then return true else D("Skipping " .. t .. " checks for " .. host) end
337 end
338 end
339 return false
340end
341
342APT.execute = function (s)
343 D(" executing <pre><code>" .. s .. "</code></pre>")
344 --[[ Damn os.execute()
345 Lua 5.1 says it returns "a status code, which is system-dependent"
346 Lua 5.2 says it returns true/nil, "exit"/"signal", the status code.
347 I'm getting 7168 or 0. No idea what the fuck that is.
348 local ok, rslt, status = os.execute(s)
349 ]]
350 local f = io.popen(s .. ' ; echo "$?"', 'r')
351 local status = ""
352 local result = ""
353 -- The last line will be the command's returned status, collect everything else in result.
354 for l in f:lines() do
355 result = result .. status .. "\n"
356 status = l
357 end
358 return status, result
359end
360
361APT.fork = function(s)
362 D(" forking <pre><code>" .. s .. "</code></pre>")
363 os.execute(s .. " &")
364end
365
366APT.checkExes = function (exe)
367 local count = io.popen('ps x | grep "' .. exe .. '" | grep -v " grep " | grep -v "flock -n apt-panopticon.lock " | wc -l'):read("*l")
368 D(count .. " " .. exe .. " commands still running.")
369 return tonumber(count)
370end
371
372APT.checkFile = function(f)
373 local h, e = io.open(f, "r")
374 if nil == h then return false else h:close(); return true end
375end
376
377APT.plurals = function(e, w)
378 local result = ""
379 if 1 == e then
380 result = e .. " error"
381 elseif e ~= 0 then
382 result = e .. " errors"
383 end
384 if ("" ~= result) and APT.html then result = "<font color='red'><b>" .. result .. "</b></font>" end
385-- result = " " .. result
386 if 0 < w then
387 if 0 < e then result = result .. ", " end
388 if 1 == w then
389 result = result .. w .. " warning"
390 else
391 result = result .. w .. " warnings"
392 end
393 if ("" ~= result) and APT.html then result = "<font color='yellow'><b>" .. result .. "</b></font>" end
394-- result = " " .. result
395 end
396 if "" ~= result then result = " (" .. result .. ")" end
397 return result
398end
399
400APT.collate = function(host, ip, results)
401 local f = "results/" .. host .. "_" .. ip .. ".lua"
402 local rfile, e = io.open(f, "r")
403 if nil == rfile then I("opening " .. f .. " file - " .. e) else
404 rfile:close()
405 local rs = loadfile(f)()
406 for k, v in pairs(rs) do
407 if "table" == type(v) then
408 if ("speed" == k) and (nil ~= results.speed) then
409 if v.min < results.speed.min then results.speed.min = v.min end
410 if v.max > results.speed.max then results.speed.max = v.max end
411 else
412 for i, u in pairs(v) do
413 if "table" == type(u) then
414 for h, t in pairs(u) do
415 local a = results[k][i][h]
416 if nil == a then a = 0 end
417 results[k][i][h] = a + t
418 end
419 else
420 local a = results[k]
421 if nil == a then a = 0; results[k] = {} else a = a[i] end
422 if nil == a then a = 0 end
423 results[k][i] = a + u
424 end
425 end
426 end
427 elseif "timeout" ~= k then
428 local a = results[k]
429 if nil == a then a = 0 end
430 results[k] = a + v
431 end
432 end
433 end
434 return results
435end
436
437
438
439
440APT.now = 0
441local status
442status, APT.now = APT.execute('TZ="GMT" ls -l --time-style="+%s" results/stamp | cut -d " " -f 6-6')
443APT.now = tonumber(APT.now)
444APT.protocols = {'ftp', 'http', 'https', 'rsync'}
445APT.tests = {'raw', 'Integrity', 'Protocol', 'Updated', 'URLSanity', 'Speed'}
446local start = 'now-2week'
447local step = '10min'
448local hb = '150min'
449local DSIe = 'DS:IntegrityErrors:GAUGE:' .. hb .. ':0:U'
450local DSIw = 'DS:IntegrityWarnings:GAUGE:' .. hb .. ':0:U'
451local DSPe = 'DS:ProtocolErrors:GAUGE:' .. hb .. ':0:U'
452local DSPw = 'DS:ProtocolWarnings:GAUGE:' .. hb .. ':0:U'
453local DSUe = 'DS:UpdatedErrors:GAUGE:' .. hb .. ':0:U'
454local DSUw = 'DS:UpdatedWarnings:GAUGE:' .. hb .. ':0:U'
455local DSSe = 'DS:URLSanityErrors:GAUGE:' .. hb .. ':0:U'
456local DSSw = 'DS:URLSanityWarnings:GAUGE:' .. hb .. ':0:U'
457local DSx = 'DS:max:GAUGE:' .. hb .. ':0:U'
458local DSn = 'DS:min:GAUGE:' .. hb .. ':0:U'
459
460-- What Collectd uses.
461local RRAc0 = 'RRA:AVERAGE:0.9:1:1200'
462local RRAc1 = 'RRA:MIN:0.9:1:1200'
463local RRAc2 = 'RRA:MAX:0.9:1:1200'
464local RRAc3 = 'RRA:AVERAGE:0.9:7:1235'
465local RRAc4 = 'RRA:MIN:0.9:7:1235'
466local RRAc5 = 'RRA:MAX:0.9:7:1235'
467local RRAc6 = 'RRA:AVERAGE:0.9:50:1210'
468local RRAc7 = 'RRA:MIN:0.9:50:1210'
469local RRAc8 = 'RRA:MAX:0.9:50:1210'
470local RRAc9 = 'RRA:AVERAGE:0.9:223:1202'
471local RRAc10 = 'RRA:MIN:0.9:223:1202'
472local RRAc11 = 'RRA:MAX:0.9:223:1202'
473local RRAc12 = 'RRA:AVERAGE:0.9:2635:1201'
474local RRAc13 = 'RRA:MIN:0.9:2635:1201'
475local RRAc14 = 'RRA:MAX:0.9:2635:1201'
476
477-- Try LAST.
478local RRAl0 = 'RRA:LAST:0.9:1:1200'
479local RRAl1 = 'RRA:LAST:0.9:7:1235'
480local RRAl2 = 'RRA:LAST:0.9:50:1210'
481local RRAl3 = 'RRA:LAST:0.9:223:1202'
482local RRAl4 = 'RRA:LAST:0.9:2635:1201'
483
484--[[
485/var/lib/collectd/rrd/onefang/interface-eth0/if_packets.rrd
486
487 rrd/host.IP/protocol.rrd test-error count
488 rrd/host.IP/protocol.rrd test-warning count
489
490 rrh/host.IP/protocol-speed.rrd min
491 rrh/host.IP/protocol-speed.rrd max
492]]
493
494APT.createRRD = function(host, ip)
495 if nil ~= ip then host = host .. '_' .. ip end
496 for i, p in pairs(APT.protocols) do
497-- for j, t in pairs(tests) do
498 os.execute( 'mkdir -p rrd/' .. host .. '/' .. p:upper())
499 if not APT.checkFile('rrd/' .. host .. '/' .. p:upper() .. '/Speed.rrd') then
500 D('Creating ' .. 'rrd/' .. host .. '/' .. p:upper() .. '/Speed.rrd')
501-- if 'Speed' == t then
502 APT.rrd.create( 'rrd/' .. host .. '/' .. p:upper() .. '/Speed.rrd', '--start', start, '--step', step, DSx, DSn,
503 RRAc0, RRAc1, RRAc2, RRAl0, RRAc3, RRAc4, RRAc5, RRAl1, RRAc6, RRAc7, RRAc8, RRAl2, RRAc9, RRAc10, RRAc11, RRAl3, RRAc12, RRAc13, RRAc14, RRAl4)
504-- else
505 end
506 if not APT.checkFile('rrd/' .. host .. '/' .. p:upper() .. '/Tests.rrd') then
507 D('Creating ' .. 'rrd/' .. host .. '/' .. p:upper() .. '/Tests.rrd')
508 APT.rrd.create( 'rrd/' .. host .. '/' .. p:upper() .. '/Tests.rrd', '--start', start, '--step', step, DSIe, DSIw, DSPe, DSPw, DSUe, DSUw, DSSe, DSSw,
509 RRAc0, RRAc1, RRAc2, RRAl0, RRAc3, RRAc4, RRAc5, RRAl1, RRAc6, RRAc7, RRAc8, RRAl2, RRAc9, RRAc10, RRAc11, RRAl3, RRAc12, RRAc13, RRAc14, RRAl4)
510-- end
511 -- Start them at 0 so the average has something to work on.
512 APT.rrd.update( 'rrd/' .. host .. '/' .. p:upper() .. '/Speed.rrd', (APT.now - 600) .. ':0:0')
513 APT.rrd.update( 'rrd/' .. host .. '/' .. p:upper() .. '/Tests.rrd', (APT.now - 600) .. ':0:0:0:0:0:0:0:0')
514 end
515-- end
516 end
517end
518
519APT.updateRRD = function(results, host, ip)
520 if nil ~= ip then host = host .. '_' .. ip end
521 for i, p in pairs(APT.protocols) do
522 if nil ~= results.speed then
523 APT.rrd.update( 'rrd/' .. host .. '/' .. p:upper() .. '/Speed.rrd', APT.now .. ':' .. results.speed.max .. ':' .. results.speed.min)
524 end
525 if nil ~= results[p] then
526 APT.rrd.update('rrd/' .. host .. '/' .. p:upper() .. '/Tests.rrd', APT.now .. ':' ..
527 results[p]['Integrity'].errors .. ':' .. results[p]['Integrity'].warnings .. ':' ..
528 results[p]['Protocol'].errors .. ':' .. results[p]['Protocol'].warnings .. ':' ..
529 results[p]['Updated'].errors .. ':' .. results[p]['Updated'].warnings .. ':' ..
530 results[p]['URLSanity'].errors .. ':' .. results[p]['URLSanity'].warnings)
531 else
532 APT.rrd.update('rrd/' .. host .. '/' .. p:upper() .. '/Tests.rrd', APT.now .. ':U:U:U:U:U:U:U:U')
533 end
534 end
535end
536
537
538APT.doRRD = function(l, k, v)
539 if APT.checkFile(l .. "/" .. k .. ".lua") then
540 local results = loadfile(l .. "/" .. k .. ".lua")()
541 APT.createRRD(k)
542 APT.updateRRD(results, k)
543 if til ~= v then
544 local IPs = v.IPs
545 for i, u in pairs(IPs) do
546 if "table" == type(u) then
547 for h, t in pairs(u) do
548 APT.createRRD(k, h)
549 results = APT.collate(k, h, results)
550 APT.updateRRD(results, k, h)
551 end
552 else
553 APT.createRRD(k, i)
554 results = APT.collate(k, i, results)
555 APT.updateRRD(results, k, i)
556 end
557 end
558 end
559 end
560end
561
562
563return APT