obmon  1.3.1
ObSensorSystem.cpp
1 #include <ObSensorSystem.h>
2 #include <cctype>
3 #include <fstream>
4 #include <sstream>
5 
6 ObSensorSystem::ObSensorSystem(std::string name) : ObSensor(name) {
10 
11  _sysinfo = glibtop_init();
12  char **devices = glibtop_get_netlist(&_netlist);
13  glibtop_netload *net;
14  if (_netloads.empty()) {
15  for (unsigned int i = 0; i < _netlist.number; i++) {
16  net = new glibtop_netload;
17  _netloads.push_back(net);
18  _netnames.push_back(devices[i]);
19  free(devices[i]);
20  }
21  free(devices);
22  }
23 
24  // =========================================================================
25  // Disk devices filtering initialization (R.I.P. English)
26  //
27  // std::map ensures, that if user enters same device twice, then only one
28  // entry will remain.
29  std::map<std::string, bool> allowedDisks_;
30  if (getenv("OBMON_ALLOWED_DISKS")) {
31  std::istringstream envAllowDisks(getenv("OBMON_ALLOWED_DISKS"));
32 
33  // Iterate over env var
34  for (std::string diskStr; std::getline(envAllowDisks, diskStr, ','); ) {
35  bool permitAny = false;
36 
37  if (diskStr.empty()) // Either disallow...
38  {
39  continue;
40  }
41  if (diskStr.back() == '+') // ... or allow all devices beginning w/ string
42  {
43  diskStr.pop_back();
44  permitAny = true;
45  }
46  // Add entry to map
47  allowedDisks_[diskStr] = permitAny;
48  }
49  } else // Import default permits
50  {
51  allowedDisks_["sd"] = false; // Only primary sd* device
52  allowedDisks_["nvme"] = false; // All NVMe devices
53  allowedDisks_["md"] = false; // All MegaRaid devices
54  }
55 
56  // Convert map to std::vector (yes, I know it's terrible...)
57  for (auto entry : allowedDisks_) {
58  allowedDisks.push_back(entry);
59  }
60 
61  // Sort vector by string length, so we get longest matches first
62  std::sort(allowedDisks.begin(), allowedDisks.end(),
63  [](std::pair<std::string, bool> const &a,
64  std::pair<std::string, bool> const &b) {
65  return a.first.length() > b.first.length();
66  });
67  // Some rudimentary logging
68  for (auto &a : allowedDisks) {
69  _logger->info("Allowed disk device [{}]:[{}]", a.first, a.second);
70  }
71 
72  // =========================================================================
73  // Network devices filtering initialization
74  //
75  if (getenv("OBMON_ALLOWED_NETWORK")) {
76  std::istringstream envAllowNICs(getenv("OBMON_ALLOWED_NETWORK"));
77  // Since NICs will be only explicit, we're gonna just create a vector.
78  // Also, this is not user-proof, but that shouldn't be a problem.
79  for (std::string netStr; std::getline(envAllowNICs, netStr, ','); ) {
80  allowedNICs.push_back(netStr);
81  }
82  }
83 
84  std::string const diskSpeedEnvStr = "OBMON_MAX_DISK_SPEED";
85  if (getenv(diskSpeedEnvStr.c_str())) {
86  // Get env and parse it to int
87  // Cast int to uint
88  uint32_t usrDiskSpeed =
89  static_cast<uint32_t>(std::stoi(getenv(diskSpeedEnvStr.c_str())));
90  if (usrDiskSpeed > 1) {
91  maxDiskSpeed = static_cast<double>(sz_MiB_kiB_(usrDiskSpeed));
92  }
93  }
94 }
95 
100 
101  for (auto net : _netloads)
102  delete net;
103 }
104 
109 
110  _first = new ObSensorSystem("value");
111  _second = new ObSensorSystem("value");
112  _change = new ObSensorSystem("change");
113 
114  return true;
115 }
116 
121 
122  _logger->trace("ObSensorSystem::process cpu ...");
123  glibtop_get_cpu(&_cpu);
124  _logger->trace("ObSensorSystem::process load average ...");
125  glibtop_get_loadavg(&_loadavg);
126  _logger->trace("ObSensorSystem::process mem ...");
127  glibtop_get_mem(&_mem);
128 
129  std::string net_name;
130  unsigned int i = 0;
131  for (auto net : _netloads) {
132  net_name = _netnames.at(i++);
133  _logger->trace("ObSensorSystem::process net {}", net_name.data());
134  glibtop_get_netload(net, net_name.data());
135  }
136 
137  // Drive stats
138  // Intermediate variables... Ugly, but working
139  uint64_t majorID = 0;
140  uint64_t minorID = 0;
141  std::string devName;
142 
143  uint64_t rCount = 0;
144  uint64_t rMerged = 0;
145  uint64_t rSectors = 0;
146  uint64_t rTime = 0;
147 
148  uint64_t wCount = 0;
149  uint64_t wMerged = 0;
150  uint64_t wSectors = 0;
151  uint64_t wTime = 0;
152 
153  uint64_t currentIOCount = 0;
154  uint64_t timeIO = 0;
155  uint64_t weightedTimeIO = 0;
156 
157  std::ifstream procDisksFile("/proc/diskstats");
158 
159  // I know what you wanna say, but since it works (and it actually works pretty
160  // well) then I don't recommend touching it unless you know what you're doing.
161  while ( //
162  procDisksFile // Read from /proc/diskstats file
163  >> majorID >> minorID >> devName // Get dev/part info
164  >> rCount >> rMerged >> rSectors >> rTime // Get read stats
165  >> wCount >> wMerged >> wSectors >> wTime // Get write stats
166  >> currentIOCount >> timeIO >> weightedTimeIO // Get total IO stats
167  ) {
168  bool allowInfo = false;
169 
170  for (auto permit : allowedDisks) {
171  if (devName.find(permit.first) == 0) {
172  if (permit.second == true) {
173  allowInfo = true;
174  } else {
175  if (permit.first == devName) {
176  allowInfo = true;
177  }
178 
179  if (permit.first == "sd") {
180  if (devName.back() > '9') {
181  allowInfo = true;
182  }
183  } else if (permit.first == "nvme" || permit.first == "md") {
184  if (devName.find('p') == std::string::npos) {
185  allowInfo = true;
186  }
187  }
188  }
189 
190  break;
191  }
192  }
193 
194  if (!/*no*/ allowInfo) {
195  _logger->trace("Disk: Denied [{}:{}:{}]", majorID, minorID, devName);
196  continue;
197  }
198 
199  _logger->debug("Disk: Allowed [{}:{}:{}]", majorID, minorID, devName);
200 
201  // Set shortcut for current device (References FTW!)
202  auto &disk = diskInfos[devName];
203 
204  // Talk about redundancy...
205  disk.majorID = majorID;
206  disk.minorID = minorID;
207  disk.devName = devName;
208  disk.rCount = rCount;
209  disk.rMerged = rMerged;
210  disk.rSectors = rSectors;
211  disk.rTime = rTime;
212  disk.wCount = wCount;
213  disk.wMerged = wMerged;
214  disk.wSectors = wSectors;
215  disk.wTime = wTime;
216  disk.currentIOCount = currentIOCount;
217  disk.timeIO = timeIO;
218  disk.weightedTimeIO = weightedTimeIO;
219  }
220  procDisksFile.close();
221 }
222 
223 void ObSensorSystem::speed(ObSensor *s1, ObSensor *s2, unsigned int timeout) {
227 
228  ObSensorSystem *ss1 = static_cast<ObSensorSystem *>(s1);
229  ObSensorSystem *ss2 = static_cast<ObSensorSystem *>(s2);
230  glibtop_cpu cpu1 = ss1->cpu();
231  glibtop_cpu cpu2 = ss2->cpu();
232 
233  _cpu.total = (cpu2.total - cpu1.total);
234  _cpu.sys = (cpu2.sys - cpu1.sys);
235  _cpu.user = (cpu2.user - cpu1.user);
236  _cpu.nice = (cpu2.nice - cpu1.nice);
237  _cpu.idle = (cpu2.idle - cpu1.idle);
238  _cpu.iowait = (cpu2.iowait - cpu1.iowait);
239  _cpu.irq = (cpu2.irq - cpu1.irq);
240  _cpu.softirq = (cpu2.softirq - cpu1.softirq);
241  _cpu.frequency = (cpu2.frequency - cpu1.frequency);
242 
243  glibtop_mem mem1 = ss1->mem();
244  glibtop_mem mem2 = ss2->mem();
245 
246  _mem.total = (mem2.total - mem1.total) * 1000 / timeout;
247  _mem.used = (mem2.used - mem1.used) * 1000 / timeout;
248  _mem.free = (mem2.free - mem1.free) * 1000 / timeout;
249  _mem.shared = (mem2.shared - mem1.shared) * 1000 / timeout;
250  _mem.buffer = (mem2.buffer - mem1.buffer) * 1000 / timeout;
251  _mem.cached = (mem2.cached - mem1.cached) * 1000 / timeout;
252  _mem.locked = (mem2.locked - mem1.locked) * 1000 / timeout;
253 
254  std::string net_name;
255  glibtop_netload *net1, *net2;
256  unsigned int i = 0;
257  for (auto net : _netloads) {
258  net_name = _netnames.at(i);
259  net1 = ss1->netloads().at(i);
260  net2 = ss2->netloads().at(i);
261  if (net) {
262  net->address = (net2->address - net1->address) * 1000 / timeout;
263  net->bytes_in = (net2->bytes_in - net1->bytes_in) * 1000 / timeout;
264  net->bytes_out = (net2->bytes_out - net1->bytes_out) * 1000 / timeout;
265  net->errors_in = (net2->errors_in - net1->errors_in) * 1000 / timeout;
266  net->errors_out = (net2->errors_out - net1->errors_out) * 1000 / timeout;
267  net->packets_in = (net2->packets_in - net1->packets_in) * 1000 / timeout;
268  net->packets_out =
269  (net2->packets_out - net1->packets_out) * 1000 / timeout;
270  }
271  i++;
272  }
273 
274  auto newDisks = ss2->disks();
275  auto oldDisks = ss1->disks();
276  for (auto &newDisk_ : newDisks) {
277  auto &newDisk = newDisk_.second;
278  auto &oldDisk = oldDisks[newDisk.devName];
279  auto &disk = diskInfos[newDisk.devName];
280 
281  // For now process only r/w sector count...
282  disk.majorID = newDisk.majorID;
283  disk.minorID = newDisk.minorID;
284  disk.devName = newDisk.devName;
285  disk.rSectors = newDisk.rSectors - oldDisk.rSectors;
286  disk.wSectors = newDisk.wSectors - oldDisk.wSectors;
287  }
288 }
289 
290 std::string ObSensorSystem::json(const std::string name) const {
294 
295  using namespace fmt::literals;
296  std::string json;
297  double percent = static_cast<double>(_cpu.total);
298 
299  json += "\"" + name + "\": {";
300 
301  if (type() == SensorType::SPEED) {
302  double busy =
303  ((static_cast<double>(_cpu.total) / percent * 100.0) > 1.0) ? 1.0 : 0.0;
304  // = = = = = = = = = = CPU STAT = = = = = = = = = =
305  json += fmt::format(
306  R"("cpu": {{)"
307  R"("total": {{ "value": {totalV:.3f}, "alpha": {totalA:.2f} }},)"
308  R"("sys": {{ "value": {sysV:.3f}, "alpha": {sysA:.2f} }},)"
309  R"("user": {{ "value": {userV:.3f}, "alpha": {userA:.2f} }},)"
310  R"("nice": {{ "value": {niceV:.3f}, "alpha": {niceA:.2f} }},)"
311  R"("idle": {{ "value": {idleV:.3f}, "alpha": {idleA:.2f} }},)"
312  R"("frequency": {{ "value": {freqV:.3f}, "alpha": 0 }},)"
313 
314  R"("iowait": {{ "value": {iowaitV:.3f}, "alpha": {iowaitA:.2f} }},)"
315  R"("busy": {{ "value": {busyV}, "alpha": {busyA} }})"
316  R"(}}, )",
317  // ----- Variables ----- //
318  "totalV"_a = static_cast<double>(_cpu.total) / percent * 100.0,
319  "totalA"_a = static_cast<double>(_cpu.total) / percent,
320  "sysV"_a = static_cast<double>(_cpu.sys) / percent * 100.0,
321  "sysA"_a = static_cast<double>(_cpu.sys) / percent,
322  "userV"_a = static_cast<double>(_cpu.user) / percent * 100.0,
323  "userA"_a = static_cast<double>(_cpu.user) / percent,
324  "niceV"_a = static_cast<double>(_cpu.nice) / percent * 100.0,
325  "niceA"_a = static_cast<double>(_cpu.nice) / percent,
326  "idleV"_a = static_cast<double>(_cpu.idle) / percent * 100.0,
327  "idleA"_a = 1.0 - static_cast<double>(_cpu.idle) / percent,
328  "iowaitV"_a = static_cast<double>(_cpu.iowait) / percent * 100.0,
329  "iowaitA"_a = static_cast<double>(_cpu.iowait) / percent,
330  "freqV"_a = static_cast<double>(_cpu.frequency), "busyV"_a = busy,
331  "busyA"_a = busy
332  // ----- Clang-format force newline ----- //
333  );
334  }
335 
336  if (type() == SensorType::VALUE) {
337  // = = = = = = = = = = AVG STAT = = = = = = = = = =
338  double ncpu = _sysinfo->real_ncpu + 1;
339  auto &avg = _loadavg.loadavg;
340  json += fmt::format(R"("load1": {{ "value": {:.3f}, "alpha": {:.2f} }},)"
341  R"("load5": {{ "value": {:.3f}, "alpha": {:.2f} }},)"
342  R"("load15": {{ "value": {:.3f}, "alpha": {:.2f} }},)"
343  R"("cpus": {},)",
344  /* Load 1 */
345  avg[0], avg[0] / ncpu,
346  /* Load 5 */
347  avg[1], avg[1] / ncpu,
348  /* Load 15 */
349  avg[2], avg[2] / ncpu,
350  /* number of cpus */
351  ncpu
352  // ----- Clang-format force newline ----- //
353  );
354  }
355 
356  if (type() == SensorType::VALUE) {
357  // = = = = = = = = = = MEM STAT = = = = = = = = = =
358  json += fmt::format(
359  R"("mem": {{)"
360  R"("total": {{ "value": {totalV}, "alpha": 1.0 }},)"
361  R"("used": {{ "value": {usedV}, "alpha": {usedA:.2f} }},)"
362  R"("free": {{ "value": {freeV}, "alpha": {freeA:.2f} }},)"
363  R"("shared": {{ "value": {sharedV}, "alpha": {sharedA:.2f} }},)"
364  R"("buffer": {{ "value": {bufferV}, "alpha": {bufferA:.2f} }},)"
365  R"("cached": {{ "value": {cachedV}, "alpha": {cachedA:.2f} }},)"
366  R"("locked": {{ "value": {lockedV}, "alpha": {lockedA:.2f} }})"
367  R"(}},)",
368  // ----- Variables ----- //
369  "totalV"_a = _mem.total, // CF Force newline
370  "usedV"_a = _mem.used,
371  "usedA"_a = static_cast<double>(_mem.used) / _mem.total,
372  "freeV"_a = _mem.free,
373  "freeA"_a = 1.0 - static_cast<double>(_mem.free) / _mem.total,
374  "sharedV"_a = _mem.shared,
375  "sharedA"_a = static_cast<double>(_mem.shared) / _mem.total,
376  "bufferV"_a = _mem.buffer,
377  "bufferA"_a = static_cast<double>(_mem.buffer) / _mem.total,
378  "cachedV"_a = _mem.cached,
379  "cachedA"_a = static_cast<double>(_mem.cached) / _mem.total,
380  "lockedV"_a = _mem.locked,
381  "lockedA"_a = static_cast<double>(_mem.locked) / _mem.total
382  // ----- Clang-format force newline ----- //
383  );
384  }
385 
386  if (type() == SensorType::SPEED) {
387  // = = = = = = = = = = NET STAT = = = = = = = = = =
388  // ---------- BEGIN Network ARRAY ----------
389  json += "\"net\" : [";
390 
391  double const MAX_BYTES_ALPHA = 100 * 1048576; // 100 MB
392 
393  std::string net_name;
394  unsigned int i = 0;
395  for (auto net : _netloads) {
396  net_name = _netnames.at(i);
397  if (allowedNICs.empty() ||
398  std::find(allowedNICs.begin(), allowedNICs.end(), net_name) !=
399  allowedNICs.end()) {
400  if (net) {
401  json += fmt::format(
402  R"({{)"
403  R"("adapter": "{name}", "address": "{address}",)"
404  R"("bytes_in": {{ "value": {inV}, "alpha": {inA:.2f} }},)"
405  R"("bytes_out": {{ "value": {outV}, "alpha": {outA:.2f} }},)"
406  R"("errors_in": {{ "value": {errI}, "alpha": 0 }},)"
407  R"("errors_out": {{ "value": {errO}, "alpha": 0 }},)"
408  R"("packets_in": {{ "value": {pacI}, "alpha": 0 }},)"
409  R"("packets_out": {{ "value": {pacO}, "alpha": 0 }})"
410  R"(}},)",
411  // ----- Variables ----- //
412  "name"_a = net_name, "address"_a = net->address,
413  "inV"_a = net->bytes_in, // CF Force newline
414  "inA"_a = static_cast<double>(net->bytes_in) / MAX_BYTES_ALPHA,
415  "outV"_a = net->bytes_out, // CF Force newline
416  "outA"_a = static_cast<double>(net->bytes_out) / MAX_BYTES_ALPHA,
417  "errI"_a = net->errors_in, "errO"_a = net->errors_out,
418  "pacI"_a = net->packets_in, "pacO"_a = net->packets_out
419  // ----- Clang-format force newline ----- //
420  );
421  }
422  }
423 
424  i++;
425  } // END for (network adapter)
426  // ---------- END Network ARRAY ----------
427 
428  if (json.back() == ',')
429  json.pop_back();
430  json += "],";
431  }
432 
433  if (type() == SensorType::SPEED) {
434  // = = = = = = = = = = DISK STAT = = = = = = = = = =
435  // ---------- BEGIN Disk ARRAY ----------
436  json += R"("disks" : [)";
437 
438  for (auto &disk_ : diskInfos) {
439  auto &disk = disk_.second;
440 
441  double raSectors = disk.rSectors / maxDiskSpeed;
442  double rwSectors = disk.wSectors / maxDiskSpeed;
443 
444  json += fmt::format( //
445  R"({{)"
446  R"("majorID": {majorID}, "minorID": {minorID},)"
447  R"("deviceName": "{devName}",)"
448 
449  R"("bytes_read": {{ "value": {rSectors}, "alpha": {raSectors:.2f} }},)"
450 
451  R"("bytes_write": {{ "value": {wSectors}, "alpha": {rwSectors:.2f} }})"
452  R"(}},)",
453  // ----- Variables ----- //
454  "majorID"_a = disk.majorID, "minorID"_a = disk.minorID,
455  "devName"_a = disk.devName, "rSectors"_a = disk.rSectors * 1024,
456  "raSectors"_a = raSectors > 1.0 ? 1.0 : raSectors,
457  "wSectors"_a = disk.wSectors * 1024,
458  "rwSectors"_a = rwSectors > 1.0 ? 1.0 : rwSectors
459  // -------------------- //
460  );
461  }
462 
463  if (json.back() == ',') {
464  json.pop_back();
465  }
466  json += "]";
467  // ---------- END Disk ARRAY ----------
468  }
469 
470  if (json.back() == ',') {
471  json.pop_back();
472  }
473  // ---------- END ROOT ----------
474  json += "}";
475 
476  return json;
477 }
glibtop_mem mem() const
returns glibtop_mem
void process() override
Process function.
std::shared_ptr< spdlog::logger > _logger
Pointer to spd logger.
Definition: ObSensor.h:56
System Obmon sensor class
ObSensorSystem(std::string name={"sys"})
glibtop_cpu cpu() const
returns glibtop_cpu
std::vector< std::string > _netnames
list of network names
bool init() override
glibtop * _sysinfo
Sys info from glitop.
void speed(ObSensor *s1, ObSensor *s2, unsigned int timeout=1000) override
Calculate time change (speed)
ObSensor * _first
Pointer to first sensor.
Definition: ObSensor.h:60
virtual ~ObSensorSystem() override
ObSensor * _change
Pointer to change sensor.
Definition: ObSensor.h:62
glibtop_mem _mem
Mem info from glitop.
ObSensor * _second
Pointer to second sensor.
Definition: ObSensor.h:61
std::string json(const std::string name={"static"}) const override
std::string name() const
Returns name of sensor.
Definition: ObSensor.h:38
SensorType type() const
Returns sensor type.
Definition: ObSensor.h:44
glibtop_cpu _cpu
CPU info from glitop.
Base Obmon sensor class
Definition: ObSensor.h:19
glibtop_loadavg _loadavg
Load average info from glibtop.
std::vector< glibtop_netload * > netloads() const
returns netloads
glibtop_netlist _netlist
NetList from glitop.
std::vector< glibtop_netload * > _netloads
List of netload.