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