1: | <?php declare(strict_types=1); |
2: | |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | |
9: | |
10: | |
11: | |
12: | |
13: | |
14: | class Argos_Module extends Module_Skeleton |
15: | { |
16: | const DEFAULT_BACKEND = 'default'; |
17: | |
18: | protected $exportedFunctions = ['*' => PRIVILEGE_ADMIN]; |
19: | |
20: | |
21: | |
22: | |
23: | |
24: | |
25: | |
26: | public function status(string $service = null): array |
27: | { |
28: | if (!IS_CLI) { |
29: | return $this->query('argos_status', $service); |
30: | } |
31: | |
32: | $ret = \Util_Process_Safe::exec(['monit', '-B', 'status', '%s'], $service); |
33: | |
34: | if (!$ret['success']) { |
35: | error('Failed to query status: %s', $ret['stderr']); |
36: | return []; |
37: | } |
38: | |
39: | $status = $this->filterStatus($ret['stdout']); |
40: | |
41: | return $service ? $status[$service] ?? [] : $status; |
42: | } |
43: | |
44: | |
45: | |
46: | |
47: | |
48: | |
49: | |
50: | public function restart(string $service): bool |
51: | { |
52: | if (!IS_CLI) { |
53: | return $this->query('argos_restart', $service); |
54: | } |
55: | |
56: | $ret = Util_Process_Safe::exec(['monit', 'restart', '%s'], $service); |
57: | |
58: | return $ret['success'] ?: error('Failed to restart %s service: %s ', $service, $ret['stderr']); |
59: | } |
60: | |
61: | |
62: | |
63: | |
64: | |
65: | |
66: | |
67: | public function unmonitor(string $service): bool |
68: | { |
69: | if (!IS_CLI) { |
70: | return $this->query('argos_unmonitor', $service); |
71: | } |
72: | |
73: | $ret = \Util_Process_Safe::exec(['monit', 'unmonitor', '%s'], $service); |
74: | |
75: | return $ret['success'] ?: error('Failed to stop %s monitoring: %s ', $service, $ret['stderr']); |
76: | } |
77: | |
78: | |
79: | |
80: | |
81: | |
82: | |
83: | |
84: | public function monitor(string $service): bool |
85: | { |
86: | if (!IS_CLI) { |
87: | return $this->query('argos_monitor', $service); |
88: | } |
89: | |
90: | $ret = \Util_Process_Safe::exec(['monit', 'monitor', '%s'], $service); |
91: | |
92: | return $ret['success'] ?: error('Failed to start %s monitoring: %s ', $service, $ret['stderr']); |
93: | } |
94: | |
95: | |
96: | |
97: | |
98: | |
99: | |
100: | public function list_monitored(): array |
101: | { |
102: | if (!IS_CLI) { |
103: | return $this->query('argos_list_monitored'); |
104: | } |
105: | |
106: | $ret = \Util_Process::exec(['monit', 'summary', '-B']); |
107: | if (!$ret['success']) { |
108: | error('Failed to query monit'); |
109: | return []; |
110: | } |
111: | |
112: | if (!preg_match_all('/^\s(?!Service Name)(\S+)/m', $ret['stdout'], $matches, PREG_SET_ORDER)) { |
113: | return []; |
114: | } |
115: | |
116: | return array_column($matches, 1); |
117: | } |
118: | |
119: | |
120: | |
121: | |
122: | |
123: | |
124: | |
125: | public function headcount(bool $down = true): int |
126: | { |
127: | if (!IS_CLI) { |
128: | return $this->query('argos_headcount', $down); |
129: | } |
130: | |
131: | $ret = \Util_Process_Safe::exec(['monit', 'report', $down ? 'down' : 'up']); |
132: | |
133: | return $ret['success'] ? (int)$ret['stdout'] : (int)error('Failed to query Monit: %s', $ret['stderr']); |
134: | } |
135: | |
136: | |
137: | |
138: | |
139: | |
140: | |
141: | public function validate_all(): array |
142: | { |
143: | if (!IS_CLI) { |
144: | return $this->query('argos_validate_all'); |
145: | } |
146: | |
147: | |
148: | $ret = \Util_Process_Safe::exec(['monit', 'validate'], [0, 1]); |
149: | |
150: | if (!$ret['success']) { |
151: | error('Failed to validate services: %s ', $ret['stdout']); |
152: | return []; |
153: | } |
154: | |
155: | return $this->filterStatus($ret['stdout']); |
156: | } |
157: | |
158: | private function filterStatus(string $response): array |
159: | { |
160: | $serviceBuckets = preg_split('/^(?=Process|Filesystem|Program|System\s+)/m', $response); |
161: | array_shift($serviceBuckets); |
162: | $services = []; |
163: | foreach ($serviceBuckets as $bucket) { |
164: | if (!preg_match_all(Regex::ARGOS_SERVICE_STATUS, $bucket, $matches, PREG_SET_ORDER)) { |
165: | continue; |
166: | } |
167: | |
168: | $processName = null; |
169: | $fields = [ |
170: | |
171: | 'timing' => null |
172: | ]; |
173: | |
174: | foreach ($matches as $m) { |
175: | if ($m['proc']) { |
176: | $processName = $m['proc']; |
177: | $fields['type'] = strtolower($m['type']); |
178: | continue; |
179: | } |
180: | |
181: | $var = str_replace(' ', '_', $m['name']); |
182: | $value = $m['value']; |
183: | |
184: | switch ($var) { |
185: | case 'inodes_free': |
186: | $value = strtok($value, ' '); |
187: | case 'pid': |
188: | case 'parent_pid': |
189: | case 'uid': |
190: | case 'gid': |
191: | case 'threads': |
192: | case 'children': |
193: | case 'effective_uid': |
194: | case 'last_exit_value': |
195: | case 'inodes_total': |
196: | $value = (int)$value; |
197: | break; |
198: | case 'unix_socket_response_time': |
199: | case 'port_response_time': |
200: | $fields['timing'] = (float)strtok($value, ' '); |
201: | break; |
202: | |
203: | case 'cpu': |
204: | case 'cpu_total': |
205: | if (false === strpos($value, ' ')) { |
206: | $value = (float)$value / 100; |
207: | } |
208: | break; |
209: | case 'memory': |
210: | case 'memory_total': |
211: | $fields[$var . '_raw'] = (int)Formatter::changeBytes(str_replace(' ', '', substr($value, strpos($value, '[') + 1, -1))); |
212: | break; |
213: | case 'disk_write': |
214: | case 'disk_read': |
215: | $fields[$var . '_raw'] = (int)Formatter::changeBytes(str_replace(' ', '', |
216: | substr($value, $pos = strpos($value, '[') + 1, strrpos($value, ' ') - $pos))); |
217: | $fields[$var . '_bw_raw'] = (int)Formatter::changeBytes(str_replace(' ', '', |
218: | substr($value, 0, strpos($value, '/')))); |
219: | break; |
220: | case 'block_size': |
221: | $value = (int)Formatter::changeBytes($value, 'B'); |
222: | break; |
223: | case 'read': |
224: | case 'write': |
225: | $fields[$var . '_bw_raw'] = (int)Formatter::changeBytes( |
226: | str_replace(' ', '', substr($value, 0, strpos($value, '/'))) |
227: | ); |
228: | |
229: | $fields[$var . '_iops_raw'] = (float)strtok(substr( |
230: | $value, |
231: | $pos = strpos($value, ',')+1 |
232: | ), ' '); |
233: | |
234: | break; |
235: | case 'data_collected': |
236: | |
237: | $value = DateTime::createFromFormat('D, d M Y H:i:s', $value, |
238: | new DateTimeZone(TIMEZONE))->getTimestamp(); |
239: | break; |
240: | } |
241: | $fields[$var] = $value; |
242: | $fields['failed'] = $fields['status'] !== 'OK'; |
243: | $fields['monitored'] = 0 !== strcasecmp($fields['status'], 'not monitored'); |
244: | } |
245: | |
246: | $services[$processName] = $fields; |
247: | } |
248: | |
249: | return $services; |
250: | } |
251: | |
252: | public function config(string $backend, ?array $newparams) |
253: | { |
254: | deprecated_func('use config_relay'); |
255: | return $this->config_relay($backend, $newparams); |
256: | } |
257: | |
258: | |
259: | |
260: | |
261: | |
262: | |
263: | |
264: | |
265: | public function config_relay(string $backend, ?array $newparams) |
266: | { |
267: | if (!IS_CLI) { |
268: | return $this->query('argos_config_relay', $backend, $newparams); |
269: | } |
270: | |
271: | if (!\in_array($backend, \Opcenter\Argos\Config::get()->getBackends(), true)) { |
272: | return error("Unknown backend `%s'", $backend); |
273: | } |
274: | |
275: | |
276: | if (null === $newparams) { |
277: | $provider = array_get(\Opcenter\Argos\Config::get()->backend($backend), 'backend', $backend); |
278: | if (!\Opcenter\Argos\Config::get()->deleteBackend($backend)) { |
279: | warn("Failed to delete backend `%s'", $backend); |
280: | } |
281: | if (!\Opcenter\Argos\Config::get()->createBackend($provider, $backend)) { |
282: | return error("Failed to create backend `%s'", $backend); |
283: | } |
284: | |
285: | return true; |
286: | } |
287: | |
288: | $cfg = \Opcenter\Argos\Config::get(); |
289: | $backend = $cfg->backend($backend); |
290: | |
291: | foreach ($newparams as $k => $v) { |
292: | $backend[$k] = $v; |
293: | } |
294: | |
295: | return true; |
296: | } |
297: | |
298: | |
299: | |
300: | |
301: | |
302: | |
303: | |
304: | |
305: | |
306: | public function get_config_relay(string $backend, $param = null): ?array |
307: | { |
308: | if (!IS_CLI) { |
309: | return $this->query('argos_get_config_relay', $backend); |
310: | } |
311: | |
312: | if (!\in_array($backend, \Opcenter\Argos\Config::get()->getBackends(), true)) { |
313: | error("Unknown backend `%s'", $backend); |
314: | return null; |
315: | } |
316: | |
317: | |
318: | $cfg = \Opcenter\Argos\Config::get()->backend($backend)->toArray(); |
319: | |
320: | return $param ? array_get($cfg, $param, null) : $cfg; |
321: | } |
322: | |
323: | |
324: | |
325: | |
326: | |
327: | |
328: | |
329: | public function set_default_relay($backend) |
330: | { |
331: | if (!IS_CLI) { |
332: | return $this->query('argos_set_default_relay', $backend); |
333: | } |
334: | $backends = $this->get_backends(); |
335: | foreach ((array)$backend as $b) { |
336: | if (!\in_array($b, $backends, true)) { |
337: | return error("Invalid backend `%s'", $b); |
338: | } |
339: | } |
340: | |
341: | return \Opcenter\Argos\Config::get()->setDefault($backend); |
342: | } |
343: | |
344: | |
345: | |
346: | |
347: | |
348: | |
349: | public function get_backends(): ?array |
350: | { |
351: | if (!IS_CLI) { |
352: | return $this->query('argos_get_backends'); |
353: | } |
354: | |
355: | if (!($cfg = \Opcenter\Argos\Config::get())) { |
356: | return null; |
357: | } |
358: | |
359: | return $cfg->getBackends(); |
360: | } |
361: | |
362: | |
363: | |
364: | |
365: | |
366: | |
367: | |
368: | |
369: | public function create_backend(string $name, string $driver): bool |
370: | { |
371: | if (!IS_CLI) { |
372: | return $this->query('argos_create_backend', $name, $driver); |
373: | } |
374: | if (\in_array($name, $this->get_backends(), true)) { |
375: | return error("Backend `%s' already exists", $name); |
376: | } |
377: | if (!\in_array($driver, $this->get_backend_relays(), true)) { |
378: | return error("Invalid backend relay `%s'. Use get_backend_relays() to view all", $driver); |
379: | } |
380: | |
381: | $conf = \Opcenter\Argos\Config::get(); |
382: | $conf->createBackend($driver, $name); |
383: | $conf->sync(); |
384: | |
385: | return true; |
386: | } |
387: | |
388: | |
389: | |
390: | |
391: | |
392: | |
393: | public function get_backend_relays(): array |
394: | { |
395: | if (!IS_CLI) { |
396: | return $this->query('argos_get_backend_relays'); |
397: | } |
398: | |
399: | return \Opcenter\Argos\Backend::getBackends(); |
400: | } |
401: | |
402: | |
403: | |
404: | |
405: | |
406: | |
407: | |
408: | public function test(string $backend = null) |
409: | { |
410: | return $this->send('Argos test alert', $backend, '💯 test'); |
411: | } |
412: | |
413: | |
414: | |
415: | |
416: | |
417: | |
418: | |
419: | |
420: | |
421: | public function send(string $msg, string $backend = null, string $title = null) |
422: | { |
423: | if (DEMO_ADMIN_LOCK && posix_getuid()) { |
424: | return error("Demo may not send Argos relays"); |
425: | } |
426: | |
427: | if (!IS_CLI) { |
428: | return $this->query('argos_send', $msg, $backend, $title); |
429: | } |
430: | |
431: | if (!file_exists(\Opcenter\Argos\Config::CONFIGURATION_FILE)) { |
432: | return error( |
433: | "%s is missing - run argos.init Scope first. See Monitoring.md in docs/", |
434: | \Opcenter\Argos\Config::CONFIGURATION_FILE |
435: | ); |
436: | } |
437: | if ($title) { |
438: | $title = '-t ' . escapeshellarg($title); |
439: | } |
440: | if ($backend) { |
441: | $backend = '-b ' . escapeshellarg($backend); |
442: | } |
443: | |
444: | return array_get( |
445: | \Util_Process_Safe::exec('ntfy -c %(config)s ' . $title . ' ' . $backend . ' send %(msg)s', |
446: | [ |
447: | 'config' => \Opcenter\Argos\Config::CONFIGURATION_FILE, |
448: | 'msg' => $msg, |
449: | ] |
450: | ), |
451: | 'success', |
452: | false |
453: | ); |
454: | } |
455: | |
456: | |
457: | |
458: | |
459: | |
460: | |
461: | public function active(): bool |
462: | { |
463: | return (new \Opcenter\Dbus\Systemd)->read('monit', 'ActiveState', 'unit') === 'active'; |
464: | } |
465: | |
466: | |
467: | |
468: | |
469: | |
470: | |
471: | public function reset_all(): bool |
472: | { |
473: | $ret = \Util_Process_Safe::exec(['monit', 'monitor', 'all'], [0]); |
474: | return $ret['success'] ?: error('Failed to reset monitoring services: %s ', $ret['stdout']); |
475: | } |
476: | |
477: | public function _housekeeping() |
478: | { |
479: | $this->reset_all(); |
480: | } |
481: | |
482: | } |
483: | |