| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "collectors/all.h" |
| 4 | #include "libnetdata/libnetdata.h" |
| 5 | |
| 6 | #include <linux/capability.h> |
| 7 | #include <syslog.h> |
| 8 | #include <systemd/sd-bus.h> |
| 9 | |
| 10 | #define ND_SD_JOURNAL_WORKER_THREADS 2 |
| 11 | #define ND_SD_UNITS_FUNCTION_DESCRIPTION "View the status of systemd units" |
| 12 | #define ND_SD_UNITS_FUNCTION_NAME "systemd-list-units" |
| 13 | #define ND_SD_UNITS_DEFAULT_TIMEOUT 30 |
| 14 | |
| 15 | #define ND_SD_UNITS_MAX_PARAMS 10 |
| 16 | #define ND_SD_UNITS_DBUS_TYPES "(ssssssouso)" |
| 17 | |
| 18 | netdata_mutex_t stdout_mutex; |
| 19 | |
| 20 | static void __attribute__((constructor)) init_mutex(void) { |
| 21 | netdata_mutex_init(&stdout_mutex); |
| 22 | } |
| 23 | |
| 24 | static void __attribute__((destructor)) destroy_mutex(void) { |
| 25 | netdata_mutex_destroy(&stdout_mutex); |
| 26 | } |
| 27 | |
| 28 | // ---------------------------------------------------------------------------- |
| 29 | // copied from systemd: string-table.h |
| 30 | |
| 31 | typedef char sd_char; |
| 32 | #define XCONCATENATE(x, y) x##y |
| 33 | #define CONCATENATE(x, y) XCONCATENATE(x, y) |
| 34 | |
| 35 | #ifndef __COVERITY__ |
| 36 | #define VOID_0 ((void)0) |
| 37 | #else |
| 38 | #define VOID_0 ((void *)0) |
| 39 | #endif |
| 40 | |
| 41 | #define ELEMENTSOF(x) \ |
| 42 | (__builtin_choose_expr(!__builtin_types_compatible_p(typeof(x), typeof(&*(x))), sizeof(x) / sizeof((x)[0]), VOID_0)) |
| 43 | |
| 44 | #define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq)) |
| 45 | #define UNIQ __COUNTER__ |
| 46 | #define __CMP(aq, a, bq, b) \ |
| 47 | ({ \ |
| 48 | const typeof(a) UNIQ_T(A, aq) = (a); \ |
| 49 | const typeof(b) UNIQ_T(B, bq) = (b); \ |
| 50 | UNIQ_T(A, aq)<UNIQ_T(B, bq) ? -1 : UNIQ_T(A, aq)> UNIQ_T(B, bq) ? 1 : 0; \ |
| 51 | }) |
| 52 | #define CMP(a, b) __CMP(UNIQ, (a), UNIQ, (b)) |
| 53 | |
| 54 | static inline int strcmp_ptr(const sd_char *a, const sd_char *b) |
| 55 | { |
| 56 | if (a && b) |
| 57 | return strcmp(a, b); |
| 58 | |
| 59 | return CMP(a, b); |
| 60 | } |
| 61 | |
| 62 | static inline bool streq_ptr(const sd_char *a, const sd_char *b) |
| 63 | { |
| 64 | return strcmp_ptr(a, b) == 0; |
| 65 | } |
| 66 | |
| 67 | static ssize_t string_table_lookup(const char *const *table, size_t len, const char *key) |
| 68 | { |
| 69 | if (!key || !*key) |
| 70 | return -EINVAL; |
| 71 | |
| 72 | for (size_t i = 0; i < len; ++i) |
| 73 | if (streq_ptr(table[i], key)) |
| 74 | return (ssize_t)i; |
| 75 | |
| 76 | return -EINVAL; |
| 77 | } |
| 78 | |
| 79 | /* For basic lookup tables with strictly enumerated entries */ |
| 80 | #define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name, type, scope) \ |
| 81 | scope const char *name##_to_string(type i) \ |
| 82 | { \ |
| 83 | if (i < 0 || i >= (type)ELEMENTSOF(name##_table)) \ |
| 84 | return NULL; \ |
| 85 | return name##_table[i]; \ |
| 86 | } |
| 87 | |
| 88 | #define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name, type, scope) \ |
| 89 | scope type name##_from_string(const char *s) \ |
| 90 | { \ |
| 91 | return (type)string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \ |
| 92 | } |
| 93 | |
| 94 | #define _DEFINE_STRING_TABLE_LOOKUP(name, type, scope) \ |
| 95 | _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name, type, scope) \ |
| 96 | _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name, type, scope) |
| 97 | |
| 98 | #define DEFINE_STRING_TABLE_LOOKUP(name, type) _DEFINE_STRING_TABLE_LOOKUP(name, type, ) |
| 99 | |
| 100 | // ---------------------------------------------------------------------------- |
| 101 | // copied from systemd: unit-def.h |
| 102 | |
| 103 | typedef enum UnitType { |
| 104 | UNIT_SERVICE, |
| 105 | UNIT_MOUNT, |
| 106 | UNIT_SWAP, |
| 107 | UNIT_SOCKET, |
| 108 | UNIT_TARGET, |
| 109 | UNIT_DEVICE, |
| 110 | UNIT_AUTOMOUNT, |
| 111 | UNIT_TIMER, |
| 112 | UNIT_PATH, |
| 113 | UNIT_SLICE, |
| 114 | UNIT_SCOPE, |
| 115 | _UNIT_TYPE_MAX, |
| 116 | _UNIT_TYPE_INVALID = -EINVAL, |
| 117 | } UnitType; |
| 118 | |
| 119 | typedef enum UnitLoadState { |
| 120 | UNIT_STUB, |
| 121 | UNIT_LOADED, |
| 122 | UNIT_NOT_FOUND, /* error condition #1: unit file not found */ |
| 123 | UNIT_BAD_SETTING, /* error condition #2: we couldn't parse some essential unit file setting */ |
| 124 | UNIT_ERROR, /* error condition #3: other "system" error, catchall for the rest */ |
| 125 | UNIT_MERGED, |
| 126 | UNIT_MASKED, |
| 127 | _UNIT_LOAD_STATE_MAX, |
| 128 | _UNIT_LOAD_STATE_INVALID = -EINVAL, |
| 129 | } UnitLoadState; |
| 130 | |
| 131 | typedef enum UnitActiveState { |
| 132 | UNIT_ACTIVE, |
| 133 | UNIT_RELOADING, |
| 134 | UNIT_INACTIVE, |
| 135 | UNIT_FAILED, |
| 136 | UNIT_ACTIVATING, |
| 137 | UNIT_DEACTIVATING, |
| 138 | UNIT_MAINTENANCE, |
| 139 | _UNIT_ACTIVE_STATE_MAX, |
| 140 | _UNIT_ACTIVE_STATE_INVALID = -EINVAL, |
| 141 | } UnitActiveState; |
| 142 | |
| 143 | typedef enum AutomountState { |
| 144 | AUTOMOUNT_DEAD, |
| 145 | AUTOMOUNT_WAITING, |
| 146 | AUTOMOUNT_RUNNING, |
| 147 | AUTOMOUNT_FAILED, |
| 148 | _AUTOMOUNT_STATE_MAX, |
| 149 | _AUTOMOUNT_STATE_INVALID = -EINVAL, |
| 150 | } AutomountState; |
| 151 | |
| 152 | typedef enum DeviceState { |
| 153 | DEVICE_DEAD, |
| 154 | DEVICE_TENTATIVE, /* mounted or swapped, but not (yet) announced by udev */ |
| 155 | DEVICE_PLUGGED, /* announced by udev */ |
| 156 | _DEVICE_STATE_MAX, |
| 157 | _DEVICE_STATE_INVALID = -EINVAL, |
| 158 | } DeviceState; |
| 159 | |
| 160 | typedef enum MountState { |
| 161 | MOUNT_DEAD, |
| 162 | MOUNT_MOUNTING, /* /usr/bin/mount is running, but the mount is not done yet. */ |
| 163 | MOUNT_MOUNTING_DONE, /* /usr/bin/mount is running, and the mount is done. */ |
| 164 | MOUNT_MOUNTED, |
| 165 | MOUNT_REMOUNTING, |
| 166 | MOUNT_UNMOUNTING, |
| 167 | MOUNT_REMOUNTING_SIGTERM, |
| 168 | MOUNT_REMOUNTING_SIGKILL, |
| 169 | MOUNT_UNMOUNTING_SIGTERM, |
| 170 | MOUNT_UNMOUNTING_SIGKILL, |
| 171 | MOUNT_FAILED, |
| 172 | MOUNT_CLEANING, |
| 173 | _MOUNT_STATE_MAX, |
| 174 | _MOUNT_STATE_INVALID = -EINVAL, |
| 175 | } MountState; |
| 176 | |
| 177 | typedef enum PathState { |
| 178 | PATH_DEAD, |
| 179 | PATH_WAITING, |
| 180 | PATH_RUNNING, |
| 181 | PATH_FAILED, |
| 182 | _PATH_STATE_MAX, |
| 183 | _PATH_STATE_INVALID = -EINVAL, |
| 184 | } PathState; |
| 185 | |
| 186 | typedef enum ScopeState { |
| 187 | SCOPE_DEAD, |
| 188 | SCOPE_START_CHOWN, |
| 189 | SCOPE_RUNNING, |
| 190 | SCOPE_ABANDONED, |
| 191 | SCOPE_STOP_SIGTERM, |
| 192 | SCOPE_STOP_SIGKILL, |
| 193 | SCOPE_FAILED, |
| 194 | _SCOPE_STATE_MAX, |
| 195 | _SCOPE_STATE_INVALID = -EINVAL, |
| 196 | } ScopeState; |
| 197 | |
| 198 | typedef enum ServiceState { |
| 199 | SERVICE_DEAD, |
| 200 | SERVICE_CONDITION, |
| 201 | SERVICE_START_PRE, |
| 202 | SERVICE_START, |
| 203 | SERVICE_START_POST, |
| 204 | SERVICE_RUNNING, |
| 205 | SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */ |
| 206 | SERVICE_RELOAD, /* Reloading via ExecReload= */ |
| 207 | SERVICE_RELOAD_SIGNAL, /* Reloading via SIGHUP requested */ |
| 208 | SERVICE_RELOAD_NOTIFY, /* Waiting for READY=1 after RELOADING=1 notify */ |
| 209 | SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */ |
| 210 | SERVICE_STOP_WATCHDOG, |
| 211 | SERVICE_STOP_SIGTERM, |
| 212 | SERVICE_STOP_SIGKILL, |
| 213 | SERVICE_STOP_POST, |
| 214 | SERVICE_FINAL_WATCHDOG, /* In case the STOP_POST executable needs to be aborted. */ |
| 215 | SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */ |
| 216 | SERVICE_FINAL_SIGKILL, |
| 217 | SERVICE_FAILED, |
| 218 | SERVICE_DEAD_BEFORE_AUTO_RESTART, |
| 219 | SERVICE_FAILED_BEFORE_AUTO_RESTART, |
| 220 | SERVICE_DEAD_RESOURCES_PINNED, /* Like SERVICE_DEAD, but with pinned resources */ |
| 221 | SERVICE_AUTO_RESTART, |
| 222 | SERVICE_AUTO_RESTART_QUEUED, |
| 223 | SERVICE_CLEANING, |
| 224 | _SERVICE_STATE_MAX, |
| 225 | _SERVICE_STATE_INVALID = -EINVAL, |
| 226 | } ServiceState; |
| 227 | |
| 228 | typedef enum SliceState { |
| 229 | SLICE_DEAD, |
| 230 | SLICE_ACTIVE, |
| 231 | _SLICE_STATE_MAX, |
| 232 | _SLICE_STATE_INVALID = -EINVAL, |
| 233 | } SliceState; |
| 234 | |
| 235 | typedef enum SocketState { |
| 236 | SOCKET_DEAD, |
| 237 | SOCKET_START_PRE, |
| 238 | SOCKET_START_CHOWN, |
| 239 | SOCKET_START_POST, |
| 240 | SOCKET_LISTENING, |
| 241 | SOCKET_RUNNING, |
| 242 | SOCKET_STOP_PRE, |
| 243 | SOCKET_STOP_PRE_SIGTERM, |
| 244 | SOCKET_STOP_PRE_SIGKILL, |
| 245 | SOCKET_STOP_POST, |
| 246 | SOCKET_FINAL_SIGTERM, |
| 247 | SOCKET_FINAL_SIGKILL, |
| 248 | SOCKET_FAILED, |
| 249 | SOCKET_CLEANING, |
| 250 | _SOCKET_STATE_MAX, |
| 251 | _SOCKET_STATE_INVALID = -EINVAL, |
| 252 | } SocketState; |
| 253 | |
| 254 | typedef enum SwapState { |
| 255 | SWAP_DEAD, |
| 256 | SWAP_ACTIVATING, /* /sbin/swapon is running, but the swap not yet enabled. */ |
| 257 | SWAP_ACTIVATING_DONE, /* /sbin/swapon is running, and the swap is done. */ |
| 258 | SWAP_ACTIVE, |
| 259 | SWAP_DEACTIVATING, |
| 260 | SWAP_DEACTIVATING_SIGTERM, |
| 261 | SWAP_DEACTIVATING_SIGKILL, |
| 262 | SWAP_FAILED, |
| 263 | SWAP_CLEANING, |
| 264 | _SWAP_STATE_MAX, |
| 265 | _SWAP_STATE_INVALID = -EINVAL, |
| 266 | } SwapState; |
| 267 | |
| 268 | typedef enum TargetState { |
| 269 | TARGET_DEAD, |
| 270 | TARGET_ACTIVE, |
| 271 | _TARGET_STATE_MAX, |
| 272 | _TARGET_STATE_INVALID = -EINVAL, |
| 273 | } TargetState; |
| 274 | |
| 275 | typedef enum TimerState { |
| 276 | TIMER_DEAD, |
| 277 | TIMER_WAITING, |
| 278 | TIMER_RUNNING, |
| 279 | TIMER_ELAPSED, |
| 280 | TIMER_FAILED, |
| 281 | _TIMER_STATE_MAX, |
| 282 | _TIMER_STATE_INVALID = -EINVAL, |
| 283 | } TimerState; |
| 284 | |
| 285 | typedef enum FreezerState { |
| 286 | FREEZER_RUNNING, |
| 287 | FREEZER_FREEZING, |
| 288 | FREEZER_FROZEN, |
| 289 | FREEZER_THAWING, |
| 290 | _FREEZER_STATE_MAX, |
| 291 | _FREEZER_STATE_INVALID = -EINVAL, |
| 292 | } FreezerState; |
| 293 | |
| 294 | // ---------------------------------------------------------------------------- |
| 295 | // copied from systemd: unit-def.c |
| 296 | |
| 297 | static const char *const unit_type_table[_UNIT_TYPE_MAX] = { |
| 298 | [UNIT_SERVICE] = "service", |
| 299 | [UNIT_SOCKET] = "socket", |
| 300 | [UNIT_TARGET] = "target", |
| 301 | [UNIT_DEVICE] = "device", |
| 302 | [UNIT_MOUNT] = "mount", |
| 303 | [UNIT_AUTOMOUNT] = "automount", |
| 304 | [UNIT_SWAP] = "swap", |
| 305 | [UNIT_TIMER] = "timer", |
| 306 | [UNIT_PATH] = "path", |
| 307 | [UNIT_SLICE] = "slice", |
| 308 | [UNIT_SCOPE] = "scope", |
| 309 | }; |
| 310 | |
| 311 | DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType); |
| 312 | |
| 313 | static const char *const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { |
| 314 | [UNIT_STUB] = "stub", |
| 315 | [UNIT_LOADED] = "loaded", |
| 316 | [UNIT_NOT_FOUND] = "not-found", |
| 317 | [UNIT_BAD_SETTING] = "bad-setting", |
| 318 | [UNIT_ERROR] = "error", |
| 319 | [UNIT_MERGED] = "merged", |
| 320 | [UNIT_MASKED] = "masked"}; |
| 321 | |
| 322 | DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); |
| 323 | |
| 324 | static const char *const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { |
| 325 | [UNIT_ACTIVE] = "active", |
| 326 | [UNIT_RELOADING] = "reloading", |
| 327 | [UNIT_INACTIVE] = "inactive", |
| 328 | [UNIT_FAILED] = "failed", |
| 329 | [UNIT_ACTIVATING] = "activating", |
| 330 | [UNIT_DEACTIVATING] = "deactivating", |
| 331 | [UNIT_MAINTENANCE] = "maintenance", |
| 332 | }; |
| 333 | |
| 334 | DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); |
| 335 | |
| 336 | static const char *const automount_state_table[_AUTOMOUNT_STATE_MAX] = { |
| 337 | [AUTOMOUNT_DEAD] = "dead", |
| 338 | [AUTOMOUNT_WAITING] = "waiting", |
| 339 | [AUTOMOUNT_RUNNING] = "running", |
| 340 | [AUTOMOUNT_FAILED] = "failed"}; |
| 341 | |
| 342 | DEFINE_STRING_TABLE_LOOKUP(automount_state, AutomountState); |
| 343 | |
| 344 | static const char *const device_state_table[_DEVICE_STATE_MAX] = { |
| 345 | [DEVICE_DEAD] = "dead", |
| 346 | [DEVICE_TENTATIVE] = "tentative", |
| 347 | [DEVICE_PLUGGED] = "plugged", |
| 348 | }; |
| 349 | |
| 350 | DEFINE_STRING_TABLE_LOOKUP(device_state, DeviceState); |
| 351 | |
| 352 | static const char *const mount_state_table[_MOUNT_STATE_MAX] = { |
| 353 | [MOUNT_DEAD] = "dead", |
| 354 | [MOUNT_MOUNTING] = "mounting", |
| 355 | [MOUNT_MOUNTING_DONE] = "mounting-done", |
| 356 | [MOUNT_MOUNTED] = "mounted", |
| 357 | [MOUNT_REMOUNTING] = "remounting", |
| 358 | [MOUNT_UNMOUNTING] = "unmounting", |
| 359 | [MOUNT_REMOUNTING_SIGTERM] = "remounting-sigterm", |
| 360 | [MOUNT_REMOUNTING_SIGKILL] = "remounting-sigkill", |
| 361 | [MOUNT_UNMOUNTING_SIGTERM] = "unmounting-sigterm", |
| 362 | [MOUNT_UNMOUNTING_SIGKILL] = "unmounting-sigkill", |
| 363 | [MOUNT_FAILED] = "failed", |
| 364 | [MOUNT_CLEANING] = "cleaning", |
| 365 | }; |
| 366 | |
| 367 | DEFINE_STRING_TABLE_LOOKUP(mount_state, MountState); |
| 368 | |
| 369 | static const char *const path_state_table[_PATH_STATE_MAX] = |
| 370 | {[PATH_DEAD] = "dead", [PATH_WAITING] = "waiting", [PATH_RUNNING] = "running", [PATH_FAILED] = "failed"}; |
| 371 | |
| 372 | DEFINE_STRING_TABLE_LOOKUP(path_state, PathState); |
| 373 | |
| 374 | static const char *const scope_state_table[_SCOPE_STATE_MAX] = { |
| 375 | [SCOPE_DEAD] = "dead", |
| 376 | [SCOPE_START_CHOWN] = "start-chown", |
| 377 | [SCOPE_RUNNING] = "running", |
| 378 | [SCOPE_ABANDONED] = "abandoned", |
| 379 | [SCOPE_STOP_SIGTERM] = "stop-sigterm", |
| 380 | [SCOPE_STOP_SIGKILL] = "stop-sigkill", |
| 381 | [SCOPE_FAILED] = "failed", |
| 382 | }; |
| 383 | |
| 384 | DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState); |
| 385 | |
| 386 | static const char *const service_state_table[_SERVICE_STATE_MAX] = { |
| 387 | [SERVICE_DEAD] = "dead", |
| 388 | [SERVICE_CONDITION] = "condition", |
| 389 | [SERVICE_START_PRE] = "start-pre", |
| 390 | [SERVICE_START] = "start", |
| 391 | [SERVICE_START_POST] = "start-post", |
| 392 | [SERVICE_RUNNING] = "running", |
| 393 | [SERVICE_EXITED] = "exited", |
| 394 | [SERVICE_RELOAD] = "reload", |
| 395 | [SERVICE_RELOAD_SIGNAL] = "reload-signal", |
| 396 | [SERVICE_RELOAD_NOTIFY] = "reload-notify", |
| 397 | [SERVICE_STOP] = "stop", |
| 398 | [SERVICE_STOP_WATCHDOG] = "stop-watchdog", |
| 399 | [SERVICE_STOP_SIGTERM] = "stop-sigterm", |
| 400 | [SERVICE_STOP_SIGKILL] = "stop-sigkill", |
| 401 | [SERVICE_STOP_POST] = "stop-post", |
| 402 | [SERVICE_FINAL_WATCHDOG] = "final-watchdog", |
| 403 | [SERVICE_FINAL_SIGTERM] = "final-sigterm", |
| 404 | [SERVICE_FINAL_SIGKILL] = "final-sigkill", |
| 405 | [SERVICE_FAILED] = "failed", |
| 406 | [SERVICE_DEAD_BEFORE_AUTO_RESTART] = "dead-before-auto-restart", |
| 407 | [SERVICE_FAILED_BEFORE_AUTO_RESTART] = "failed-before-auto-restart", |
| 408 | [SERVICE_DEAD_RESOURCES_PINNED] = "dead-resources-pinned", |
| 409 | [SERVICE_AUTO_RESTART] = "auto-restart", |
| 410 | [SERVICE_AUTO_RESTART_QUEUED] = "auto-restart-queued", |
| 411 | [SERVICE_CLEANING] = "cleaning", |
| 412 | }; |
| 413 | |
| 414 | DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState); |
| 415 | |
| 416 | static const char *const slice_state_table[_SLICE_STATE_MAX] = {[SLICE_DEAD] = "dead", [SLICE_ACTIVE] = "active"}; |
| 417 | |
| 418 | DEFINE_STRING_TABLE_LOOKUP(slice_state, SliceState); |
| 419 | |
| 420 | static const char *const socket_state_table[_SOCKET_STATE_MAX] = { |
| 421 | [SOCKET_DEAD] = "dead", |
| 422 | [SOCKET_START_PRE] = "start-pre", |
| 423 | [SOCKET_START_CHOWN] = "start-chown", |
| 424 | [SOCKET_START_POST] = "start-post", |
| 425 | [SOCKET_LISTENING] = "listening", |
| 426 | [SOCKET_RUNNING] = "running", |
| 427 | [SOCKET_STOP_PRE] = "stop-pre", |
| 428 | [SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm", |
| 429 | [SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill", |
| 430 | [SOCKET_STOP_POST] = "stop-post", |
| 431 | [SOCKET_FINAL_SIGTERM] = "final-sigterm", |
| 432 | [SOCKET_FINAL_SIGKILL] = "final-sigkill", |
| 433 | [SOCKET_FAILED] = "failed", |
| 434 | [SOCKET_CLEANING] = "cleaning", |
| 435 | }; |
| 436 | |
| 437 | DEFINE_STRING_TABLE_LOOKUP(socket_state, SocketState); |
| 438 | |
| 439 | static const char *const swap_state_table[_SWAP_STATE_MAX] = { |
| 440 | [SWAP_DEAD] = "dead", |
| 441 | [SWAP_ACTIVATING] = "activating", |
| 442 | [SWAP_ACTIVATING_DONE] = "activating-done", |
| 443 | [SWAP_ACTIVE] = "active", |
| 444 | [SWAP_DEACTIVATING] = "deactivating", |
| 445 | [SWAP_DEACTIVATING_SIGTERM] = "deactivating-sigterm", |
| 446 | [SWAP_DEACTIVATING_SIGKILL] = "deactivating-sigkill", |
| 447 | [SWAP_FAILED] = "failed", |
| 448 | [SWAP_CLEANING] = "cleaning", |
| 449 | }; |
| 450 | |
| 451 | DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState); |
| 452 | |
| 453 | static const char *const target_state_table[_TARGET_STATE_MAX] = {[TARGET_DEAD] = "dead", [TARGET_ACTIVE] = "active"}; |
| 454 | |
| 455 | DEFINE_STRING_TABLE_LOOKUP(target_state, TargetState); |
| 456 | |
| 457 | static const char *const timer_state_table[_TIMER_STATE_MAX] = { |
| 458 | [TIMER_DEAD] = "dead", |
| 459 | [TIMER_WAITING] = "waiting", |
| 460 | [TIMER_RUNNING] = "running", |
| 461 | [TIMER_ELAPSED] = "elapsed", |
| 462 | [TIMER_FAILED] = "failed"}; |
| 463 | |
| 464 | DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState); |
| 465 | |
| 466 | static const char *const freezer_state_table[_FREEZER_STATE_MAX] = { |
| 467 | [FREEZER_RUNNING] = "running", |
| 468 | [FREEZER_FREEZING] = "freezing", |
| 469 | [FREEZER_FROZEN] = "frozen", |
| 470 | [FREEZER_THAWING] = "thawing", |
| 471 | }; |
| 472 | |
| 473 | DEFINE_STRING_TABLE_LOOKUP(freezer_state, FreezerState); |
| 474 | |
| 475 | // ---------------------------------------------------------------------------- |
| 476 | // our code |
| 477 | |
| 478 | typedef struct UnitAttribute { |
| 479 | union { |
| 480 | int boolean; |
| 481 | char *str; |
| 482 | uint64_t uint64; |
| 483 | int64_t int64; |
| 484 | uint32_t uint32; |
| 485 | int32_t int32; |
| 486 | double dbl; |
| 487 | }; |
| 488 | } UnitAttribute; |
| 489 | |
| 490 | struct UnitInfo; |
| 491 | typedef void (*attribute_handler_t)(struct UnitInfo *u, UnitAttribute *ua); |
| 492 | |
| 493 | static void update_freezer_state(struct UnitInfo *u, UnitAttribute *ua); |
| 494 | |
| 495 | static const struct { |
| 496 | const char *member; |
| 497 | char value_type; |
| 498 | |
| 499 | const char *show_as; |
| 500 | const char *info; |
| 501 | RRDF_FIELD_OPTIONS options; |
| 502 | RRDF_FIELD_FILTER filter; |
| 503 | |
| 504 | attribute_handler_t handler; |
| 505 | } unit_attributes[] = { |
| 506 | { |
| 507 | .member = "Type", |
| 508 | .value_type = SD_BUS_TYPE_STRING, |
| 509 | .show_as = "ServiceType", |
| 510 | .info = "Service Type", |
| 511 | .options = RRDF_FIELD_OPTS_VISIBLE, |
| 512 | .filter = RRDF_FIELD_FILTER_MULTISELECT, |
| 513 | }, |
| 514 | { |
| 515 | .member = "Result", |
| 516 | .value_type = SD_BUS_TYPE_STRING, |
| 517 | .show_as = "Result", |
| 518 | .info = "Result", |
| 519 | .options = RRDF_FIELD_OPTS_VISIBLE, |
| 520 | .filter = RRDF_FIELD_FILTER_MULTISELECT, |
| 521 | }, |
| 522 | { |
| 523 | .member = "UnitFileState", |
| 524 | .value_type = SD_BUS_TYPE_STRING, |
| 525 | .show_as = "Enabled", |
| 526 | .info = "Unit File State", |
| 527 | .options = RRDF_FIELD_OPTS_NONE, |
| 528 | .filter = RRDF_FIELD_FILTER_MULTISELECT, |
| 529 | }, |
| 530 | { |
| 531 | .member = "UnitFilePreset", |
| 532 | .value_type = SD_BUS_TYPE_STRING, |
| 533 | .show_as = "Preset", |
| 534 | .info = "Unit File Preset", |
| 535 | .options = RRDF_FIELD_OPTS_NONE, |
| 536 | .filter = RRDF_FIELD_FILTER_MULTISELECT, |
| 537 | }, |
| 538 | { |
| 539 | .member = "FreezerState", |
| 540 | .value_type = SD_BUS_TYPE_STRING, |
| 541 | .show_as = "FreezerState", |
| 542 | .info = "Freezer State", |
| 543 | .options = RRDF_FIELD_OPTS_NONE, |
| 544 | .filter = RRDF_FIELD_FILTER_MULTISELECT, |
| 545 | .handler = update_freezer_state, |
| 546 | }, |
| 547 | // { .member = "Id", .signature = "s", }, |
| 548 | // { .member = "LoadState", .signature = "s", }, |
| 549 | // { .member = "ActiveState", .signature = "s", }, |
| 550 | // { .member = "SubState", .signature = "s", }, |
| 551 | // { .member = "Description", .signature = "s", }, |
| 552 | // { .member = "Following", .signature = "s", }, |
| 553 | // { .member = "Documentation", .signature = "as", }, |
| 554 | // { .member = "FragmentPath", .signature = "s", }, |
| 555 | // { .member = "SourcePath", .signature = "s", }, |
| 556 | // { .member = "ControlGroup", .signature = "s", }, |
| 557 | // { .member = "DropInPaths", .signature = "as", }, |
| 558 | // { .member = "LoadError", .signature = "(ss)", }, |
| 559 | // { .member = "TriggeredBy", .signature = "as", }, |
| 560 | // { .member = "Triggers", .signature = "as", }, |
| 561 | // { .member = "InactiveExitTimestamp", .signature = "t", }, |
| 562 | // { .member = "InactiveExitTimestampMonotonic", .signature = "t", }, |
| 563 | // { .member = "ActiveEnterTimestamp", .signature = "t", }, |
| 564 | // { .member = "ActiveExitTimestamp", .signature = "t", }, |
| 565 | // { .member = "RuntimeMaxUSec", .signature = "t", }, |
| 566 | // { .member = "InactiveEnterTimestamp", .signature = "t", }, |
| 567 | // { .member = "NeedDaemonReload", .signature = "b", }, |
| 568 | // { .member = "Transient", .signature = "b", }, |
| 569 | // { .member = "ExecMainPID", .signature = "u", }, |
| 570 | // { .member = "MainPID", .signature = "u", }, |
| 571 | // { .member = "ControlPID", .signature = "u", }, |
| 572 | // { .member = "StatusText", .signature = "s", }, |
| 573 | // { .member = "PIDFile", .signature = "s", }, |
| 574 | // { .member = "StatusErrno", .signature = "i", }, |
| 575 | // { .member = "FileDescriptorStoreMax", .signature = "u", }, |
| 576 | // { .member = "NFileDescriptorStore", .signature = "u", }, |
| 577 | // { .member = "ExecMainStartTimestamp", .signature = "t", }, |
| 578 | // { .member = "ExecMainExitTimestamp", .signature = "t", }, |
| 579 | // { .member = "ExecMainCode", .signature = "i", }, |
| 580 | // { .member = "ExecMainStatus", .signature = "i", }, |
| 581 | // { .member = "LogNamespace", .signature = "s", }, |
| 582 | // { .member = "ConditionTimestamp", .signature = "t", }, |
| 583 | // { .member = "ConditionResult", .signature = "b", }, |
| 584 | // { .member = "Conditions", .signature = "a(sbbsi)", }, |
| 585 | // { .member = "AssertTimestamp", .signature = "t", }, |
| 586 | // { .member = "AssertResult", .signature = "b", }, |
| 587 | // { .member = "Asserts", .signature = "a(sbbsi)", }, |
| 588 | // { .member = "NextElapseUSecRealtime", .signature = "t", }, |
| 589 | // { .member = "NextElapseUSecMonotonic", .signature = "t", }, |
| 590 | // { .member = "NAccepted", .signature = "u", }, |
| 591 | // { .member = "NConnections", .signature = "u", }, |
| 592 | // { .member = "NRefused", .signature = "u", }, |
| 593 | // { .member = "Accept", .signature = "b", }, |
| 594 | // { .member = "Listen", .signature = "a(ss)", }, |
| 595 | // { .member = "SysFSPath", .signature = "s", }, |
| 596 | // { .member = "Where", .signature = "s", }, |
| 597 | // { .member = "What", .signature = "s", }, |
| 598 | // { .member = "MemoryCurrent", .signature = "t", }, |
| 599 | // { .member = "MemoryAvailable", .signature = "t", }, |
| 600 | // { .member = "DefaultMemoryMin", .signature = "t", }, |
| 601 | // { .member = "DefaultMemoryLow", .signature = "t", }, |
| 602 | // { .member = "DefaultStartupMemoryLow", .signature = "t", }, |
| 603 | // { .member = "MemoryMin", .signature = "t", }, |
| 604 | // { .member = "MemoryLow", .signature = "t", }, |
| 605 | // { .member = "StartupMemoryLow", .signature = "t", }, |
| 606 | // { .member = "MemoryHigh", .signature = "t", }, |
| 607 | // { .member = "StartupMemoryHigh", .signature = "t", }, |
| 608 | // { .member = "MemoryMax", .signature = "t", }, |
| 609 | // { .member = "StartupMemoryMax", .signature = "t", }, |
| 610 | // { .member = "MemorySwapMax", .signature = "t", }, |
| 611 | // { .member = "StartupMemorySwapMax", .signature = "t", }, |
| 612 | // { .member = "MemoryZSwapMax", .signature = "t", }, |
| 613 | // { .member = "StartupMemoryZSwapMax", .signature = "t", }, |
| 614 | // { .member = "MemoryLimit", .signature = "t", }, |
| 615 | // { .member = "CPUUsageNSec", .signature = "t", }, |
| 616 | // { .member = "TasksCurrent", .signature = "t", }, |
| 617 | // { .member = "TasksMax", .signature = "t", }, |
| 618 | // { .member = "IPIngressBytes", .signature = "t", }, |
| 619 | // { .member = "IPEgressBytes", .signature = "t", }, |
| 620 | // { .member = "IOReadBytes", .signature = "t", }, |
| 621 | // { .member = "IOWriteBytes", .signature = "t", }, |
| 622 | // { .member = "ExecCondition", .signature = "a(sasbttttuii)", }, |
| 623 | // { .member = "ExecConditionEx", .signature = "a(sasasttttuii)", }, |
| 624 | // { .member = "ExecStartPre", .signature = "a(sasbttttuii)", }, |
| 625 | // { .member = "ExecStartPreEx", .signature = "a(sasasttttuii)", }, |
| 626 | // { .member = "ExecStart", .signature = "a(sasbttttuii)", }, |
| 627 | // { .member = "ExecStartEx", .signature = "a(sasasttttuii)", }, |
| 628 | // { .member = "ExecStartPost", .signature = "a(sasbttttuii)", }, |
| 629 | // { .member = "ExecStartPostEx", .signature = "a(sasasttttuii)", }, |
| 630 | // { .member = "ExecReload", .signature = "a(sasbttttuii)", }, |
| 631 | // { .member = "ExecReloadEx", .signature = "a(sasasttttuii)", }, |
| 632 | // { .member = "ExecStopPre", .signature = "a(sasbttttuii)", }, |
| 633 | // { .member = "ExecStop", .signature = "a(sasbttttuii)", }, |
| 634 | // { .member = "ExecStopEx", .signature = "a(sasasttttuii)", }, |
| 635 | // { .member = "ExecStopPost", .signature = "a(sasbttttuii)", }, |
| 636 | // { .member = "ExecStopPostEx", .signature = "a(sasasttttuii)", }, |
| 637 | }; |
| 638 | |
| 639 | #define _UNIT_ATTRIBUTE_MAX (sizeof(unit_attributes) / sizeof(unit_attributes[0])) |
| 640 | |
| 641 | typedef struct UnitInfo { |
| 642 | char *id; |
| 643 | char *type; |
| 644 | char *description; |
| 645 | char *load_state; |
| 646 | char *active_state; |
| 647 | char *sub_state; |
| 648 | char *following; |
| 649 | char *unit_path; |
| 650 | uint32_t job_id; |
| 651 | char *job_type; |
| 652 | char *job_path; |
| 653 | |
| 654 | UnitType UnitType; |
| 655 | UnitLoadState UnitLoadState; |
| 656 | UnitActiveState UnitActiveState; |
| 657 | FreezerState FreezerState; |
| 658 | |
| 659 | union { |
| 660 | AutomountState AutomountState; |
| 661 | DeviceState DeviceState; |
| 662 | MountState MountState; |
| 663 | PathState PathState; |
| 664 | ScopeState ScopeState; |
| 665 | ServiceState ServiceState; |
| 666 | SliceState SliceState; |
| 667 | SocketState SocketState; |
| 668 | SwapState SwapState; |
| 669 | TargetState TargetState; |
| 670 | TimerState TimerState; |
| 671 | }; |
| 672 | |
| 673 | struct UnitAttribute attributes[_UNIT_ATTRIBUTE_MAX]; |
| 674 | |
| 675 | FACET_ROW_SEVERITY severity; |
| 676 | uint32_t prio; |
| 677 | |
| 678 | struct UnitInfo *prev, *next; |
| 679 | } UnitInfo; |
| 680 | |
| 681 | static void update_freezer_state(UnitInfo *u, UnitAttribute *ua) |
| 682 | { |
| 683 | u->FreezerState = freezer_state_from_string(ua->str); |
| 684 | } |
| 685 | |
| 686 | // ---------------------------------------------------------------------------- |
| 687 | // common helpers |
| 688 | |
| 689 | static void log_dbus_error(int r, const char *msg) |
| 690 | { |
| 691 | netdata_log_error("ND_SD_UNITS: %s failed with error %d (%s)", msg, r, strerror(-r)); |
| 692 | } |
| 693 | |
| 694 | // ---------------------------------------------------------------------------- |
| 695 | // attributes management |
| 696 | |
| 697 | static inline ssize_t unit_property_slot_from_string(const char *s) |
| 698 | { |
| 699 | if (!s || !*s) |
| 700 | return -EINVAL; |
| 701 | |
| 702 | for (size_t i = 0; i < _UNIT_ATTRIBUTE_MAX; i++) |
| 703 | if (streq_ptr(unit_attributes[i].member, s)) |
| 704 | return (ssize_t)i; |
| 705 | |
| 706 | return -EINVAL; |
| 707 | } |
| 708 | |
| 709 | static inline const char *unit_property_name_to_string_from_slot(ssize_t i) |
| 710 | { |
| 711 | if (i >= 0 && i < (ssize_t)_UNIT_ATTRIBUTE_MAX) |
| 712 | return unit_attributes[i].member; |
| 713 | |
| 714 | return NULL; |
| 715 | } |
| 716 | |
| 717 | static inline void systemd_unit_free_property(char type, struct UnitAttribute *at) |
| 718 | { |
| 719 | switch (type) { |
| 720 | case SD_BUS_TYPE_STRING: |
| 721 | case SD_BUS_TYPE_OBJECT_PATH: |
| 722 | freez(at->str); |
| 723 | at->str = NULL; |
| 724 | break; |
| 725 | |
| 726 | default: |
| 727 | break; |
| 728 | } |
| 729 | } |
| 730 | |
| 731 | static int systemd_unit_get_property(sd_bus_message *m, UnitInfo *u, const char *name) |
| 732 | { |
| 733 | int r; |
| 734 | char type; |
| 735 | |
| 736 | r = sd_bus_message_peek_type(m, &type, NULL); |
| 737 | if (r < 0) { |
| 738 | log_dbus_error(r, "sd_bus_message_peek_type()"); |
| 739 | return r; |
| 740 | } |
| 741 | |
| 742 | ssize_t slot = unit_property_slot_from_string(name); |
| 743 | if (slot < 0) { |
| 744 | // internal_error(true, "unused attribute '%s' for unit '%s'", name, u->id); |
| 745 | sd_bus_message_skip(m, NULL); |
| 746 | return 0; |
| 747 | } |
| 748 | |
| 749 | systemd_unit_free_property(unit_attributes[slot].value_type, &u->attributes[slot]); |
| 750 | |
| 751 | if (unit_attributes[slot].value_type != type) { |
| 752 | netdata_log_error( |
| 753 | "Type of field '%s' expected to be '%c' but found '%c'. Ignoring field.", |
| 754 | unit_attributes[slot].member, |
| 755 | unit_attributes[slot].value_type, |
| 756 | type); |
| 757 | sd_bus_message_skip(m, NULL); |
| 758 | return 0; |
| 759 | } |
| 760 | |
| 761 | switch (type) { |
| 762 | case SD_BUS_TYPE_OBJECT_PATH: |
| 763 | case SD_BUS_TYPE_STRING: { |
| 764 | char *s; |
| 765 | |
| 766 | r = sd_bus_message_read_basic(m, type, &s); |
| 767 | if (r < 0) { |
| 768 | log_dbus_error(r, "sd_bus_message_read_basic()"); |
| 769 | return r; |
| 770 | } |
| 771 | |
| 772 | if (s && *s) |
| 773 | u->attributes[slot].str = strdupz(s); |
| 774 | } break; |
| 775 | |
| 776 | case SD_BUS_TYPE_BOOLEAN: { |
| 777 | r = sd_bus_message_read_basic(m, type, &u->attributes[slot].boolean); |
| 778 | if (r < 0) { |
| 779 | log_dbus_error(r, "sd_bus_message_read_basic()"); |
| 780 | return r; |
| 781 | } |
| 782 | } break; |
| 783 | |
| 784 | case SD_BUS_TYPE_UINT64: { |
| 785 | r = sd_bus_message_read_basic(m, type, &u->attributes[slot].uint64); |
| 786 | if (r < 0) { |
| 787 | log_dbus_error(r, "sd_bus_message_read_basic()"); |
| 788 | return r; |
| 789 | } |
| 790 | } break; |
| 791 | |
| 792 | case SD_BUS_TYPE_INT64: { |
| 793 | r = sd_bus_message_read_basic(m, type, &u->attributes[slot].int64); |
| 794 | if (r < 0) { |
| 795 | log_dbus_error(r, "sd_bus_message_read_basic()"); |
| 796 | return r; |
| 797 | } |
| 798 | } break; |
| 799 | |
| 800 | case SD_BUS_TYPE_UINT32: { |
| 801 | r = sd_bus_message_read_basic(m, type, &u->attributes[slot].uint32); |
| 802 | if (r < 0) { |
| 803 | log_dbus_error(r, "sd_bus_message_read_basic()"); |
| 804 | return r; |
| 805 | } |
| 806 | } break; |
| 807 | |
| 808 | case SD_BUS_TYPE_INT32: { |
| 809 | r = sd_bus_message_read_basic(m, type, &u->attributes[slot].int32); |
| 810 | if (r < 0) { |
| 811 | log_dbus_error(r, "sd_bus_message_read_basic()"); |
| 812 | return r; |
| 813 | } |
| 814 | } break; |
| 815 | |
| 816 | case SD_BUS_TYPE_DOUBLE: { |
| 817 | r = sd_bus_message_read_basic(m, type, &u->attributes[slot].dbl); |
| 818 | if (r < 0) { |
| 819 | log_dbus_error(r, "sd_bus_message_read_basic()"); |
| 820 | return r; |
| 821 | } |
| 822 | } break; |
| 823 | |
| 824 | case SD_BUS_TYPE_ARRAY: { |
| 825 | internal_error(true, "member '%s' is an array", name); |
| 826 | sd_bus_message_skip(m, NULL); |
| 827 | return 0; |
| 828 | } break; |
| 829 | |
| 830 | default: { |
| 831 | internal_error(true, "unknown field type '%c' for key '%s'", type, name); |
| 832 | sd_bus_message_skip(m, NULL); |
| 833 | return 0; |
| 834 | } break; |
| 835 | } |
| 836 | |
| 837 | if (unit_attributes[slot].handler) |
| 838 | unit_attributes[slot].handler(u, &u->attributes[slot]); |
| 839 | |
| 840 | return 0; |
| 841 | } |
| 842 | |
| 843 | static int systemd_unit_get_all_properties(sd_bus *bus, UnitInfo *u) |
| 844 | { |
| 845 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; |
| 846 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
| 847 | int r; |
| 848 | |
| 849 | r = sd_bus_call_method( |
| 850 | bus, "org.freedesktop.systemd1", u->unit_path, "org.freedesktop.DBus.Properties", "GetAll", &error, &m, "s", ""); |
| 851 | if (r < 0) { |
| 852 | log_dbus_error(r, "sd_bus_call_method(p1)"); |
| 853 | return r; |
| 854 | } |
| 855 | |
| 856 | r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}"); |
| 857 | if (r < 0) { |
| 858 | log_dbus_error(r, "sd_bus_message_enter_container(p2)"); |
| 859 | return r; |
| 860 | } |
| 861 | |
| 862 | while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { |
| 863 | const char *member, *contents; |
| 864 | |
| 865 | r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member); |
| 866 | if (r < 0) { |
| 867 | log_dbus_error(r, "sd_bus_message_read_basic(p3)"); |
| 868 | return r; |
| 869 | } |
| 870 | |
| 871 | r = sd_bus_message_peek_type(m, NULL, &contents); |
| 872 | if (r < 0) { |
| 873 | log_dbus_error(r, "sd_bus_message_peek_type(p4)"); |
| 874 | return r; |
| 875 | } |
| 876 | |
| 877 | r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); |
| 878 | if (r < 0) { |
| 879 | log_dbus_error(r, "sd_bus_message_enter_container(p5)"); |
| 880 | return r; |
| 881 | } |
| 882 | |
| 883 | systemd_unit_get_property(m, u, member); |
| 884 | |
| 885 | r = sd_bus_message_exit_container(m); |
| 886 | if (r < 0) { |
| 887 | log_dbus_error(r, "sd_bus_message_exit_container(p6)"); |
| 888 | return r; |
| 889 | } |
| 890 | |
| 891 | r = sd_bus_message_exit_container(m); |
| 892 | if (r < 0) { |
| 893 | log_dbus_error(r, "sd_bus_message_exit_container(p7)"); |
| 894 | return r; |
| 895 | } |
| 896 | } |
| 897 | if (r < 0) { |
| 898 | log_dbus_error(r, "sd_bus_message_enter_container(p8)"); |
| 899 | return r; |
| 900 | } |
| 901 | |
| 902 | r = sd_bus_message_exit_container(m); |
| 903 | if (r < 0) { |
| 904 | log_dbus_error(r, "sd_bus_message_exit_container(p9)"); |
| 905 | return r; |
| 906 | } |
| 907 | |
| 908 | return 0; |
| 909 | } |
| 910 | |
| 911 | static void systemd_units_get_all_properties(sd_bus *bus, UnitInfo *base) |
| 912 | { |
| 913 | for (UnitInfo *u = base; u; u = u->next) |
| 914 | systemd_unit_get_all_properties(bus, u); |
| 915 | } |
| 916 | |
| 917 | // ---------------------------------------------------------------------------- |
| 918 | // main unit info |
| 919 | |
| 920 | static int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u) |
| 921 | { |
| 922 | assert(message); |
| 923 | assert(u); |
| 924 | |
| 925 | u->type = NULL; |
| 926 | |
| 927 | int r = sd_bus_message_read( |
| 928 | message, |
| 929 | ND_SD_UNITS_DBUS_TYPES, |
| 930 | &u->id, |
| 931 | &u->description, |
| 932 | &u->load_state, |
| 933 | &u->active_state, |
| 934 | &u->sub_state, |
| 935 | &u->following, |
| 936 | &u->unit_path, |
| 937 | &u->job_id, |
| 938 | &u->job_type, |
| 939 | &u->job_path); |
| 940 | |
| 941 | if (r <= 0) |
| 942 | return r; |
| 943 | |
| 944 | char *dot; |
| 945 | if (u->id && (dot = strrchr(u->id, '.')) != NULL) |
| 946 | u->type = &dot[1]; |
| 947 | else |
| 948 | u->type = "unknown"; |
| 949 | |
| 950 | u->UnitType = unit_type_from_string(u->type); |
| 951 | u->UnitLoadState = unit_load_state_from_string(u->load_state); |
| 952 | u->UnitActiveState = unit_active_state_from_string(u->active_state); |
| 953 | |
| 954 | switch (u->UnitType) { |
| 955 | case UNIT_SERVICE: |
| 956 | u->ServiceState = service_state_from_string(u->sub_state); |
| 957 | break; |
| 958 | |
| 959 | case UNIT_MOUNT: |
| 960 | u->MountState = mount_state_from_string(u->sub_state); |
| 961 | break; |
| 962 | |
| 963 | case UNIT_SWAP: |
| 964 | u->SwapState = swap_state_from_string(u->sub_state); |
| 965 | break; |
| 966 | |
| 967 | case UNIT_SOCKET: |
| 968 | u->SocketState = socket_state_from_string(u->sub_state); |
| 969 | break; |
| 970 | |
| 971 | case UNIT_TARGET: |
| 972 | u->TargetState = target_state_from_string(u->sub_state); |
| 973 | break; |
| 974 | |
| 975 | case UNIT_DEVICE: |
| 976 | u->DeviceState = device_state_from_string(u->sub_state); |
| 977 | break; |
| 978 | |
| 979 | case UNIT_AUTOMOUNT: |
| 980 | u->AutomountState = automount_state_from_string(u->sub_state); |
| 981 | break; |
| 982 | |
| 983 | case UNIT_TIMER: |
| 984 | u->TimerState = timer_state_from_string(u->sub_state); |
| 985 | break; |
| 986 | |
| 987 | case UNIT_PATH: |
| 988 | u->PathState = path_state_from_string(u->sub_state); |
| 989 | break; |
| 990 | |
| 991 | case UNIT_SLICE: |
| 992 | u->SliceState = slice_state_from_string(u->sub_state); |
| 993 | break; |
| 994 | |
| 995 | case UNIT_SCOPE: |
| 996 | u->ScopeState = scope_state_from_string(u->sub_state); |
| 997 | break; |
| 998 | |
| 999 | default: |
| 1000 | break; |
| 1001 | } |
| 1002 | |
| 1003 | return r; |
| 1004 | } |
| 1005 | |
| 1006 | static int hex_to_int(char c) |
| 1007 | { |
| 1008 | if (c >= '0' && c <= '9') |
| 1009 | return c - '0'; |
| 1010 | if (c >= 'a' && c <= 'f') |
| 1011 | return c - 'a' + 10; |
| 1012 | if (c >= 'A' && c <= 'F') |
| 1013 | return c - 'A' + 10; |
| 1014 | return 0; |
| 1015 | } |
| 1016 | |
| 1017 | // un-escape hex sequences (\xNN) in id |
| 1018 | static void txt_decode(char *txt) |
| 1019 | { |
| 1020 | if (!txt || !*txt) |
| 1021 | return; |
| 1022 | |
| 1023 | char *src = txt, *dst = txt; |
| 1024 | |
| 1025 | size_t id_len = strlen(src); |
| 1026 | size_t s = 0, d = 0; |
| 1027 | for (; s < id_len; s++) { |
| 1028 | if (src[s] == '\\' && src[s + 1] == 'x' && isxdigit(src[s + 2]) && isxdigit(src[s + 3])) { |
| 1029 | int value = (hex_to_int(src[s + 2]) << 4) + hex_to_int(src[s + 3]); |
| 1030 | dst[d++] = (char)value; |
| 1031 | s += 3; |
| 1032 | } else |
| 1033 | dst[d++] = src[s]; |
| 1034 | } |
| 1035 | dst[d] = '\0'; |
| 1036 | } |
| 1037 | |
| 1038 | static UnitInfo *systemd_units_get_all(void) |
| 1039 | { |
| 1040 | _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; |
| 1041 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
| 1042 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; |
| 1043 | |
| 1044 | UnitInfo *base = NULL; |
| 1045 | int r; |
| 1046 | |
| 1047 | r = sd_bus_default_system(&bus); |
| 1048 | if (r < 0) { |
| 1049 | log_dbus_error(r, "sd_bus_default_system()"); |
| 1050 | return base; |
| 1051 | } |
| 1052 | |
| 1053 | // This calls the ListUnits method of the org.freedesktop.systemd1.Manager interface |
| 1054 | // Replace "ListUnits" with "ListUnitsFiltered" to get specific units based on filters |
| 1055 | r = sd_bus_call_method( |
| 1056 | bus, |
| 1057 | "org.freedesktop.systemd1", /* service to contact */ |
| 1058 | "/org/freedesktop/systemd1", /* object path */ |
| 1059 | "org.freedesktop.systemd1.Manager", /* interface name */ |
| 1060 | "ListUnits", /* method name */ |
| 1061 | &error, /* object to return error in */ |
| 1062 | &reply, /* return message on success */ |
| 1063 | NULL); /* input signature */ |
| 1064 | if (r < 0) { |
| 1065 | log_dbus_error(r, "sd_bus_call_method()"); |
| 1066 | return base; |
| 1067 | } |
| 1068 | |
| 1069 | r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, ND_SD_UNITS_DBUS_TYPES); |
| 1070 | if (r < 0) { |
| 1071 | log_dbus_error(r, "sd_bus_message_enter_container()"); |
| 1072 | return base; |
| 1073 | } |
| 1074 | |
| 1075 | UnitInfo u; |
| 1076 | memset(&u, 0, sizeof(u)); |
| 1077 | while ((r = bus_parse_unit_info(reply, &u)) > 0) { |
| 1078 | UnitInfo *i = callocz(1, sizeof(u)); |
| 1079 | *i = u; |
| 1080 | |
| 1081 | i->id = strdupz(u.id && *u.id ? u.id : "-"); |
| 1082 | txt_decode(i->id); |
| 1083 | |
| 1084 | i->type = strdupz(u.type && *u.type ? u.type : "-"); |
| 1085 | i->description = strdupz(u.description && *u.description ? u.description : "-"); |
| 1086 | txt_decode(i->description); |
| 1087 | |
| 1088 | i->load_state = strdupz(u.load_state && *u.load_state ? u.load_state : "-"); |
| 1089 | i->active_state = strdupz(u.active_state && *u.active_state ? u.active_state : "-"); |
| 1090 | i->sub_state = strdupz(u.sub_state && *u.sub_state ? u.sub_state : "-"); |
| 1091 | i->following = strdupz(u.following && *u.following ? u.following : "-"); |
| 1092 | i->unit_path = strdupz(u.unit_path && *u.unit_path ? u.unit_path : "-"); |
| 1093 | i->job_type = strdupz(u.job_type && *u.job_type ? u.job_type : "-"); |
| 1094 | i->job_path = strdupz(u.job_path && *u.job_path ? u.job_path : "-"); |
| 1095 | i->job_id = u.job_id; |
| 1096 | |
| 1097 | DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(base, i, prev, next); |
| 1098 | memset(&u, 0, sizeof(u)); |
| 1099 | } |
| 1100 | if (r < 0) { |
| 1101 | log_dbus_error(r, "sd_bus_message_read()"); |
| 1102 | return base; |
| 1103 | } |
| 1104 | |
| 1105 | r = sd_bus_message_exit_container(reply); |
| 1106 | if (r < 0) { |
| 1107 | log_dbus_error(r, "sd_bus_message_exit_container()"); |
| 1108 | return base; |
| 1109 | } |
| 1110 | |
| 1111 | systemd_units_get_all_properties(bus, base); |
| 1112 | |
| 1113 | return base; |
| 1114 | } |
| 1115 | |
| 1116 | static void systemd_units_free_all(UnitInfo *base) |
| 1117 | { |
| 1118 | while (base) { |
| 1119 | UnitInfo *u = base; |
| 1120 | DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(base, u, prev, next); |
| 1121 | freez((void *)u->id); |
| 1122 | freez((void *)u->type); |
| 1123 | freez((void *)u->description); |
| 1124 | freez((void *)u->load_state); |
| 1125 | freez((void *)u->active_state); |
| 1126 | freez((void *)u->sub_state); |
| 1127 | freez((void *)u->following); |
| 1128 | freez((void *)u->unit_path); |
| 1129 | freez((void *)u->job_type); |
| 1130 | freez((void *)u->job_path); |
| 1131 | |
| 1132 | for (int i = 0; i < (ssize_t)_UNIT_ATTRIBUTE_MAX; i++) |
| 1133 | systemd_unit_free_property(unit_attributes[i].value_type, &u->attributes[i]); |
| 1134 | |
| 1135 | freez(u); |
| 1136 | } |
| 1137 | } |
| 1138 | |
| 1139 | // ---------------------------------------------------------------------------- |
| 1140 | |
| 1141 | static void netdata_systemd_units_function_help(const char *transaction) |
| 1142 | { |
| 1143 | BUFFER *wb = buffer_create(0, NULL); |
| 1144 | buffer_sprintf( |
| 1145 | wb, |
| 1146 | "%s / %s\n" |
| 1147 | "\n" |
| 1148 | "%s\n" |
| 1149 | "\n" |
| 1150 | "The following parameters are supported:\n" |
| 1151 | "\n" |
| 1152 | " help\n" |
| 1153 | " Shows this help message.\n" |
| 1154 | "\n" |
| 1155 | " info\n" |
| 1156 | " Request initial configuration information about the plugin.\n" |
| 1157 | " The key entity returned is the required_params array, which includes\n" |
| 1158 | " all the available systemd journal sources.\n" |
| 1159 | " When `info` is requested, all other parameters are ignored.\n" |
| 1160 | "\n", |
| 1161 | program_name, |
| 1162 | ND_SD_UNITS_FUNCTION_NAME, |
| 1163 | ND_SD_UNITS_FUNCTION_DESCRIPTION); |
| 1164 | |
| 1165 | netdata_mutex_lock(&stdout_mutex); |
| 1166 | wb->response_code = HTTP_RESP_OK; |
| 1167 | wb->content_type = CT_TEXT_PLAIN; |
| 1168 | wb->expires = now_realtime_sec() + 3600; |
| 1169 | pluginsd_function_result_to_stdout(transaction, wb); |
| 1170 | netdata_mutex_unlock(&stdout_mutex); |
| 1171 | |
| 1172 | buffer_free(wb); |
| 1173 | } |
| 1174 | |
| 1175 | static void netdata_systemd_units_function_info(const char *transaction) |
| 1176 | { |
| 1177 | BUFFER *wb = buffer_create(0, NULL); |
| 1178 | buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY); |
| 1179 | |
| 1180 | buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK); |
| 1181 | buffer_json_member_add_string(wb, "type", "table"); |
| 1182 | buffer_json_member_add_boolean(wb, "has_history", false); |
| 1183 | buffer_json_member_add_string(wb, "help", ND_SD_UNITS_FUNCTION_DESCRIPTION); |
| 1184 | |
| 1185 | buffer_json_finalize(wb); |
| 1186 | netdata_mutex_lock(&stdout_mutex); |
| 1187 | wb->response_code = HTTP_RESP_OK; |
| 1188 | wb->content_type = CT_APPLICATION_JSON; |
| 1189 | wb->expires = now_realtime_sec() + 3600; |
| 1190 | pluginsd_function_result_to_stdout(transaction, wb); |
| 1191 | netdata_mutex_unlock(&stdout_mutex); |
| 1192 | |
| 1193 | buffer_free(wb); |
| 1194 | } |
| 1195 | |
| 1196 | // ---------------------------------------------------------------------------- |
| 1197 | |
| 1198 | static void systemd_unit_priority(UnitInfo *u, size_t units) |
| 1199 | { |
| 1200 | uint32_t prio; |
| 1201 | |
| 1202 | switch (u->severity) { |
| 1203 | case FACET_ROW_SEVERITY_CRITICAL: |
| 1204 | prio = 0; |
| 1205 | break; |
| 1206 | |
| 1207 | default: |
| 1208 | case FACET_ROW_SEVERITY_WARNING: |
| 1209 | prio = 1; |
| 1210 | break; |
| 1211 | |
| 1212 | case FACET_ROW_SEVERITY_NOTICE: |
| 1213 | prio = 2; |
| 1214 | break; |
| 1215 | |
| 1216 | case FACET_ROW_SEVERITY_NORMAL: |
| 1217 | prio = 3; |
| 1218 | break; |
| 1219 | |
| 1220 | case FACET_ROW_SEVERITY_DEBUG: |
| 1221 | prio = 4; |
| 1222 | break; |
| 1223 | } |
| 1224 | |
| 1225 | prio = prio * (uint32_t)(_UNIT_TYPE_MAX + 1) + (uint32_t)u->UnitType; |
| 1226 | u->prio = (prio * units) + u->prio; |
| 1227 | } |
| 1228 | |
| 1229 | static inline FACET_ROW_SEVERITY if_less(FACET_ROW_SEVERITY current, FACET_ROW_SEVERITY max, FACET_ROW_SEVERITY target) |
| 1230 | { |
| 1231 | FACET_ROW_SEVERITY wanted = current; |
| 1232 | if (current < target) |
| 1233 | wanted = target > max ? max : target; |
| 1234 | return wanted; |
| 1235 | } |
| 1236 | |
| 1237 | static inline FACET_ROW_SEVERITY |
| 1238 | if_normal(FACET_ROW_SEVERITY current, FACET_ROW_SEVERITY max, FACET_ROW_SEVERITY target) |
| 1239 | { |
| 1240 | FACET_ROW_SEVERITY wanted = current; |
| 1241 | if (current == FACET_ROW_SEVERITY_NORMAL) |
| 1242 | wanted = target > max ? max : target; |
| 1243 | return wanted; |
| 1244 | } |
| 1245 | |
| 1246 | static FACET_ROW_SEVERITY system_unit_severity(UnitInfo *u) |
| 1247 | { |
| 1248 | FACET_ROW_SEVERITY severity, max_severity; |
| 1249 | |
| 1250 | switch (u->UnitLoadState) { |
| 1251 | case UNIT_ERROR: |
| 1252 | case UNIT_BAD_SETTING: |
| 1253 | severity = FACET_ROW_SEVERITY_CRITICAL; |
| 1254 | max_severity = FACET_ROW_SEVERITY_CRITICAL; |
| 1255 | break; |
| 1256 | |
| 1257 | default: |
| 1258 | severity = FACET_ROW_SEVERITY_WARNING; |
| 1259 | max_severity = FACET_ROW_SEVERITY_CRITICAL; |
| 1260 | break; |
| 1261 | |
| 1262 | case UNIT_NOT_FOUND: |
| 1263 | severity = FACET_ROW_SEVERITY_NOTICE; |
| 1264 | max_severity = FACET_ROW_SEVERITY_NOTICE; |
| 1265 | break; |
| 1266 | |
| 1267 | case UNIT_LOADED: |
| 1268 | severity = FACET_ROW_SEVERITY_NORMAL; |
| 1269 | max_severity = FACET_ROW_SEVERITY_CRITICAL; |
| 1270 | break; |
| 1271 | |
| 1272 | case UNIT_MERGED: |
| 1273 | case UNIT_MASKED: |
| 1274 | case UNIT_STUB: |
| 1275 | severity = FACET_ROW_SEVERITY_DEBUG; |
| 1276 | max_severity = FACET_ROW_SEVERITY_DEBUG; |
| 1277 | break; |
| 1278 | } |
| 1279 | |
| 1280 | switch (u->UnitActiveState) { |
| 1281 | case UNIT_FAILED: |
| 1282 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL); |
| 1283 | break; |
| 1284 | |
| 1285 | default: |
| 1286 | case UNIT_RELOADING: |
| 1287 | case UNIT_ACTIVATING: |
| 1288 | case UNIT_DEACTIVATING: |
| 1289 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1290 | break; |
| 1291 | |
| 1292 | case UNIT_MAINTENANCE: |
| 1293 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE); |
| 1294 | break; |
| 1295 | |
| 1296 | case UNIT_ACTIVE: |
| 1297 | break; |
| 1298 | |
| 1299 | case UNIT_INACTIVE: |
| 1300 | severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG); |
| 1301 | break; |
| 1302 | } |
| 1303 | |
| 1304 | switch (u->FreezerState) { |
| 1305 | default: |
| 1306 | case FREEZER_FROZEN: |
| 1307 | case FREEZER_FREEZING: |
| 1308 | case FREEZER_THAWING: |
| 1309 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1310 | break; |
| 1311 | |
| 1312 | case FREEZER_RUNNING: |
| 1313 | break; |
| 1314 | } |
| 1315 | |
| 1316 | switch (u->UnitType) { |
| 1317 | case UNIT_SERVICE: |
| 1318 | switch (u->ServiceState) { |
| 1319 | case SERVICE_FAILED: |
| 1320 | case SERVICE_FAILED_BEFORE_AUTO_RESTART: |
| 1321 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL); |
| 1322 | break; |
| 1323 | |
| 1324 | default: |
| 1325 | case SERVICE_STOP: |
| 1326 | case SERVICE_STOP_WATCHDOG: |
| 1327 | case SERVICE_STOP_SIGTERM: |
| 1328 | case SERVICE_STOP_SIGKILL: |
| 1329 | case SERVICE_STOP_POST: |
| 1330 | case SERVICE_FINAL_WATCHDOG: |
| 1331 | case SERVICE_FINAL_SIGTERM: |
| 1332 | case SERVICE_FINAL_SIGKILL: |
| 1333 | case SERVICE_AUTO_RESTART: |
| 1334 | case SERVICE_AUTO_RESTART_QUEUED: |
| 1335 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1336 | break; |
| 1337 | |
| 1338 | case SERVICE_CONDITION: |
| 1339 | case SERVICE_START_PRE: |
| 1340 | case SERVICE_START: |
| 1341 | case SERVICE_START_POST: |
| 1342 | case SERVICE_RELOAD: |
| 1343 | case SERVICE_RELOAD_SIGNAL: |
| 1344 | case SERVICE_RELOAD_NOTIFY: |
| 1345 | case SERVICE_DEAD_RESOURCES_PINNED: |
| 1346 | case SERVICE_CLEANING: |
| 1347 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE); |
| 1348 | break; |
| 1349 | |
| 1350 | case SERVICE_EXITED: |
| 1351 | case SERVICE_RUNNING: |
| 1352 | break; |
| 1353 | |
| 1354 | case SERVICE_DEAD: |
| 1355 | case SERVICE_DEAD_BEFORE_AUTO_RESTART: |
| 1356 | severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG); |
| 1357 | break; |
| 1358 | } |
| 1359 | break; |
| 1360 | |
| 1361 | case UNIT_MOUNT: |
| 1362 | switch (u->MountState) { |
| 1363 | case MOUNT_FAILED: |
| 1364 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL); |
| 1365 | break; |
| 1366 | |
| 1367 | default: |
| 1368 | case MOUNT_REMOUNTING_SIGTERM: |
| 1369 | case MOUNT_REMOUNTING_SIGKILL: |
| 1370 | case MOUNT_UNMOUNTING_SIGTERM: |
| 1371 | case MOUNT_UNMOUNTING_SIGKILL: |
| 1372 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1373 | break; |
| 1374 | |
| 1375 | case MOUNT_MOUNTING: |
| 1376 | case MOUNT_MOUNTING_DONE: |
| 1377 | case MOUNT_REMOUNTING: |
| 1378 | case MOUNT_UNMOUNTING: |
| 1379 | case MOUNT_CLEANING: |
| 1380 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE); |
| 1381 | break; |
| 1382 | |
| 1383 | case MOUNT_MOUNTED: |
| 1384 | break; |
| 1385 | |
| 1386 | case MOUNT_DEAD: |
| 1387 | severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG); |
| 1388 | break; |
| 1389 | } |
| 1390 | break; |
| 1391 | |
| 1392 | case UNIT_SWAP: |
| 1393 | switch (u->SwapState) { |
| 1394 | case SWAP_FAILED: |
| 1395 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL); |
| 1396 | break; |
| 1397 | |
| 1398 | default: |
| 1399 | case SWAP_DEACTIVATING_SIGTERM: |
| 1400 | case SWAP_DEACTIVATING_SIGKILL: |
| 1401 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1402 | break; |
| 1403 | |
| 1404 | case SWAP_ACTIVATING: |
| 1405 | case SWAP_ACTIVATING_DONE: |
| 1406 | case SWAP_DEACTIVATING: |
| 1407 | case SWAP_CLEANING: |
| 1408 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE); |
| 1409 | break; |
| 1410 | |
| 1411 | case SWAP_ACTIVE: |
| 1412 | break; |
| 1413 | |
| 1414 | case SWAP_DEAD: |
| 1415 | severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG); |
| 1416 | break; |
| 1417 | } |
| 1418 | break; |
| 1419 | |
| 1420 | case UNIT_SOCKET: |
| 1421 | switch (u->SocketState) { |
| 1422 | case SOCKET_FAILED: |
| 1423 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL); |
| 1424 | break; |
| 1425 | |
| 1426 | default: |
| 1427 | case SOCKET_STOP_PRE_SIGTERM: |
| 1428 | case SOCKET_STOP_PRE_SIGKILL: |
| 1429 | case SOCKET_FINAL_SIGTERM: |
| 1430 | case SOCKET_FINAL_SIGKILL: |
| 1431 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1432 | break; |
| 1433 | |
| 1434 | case SOCKET_START_PRE: |
| 1435 | case SOCKET_START_CHOWN: |
| 1436 | case SOCKET_START_POST: |
| 1437 | case SOCKET_STOP_PRE: |
| 1438 | case SOCKET_STOP_POST: |
| 1439 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE); |
| 1440 | break; |
| 1441 | |
| 1442 | case SOCKET_RUNNING: |
| 1443 | case SOCKET_LISTENING: |
| 1444 | break; |
| 1445 | |
| 1446 | case SOCKET_DEAD: |
| 1447 | severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG); |
| 1448 | break; |
| 1449 | } |
| 1450 | break; |
| 1451 | |
| 1452 | case UNIT_TARGET: |
| 1453 | switch (u->TargetState) { |
| 1454 | default: |
| 1455 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1456 | break; |
| 1457 | |
| 1458 | case TARGET_ACTIVE: |
| 1459 | break; |
| 1460 | |
| 1461 | case TARGET_DEAD: |
| 1462 | severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG); |
| 1463 | break; |
| 1464 | } |
| 1465 | break; |
| 1466 | |
| 1467 | case UNIT_DEVICE: |
| 1468 | switch (u->DeviceState) { |
| 1469 | default: |
| 1470 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1471 | break; |
| 1472 | |
| 1473 | case DEVICE_TENTATIVE: |
| 1474 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE); |
| 1475 | break; |
| 1476 | |
| 1477 | case DEVICE_PLUGGED: |
| 1478 | break; |
| 1479 | |
| 1480 | case DEVICE_DEAD: |
| 1481 | severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG); |
| 1482 | break; |
| 1483 | } |
| 1484 | break; |
| 1485 | |
| 1486 | case UNIT_AUTOMOUNT: |
| 1487 | switch (u->AutomountState) { |
| 1488 | case AUTOMOUNT_FAILED: |
| 1489 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL); |
| 1490 | break; |
| 1491 | |
| 1492 | default: |
| 1493 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1494 | break; |
| 1495 | |
| 1496 | case AUTOMOUNT_WAITING: |
| 1497 | case AUTOMOUNT_RUNNING: |
| 1498 | break; |
| 1499 | |
| 1500 | case AUTOMOUNT_DEAD: |
| 1501 | severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG); |
| 1502 | break; |
| 1503 | } |
| 1504 | break; |
| 1505 | |
| 1506 | case UNIT_TIMER: |
| 1507 | switch (u->TimerState) { |
| 1508 | case TIMER_FAILED: |
| 1509 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL); |
| 1510 | break; |
| 1511 | |
| 1512 | default: |
| 1513 | case TIMER_ELAPSED: |
| 1514 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1515 | break; |
| 1516 | |
| 1517 | case TIMER_WAITING: |
| 1518 | case TIMER_RUNNING: |
| 1519 | break; |
| 1520 | |
| 1521 | case TIMER_DEAD: |
| 1522 | severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG); |
| 1523 | break; |
| 1524 | } |
| 1525 | break; |
| 1526 | |
| 1527 | case UNIT_PATH: |
| 1528 | switch (u->PathState) { |
| 1529 | case PATH_FAILED: |
| 1530 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL); |
| 1531 | break; |
| 1532 | |
| 1533 | default: |
| 1534 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1535 | break; |
| 1536 | |
| 1537 | case PATH_WAITING: |
| 1538 | case PATH_RUNNING: |
| 1539 | break; |
| 1540 | |
| 1541 | case PATH_DEAD: |
| 1542 | severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG); |
| 1543 | break; |
| 1544 | } |
| 1545 | break; |
| 1546 | |
| 1547 | case UNIT_SLICE: |
| 1548 | switch (u->SliceState) { |
| 1549 | default: |
| 1550 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1551 | break; |
| 1552 | |
| 1553 | case SLICE_ACTIVE: |
| 1554 | break; |
| 1555 | |
| 1556 | case SLICE_DEAD: |
| 1557 | severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG); |
| 1558 | break; |
| 1559 | } |
| 1560 | break; |
| 1561 | |
| 1562 | case UNIT_SCOPE: |
| 1563 | switch (u->ScopeState) { |
| 1564 | case SCOPE_FAILED: |
| 1565 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL); |
| 1566 | break; |
| 1567 | |
| 1568 | default: |
| 1569 | case SCOPE_STOP_SIGTERM: |
| 1570 | case SCOPE_STOP_SIGKILL: |
| 1571 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1572 | break; |
| 1573 | |
| 1574 | case SCOPE_ABANDONED: |
| 1575 | case SCOPE_START_CHOWN: |
| 1576 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE); |
| 1577 | break; |
| 1578 | |
| 1579 | case SCOPE_RUNNING: |
| 1580 | break; |
| 1581 | |
| 1582 | case SCOPE_DEAD: |
| 1583 | severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG); |
| 1584 | break; |
| 1585 | } |
| 1586 | break; |
| 1587 | |
| 1588 | default: |
| 1589 | severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING); |
| 1590 | break; |
| 1591 | } |
| 1592 | |
| 1593 | u->severity = severity; |
| 1594 | return severity; |
| 1595 | } |
| 1596 | |
| 1597 | static int unit_info_compar(const void *a, const void *b) |
| 1598 | { |
| 1599 | UnitInfo *u1 = *((UnitInfo **)a); |
| 1600 | UnitInfo *u2 = *((UnitInfo **)b); |
| 1601 | |
| 1602 | return strcasecmp(u1->id, u2->id); |
| 1603 | } |
| 1604 | |
| 1605 | static void systemd_units_assign_priority(UnitInfo *base) |
| 1606 | { |
| 1607 | size_t units = 0, c = 0, prio = 0; |
| 1608 | for (UnitInfo *u = base; u; u = u->next) |
| 1609 | units++; |
| 1610 | |
| 1611 | UnitInfo *array[units]; |
| 1612 | for (UnitInfo *u = base; u; u = u->next) |
| 1613 | array[c++] = u; |
| 1614 | |
| 1615 | qsort(array, units, sizeof(UnitInfo *), unit_info_compar); |
| 1616 | |
| 1617 | for (c = 0; c < units; c++) { |
| 1618 | array[c]->prio = prio++; |
| 1619 | system_unit_severity(array[c]); |
| 1620 | systemd_unit_priority(array[c], units); |
| 1621 | } |
| 1622 | } |
| 1623 | |
| 1624 | void function_systemd_units( |
| 1625 | const char *transaction, |
| 1626 | char *function, |
| 1627 | usec_t *stop_monotonic_ut __maybe_unused, |
| 1628 | bool *cancelled __maybe_unused, |
| 1629 | BUFFER *payload __maybe_unused, |
| 1630 | HTTP_ACCESS access __maybe_unused, |
| 1631 | const char *source __maybe_unused, |
| 1632 | void *data __maybe_unused) |
| 1633 | { |
| 1634 | char *words[ND_SD_UNITS_MAX_PARAMS] = {NULL}; |
| 1635 | size_t num_words = quoted_strings_splitter_whitespace(function, words, ND_SD_UNITS_MAX_PARAMS); |
| 1636 | for (int i = 1; i < ND_SD_UNITS_MAX_PARAMS; i++) { |
| 1637 | char *keyword = get_word(words, num_words, i); |
| 1638 | if (!keyword) |
| 1639 | break; |
| 1640 | |
| 1641 | if (strcmp(keyword, "info") == 0) { |
| 1642 | netdata_systemd_units_function_info(transaction); |
| 1643 | return; |
| 1644 | } else if (strcmp(keyword, "help") == 0) { |
| 1645 | netdata_systemd_units_function_help(transaction); |
| 1646 | return; |
| 1647 | } |
| 1648 | } |
| 1649 | |
| 1650 | UnitInfo *base = systemd_units_get_all(); |
| 1651 | systemd_units_assign_priority(base); |
| 1652 | |
| 1653 | BUFFER *wb = buffer_create(0, NULL); |
| 1654 | buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY); |
| 1655 | |
| 1656 | buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK); |
| 1657 | buffer_json_member_add_string(wb, "type", "table"); |
| 1658 | buffer_json_member_add_boolean(wb, "has_history", false); |
| 1659 | buffer_json_member_add_time_t(wb, "update_every", 10); |
| 1660 | buffer_json_member_add_string(wb, "help", ND_SD_UNITS_FUNCTION_DESCRIPTION); |
| 1661 | buffer_json_member_add_array(wb, "data"); |
| 1662 | |
| 1663 | size_t count[_UNIT_ATTRIBUTE_MAX] = {0}; |
| 1664 | struct UnitAttribute max[_UNIT_ATTRIBUTE_MAX]; |
| 1665 | |
| 1666 | for (UnitInfo *u = base; u; u = u->next) { |
| 1667 | buffer_json_add_array_item_array(wb); |
| 1668 | { |
| 1669 | buffer_json_add_array_item_string(wb, u->id); |
| 1670 | |
| 1671 | buffer_json_add_array_item_object(wb); |
| 1672 | { |
| 1673 | buffer_json_member_add_string(wb, "severity", facets_severity_to_string(u->severity)); |
| 1674 | } |
| 1675 | buffer_json_object_close(wb); |
| 1676 | |
| 1677 | buffer_json_add_array_item_string(wb, u->type); |
| 1678 | buffer_json_add_array_item_string(wb, u->description); |
| 1679 | buffer_json_add_array_item_string(wb, u->load_state); |
| 1680 | buffer_json_add_array_item_string(wb, u->active_state); |
| 1681 | buffer_json_add_array_item_string(wb, u->sub_state); |
| 1682 | buffer_json_add_array_item_string(wb, u->following); |
| 1683 | buffer_json_add_array_item_string(wb, u->unit_path); |
| 1684 | buffer_json_add_array_item_uint64(wb, u->job_id); |
| 1685 | buffer_json_add_array_item_string(wb, u->job_type); |
| 1686 | buffer_json_add_array_item_string(wb, u->job_path); |
| 1687 | |
| 1688 | for (ssize_t i = 0; i < (ssize_t)_UNIT_ATTRIBUTE_MAX; i++) { |
| 1689 | switch (unit_attributes[i].value_type) { |
| 1690 | case SD_BUS_TYPE_OBJECT_PATH: |
| 1691 | case SD_BUS_TYPE_STRING: |
| 1692 | buffer_json_add_array_item_string( |
| 1693 | wb, u->attributes[i].str && *u->attributes[i].str ? u->attributes[i].str : "-"); |
| 1694 | break; |
| 1695 | |
| 1696 | case SD_BUS_TYPE_UINT64: |
| 1697 | buffer_json_add_array_item_uint64(wb, u->attributes[i].uint64); |
| 1698 | if (!count[i]++) |
| 1699 | max[i].uint64 = 0; |
| 1700 | max[i].uint64 = MAX(max[i].uint64, u->attributes[i].uint64); |
| 1701 | break; |
| 1702 | |
| 1703 | case SD_BUS_TYPE_UINT32: |
| 1704 | buffer_json_add_array_item_uint64(wb, u->attributes[i].uint32); |
| 1705 | if (!count[i]++) |
| 1706 | max[i].uint32 = 0; |
| 1707 | max[i].uint32 = MAX(max[i].uint32, u->attributes[i].uint32); |
| 1708 | break; |
| 1709 | |
| 1710 | case SD_BUS_TYPE_INT64: |
| 1711 | buffer_json_add_array_item_uint64(wb, u->attributes[i].int64); |
| 1712 | if (!count[i]++) |
| 1713 | max[i].uint64 = 0; |
| 1714 | max[i].int64 = MAX(max[i].int64, u->attributes[i].int64); |
| 1715 | break; |
| 1716 | |
| 1717 | case SD_BUS_TYPE_INT32: |
| 1718 | buffer_json_add_array_item_uint64(wb, u->attributes[i].int32); |
| 1719 | if (!count[i]++) |
| 1720 | max[i].int32 = 0; |
| 1721 | max[i].int32 = MAX(max[i].int32, u->attributes[i].int32); |
| 1722 | break; |
| 1723 | |
| 1724 | case SD_BUS_TYPE_DOUBLE: |
| 1725 | buffer_json_add_array_item_double(wb, u->attributes[i].dbl); |
| 1726 | if (!count[i]++) |
| 1727 | max[i].dbl = 0.0; |
| 1728 | max[i].dbl = MAX(max[i].dbl, u->attributes[i].dbl); |
| 1729 | break; |
| 1730 | |
| 1731 | case SD_BUS_TYPE_BOOLEAN: |
| 1732 | buffer_json_add_array_item_boolean(wb, u->attributes[i].boolean); |
| 1733 | break; |
| 1734 | |
| 1735 | default: |
| 1736 | break; |
| 1737 | } |
| 1738 | } |
| 1739 | |
| 1740 | buffer_json_add_array_item_uint64(wb, u->prio); |
| 1741 | buffer_json_add_array_item_uint64(wb, 1); // count |
| 1742 | } |
| 1743 | buffer_json_array_close(wb); |
| 1744 | } |
| 1745 | |
| 1746 | buffer_json_array_close(wb); // data |
| 1747 | |
| 1748 | buffer_json_member_add_object(wb, "columns"); |
| 1749 | { |
| 1750 | size_t field_id = 0; |
| 1751 | |
| 1752 | buffer_rrdf_table_add_field( |
| 1753 | wb, |
| 1754 | field_id++, |
| 1755 | "id", |
| 1756 | "Unit ID", |
| 1757 | RRDF_FIELD_TYPE_STRING, |
| 1758 | RRDF_FIELD_VISUAL_VALUE, |
| 1759 | RRDF_FIELD_TRANSFORM_NONE, |
| 1760 | 0, |
| 1761 | NULL, |
| 1762 | NAN, |
| 1763 | RRDF_FIELD_SORT_ASCENDING, |
| 1764 | NULL, |
| 1765 | RRDF_FIELD_SUMMARY_COUNT, |
| 1766 | RRDF_FIELD_FILTER_NONE, |
| 1767 | RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_WRAP | RRDF_FIELD_OPTS_FULL_WIDTH, |
| 1768 | NULL); |
| 1769 | |
| 1770 | buffer_rrdf_table_add_field( |
| 1771 | wb, |
| 1772 | field_id++, |
| 1773 | "rowOptions", |
| 1774 | "rowOptions", |
| 1775 | RRDF_FIELD_TYPE_NONE, |
| 1776 | RRDR_FIELD_VISUAL_ROW_OPTIONS, |
| 1777 | RRDF_FIELD_TRANSFORM_NONE, |
| 1778 | 0, |
| 1779 | NULL, |
| 1780 | NAN, |
| 1781 | RRDF_FIELD_SORT_FIXED, |
| 1782 | NULL, |
| 1783 | RRDF_FIELD_SUMMARY_COUNT, |
| 1784 | RRDF_FIELD_FILTER_NONE, |
| 1785 | RRDF_FIELD_OPTS_DUMMY, |
| 1786 | NULL); |
| 1787 | |
| 1788 | buffer_rrdf_table_add_field( |
| 1789 | wb, |
| 1790 | field_id++, |
| 1791 | "type", |
| 1792 | "Unit Type", |
| 1793 | RRDF_FIELD_TYPE_STRING, |
| 1794 | RRDF_FIELD_VISUAL_VALUE, |
| 1795 | RRDF_FIELD_TRANSFORM_NONE, |
| 1796 | 0, |
| 1797 | NULL, |
| 1798 | NAN, |
| 1799 | RRDF_FIELD_SORT_ASCENDING, |
| 1800 | NULL, |
| 1801 | RRDF_FIELD_SUMMARY_COUNT, |
| 1802 | RRDF_FIELD_FILTER_MULTISELECT, |
| 1803 | RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_EXPANDED_FILTER, |
| 1804 | NULL); |
| 1805 | |
| 1806 | buffer_rrdf_table_add_field( |
| 1807 | wb, |
| 1808 | field_id++, |
| 1809 | "description", |
| 1810 | "Unit Description", |
| 1811 | RRDF_FIELD_TYPE_STRING, |
| 1812 | RRDF_FIELD_VISUAL_VALUE, |
| 1813 | RRDF_FIELD_TRANSFORM_NONE, |
| 1814 | 0, |
| 1815 | NULL, |
| 1816 | NAN, |
| 1817 | RRDF_FIELD_SORT_ASCENDING, |
| 1818 | NULL, |
| 1819 | RRDF_FIELD_SUMMARY_COUNT, |
| 1820 | RRDF_FIELD_FILTER_NONE, |
| 1821 | RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_WRAP | RRDF_FIELD_OPTS_FULL_WIDTH, |
| 1822 | NULL); |
| 1823 | |
| 1824 | buffer_rrdf_table_add_field( |
| 1825 | wb, |
| 1826 | field_id++, |
| 1827 | "loadState", |
| 1828 | "Unit Load State", |
| 1829 | RRDF_FIELD_TYPE_STRING, |
| 1830 | RRDF_FIELD_VISUAL_VALUE, |
| 1831 | RRDF_FIELD_TRANSFORM_NONE, |
| 1832 | 0, |
| 1833 | NULL, |
| 1834 | NAN, |
| 1835 | RRDF_FIELD_SORT_ASCENDING, |
| 1836 | NULL, |
| 1837 | RRDF_FIELD_SUMMARY_COUNT, |
| 1838 | RRDF_FIELD_FILTER_MULTISELECT, |
| 1839 | RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_EXPANDED_FILTER, |
| 1840 | NULL); |
| 1841 | |
| 1842 | buffer_rrdf_table_add_field( |
| 1843 | wb, |
| 1844 | field_id++, |
| 1845 | "activeState", |
| 1846 | "Unit Active State", |
| 1847 | RRDF_FIELD_TYPE_STRING, |
| 1848 | RRDF_FIELD_VISUAL_VALUE, |
| 1849 | RRDF_FIELD_TRANSFORM_NONE, |
| 1850 | 0, |
| 1851 | NULL, |
| 1852 | NAN, |
| 1853 | RRDF_FIELD_SORT_ASCENDING, |
| 1854 | NULL, |
| 1855 | RRDF_FIELD_SUMMARY_COUNT, |
| 1856 | RRDF_FIELD_FILTER_MULTISELECT, |
| 1857 | RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_EXPANDED_FILTER, |
| 1858 | NULL); |
| 1859 | |
| 1860 | buffer_rrdf_table_add_field( |
| 1861 | wb, |
| 1862 | field_id++, |
| 1863 | "subState", |
| 1864 | "Unit Sub State", |
| 1865 | RRDF_FIELD_TYPE_STRING, |
| 1866 | RRDF_FIELD_VISUAL_VALUE, |
| 1867 | RRDF_FIELD_TRANSFORM_NONE, |
| 1868 | 0, |
| 1869 | NULL, |
| 1870 | NAN, |
| 1871 | RRDF_FIELD_SORT_ASCENDING, |
| 1872 | NULL, |
| 1873 | RRDF_FIELD_SUMMARY_COUNT, |
| 1874 | RRDF_FIELD_FILTER_MULTISELECT, |
| 1875 | RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_EXPANDED_FILTER, |
| 1876 | NULL); |
| 1877 | |
| 1878 | buffer_rrdf_table_add_field( |
| 1879 | wb, |
| 1880 | field_id++, |
| 1881 | "following", |
| 1882 | "Unit Following", |
| 1883 | RRDF_FIELD_TYPE_STRING, |
| 1884 | RRDF_FIELD_VISUAL_VALUE, |
| 1885 | RRDF_FIELD_TRANSFORM_NONE, |
| 1886 | 0, |
| 1887 | NULL, |
| 1888 | NAN, |
| 1889 | RRDF_FIELD_SORT_ASCENDING, |
| 1890 | NULL, |
| 1891 | RRDF_FIELD_SUMMARY_COUNT, |
| 1892 | RRDF_FIELD_FILTER_NONE, |
| 1893 | RRDF_FIELD_OPTS_WRAP, |
| 1894 | NULL); |
| 1895 | |
| 1896 | buffer_rrdf_table_add_field( |
| 1897 | wb, |
| 1898 | field_id++, |
| 1899 | "path", |
| 1900 | "Unit Path", |
| 1901 | RRDF_FIELD_TYPE_STRING, |
| 1902 | RRDF_FIELD_VISUAL_VALUE, |
| 1903 | RRDF_FIELD_TRANSFORM_NONE, |
| 1904 | 0, |
| 1905 | NULL, |
| 1906 | NAN, |
| 1907 | RRDF_FIELD_SORT_ASCENDING, |
| 1908 | NULL, |
| 1909 | RRDF_FIELD_SUMMARY_COUNT, |
| 1910 | RRDF_FIELD_FILTER_NONE, |
| 1911 | RRDF_FIELD_OPTS_WRAP | RRDF_FIELD_OPTS_FULL_WIDTH, |
| 1912 | NULL); |
| 1913 | |
| 1914 | buffer_rrdf_table_add_field( |
| 1915 | wb, |
| 1916 | field_id++, |
| 1917 | "jobId", |
| 1918 | "Unit Job ID", |
| 1919 | RRDF_FIELD_TYPE_INTEGER, |
| 1920 | RRDF_FIELD_VISUAL_VALUE, |
| 1921 | RRDF_FIELD_TRANSFORM_NONE, |
| 1922 | 0, |
| 1923 | NULL, |
| 1924 | NAN, |
| 1925 | RRDF_FIELD_SORT_ASCENDING, |
| 1926 | NULL, |
| 1927 | RRDF_FIELD_SUMMARY_COUNT, |
| 1928 | RRDF_FIELD_FILTER_NONE, |
| 1929 | RRDF_FIELD_OPTS_NONE, |
| 1930 | NULL); |
| 1931 | |
| 1932 | buffer_rrdf_table_add_field( |
| 1933 | wb, |
| 1934 | field_id++, |
| 1935 | "jobType", |
| 1936 | "Unit Job Type", |
| 1937 | RRDF_FIELD_TYPE_STRING, |
| 1938 | RRDF_FIELD_VISUAL_VALUE, |
| 1939 | RRDF_FIELD_TRANSFORM_NONE, |
| 1940 | 0, |
| 1941 | NULL, |
| 1942 | NAN, |
| 1943 | RRDF_FIELD_SORT_ASCENDING, |
| 1944 | NULL, |
| 1945 | RRDF_FIELD_SUMMARY_COUNT, |
| 1946 | RRDF_FIELD_FILTER_MULTISELECT, |
| 1947 | RRDF_FIELD_OPTS_NONE, |
| 1948 | NULL); |
| 1949 | |
| 1950 | buffer_rrdf_table_add_field( |
| 1951 | wb, |
| 1952 | field_id++, |
| 1953 | "jobPath", |
| 1954 | "Unit Job Path", |
| 1955 | RRDF_FIELD_TYPE_STRING, |
| 1956 | RRDF_FIELD_VISUAL_VALUE, |
| 1957 | RRDF_FIELD_TRANSFORM_NONE, |
| 1958 | 0, |
| 1959 | NULL, |
| 1960 | NAN, |
| 1961 | RRDF_FIELD_SORT_ASCENDING, |
| 1962 | NULL, |
| 1963 | RRDF_FIELD_SUMMARY_COUNT, |
| 1964 | RRDF_FIELD_FILTER_NONE, |
| 1965 | RRDF_FIELD_OPTS_WRAP | RRDF_FIELD_OPTS_FULL_WIDTH, |
| 1966 | NULL); |
| 1967 | |
| 1968 | for (ssize_t i = 0; i < (ssize_t)_UNIT_ATTRIBUTE_MAX; i++) { |
| 1969 | char key[256], name[256]; |
| 1970 | |
| 1971 | if (unit_attributes[i].show_as) |
| 1972 | snprintfz(key, sizeof(key), "%s", unit_attributes[i].show_as); |
| 1973 | else |
| 1974 | snprintfz(key, sizeof(key), "attribute%s", unit_property_name_to_string_from_slot(i)); |
| 1975 | |
| 1976 | if (unit_attributes[i].info) |
| 1977 | snprintfz(name, sizeof(name), "%s", unit_attributes[i].info); |
| 1978 | else |
| 1979 | snprintfz(name, sizeof(name), "Attribute %s", unit_property_name_to_string_from_slot(i)); |
| 1980 | |
| 1981 | RRDF_FIELD_OPTIONS options = unit_attributes[i].options; |
| 1982 | RRDF_FIELD_FILTER filter = unit_attributes[i].filter; |
| 1983 | |
| 1984 | switch (unit_attributes[i].value_type) { |
| 1985 | case SD_BUS_TYPE_OBJECT_PATH: |
| 1986 | case SD_BUS_TYPE_STRING: |
| 1987 | buffer_rrdf_table_add_field( |
| 1988 | wb, |
| 1989 | field_id++, |
| 1990 | key, |
| 1991 | name, |
| 1992 | RRDF_FIELD_TYPE_STRING, |
| 1993 | RRDF_FIELD_VISUAL_VALUE, |
| 1994 | RRDF_FIELD_TRANSFORM_NONE, |
| 1995 | 0, |
| 1996 | NULL, |
| 1997 | NAN, |
| 1998 | RRDF_FIELD_SORT_ASCENDING, |
| 1999 | NULL, |
| 2000 | RRDF_FIELD_SUMMARY_COUNT, |
| 2001 | filter, |
| 2002 | RRDF_FIELD_OPTS_WRAP | options, |
| 2003 | NULL); |
| 2004 | break; |
| 2005 | |
| 2006 | case SD_BUS_TYPE_INT32: |
| 2007 | case SD_BUS_TYPE_UINT32: |
| 2008 | case SD_BUS_TYPE_INT64: |
| 2009 | case SD_BUS_TYPE_UINT64: { |
| 2010 | double m = 0.0; |
| 2011 | if (unit_attributes[i].value_type == SD_BUS_TYPE_UINT64) |
| 2012 | m = (double)max[i].uint64; |
| 2013 | else if (unit_attributes[i].value_type == SD_BUS_TYPE_INT64) |
| 2014 | m = (double)max[i].int64; |
| 2015 | else if (unit_attributes[i].value_type == SD_BUS_TYPE_UINT32) |
| 2016 | m = (double)max[i].uint32; |
| 2017 | else if (unit_attributes[i].value_type == SD_BUS_TYPE_INT32) |
| 2018 | m = (double)max[i].int32; |
| 2019 | |
| 2020 | buffer_rrdf_table_add_field( |
| 2021 | wb, |
| 2022 | field_id++, |
| 2023 | key, |
| 2024 | name, |
| 2025 | RRDF_FIELD_TYPE_INTEGER, |
| 2026 | RRDF_FIELD_VISUAL_VALUE, |
| 2027 | RRDF_FIELD_TRANSFORM_NONE, |
| 2028 | 0, |
| 2029 | NULL, |
| 2030 | m, |
| 2031 | RRDF_FIELD_SORT_ASCENDING, |
| 2032 | NULL, |
| 2033 | RRDF_FIELD_SUMMARY_SUM, |
| 2034 | filter, |
| 2035 | RRDF_FIELD_OPTS_WRAP | options, |
| 2036 | NULL); |
| 2037 | } break; |
| 2038 | |
| 2039 | case SD_BUS_TYPE_DOUBLE: |
| 2040 | buffer_rrdf_table_add_field( |
| 2041 | wb, |
| 2042 | field_id++, |
| 2043 | key, |
| 2044 | name, |
| 2045 | RRDF_FIELD_TYPE_INTEGER, |
| 2046 | RRDF_FIELD_VISUAL_VALUE, |
| 2047 | RRDF_FIELD_TRANSFORM_NONE, |
| 2048 | 2, |
| 2049 | NULL, |
| 2050 | max[i].dbl, |
| 2051 | RRDF_FIELD_SORT_ASCENDING, |
| 2052 | NULL, |
| 2053 | RRDF_FIELD_SUMMARY_SUM, |
| 2054 | filter, |
| 2055 | RRDF_FIELD_OPTS_WRAP | options, |
| 2056 | NULL); |
| 2057 | break; |
| 2058 | |
| 2059 | case SD_BUS_TYPE_BOOLEAN: |
| 2060 | buffer_rrdf_table_add_field( |
| 2061 | wb, |
| 2062 | field_id++, |
| 2063 | key, |
| 2064 | name, |
| 2065 | RRDF_FIELD_TYPE_BOOLEAN, |
| 2066 | RRDF_FIELD_VISUAL_VALUE, |
| 2067 | RRDF_FIELD_TRANSFORM_NONE, |
| 2068 | 0, |
| 2069 | NULL, |
| 2070 | NAN, |
| 2071 | RRDF_FIELD_SORT_ASCENDING, |
| 2072 | NULL, |
| 2073 | RRDF_FIELD_SUMMARY_COUNT, |
| 2074 | filter, |
| 2075 | RRDF_FIELD_OPTS_WRAP | options, |
| 2076 | NULL); |
| 2077 | break; |
| 2078 | |
| 2079 | default: |
| 2080 | break; |
| 2081 | } |
| 2082 | } |
| 2083 | |
| 2084 | buffer_rrdf_table_add_field( |
| 2085 | wb, |
| 2086 | field_id++, |
| 2087 | "priority", |
| 2088 | "Priority", |
| 2089 | RRDF_FIELD_TYPE_INTEGER, |
| 2090 | RRDF_FIELD_VISUAL_VALUE, |
| 2091 | RRDF_FIELD_TRANSFORM_NONE, |
| 2092 | 0, |
| 2093 | NULL, |
| 2094 | NAN, |
| 2095 | RRDF_FIELD_SORT_ASCENDING, |
| 2096 | NULL, |
| 2097 | RRDF_FIELD_SUMMARY_COUNT, |
| 2098 | RRDF_FIELD_FILTER_NONE, |
| 2099 | RRDF_FIELD_OPTS_NONE, |
| 2100 | NULL); |
| 2101 | |
| 2102 | buffer_rrdf_table_add_field( |
| 2103 | wb, |
| 2104 | field_id++, |
| 2105 | "count", |
| 2106 | "Count", |
| 2107 | RRDF_FIELD_TYPE_INTEGER, |
| 2108 | RRDF_FIELD_VISUAL_VALUE, |
| 2109 | RRDF_FIELD_TRANSFORM_NONE, |
| 2110 | 0, |
| 2111 | NULL, |
| 2112 | NAN, |
| 2113 | RRDF_FIELD_SORT_ASCENDING, |
| 2114 | NULL, |
| 2115 | RRDF_FIELD_SUMMARY_COUNT, |
| 2116 | RRDF_FIELD_FILTER_NONE, |
| 2117 | RRDF_FIELD_OPTS_NONE, |
| 2118 | NULL); |
| 2119 | } |
| 2120 | |
| 2121 | buffer_json_object_close(wb); // columns |
| 2122 | buffer_json_member_add_string(wb, "default_sort_column", "priority"); |
| 2123 | |
| 2124 | buffer_json_member_add_object(wb, "charts"); |
| 2125 | { |
| 2126 | buffer_json_member_add_object(wb, "count"); |
| 2127 | { |
| 2128 | buffer_json_member_add_string(wb, "name", "count"); |
| 2129 | buffer_json_member_add_string(wb, "type", "stacked-bar"); |
| 2130 | buffer_json_member_add_array(wb, "columns"); |
| 2131 | { |
| 2132 | buffer_json_add_array_item_string(wb, "count"); |
| 2133 | } |
| 2134 | buffer_json_array_close(wb); |
| 2135 | } |
| 2136 | buffer_json_object_close(wb); |
| 2137 | } |
| 2138 | buffer_json_object_close(wb); // charts |
| 2139 | |
| 2140 | buffer_json_member_add_array(wb, "default_charts"); |
| 2141 | { |
| 2142 | buffer_json_add_array_item_array(wb); |
| 2143 | buffer_json_add_array_item_string(wb, "count"); |
| 2144 | buffer_json_add_array_item_string(wb, "activeState"); |
| 2145 | buffer_json_array_close(wb); |
| 2146 | buffer_json_add_array_item_array(wb); |
| 2147 | buffer_json_add_array_item_string(wb, "count"); |
| 2148 | buffer_json_add_array_item_string(wb, "subState"); |
| 2149 | buffer_json_array_close(wb); |
| 2150 | } |
| 2151 | buffer_json_array_close(wb); |
| 2152 | |
| 2153 | buffer_json_member_add_object(wb, "group_by"); |
| 2154 | { |
| 2155 | buffer_json_member_add_object(wb, "type"); |
| 2156 | { |
| 2157 | buffer_json_member_add_string(wb, "name", "Top Down Tree"); |
| 2158 | buffer_json_member_add_array(wb, "columns"); |
| 2159 | { |
| 2160 | buffer_json_add_array_item_string(wb, "type"); |
| 2161 | buffer_json_add_array_item_string(wb, "loadState"); |
| 2162 | buffer_json_add_array_item_string(wb, "activeState"); |
| 2163 | buffer_json_add_array_item_string(wb, "subState"); |
| 2164 | } |
| 2165 | buffer_json_array_close(wb); |
| 2166 | } |
| 2167 | buffer_json_object_close(wb); |
| 2168 | |
| 2169 | buffer_json_member_add_object(wb, "subState"); |
| 2170 | { |
| 2171 | buffer_json_member_add_string(wb, "name", "Bottom Up Tree"); |
| 2172 | buffer_json_member_add_array(wb, "columns"); |
| 2173 | { |
| 2174 | buffer_json_add_array_item_string(wb, "subState"); |
| 2175 | buffer_json_add_array_item_string(wb, "activeState"); |
| 2176 | buffer_json_add_array_item_string(wb, "loadState"); |
| 2177 | buffer_json_add_array_item_string(wb, "type"); |
| 2178 | } |
| 2179 | buffer_json_array_close(wb); |
| 2180 | } |
| 2181 | buffer_json_object_close(wb); |
| 2182 | } |
| 2183 | buffer_json_object_close(wb); // group_by |
| 2184 | |
| 2185 | buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + 1); |
| 2186 | buffer_json_finalize(wb); |
| 2187 | |
| 2188 | netdata_mutex_lock(&stdout_mutex); |
| 2189 | wb->response_code = HTTP_RESP_OK; |
| 2190 | wb->content_type = CT_APPLICATION_JSON; |
| 2191 | wb->expires = now_realtime_sec() + 1; |
| 2192 | pluginsd_function_result_to_stdout(transaction, wb); |
| 2193 | netdata_mutex_unlock(&stdout_mutex); |
| 2194 | |
| 2195 | buffer_free(wb); |
| 2196 | systemd_units_free_all(base); |
| 2197 | } |
| 2198 | |
| 2199 | static bool plugin_should_exit = false; |
| 2200 | |
| 2201 | int main(int argc __maybe_unused, char **argv __maybe_unused) |
| 2202 | { |
| 2203 | nd_thread_tag_set("sd-unit.plugin"); |
| 2204 | nd_log_initialize_for_external_plugins("systemd-units.plugin"); |
| 2205 | netdata_threads_init_for_external_plugins(0); |
| 2206 | |
| 2207 | netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX"); |
| 2208 | if (verify_netdata_host_prefix(true) == -1) |
| 2209 | exit(1); |
| 2210 | |
| 2211 | // ------------------------------------------------------------------------ |
| 2212 | // debug |
| 2213 | |
| 2214 | if (argc == 2 && strcmp(argv[1], "debug-units") == 0) { |
| 2215 | bool cancelled = false; |
| 2216 | usec_t stop_monotonic_ut = now_monotonic_usec() + 600 * USEC_PER_SEC; |
| 2217 | function_systemd_units( |
| 2218 | "123", "systemd-units", &stop_monotonic_ut, &cancelled, NULL, HTTP_ACCESS_ALL, NULL, NULL); |
| 2219 | exit(1); |
| 2220 | } |
| 2221 | |
| 2222 | // ------------------------------------------------------------------------ |
| 2223 | // the event loop for functions |
| 2224 | |
| 2225 | struct functions_evloop_globals *wg = |
| 2226 | functions_evloop_init(ND_SD_JOURNAL_WORKER_THREADS, "SDU", &stdout_mutex, &plugin_should_exit, NULL); |
| 2227 | |
| 2228 | functions_evloop_add_function( |
| 2229 | wg, ND_SD_UNITS_FUNCTION_NAME, function_systemd_units, ND_SD_UNITS_DEFAULT_TIMEOUT, NULL); |
| 2230 | |
| 2231 | // ------------------------------------------------------------------------ |
| 2232 | // register functions to netdata |
| 2233 | |
| 2234 | netdata_mutex_lock(&stdout_mutex); |
| 2235 | |
| 2236 | fprintf( |
| 2237 | stdout, |
| 2238 | PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" " HTTP_ACCESS_FORMAT " %d\n", |
| 2239 | ND_SD_UNITS_FUNCTION_NAME, |
| 2240 | ND_SD_UNITS_DEFAULT_TIMEOUT, |
| 2241 | ND_SD_UNITS_FUNCTION_DESCRIPTION, |
| 2242 | (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA), |
| 2243 | RRDFUNCTIONS_PRIORITY_DEFAULT); |
| 2244 | |
| 2245 | fflush(stdout); |
| 2246 | netdata_mutex_unlock(&stdout_mutex); |
| 2247 | |
| 2248 | // ------------------------------------------------------------------------ |
| 2249 | |
| 2250 | usec_t send_newline_ut = 0; |
| 2251 | const bool tty = isatty(fileno(stdout)) == 1; |
| 2252 | |
| 2253 | heartbeat_t hb; |
| 2254 | heartbeat_init(&hb, USEC_PER_SEC); |
| 2255 | while (!__atomic_load_n(&plugin_should_exit, __ATOMIC_ACQUIRE)) { |
| 2256 | usec_t dt_ut = heartbeat_next(&hb); |
| 2257 | send_newline_ut += dt_ut; |
| 2258 | |
| 2259 | if (!tty && send_newline_ut > USEC_PER_SEC) { |
| 2260 | send_newline_and_flush(&stdout_mutex); |
| 2261 | send_newline_ut = 0; |
| 2262 | } |
| 2263 | } |
| 2264 | |
| 2265 | exit(0); |
| 2266 | } |