1: <?php
2: declare(strict_types=1);
3: /**
4: * +------------------------------------------------------------+
5: * | apnscp |
6: * +------------------------------------------------------------+
7: * | Copyright (c) Apis Networks |
8: * +------------------------------------------------------------+
9: * | Licensed under Artistic License 2.0 |
10: * +------------------------------------------------------------+
11: * | Author: Matt Saladna (msaladna@apisnetworks.com) |
12: * +------------------------------------------------------------+
13: */
14:
15: use Daphnie\Collector;
16: use Daphnie\Metrics\Memory as MemoryMetric;
17: use Module\Skeleton\Contracts\Tasking;
18: use Opcenter\System\Memory;
19:
20: /**
21: * Statistics/hardware information
22: *
23: * @package core
24: */
25: class Stats_Module extends Module_Skeleton implements Tasking
26: {
27: /**
28: * {{{ void __construct(void)
29: *
30: * @ignore
31: */
32: public function __construct()
33: {
34: parent::__construct();
35: $this->exportedFunctions = array(
36: '*' => PRIVILEGE_ALL
37: );
38: }
39:
40: /**
41: * Get OS release version
42: *
43: * @return array
44: */
45: public function release(): array
46: {
47: $contents = file('/etc/os-release', FILE_SKIP_EMPTY_LINES|FILE_IGNORE_NEW_LINES);
48: $vars = array_build($contents, function ($index, $line) {
49: [$k,$v] = explode('=', $line);
50: return [
51: strtolower($k),
52: \Util_Conf::inferType(trim($v, '"\''))
53: ];
54: });
55: $vars['id_like'] = explode(' ', $vars['id_like']);
56:
57: return $vars;
58: }
59:
60: /**
61: * array get_partition_information
62: *
63: * @return array
64: */
65: public function get_partition_information()
66: {
67: $fsoptions = array();
68:
69: $buffer = file('/proc/mounts', FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
70:
71: $results = [];
72: $knownMounts = [];
73: foreach ($buffer as $line) {
74: if (0 !== strncmp($line, '/dev/', 5)) {
75: continue;
76: }
77:
78: [$dev, $mpoint, $type, $options, $fsck, $_] = explode(' ', $line);
79:
80: if (isset($knownMounts[$dev])) {
81: continue;
82: }
83:
84: $knownMounts[$dev] = 1;
85:
86: $total = disk_total_space($mpoint)/1024;
87: if (!$total) {
88: // device mount, hugetlbfs/devpts
89: continue;
90: }
91: $free = disk_free_space($mpoint)/1024;
92:
93: $results[] = [
94: 'disk' => $dev,
95: 'size' => (int)$total,
96: 'used' => (int)($total-$free),
97: 'free' => (int)$free,
98: 'percent' => round(($total - $free)/$total * 100) . '%',
99: 'mount' => $mpoint,
100: 'fstype' => $type,
101: 'options' => $options
102: ];
103: }
104:
105: return $results;
106: }
107:
108: public function get_spamassassin_stats()
109: {
110: $sastats = array();
111: if (!file_exists('/tmp/sa-stats')) {
112: return $sastats;
113: }
114: $fp = fopen('/tmp/sa-stats', 'r');
115: while (false !== ($line = fgets($fp))) {
116: $buffer = ' ';
117: if (false !== strpos($line, 'Period Beginning')) {
118: $data = explode(':', $line);
119: $sastats['begin_date'] = trim(implode(':', array_slice($data, 1)));
120: } else if (false !== strpos($line, 'Period Ending')) {
121: $data = explode(':', $line);
122: $sastats['end_date'] = trim(implode(':', array_slice($data, 1)));
123: } else {
124: if (false !== strpos($line, 'Reporting Period')) {
125: /** Get the whole section of reporting period... */
126: $line = fgets($fp);
127: fgets($fp); // remove --------- line
128: while (false !== ($line = fgets($fp))) {
129: if ($buffer[strlen($buffer) - 2] == "\n" && $buffer[strlen($buffer) - 1] == "\n" && $line == "\n") {
130: break;
131: }
132: $buffer .= $line;
133: }
134: $sastats['reporting_information'] = trim($buffer);
135: } else {
136: if (false !== strpos($line, 'Statistics by Hour')) {
137: $line = fgets($fp); // remove --------- line
138: while (false !== ($line = fgets($fp))) {
139:
140: if ($buffer[strlen($buffer) - 2] == "\n" && $buffer[strlen($buffer) - 1] == "\n" && $line == "\n") {
141: break;
142: }
143: $buffer .= $line;
144: }
145: $sastats['stats_by_hour'] = trim($buffer);
146: } else {
147: if (false !== strpos($line, 'Done. Report generated')) {
148: while (false !== ($line = fgets($fp))) {
149: /**
150: * nasty hack, we shouldn't assume TOP [SPAM, HAM]
151: * RULES FIRED is coming next
152: */
153: if (false !== strpos($line, 'TOP')) {
154: $bufftmp = $buffer;
155: $buffer = $line;
156: while (false !== ($line = fgets($fp))) {
157: $buffer .= $line;
158: }
159: $sastats['rule_information'] = trim($buffer);
160: $buffer = $bufftmp;
161: break;
162: }
163: $buffer .= $line;
164: }
165: $sastats['reporting_information'] .= "\n\n" . trim($buffer);
166: } else {
167: if (false !== strpos($line, 'TOP')) {
168:
169: }
170: }
171: }
172: }
173: }
174: }
175: fclose($fp);
176:
177: return $sastats;
178: }
179:
180: /**
181: * array get_memory_information()
182: *
183: * @return array
184: */
185: public function get_memory_information()
186: {
187: if (false === ($fd = fopen('/proc/meminfo', 'r'))) {
188: return new FileError('/proc/meminfo does not exist');
189: }
190:
191: $stats = Memory::stats();
192: $results['swap'] = array(
193: 'total' => 0,
194: 'used' => 0,
195: 'percent' => 0
196: );
197: $results['devswap'] = array();
198:
199: $results['ram'] = array_combine(
200: ['total', 't_free', 'cached', 'buffers', 'available'],
201: array_only($stats, ['memtotal', 'memfree', 'cached', 'buffers', 'memavailable'])
202: );
203:
204: if (!empty($stats['swaptotal'])) {
205: $results['swap'] = array_combine(
206: ['total', 'free'],
207: array_only($stats, ['swaptotal', 'swapfree'])
208: );
209: }
210:
211: $results['ram']['t_used'] = $results['ram']['total'] - $results['ram']['t_free'];
212: $results['ram']['percent'] = round(($results['ram']['t_used'] * 100) / $results['ram']['total']);
213: $results['swap']['used'] = $results['swap']['total'] - $results['swap']['free'];
214: if ($results['swap']['total'] > 0) {
215: $results['swap']['percent'] = round(($results['swap']['used'] * 100) / $results['swap']['total']);
216: }
217:
218: // values for splitting memory usage
219: if (isset($results['ram']['cached']) && isset($results['ram']['buffers'])) {
220: $results['ram']['app'] = $results['ram']['t_used'] - $results['ram']['cached'] - $results['ram']['buffers'];
221: $results['ram']['app_percent'] = round(($results['ram']['app'] * 100) / $results['ram']['total']);
222: $results['ram']['buffers_percent'] = round(($results['ram']['buffers'] * 100) / $results['ram']['total']);
223: $results['ram']['cached_percent'] = round(($results['ram']['cached'] * 100) / $results['ram']['total']);
224: }
225:
226: $swaps = file('/proc/swaps');
227: for ($i = 1, $n = count($swaps); $i < $n; $i++) {
228: $ar_buf = preg_split('/\s+/', $swaps[$i], 6);
229: $results['devswap'][$ar_buf[0]] = array();
230: $results['devswap'][$ar_buf[0]]['total'] = $ar_buf[2];
231: $results['devswap'][$ar_buf[0]]['used'] = $ar_buf[3];
232: $results['devswap'][$ar_buf[0]]['free'] = ($results['devswap'][$ar_buf[0]]['total'] - $results['devswap'][$ar_buf[0]]['used']);
233: $results['devswap'][$ar_buf[0]]['percent'] = round(($ar_buf[3] * 100) / $ar_buf[2]);
234: }
235:
236: return $results;
237: }
238:
239: /**
240: * Report virtual memory statistics
241: *
242: * @return array
243: */
244: public function vmstat(): array {
245: $stat = file('/proc/stat', FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
246: $ts = microtime(true);
247: $fields = preg_split('/\s+/', $stat[0]);
248:
249: $labels = [
250: 'user',
251: 'nice',
252: 'system',
253: 'idle',
254: 'iowait',
255: 'irq',
256: 'softirq',
257: 'steal',
258: 'guest',
259: 'guest_nice'
260: ];
261: array_shift($fields);
262: $values = array_combine($labels, array_map(static function ($v) {
263: return (int)$v;
264: }, $fields)
265: );
266: $values['ts'] = $ts;
267:
268:
269: return $values;
270: }
271: /**
272: * array get_network_device_information
273: *
274: * @return array key, device name, values:
275: * [tx,rx]_bytes, [tx,rx]_packets,
276: * [tx,rx]_errs, [tx,rx]_drop
277: */
278: public function get_network_device_information()
279: {
280: $results = array();
281:
282: if (false === ($fd = fopen('/proc/net/dev', 'r'))) {
283: return new IOError('/proc/net/dev does not exist');
284: }
285:
286: while ($buf = fgets($fd, 4096)) {
287: if (preg_match('/:/', $buf)) {
288: list($dev_name, $stats_list) = explode(":", $buf, 2);
289: $stats = preg_split('/\s+/', trim($stats_list));
290: $results[$dev_name] = array();
291:
292: $results[$dev_name]['rx_bytes'] = $stats[0];
293: $results[$dev_name]['rx_packets'] = $stats[1];
294: $results[$dev_name]['rx_errs'] = $stats[2];
295: $results[$dev_name]['rx_drop'] = $stats[3];
296:
297: $results[$dev_name]['tx_bytes'] = $stats[8];
298: $results[$dev_name]['tx_packets'] = $stats[9];
299: $results[$dev_name]['tx_errs'] = $stats[10];
300: $results[$dev_name]['tx_drop'] = $stats[11];
301:
302: $results[$dev_name]['errs'] = $stats[2] + $stats[10];
303: $results[$dev_name]['drop'] = $stats[3] + $stats[11];
304: }
305: }
306:
307: return $results;
308:
309: }
310:
311: public function _cron(Cronus $c)
312: {
313: if (!TELEMETRY_ENABLED) {
314: return;
315: }
316: $collector = new Collector(PostgreSQL::pdo());
317: $status = Memory::stats();
318: foreach (MemoryMetric::getAttributeMap() as $attr => $metric) {
319: $val = $status[$metric];
320:
321: if ($val instanceof Closure) {
322: $val = $val($status);
323: }
324: $collector->add("memory-{$attr}", null, $val);
325: }
326: }
327: }