mirror of
https://github.com/netdata/netdata.git
synced 2025-04-17 19:22:40 +00:00
Systemd units function (#16318)
* split systemd-journal.c * split fstat caching * split systemd-journal further * working systemd-units function * do not enable systemd-units when libsystemd does not provide the interface * move the header to the right place * mixed parantheses * update codacy exlcusions * update codacy exlcusions * update codacy exlcusions * added option to show show expanded filters by default * keep the original extension and decode descriptions too * updated systemd-units function to handle all known unit states * dont show the path by default * final touches * remove trailing spaces
This commit is contained in:
parent
4e6883b881
commit
236c04b925
14 changed files with 2794 additions and 1297 deletions
.codacy.ymlMakefile.am
collectors/systemd-journal.plugin
systemd-internals.hsystemd-journal-annotations.csystemd-journal-files.csystemd-journal-fstat.csystemd-journal.csystemd-main.csystemd-units.c
configure.acdatabase
libnetdata
|
@ -10,9 +10,16 @@ exclude_paths:
|
|||
- web/gui/lib/**
|
||||
- web/gui/old/**
|
||||
- web/gui/src/**
|
||||
- web/gui/v1/**
|
||||
- web/gui/v2/**
|
||||
- web/gui/main.js
|
||||
- tests/**
|
||||
- aclk/tests/**
|
||||
- libnetdata/libjudy/**
|
||||
|
||||
- database/sqlite/sqlite3.c
|
||||
- ml/dlib/**
|
||||
- web/server/h2o/libh2o/**
|
||||
- build/**
|
||||
- build_external/**
|
||||
- libnetdata/dyn_conf/tests/**
|
||||
- packaging/**
|
||||
|
|
|
@ -312,7 +312,13 @@ FREEIPMI_PLUGIN_FILES = \
|
|||
$(NULL)
|
||||
|
||||
SYSTEMD_JOURNAL_PLUGIN_FILES = \
|
||||
collectors/systemd-journal.plugin/systemd-internals.h \
|
||||
collectors/systemd-journal.plugin/systemd-main.c \
|
||||
collectors/systemd-journal.plugin/systemd-units.c \
|
||||
collectors/systemd-journal.plugin/systemd-journal.c \
|
||||
collectors/systemd-journal.plugin/systemd-journal-annotations.c \
|
||||
collectors/systemd-journal.plugin/systemd-journal-files.c \
|
||||
collectors/systemd-journal.plugin/systemd-journal-fstat.c \
|
||||
$(LIBNETDATA_FILES) \
|
||||
$(NULL)
|
||||
|
||||
|
|
108
collectors/systemd-journal.plugin/systemd-internals.h
Normal file
108
collectors/systemd-journal.plugin/systemd-internals.h
Normal file
|
@ -0,0 +1,108 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef NETDATA_COLLECTORS_SYSTEMD_INTERNALS_H
|
||||
#define NETDATA_COLLECTORS_SYSTEMD_INTERNALS_H
|
||||
|
||||
#include "collectors/all.h"
|
||||
#include "libnetdata/libnetdata.h"
|
||||
|
||||
#include <linux/capability.h>
|
||||
#include <systemd/sd-journal.h>
|
||||
#include <syslog.h>
|
||||
|
||||
#define SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION "View, search and analyze systemd journal entries."
|
||||
#define SYSTEMD_JOURNAL_FUNCTION_NAME "systemd-journal"
|
||||
#define SYSTEMD_JOURNAL_DEFAULT_TIMEOUT 60
|
||||
|
||||
#define SYSTEMD_UNITS_FUNCTION_DESCRIPTION "View the status of systemd units"
|
||||
#define SYSTEMD_UNITS_FUNCTION_NAME "systemd-units"
|
||||
#define SYSTEMD_UNITS_DEFAULT_TIMEOUT 30
|
||||
|
||||
extern __thread size_t fstat_thread_calls;
|
||||
extern __thread size_t fstat_thread_cached_responses;
|
||||
void fstat_cache_enable_on_thread(void);
|
||||
void fstat_cache_disable_on_thread(void);
|
||||
|
||||
extern netdata_mutex_t stdout_mutex;
|
||||
|
||||
|
||||
typedef enum {
|
||||
ND_SD_JOURNAL_NO_FILE_MATCHED,
|
||||
ND_SD_JOURNAL_FAILED_TO_OPEN,
|
||||
ND_SD_JOURNAL_FAILED_TO_SEEK,
|
||||
ND_SD_JOURNAL_TIMED_OUT,
|
||||
ND_SD_JOURNAL_OK,
|
||||
ND_SD_JOURNAL_NOT_MODIFIED,
|
||||
ND_SD_JOURNAL_CANCELLED,
|
||||
} ND_SD_JOURNAL_STATUS;
|
||||
|
||||
typedef enum {
|
||||
SDJF_NONE = 0,
|
||||
SDJF_ALL = (1 << 0),
|
||||
SDJF_LOCAL_ALL = (1 << 1),
|
||||
SDJF_REMOTE_ALL = (1 << 2),
|
||||
SDJF_LOCAL_SYSTEM = (1 << 3),
|
||||
SDJF_LOCAL_USER = (1 << 4),
|
||||
SDJF_LOCAL_NAMESPACE = (1 << 5),
|
||||
SDJF_LOCAL_OTHER = (1 << 6),
|
||||
} SD_JOURNAL_FILE_SOURCE_TYPE;
|
||||
|
||||
struct journal_file {
|
||||
const char *filename;
|
||||
size_t filename_len;
|
||||
STRING *source;
|
||||
SD_JOURNAL_FILE_SOURCE_TYPE source_type;
|
||||
usec_t file_last_modified_ut;
|
||||
usec_t msg_first_ut;
|
||||
usec_t msg_last_ut;
|
||||
usec_t last_scan_ut;
|
||||
size_t size;
|
||||
bool logged_failure;
|
||||
usec_t max_journal_vs_realtime_delta_ut;
|
||||
};
|
||||
|
||||
#define SDJF_SOURCE_ALL_NAME "all"
|
||||
#define SDJF_SOURCE_LOCAL_NAME "all-local-logs"
|
||||
#define SDJF_SOURCE_LOCAL_SYSTEM_NAME "all-local-system-logs"
|
||||
#define SDJF_SOURCE_LOCAL_USERS_NAME "all-local-user-logs"
|
||||
#define SDJF_SOURCE_LOCAL_OTHER_NAME "all-uncategorized"
|
||||
#define SDJF_SOURCE_NAMESPACES_NAME "all-local-namespaces"
|
||||
#define SDJF_SOURCE_REMOTES_NAME "all-remote-systems"
|
||||
|
||||
#define ND_SD_JOURNAL_OPEN_FLAGS (0)
|
||||
|
||||
#define JOURNAL_VS_REALTIME_DELTA_DEFAULT_UT (5 * USEC_PER_SEC) // assume always 5 seconds latency
|
||||
#define JOURNAL_VS_REALTIME_DELTA_MAX_UT (2 * 60 * USEC_PER_SEC) // up to 2 minutes latency
|
||||
|
||||
extern DICTIONARY *journal_files_registry;
|
||||
extern DICTIONARY *used_hashes_registry;
|
||||
extern DICTIONARY *function_query_status_dict;
|
||||
extern DICTIONARY *boot_ids_to_first_ut;
|
||||
|
||||
int journal_file_dict_items_backward_compar(const void *a, const void *b);
|
||||
int journal_file_dict_items_forward_compar(const void *a, const void *b);
|
||||
void buffer_json_journal_versions(BUFFER *wb);
|
||||
void available_journal_file_sources_to_json_array(BUFFER *wb);
|
||||
|
||||
FACET_ROW_SEVERITY syslog_priority_to_facet_severity(FACETS *facets, FACET_ROW *row, void *data);
|
||||
|
||||
void netdata_systemd_journal_dynamic_row_id(FACETS *facets, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row, void *data);
|
||||
void netdata_systemd_journal_transform_priority(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
|
||||
void netdata_systemd_journal_transform_syslog_facility(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
|
||||
void netdata_systemd_journal_transform_errno(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
|
||||
void netdata_systemd_journal_transform_boot_id(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
|
||||
void netdata_systemd_journal_transform_uid(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
|
||||
void netdata_systemd_journal_transform_gid(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
|
||||
void netdata_systemd_journal_transform_cap_effective(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
|
||||
void netdata_systemd_journal_transform_timestamp_usec(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
|
||||
|
||||
void journal_init_files_and_directories(void);
|
||||
void journal_init_query_status(void);
|
||||
void function_systemd_journal(const char *transaction, char *function, int timeout, bool *cancelled);
|
||||
void journal_files_registry_update(void);
|
||||
|
||||
#ifdef ENABLE_SYSTEMD_DBUS
|
||||
void function_systemd_units(const char *transaction, char *function, int timeout, bool *cancelled);
|
||||
#endif
|
||||
|
||||
#endif //NETDATA_COLLECTORS_SYSTEMD_INTERNALS_H
|
590
collectors/systemd-journal.plugin/systemd-journal-annotations.c
Normal file
590
collectors/systemd-journal.plugin/systemd-journal-annotations.c
Normal file
|
@ -0,0 +1,590 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "systemd-internals.h"
|
||||
|
||||
const char *errno_map[] = {
|
||||
[1] = "1 (EPERM)", // "Operation not permitted",
|
||||
[2] = "2 (ENOENT)", // "No such file or directory",
|
||||
[3] = "3 (ESRCH)", // "No such process",
|
||||
[4] = "4 (EINTR)", // "Interrupted system call",
|
||||
[5] = "5 (EIO)", // "Input/output error",
|
||||
[6] = "6 (ENXIO)", // "No such device or address",
|
||||
[7] = "7 (E2BIG)", // "Argument list too long",
|
||||
[8] = "8 (ENOEXEC)", // "Exec format error",
|
||||
[9] = "9 (EBADF)", // "Bad file descriptor",
|
||||
[10] = "10 (ECHILD)", // "No child processes",
|
||||
[11] = "11 (EAGAIN)", // "Resource temporarily unavailable",
|
||||
[12] = "12 (ENOMEM)", // "Cannot allocate memory",
|
||||
[13] = "13 (EACCES)", // "Permission denied",
|
||||
[14] = "14 (EFAULT)", // "Bad address",
|
||||
[15] = "15 (ENOTBLK)", // "Block device required",
|
||||
[16] = "16 (EBUSY)", // "Device or resource busy",
|
||||
[17] = "17 (EEXIST)", // "File exists",
|
||||
[18] = "18 (EXDEV)", // "Invalid cross-device link",
|
||||
[19] = "19 (ENODEV)", // "No such device",
|
||||
[20] = "20 (ENOTDIR)", // "Not a directory",
|
||||
[21] = "21 (EISDIR)", // "Is a directory",
|
||||
[22] = "22 (EINVAL)", // "Invalid argument",
|
||||
[23] = "23 (ENFILE)", // "Too many open files in system",
|
||||
[24] = "24 (EMFILE)", // "Too many open files",
|
||||
[25] = "25 (ENOTTY)", // "Inappropriate ioctl for device",
|
||||
[26] = "26 (ETXTBSY)", // "Text file busy",
|
||||
[27] = "27 (EFBIG)", // "File too large",
|
||||
[28] = "28 (ENOSPC)", // "No space left on device",
|
||||
[29] = "29 (ESPIPE)", // "Illegal seek",
|
||||
[30] = "30 (EROFS)", // "Read-only file system",
|
||||
[31] = "31 (EMLINK)", // "Too many links",
|
||||
[32] = "32 (EPIPE)", // "Broken pipe",
|
||||
[33] = "33 (EDOM)", // "Numerical argument out of domain",
|
||||
[34] = "34 (ERANGE)", // "Numerical result out of range",
|
||||
[35] = "35 (EDEADLK)", // "Resource deadlock avoided",
|
||||
[36] = "36 (ENAMETOOLONG)", // "File name too long",
|
||||
[37] = "37 (ENOLCK)", // "No locks available",
|
||||
[38] = "38 (ENOSYS)", // "Function not implemented",
|
||||
[39] = "39 (ENOTEMPTY)", // "Directory not empty",
|
||||
[40] = "40 (ELOOP)", // "Too many levels of symbolic links",
|
||||
[42] = "42 (ENOMSG)", // "No message of desired type",
|
||||
[43] = "43 (EIDRM)", // "Identifier removed",
|
||||
[44] = "44 (ECHRNG)", // "Channel number out of range",
|
||||
[45] = "45 (EL2NSYNC)", // "Level 2 not synchronized",
|
||||
[46] = "46 (EL3HLT)", // "Level 3 halted",
|
||||
[47] = "47 (EL3RST)", // "Level 3 reset",
|
||||
[48] = "48 (ELNRNG)", // "Link number out of range",
|
||||
[49] = "49 (EUNATCH)", // "Protocol driver not attached",
|
||||
[50] = "50 (ENOCSI)", // "No CSI structure available",
|
||||
[51] = "51 (EL2HLT)", // "Level 2 halted",
|
||||
[52] = "52 (EBADE)", // "Invalid exchange",
|
||||
[53] = "53 (EBADR)", // "Invalid request descriptor",
|
||||
[54] = "54 (EXFULL)", // "Exchange full",
|
||||
[55] = "55 (ENOANO)", // "No anode",
|
||||
[56] = "56 (EBADRQC)", // "Invalid request code",
|
||||
[57] = "57 (EBADSLT)", // "Invalid slot",
|
||||
[59] = "59 (EBFONT)", // "Bad font file format",
|
||||
[60] = "60 (ENOSTR)", // "Device not a stream",
|
||||
[61] = "61 (ENODATA)", // "No data available",
|
||||
[62] = "62 (ETIME)", // "Timer expired",
|
||||
[63] = "63 (ENOSR)", // "Out of streams resources",
|
||||
[64] = "64 (ENONET)", // "Machine is not on the network",
|
||||
[65] = "65 (ENOPKG)", // "Package not installed",
|
||||
[66] = "66 (EREMOTE)", // "Object is remote",
|
||||
[67] = "67 (ENOLINK)", // "Link has been severed",
|
||||
[68] = "68 (EADV)", // "Advertise error",
|
||||
[69] = "69 (ESRMNT)", // "Srmount error",
|
||||
[70] = "70 (ECOMM)", // "Communication error on send",
|
||||
[71] = "71 (EPROTO)", // "Protocol error",
|
||||
[72] = "72 (EMULTIHOP)", // "Multihop attempted",
|
||||
[73] = "73 (EDOTDOT)", // "RFS specific error",
|
||||
[74] = "74 (EBADMSG)", // "Bad message",
|
||||
[75] = "75 (EOVERFLOW)", // "Value too large for defined data type",
|
||||
[76] = "76 (ENOTUNIQ)", // "Name not unique on network",
|
||||
[77] = "77 (EBADFD)", // "File descriptor in bad state",
|
||||
[78] = "78 (EREMCHG)", // "Remote address changed",
|
||||
[79] = "79 (ELIBACC)", // "Can not access a needed shared library",
|
||||
[80] = "80 (ELIBBAD)", // "Accessing a corrupted shared library",
|
||||
[81] = "81 (ELIBSCN)", // ".lib section in a.out corrupted",
|
||||
[82] = "82 (ELIBMAX)", // "Attempting to link in too many shared libraries",
|
||||
[83] = "83 (ELIBEXEC)", // "Cannot exec a shared library directly",
|
||||
[84] = "84 (EILSEQ)", // "Invalid or incomplete multibyte or wide character",
|
||||
[85] = "85 (ERESTART)", // "Interrupted system call should be restarted",
|
||||
[86] = "86 (ESTRPIPE)", // "Streams pipe error",
|
||||
[87] = "87 (EUSERS)", // "Too many users",
|
||||
[88] = "88 (ENOTSOCK)", // "Socket operation on non-socket",
|
||||
[89] = "89 (EDESTADDRREQ)", // "Destination address required",
|
||||
[90] = "90 (EMSGSIZE)", // "Message too long",
|
||||
[91] = "91 (EPROTOTYPE)", // "Protocol wrong type for socket",
|
||||
[92] = "92 (ENOPROTOOPT)", // "Protocol not available",
|
||||
[93] = "93 (EPROTONOSUPPORT)", // "Protocol not supported",
|
||||
[94] = "94 (ESOCKTNOSUPPORT)", // "Socket type not supported",
|
||||
[95] = "95 (ENOTSUP)", // "Operation not supported",
|
||||
[96] = "96 (EPFNOSUPPORT)", // "Protocol family not supported",
|
||||
[97] = "97 (EAFNOSUPPORT)", // "Address family not supported by protocol",
|
||||
[98] = "98 (EADDRINUSE)", // "Address already in use",
|
||||
[99] = "99 (EADDRNOTAVAIL)", // "Cannot assign requested address",
|
||||
[100] = "100 (ENETDOWN)", // "Network is down",
|
||||
[101] = "101 (ENETUNREACH)", // "Network is unreachable",
|
||||
[102] = "102 (ENETRESET)", // "Network dropped connection on reset",
|
||||
[103] = "103 (ECONNABORTED)", // "Software caused connection abort",
|
||||
[104] = "104 (ECONNRESET)", // "Connection reset by peer",
|
||||
[105] = "105 (ENOBUFS)", // "No buffer space available",
|
||||
[106] = "106 (EISCONN)", // "Transport endpoint is already connected",
|
||||
[107] = "107 (ENOTCONN)", // "Transport endpoint is not connected",
|
||||
[108] = "108 (ESHUTDOWN)", // "Cannot send after transport endpoint shutdown",
|
||||
[109] = "109 (ETOOMANYREFS)", // "Too many references: cannot splice",
|
||||
[110] = "110 (ETIMEDOUT)", // "Connection timed out",
|
||||
[111] = "111 (ECONNREFUSED)", // "Connection refused",
|
||||
[112] = "112 (EHOSTDOWN)", // "Host is down",
|
||||
[113] = "113 (EHOSTUNREACH)", // "No route to host",
|
||||
[114] = "114 (EALREADY)", // "Operation already in progress",
|
||||
[115] = "115 (EINPROGRESS)", // "Operation now in progress",
|
||||
[116] = "116 (ESTALE)", // "Stale file handle",
|
||||
[117] = "117 (EUCLEAN)", // "Structure needs cleaning",
|
||||
[118] = "118 (ENOTNAM)", // "Not a XENIX named type file",
|
||||
[119] = "119 (ENAVAIL)", // "No XENIX semaphores available",
|
||||
[120] = "120 (EISNAM)", // "Is a named type file",
|
||||
[121] = "121 (EREMOTEIO)", // "Remote I/O error",
|
||||
[122] = "122 (EDQUOT)", // "Disk quota exceeded",
|
||||
[123] = "123 (ENOMEDIUM)", // "No medium found",
|
||||
[124] = "124 (EMEDIUMTYPE)", // "Wrong medium type",
|
||||
[125] = "125 (ECANCELED)", // "Operation canceled",
|
||||
[126] = "126 (ENOKEY)", // "Required key not available",
|
||||
[127] = "127 (EKEYEXPIRED)", // "Key has expired",
|
||||
[128] = "128 (EKEYREVOKED)", // "Key has been revoked",
|
||||
[129] = "129 (EKEYREJECTED)", // "Key was rejected by service",
|
||||
[130] = "130 (EOWNERDEAD)", // "Owner died",
|
||||
[131] = "131 (ENOTRECOVERABLE)", // "State not recoverable",
|
||||
[132] = "132 (ERFKILL)", // "Operation not possible due to RF-kill",
|
||||
[133] = "133 (EHWPOISON)", // "Memory page has hardware error",
|
||||
};
|
||||
|
||||
const char *linux_capabilities[] = {
|
||||
[CAP_CHOWN] = "CHOWN",
|
||||
[CAP_DAC_OVERRIDE] = "DAC_OVERRIDE",
|
||||
[CAP_DAC_READ_SEARCH] = "DAC_READ_SEARCH",
|
||||
[CAP_FOWNER] = "FOWNER",
|
||||
[CAP_FSETID] = "FSETID",
|
||||
[CAP_KILL] = "KILL",
|
||||
[CAP_SETGID] = "SETGID",
|
||||
[CAP_SETUID] = "SETUID",
|
||||
[CAP_SETPCAP] = "SETPCAP",
|
||||
[CAP_LINUX_IMMUTABLE] = "LINUX_IMMUTABLE",
|
||||
[CAP_NET_BIND_SERVICE] = "NET_BIND_SERVICE",
|
||||
[CAP_NET_BROADCAST] = "NET_BROADCAST",
|
||||
[CAP_NET_ADMIN] = "NET_ADMIN",
|
||||
[CAP_NET_RAW] = "NET_RAW",
|
||||
[CAP_IPC_LOCK] = "IPC_LOCK",
|
||||
[CAP_IPC_OWNER] = "IPC_OWNER",
|
||||
[CAP_SYS_MODULE] = "SYS_MODULE",
|
||||
[CAP_SYS_RAWIO] = "SYS_RAWIO",
|
||||
[CAP_SYS_CHROOT] = "SYS_CHROOT",
|
||||
[CAP_SYS_PTRACE] = "SYS_PTRACE",
|
||||
[CAP_SYS_PACCT] = "SYS_PACCT",
|
||||
[CAP_SYS_ADMIN] = "SYS_ADMIN",
|
||||
[CAP_SYS_BOOT] = "SYS_BOOT",
|
||||
[CAP_SYS_NICE] = "SYS_NICE",
|
||||
[CAP_SYS_RESOURCE] = "SYS_RESOURCE",
|
||||
[CAP_SYS_TIME] = "SYS_TIME",
|
||||
[CAP_SYS_TTY_CONFIG] = "SYS_TTY_CONFIG",
|
||||
[CAP_MKNOD] = "MKNOD",
|
||||
[CAP_LEASE] = "LEASE",
|
||||
[CAP_AUDIT_WRITE] = "AUDIT_WRITE",
|
||||
[CAP_AUDIT_CONTROL] = "AUDIT_CONTROL",
|
||||
[CAP_SETFCAP] = "SETFCAP",
|
||||
[CAP_MAC_OVERRIDE] = "MAC_OVERRIDE",
|
||||
[CAP_MAC_ADMIN] = "MAC_ADMIN",
|
||||
[CAP_SYSLOG] = "SYSLOG",
|
||||
[CAP_WAKE_ALARM] = "WAKE_ALARM",
|
||||
[CAP_BLOCK_SUSPEND] = "BLOCK_SUSPEND",
|
||||
[37 /*CAP_AUDIT_READ*/] = "AUDIT_READ",
|
||||
[38 /*CAP_PERFMON*/] = "PERFMON",
|
||||
[39 /*CAP_BPF*/] = "BPF",
|
||||
[40 /* CAP_CHECKPOINT_RESTORE */] = "CHECKPOINT_RESTORE",
|
||||
};
|
||||
|
||||
static const char *syslog_facility_to_name(int facility) {
|
||||
switch (facility) {
|
||||
case LOG_FAC(LOG_KERN): return "kern";
|
||||
case LOG_FAC(LOG_USER): return "user";
|
||||
case LOG_FAC(LOG_MAIL): return "mail";
|
||||
case LOG_FAC(LOG_DAEMON): return "daemon";
|
||||
case LOG_FAC(LOG_AUTH): return "auth";
|
||||
case LOG_FAC(LOG_SYSLOG): return "syslog";
|
||||
case LOG_FAC(LOG_LPR): return "lpr";
|
||||
case LOG_FAC(LOG_NEWS): return "news";
|
||||
case LOG_FAC(LOG_UUCP): return "uucp";
|
||||
case LOG_FAC(LOG_CRON): return "cron";
|
||||
case LOG_FAC(LOG_AUTHPRIV): return "authpriv";
|
||||
case LOG_FAC(LOG_FTP): return "ftp";
|
||||
case LOG_FAC(LOG_LOCAL0): return "local0";
|
||||
case LOG_FAC(LOG_LOCAL1): return "local1";
|
||||
case LOG_FAC(LOG_LOCAL2): return "local2";
|
||||
case LOG_FAC(LOG_LOCAL3): return "local3";
|
||||
case LOG_FAC(LOG_LOCAL4): return "local4";
|
||||
case LOG_FAC(LOG_LOCAL5): return "local5";
|
||||
case LOG_FAC(LOG_LOCAL6): return "local6";
|
||||
case LOG_FAC(LOG_LOCAL7): return "local7";
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *syslog_priority_to_name(int priority) {
|
||||
switch (priority) {
|
||||
case LOG_ALERT: return "alert";
|
||||
case LOG_CRIT: return "critical";
|
||||
case LOG_DEBUG: return "debug";
|
||||
case LOG_EMERG: return "panic";
|
||||
case LOG_ERR: return "error";
|
||||
case LOG_INFO: return "info";
|
||||
case LOG_NOTICE: return "notice";
|
||||
case LOG_WARNING: return "warning";
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
FACET_ROW_SEVERITY syslog_priority_to_facet_severity(FACETS *facets __maybe_unused, FACET_ROW *row, void *data __maybe_unused) {
|
||||
// same to
|
||||
// https://github.com/systemd/systemd/blob/aab9e4b2b86905a15944a1ac81e471b5b7075932/src/basic/terminal-util.c#L1501
|
||||
// function get_log_colors()
|
||||
|
||||
FACET_ROW_KEY_VALUE *priority_rkv = dictionary_get(row->dict, "PRIORITY");
|
||||
if(!priority_rkv || priority_rkv->empty)
|
||||
return FACET_ROW_SEVERITY_NORMAL;
|
||||
|
||||
int priority = str2i(buffer_tostring(priority_rkv->wb));
|
||||
|
||||
if(priority <= LOG_ERR)
|
||||
return FACET_ROW_SEVERITY_CRITICAL;
|
||||
|
||||
else if (priority <= LOG_WARNING)
|
||||
return FACET_ROW_SEVERITY_WARNING;
|
||||
|
||||
else if(priority <= LOG_NOTICE)
|
||||
return FACET_ROW_SEVERITY_NOTICE;
|
||||
|
||||
else if(priority >= LOG_DEBUG)
|
||||
return FACET_ROW_SEVERITY_DEBUG;
|
||||
|
||||
return FACET_ROW_SEVERITY_NORMAL;
|
||||
}
|
||||
|
||||
static char *uid_to_username(uid_t uid, char *buffer, size_t buffer_size) {
|
||||
static __thread char tmp[1024 + 1];
|
||||
struct passwd pw, *result = NULL;
|
||||
|
||||
if (getpwuid_r(uid, &pw, tmp, sizeof(tmp), &result) != 0 || !result || !pw.pw_name || !(*pw.pw_name))
|
||||
snprintfz(buffer, buffer_size - 1, "%u", uid);
|
||||
else
|
||||
snprintfz(buffer, buffer_size - 1, "%u (%s)", uid, pw.pw_name);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static char *gid_to_groupname(gid_t gid, char* buffer, size_t buffer_size) {
|
||||
static __thread char tmp[1024];
|
||||
struct group grp, *result = NULL;
|
||||
|
||||
if (getgrgid_r(gid, &grp, tmp, sizeof(tmp), &result) != 0 || !result || !grp.gr_name || !(*grp.gr_name))
|
||||
snprintfz(buffer, buffer_size - 1, "%u", gid);
|
||||
else
|
||||
snprintfz(buffer, buffer_size - 1, "%u (%s)", gid, grp.gr_name);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void netdata_systemd_journal_transform_syslog_facility(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
|
||||
const char *v = buffer_tostring(wb);
|
||||
if(*v && isdigit(*v)) {
|
||||
int facility = str2i(buffer_tostring(wb));
|
||||
const char *name = syslog_facility_to_name(facility);
|
||||
if (name) {
|
||||
buffer_flush(wb);
|
||||
buffer_strcat(wb, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void netdata_systemd_journal_transform_priority(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
|
||||
if(scope == FACETS_TRANSFORM_FACET_SORT)
|
||||
return;
|
||||
|
||||
const char *v = buffer_tostring(wb);
|
||||
if(*v && isdigit(*v)) {
|
||||
int priority = str2i(buffer_tostring(wb));
|
||||
const char *name = syslog_priority_to_name(priority);
|
||||
if (name) {
|
||||
buffer_flush(wb);
|
||||
buffer_strcat(wb, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void netdata_systemd_journal_transform_errno(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
|
||||
if(scope == FACETS_TRANSFORM_FACET_SORT)
|
||||
return;
|
||||
|
||||
const char *v = buffer_tostring(wb);
|
||||
if(*v && isdigit(*v)) {
|
||||
unsigned err_no = str2u(buffer_tostring(wb));
|
||||
if(err_no > 0 && err_no < sizeof(errno_map) / sizeof(*errno_map)) {
|
||||
const char *name = errno_map[err_no];
|
||||
if(name) {
|
||||
buffer_flush(wb);
|
||||
buffer_strcat(wb, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// UID and GID transformation
|
||||
|
||||
#define UID_GID_HASHTABLE_SIZE 10000
|
||||
|
||||
struct word_t2str_hashtable_entry {
|
||||
struct word_t2str_hashtable_entry *next;
|
||||
Word_t hash;
|
||||
size_t len;
|
||||
char str[];
|
||||
};
|
||||
|
||||
struct word_t2str_hashtable {
|
||||
SPINLOCK spinlock;
|
||||
size_t size;
|
||||
struct word_t2str_hashtable_entry *hashtable[UID_GID_HASHTABLE_SIZE];
|
||||
};
|
||||
|
||||
struct word_t2str_hashtable uid_hashtable = {
|
||||
.size = UID_GID_HASHTABLE_SIZE,
|
||||
};
|
||||
|
||||
struct word_t2str_hashtable gid_hashtable = {
|
||||
.size = UID_GID_HASHTABLE_SIZE,
|
||||
};
|
||||
|
||||
struct word_t2str_hashtable_entry **word_t2str_hashtable_slot(struct word_t2str_hashtable *ht, Word_t hash) {
|
||||
size_t slot = hash % ht->size;
|
||||
struct word_t2str_hashtable_entry **e = &ht->hashtable[slot];
|
||||
|
||||
while(*e && (*e)->hash != hash)
|
||||
e = &((*e)->next);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
const char *uid_to_username_cached(uid_t uid, size_t *length) {
|
||||
spinlock_lock(&uid_hashtable.spinlock);
|
||||
|
||||
struct word_t2str_hashtable_entry **e = word_t2str_hashtable_slot(&uid_hashtable, uid);
|
||||
if(!(*e)) {
|
||||
static __thread char buf[1024];
|
||||
const char *name = uid_to_username(uid, buf, sizeof(buf));
|
||||
size_t size = strlen(name) + 1;
|
||||
|
||||
*e = callocz(1, sizeof(struct word_t2str_hashtable_entry) + size);
|
||||
(*e)->len = size - 1;
|
||||
(*e)->hash = uid;
|
||||
memcpy((*e)->str, name, size);
|
||||
}
|
||||
|
||||
spinlock_unlock(&uid_hashtable.spinlock);
|
||||
|
||||
*length = (*e)->len;
|
||||
return (*e)->str;
|
||||
}
|
||||
|
||||
const char *gid_to_groupname_cached(gid_t gid, size_t *length) {
|
||||
spinlock_lock(&gid_hashtable.spinlock);
|
||||
|
||||
struct word_t2str_hashtable_entry **e = word_t2str_hashtable_slot(&gid_hashtable, gid);
|
||||
if(!(*e)) {
|
||||
static __thread char buf[1024];
|
||||
const char *name = gid_to_groupname(gid, buf, sizeof(buf));
|
||||
size_t size = strlen(name) + 1;
|
||||
|
||||
*e = callocz(1, sizeof(struct word_t2str_hashtable_entry) + size);
|
||||
(*e)->len = size - 1;
|
||||
(*e)->hash = gid;
|
||||
memcpy((*e)->str, name, size);
|
||||
}
|
||||
|
||||
spinlock_unlock(&gid_hashtable.spinlock);
|
||||
|
||||
*length = (*e)->len;
|
||||
return (*e)->str;
|
||||
}
|
||||
|
||||
DICTIONARY *boot_ids_to_first_ut = NULL;
|
||||
|
||||
void netdata_systemd_journal_transform_boot_id(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
|
||||
const char *boot_id = buffer_tostring(wb);
|
||||
if(*boot_id && isxdigit(*boot_id)) {
|
||||
usec_t ut = UINT64_MAX;
|
||||
usec_t *p_ut = dictionary_get(boot_ids_to_first_ut, boot_id);
|
||||
if(!p_ut) {
|
||||
struct journal_file *jf;
|
||||
dfe_start_read(journal_files_registry, jf) {
|
||||
const char *files[2] = {
|
||||
[0] = jf_dfe.name,
|
||||
[1] = NULL,
|
||||
};
|
||||
|
||||
sd_journal *j = NULL;
|
||||
if(sd_journal_open_files(&j, files, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j) {
|
||||
internal_error(true, "JOURNAL: cannot open file '%s' to get boot_id", jf_dfe.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
char m[100];
|
||||
size_t len = snprintfz(m, sizeof(m), "_BOOT_ID=%s", boot_id);
|
||||
|
||||
if(sd_journal_add_match(j, m, len) < 0) {
|
||||
internal_error(true, "JOURNAL: cannot add match '%s' to file '%s'", m, jf_dfe.name);
|
||||
sd_journal_close(j);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(sd_journal_seek_head(j) < 0) {
|
||||
internal_error(true, "JOURNAL: cannot seek head to file '%s'", jf_dfe.name);
|
||||
sd_journal_close(j);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(sd_journal_next(j) < 0) {
|
||||
internal_error(true, "JOURNAL: cannot get next of file '%s'", jf_dfe.name);
|
||||
sd_journal_close(j);
|
||||
continue;
|
||||
}
|
||||
|
||||
usec_t t_ut = 0;
|
||||
if(sd_journal_get_realtime_usec(j, &t_ut) < 0 || !t_ut) {
|
||||
internal_error(true, "JOURNAL: cannot get realtime_usec of file '%s'", jf_dfe.name);
|
||||
sd_journal_close(j);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(t_ut < ut)
|
||||
ut = t_ut;
|
||||
|
||||
sd_journal_close(j);
|
||||
}
|
||||
dfe_done(jf);
|
||||
|
||||
dictionary_set(boot_ids_to_first_ut, boot_id, &ut, sizeof(ut));
|
||||
}
|
||||
else
|
||||
ut = *p_ut;
|
||||
|
||||
if(ut != UINT64_MAX) {
|
||||
time_t timestamp_sec = (time_t)(ut / USEC_PER_SEC);
|
||||
struct tm tm;
|
||||
char buffer[30];
|
||||
|
||||
gmtime_r(×tamp_sec, &tm);
|
||||
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
|
||||
|
||||
switch(scope) {
|
||||
default:
|
||||
case FACETS_TRANSFORM_DATA:
|
||||
case FACETS_TRANSFORM_VALUE:
|
||||
buffer_sprintf(wb, " (%s UTC) ", buffer);
|
||||
break;
|
||||
|
||||
case FACETS_TRANSFORM_FACET:
|
||||
case FACETS_TRANSFORM_FACET_SORT:
|
||||
case FACETS_TRANSFORM_HISTOGRAM:
|
||||
buffer_flush(wb);
|
||||
buffer_sprintf(wb, "%s UTC", buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void netdata_systemd_journal_transform_uid(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
|
||||
if(scope == FACETS_TRANSFORM_FACET_SORT)
|
||||
return;
|
||||
|
||||
const char *v = buffer_tostring(wb);
|
||||
if(*v && isdigit(*v)) {
|
||||
uid_t uid = str2i(buffer_tostring(wb));
|
||||
size_t len;
|
||||
const char *name = uid_to_username_cached(uid, &len);
|
||||
buffer_contents_replace(wb, name, len);
|
||||
}
|
||||
}
|
||||
|
||||
void netdata_systemd_journal_transform_gid(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
|
||||
if(scope == FACETS_TRANSFORM_FACET_SORT)
|
||||
return;
|
||||
|
||||
const char *v = buffer_tostring(wb);
|
||||
if(*v && isdigit(*v)) {
|
||||
gid_t gid = str2i(buffer_tostring(wb));
|
||||
size_t len;
|
||||
const char *name = gid_to_groupname_cached(gid, &len);
|
||||
buffer_contents_replace(wb, name, len);
|
||||
}
|
||||
}
|
||||
|
||||
void netdata_systemd_journal_transform_cap_effective(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
|
||||
if(scope == FACETS_TRANSFORM_FACET_SORT)
|
||||
return;
|
||||
|
||||
const char *v = buffer_tostring(wb);
|
||||
if(*v && isdigit(*v)) {
|
||||
uint64_t cap = strtoul(buffer_tostring(wb), NULL, 16);
|
||||
if(cap) {
|
||||
buffer_fast_strcat(wb, " (", 2);
|
||||
for (size_t i = 0, added = 0; i < sizeof(linux_capabilities) / sizeof(linux_capabilities[0]); i++) {
|
||||
if (linux_capabilities[i] && (cap & (1ULL << i))) {
|
||||
|
||||
if (added)
|
||||
buffer_fast_strcat(wb, " | ", 3);
|
||||
|
||||
buffer_strcat(wb, linux_capabilities[i]);
|
||||
added++;
|
||||
}
|
||||
}
|
||||
buffer_fast_strcat(wb, ")", 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void netdata_systemd_journal_transform_timestamp_usec(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
|
||||
if(scope == FACETS_TRANSFORM_FACET_SORT)
|
||||
return;
|
||||
|
||||
const char *v = buffer_tostring(wb);
|
||||
if(*v && isdigit(*v)) {
|
||||
uint64_t ut = str2ull(buffer_tostring(wb), NULL);
|
||||
if(ut) {
|
||||
time_t timestamp_sec = (time_t)(ut / USEC_PER_SEC);
|
||||
struct tm tm;
|
||||
char buffer[30];
|
||||
|
||||
gmtime_r(×tamp_sec, &tm);
|
||||
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
|
||||
buffer_sprintf(wb, " (%s.%06llu UTC)", buffer, ut % USEC_PER_SEC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void netdata_systemd_journal_dynamic_row_id(FACETS *facets __maybe_unused, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row, void *data __maybe_unused) {
|
||||
FACET_ROW_KEY_VALUE *pid_rkv = dictionary_get(row->dict, "_PID");
|
||||
const char *pid = pid_rkv ? buffer_tostring(pid_rkv->wb) : FACET_VALUE_UNSET;
|
||||
|
||||
const char *identifier = NULL;
|
||||
FACET_ROW_KEY_VALUE *container_name_rkv = dictionary_get(row->dict, "CONTAINER_NAME");
|
||||
if(container_name_rkv && !container_name_rkv->empty)
|
||||
identifier = buffer_tostring(container_name_rkv->wb);
|
||||
|
||||
if(!identifier) {
|
||||
FACET_ROW_KEY_VALUE *syslog_identifier_rkv = dictionary_get(row->dict, "SYSLOG_IDENTIFIER");
|
||||
if(syslog_identifier_rkv && !syslog_identifier_rkv->empty)
|
||||
identifier = buffer_tostring(syslog_identifier_rkv->wb);
|
||||
|
||||
if(!identifier) {
|
||||
FACET_ROW_KEY_VALUE *comm_rkv = dictionary_get(row->dict, "_COMM");
|
||||
if(comm_rkv && !comm_rkv->empty)
|
||||
identifier = buffer_tostring(comm_rkv->wb);
|
||||
}
|
||||
}
|
||||
|
||||
buffer_flush(rkv->wb);
|
||||
|
||||
if(!identifier || !*identifier)
|
||||
buffer_strcat(rkv->wb, FACET_VALUE_UNSET);
|
||||
else if(!pid || !*pid)
|
||||
buffer_sprintf(rkv->wb, "%s", identifier);
|
||||
else
|
||||
buffer_sprintf(rkv->wb, "%s[%s]", identifier, pid);
|
||||
|
||||
buffer_json_add_array_item_string(json_array, buffer_tostring(rkv->wb));
|
||||
}
|
||||
|
||||
static void netdata_systemd_journal_rich_message(FACETS *facets __maybe_unused, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row __maybe_unused, void *data __maybe_unused) {
|
||||
buffer_json_add_array_item_object(json_array);
|
||||
buffer_json_member_add_string(json_array, "value", buffer_tostring(rkv->wb));
|
||||
buffer_json_object_close(json_array);
|
||||
}
|
477
collectors/systemd-journal.plugin/systemd-journal-files.c
Normal file
477
collectors/systemd-journal.plugin/systemd-journal-files.c
Normal file
|
@ -0,0 +1,477 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "systemd-internals.h"
|
||||
|
||||
#define SYSTEMD_JOURNAL_MAX_SOURCE_LEN 64
|
||||
#define VAR_LOG_JOURNAL_MAX_DEPTH 10
|
||||
#define MAX_JOURNAL_DIRECTORIES 100
|
||||
|
||||
struct journal_directory {
|
||||
char *path;
|
||||
bool logged_failure;
|
||||
};
|
||||
|
||||
static struct journal_directory journal_directories[MAX_JOURNAL_DIRECTORIES] = { 0 };
|
||||
DICTIONARY *journal_files_registry = NULL;
|
||||
DICTIONARY *used_hashes_registry = NULL;
|
||||
|
||||
static usec_t systemd_journal_session = 0;
|
||||
|
||||
void buffer_json_journal_versions(BUFFER *wb) {
|
||||
buffer_json_member_add_object(wb, "versions");
|
||||
{
|
||||
buffer_json_member_add_uint64(wb, "sources",
|
||||
systemd_journal_session + dictionary_version(journal_files_registry));
|
||||
}
|
||||
buffer_json_object_close(wb);
|
||||
}
|
||||
|
||||
static void journal_file_update_msg_ut(const char *filename, struct journal_file *jf) {
|
||||
fstat_cache_enable_on_thread();
|
||||
|
||||
const char *files[2] = {
|
||||
[0] = filename,
|
||||
[1] = NULL,
|
||||
};
|
||||
|
||||
sd_journal *j = NULL;
|
||||
if(sd_journal_open_files(&j, files, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j) {
|
||||
netdata_log_error("JOURNAL: cannot open file '%s' to update msg_ut", filename);
|
||||
fstat_cache_disable_on_thread();
|
||||
|
||||
if(!jf->logged_failure) {
|
||||
netdata_log_error("cannot open journal file '%s', using file timestamps to understand time-frame.", filename);
|
||||
jf->logged_failure = true;
|
||||
}
|
||||
|
||||
jf->msg_first_ut = 0;
|
||||
jf->msg_last_ut = jf->file_last_modified_ut;
|
||||
return;
|
||||
}
|
||||
|
||||
usec_t first_ut = 0, last_ut = 0;
|
||||
|
||||
if(sd_journal_seek_head(j) < 0 || sd_journal_next(j) < 0 || sd_journal_get_realtime_usec(j, &first_ut) < 0 || !first_ut) {
|
||||
internal_error(true, "cannot find the timestamp of the first message in '%s'", filename);
|
||||
first_ut = 0;
|
||||
}
|
||||
|
||||
if(sd_journal_seek_tail(j) < 0 || sd_journal_previous(j) < 0 || sd_journal_get_realtime_usec(j, &last_ut) < 0 || !last_ut) {
|
||||
internal_error(true, "cannot find the timestamp of the last message in '%s'", filename);
|
||||
last_ut = jf->file_last_modified_ut;
|
||||
}
|
||||
|
||||
sd_journal_close(j);
|
||||
fstat_cache_disable_on_thread();
|
||||
|
||||
if(first_ut > last_ut) {
|
||||
internal_error(true, "timestamps are flipped in file '%s'", filename);
|
||||
usec_t t = first_ut;
|
||||
first_ut = last_ut;
|
||||
last_ut = t;
|
||||
}
|
||||
|
||||
jf->msg_first_ut = first_ut;
|
||||
jf->msg_last_ut = last_ut;
|
||||
}
|
||||
|
||||
static STRING *string_strdupz_source(const char *s, const char *e, size_t max_len, const char *prefix) {
|
||||
char buf[max_len];
|
||||
size_t len;
|
||||
char *dst = buf;
|
||||
|
||||
if(prefix) {
|
||||
len = strlen(prefix);
|
||||
memcpy(buf, prefix, len);
|
||||
dst = &buf[len];
|
||||
max_len -= len;
|
||||
}
|
||||
|
||||
len = e - s;
|
||||
if(len >= max_len)
|
||||
len = max_len - 1;
|
||||
memcpy(dst, s, len);
|
||||
dst[len] = '\0';
|
||||
buf[max_len - 1] = '\0';
|
||||
|
||||
for(size_t i = 0; buf[i] ;i++)
|
||||
if(!isalnum(buf[i]) && buf[i] != '-' && buf[i] != '.' && buf[i] != ':')
|
||||
buf[i] = '_';
|
||||
|
||||
return string_strdupz(buf);
|
||||
}
|
||||
|
||||
static void files_registry_insert_cb(const DICTIONARY_ITEM *item, void *value, void *data __maybe_unused) {
|
||||
struct journal_file *jf = value;
|
||||
jf->filename = dictionary_acquired_item_name(item);
|
||||
jf->filename_len = strlen(jf->filename);
|
||||
jf->source_type = SDJF_ALL;
|
||||
|
||||
// based on the filename
|
||||
// decide the source to show to the user
|
||||
const char *s = strrchr(jf->filename, '/');
|
||||
if(s) {
|
||||
if(strstr(jf->filename, "/remote/")) {
|
||||
jf->source_type |= SDJF_REMOTE_ALL;
|
||||
|
||||
if(strncmp(s, "/remote-", 8) == 0) {
|
||||
s = &s[8]; // skip "/remote-"
|
||||
|
||||
char *e = strchr(s, '@');
|
||||
if(!e)
|
||||
e = strstr(s, ".journal");
|
||||
|
||||
if(e) {
|
||||
const char *d = s;
|
||||
for(; d < e && (isdigit(*d) || *d == '.' || *d == ':') ; d++) ;
|
||||
if(d == e) {
|
||||
// a valid IP address
|
||||
char ip[e - s + 1];
|
||||
memcpy(ip, s, e - s);
|
||||
ip[e - s] = '\0';
|
||||
char buf[SYSTEMD_JOURNAL_MAX_SOURCE_LEN];
|
||||
if(ip_to_hostname(ip, buf, sizeof(buf)))
|
||||
jf->source = string_strdupz_source(buf, &buf[strlen(buf)], SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
|
||||
else {
|
||||
internal_error(true, "Cannot find the hostname for IP '%s'", ip);
|
||||
jf->source = string_strdupz_source(s, e, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
|
||||
}
|
||||
}
|
||||
else
|
||||
jf->source = string_strdupz_source(s, e, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
jf->source_type |= SDJF_LOCAL_ALL;
|
||||
|
||||
const char *t = s - 1;
|
||||
while(t >= jf->filename && *t != '.' && *t != '/')
|
||||
t--;
|
||||
|
||||
if(t >= jf->filename && *t == '.') {
|
||||
jf->source_type |= SDJF_LOCAL_NAMESPACE;
|
||||
jf->source = string_strdupz_source(t + 1, s, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "namespace-");
|
||||
}
|
||||
else if(strncmp(s, "/system", 7) == 0)
|
||||
jf->source_type |= SDJF_LOCAL_SYSTEM;
|
||||
|
||||
else if(strncmp(s, "/user", 5) == 0)
|
||||
jf->source_type |= SDJF_LOCAL_USER;
|
||||
|
||||
else
|
||||
jf->source_type |= SDJF_LOCAL_OTHER;
|
||||
}
|
||||
}
|
||||
else
|
||||
jf->source_type |= SDJF_LOCAL_ALL | SDJF_LOCAL_OTHER;
|
||||
|
||||
journal_file_update_msg_ut(jf->filename, jf);
|
||||
|
||||
internal_error(true,
|
||||
"found journal file '%s', type %d, source '%s', "
|
||||
"file modified: %"PRIu64", "
|
||||
"msg {first: %"PRIu64", last: %"PRIu64"}",
|
||||
jf->filename, jf->source_type, jf->source ? string2str(jf->source) : "<unset>",
|
||||
jf->file_last_modified_ut,
|
||||
jf->msg_first_ut, jf->msg_last_ut);
|
||||
}
|
||||
|
||||
static bool files_registry_conflict_cb(const DICTIONARY_ITEM *item, void *old_value, void *new_value, void *data __maybe_unused) {
|
||||
struct journal_file *jf = old_value;
|
||||
struct journal_file *njf = new_value;
|
||||
|
||||
if(njf->last_scan_ut > jf->last_scan_ut)
|
||||
jf->last_scan_ut = njf->last_scan_ut;
|
||||
|
||||
if(njf->file_last_modified_ut > jf->file_last_modified_ut) {
|
||||
jf->file_last_modified_ut = njf->file_last_modified_ut;
|
||||
jf->size = njf->size;
|
||||
|
||||
const char *filename = dictionary_acquired_item_name(item);
|
||||
journal_file_update_msg_ut(filename, jf);
|
||||
|
||||
// internal_error(true,
|
||||
// "updated journal file '%s', type %d, "
|
||||
// "file modified: %"PRIu64", "
|
||||
// "msg {first: %"PRIu64", last: %"PRIu64"}",
|
||||
// filename, jf->source_type,
|
||||
// jf->file_last_modified_ut,
|
||||
// jf->msg_first_ut, jf->msg_last_ut);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct journal_file_source {
|
||||
usec_t first_ut;
|
||||
usec_t last_ut;
|
||||
size_t count;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
static void human_readable_size_ib(uint64_t size, char *dst, size_t dst_len) {
|
||||
if(size > 1024ULL * 1024 * 1024 * 1024)
|
||||
snprintfz(dst, dst_len, "%0.2f TiB", (double)size / 1024.0 / 1024.0 / 1024.0 / 1024.0);
|
||||
else if(size > 1024ULL * 1024 * 1024)
|
||||
snprintfz(dst, dst_len, "%0.2f GiB", (double)size / 1024.0 / 1024.0 / 1024.0);
|
||||
else if(size > 1024ULL * 1024)
|
||||
snprintfz(dst, dst_len, "%0.2f MiB", (double)size / 1024.0 / 1024.0);
|
||||
else if(size > 1024ULL)
|
||||
snprintfz(dst, dst_len, "%0.2f KiB", (double)size / 1024.0);
|
||||
else
|
||||
snprintfz(dst, dst_len, "%"PRIu64" B", size);
|
||||
}
|
||||
|
||||
#define print_duration(dst, dst_len, pos, remaining, duration, one, many, printed) do { \
|
||||
if((remaining) > (duration)) { \
|
||||
uint64_t _count = (remaining) / (duration); \
|
||||
uint64_t _rem = (remaining) - (_count * (duration)); \
|
||||
(pos) += snprintfz(&(dst)[pos], (dst_len) - (pos), "%s%s%"PRIu64" %s", (printed) ? ", " : "", _rem ? "" : "and ", _count, _count > 1 ? (many) : (one)); \
|
||||
(remaining) = _rem; \
|
||||
(printed) = true; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
static void human_readable_duration_s(time_t duration_s, char *dst, size_t dst_len) {
|
||||
if(duration_s < 0)
|
||||
duration_s = -duration_s;
|
||||
|
||||
size_t pos = 0;
|
||||
dst[0] = 0 ;
|
||||
|
||||
bool printed = false;
|
||||
print_duration(dst, dst_len, pos, duration_s, 86400 * 365, "year", "years", printed);
|
||||
print_duration(dst, dst_len, pos, duration_s, 86400 * 30, "month", "months", printed);
|
||||
print_duration(dst, dst_len, pos, duration_s, 86400 * 1, "day", "days", printed);
|
||||
print_duration(dst, dst_len, pos, duration_s, 3600 * 1, "hour", "hours", printed);
|
||||
print_duration(dst, dst_len, pos, duration_s, 60 * 1, "min", "mins", printed);
|
||||
print_duration(dst, dst_len, pos, duration_s, 1, "sec", "secs", printed);
|
||||
}
|
||||
|
||||
static int journal_file_to_json_array_cb(const DICTIONARY_ITEM *item, void *entry, void *data) {
|
||||
struct journal_file_source *jfs = entry;
|
||||
BUFFER *wb = data;
|
||||
|
||||
const char *name = dictionary_acquired_item_name(item);
|
||||
|
||||
buffer_json_add_array_item_object(wb);
|
||||
{
|
||||
char size_for_humans[100];
|
||||
human_readable_size_ib(jfs->size, size_for_humans, sizeof(size_for_humans));
|
||||
|
||||
char duration_for_humans[1024];
|
||||
human_readable_duration_s((time_t)((jfs->last_ut - jfs->first_ut) / USEC_PER_SEC),
|
||||
duration_for_humans, sizeof(duration_for_humans));
|
||||
|
||||
char info[1024];
|
||||
snprintfz(info, sizeof(info), "%zu files, with a total size of %s, covering %s",
|
||||
jfs->count, size_for_humans, duration_for_humans);
|
||||
|
||||
buffer_json_member_add_string(wb, "id", name);
|
||||
buffer_json_member_add_string(wb, "name", name);
|
||||
buffer_json_member_add_string(wb, "pill", size_for_humans);
|
||||
buffer_json_member_add_string(wb, "info", info);
|
||||
}
|
||||
buffer_json_object_close(wb); // options object
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool journal_file_merge_sizes(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value , void *data __maybe_unused) {
|
||||
struct journal_file_source *jfs = old_value, *njfs = new_value;
|
||||
jfs->count += njfs->count;
|
||||
jfs->size += njfs->size;
|
||||
|
||||
if(njfs->first_ut && njfs->first_ut < jfs->first_ut)
|
||||
jfs->first_ut = njfs->first_ut;
|
||||
|
||||
if(njfs->last_ut && njfs->last_ut > jfs->last_ut)
|
||||
jfs->last_ut = njfs->last_ut;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void available_journal_file_sources_to_json_array(BUFFER *wb) {
|
||||
DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_NAME_LINK_DONT_CLONE|DICT_OPTION_DONT_OVERWRITE_VALUE);
|
||||
dictionary_register_conflict_callback(dict, journal_file_merge_sizes, NULL);
|
||||
|
||||
struct journal_file_source t = { 0 };
|
||||
|
||||
struct journal_file *jf;
|
||||
dfe_start_read(journal_files_registry, jf) {
|
||||
t.first_ut = jf->msg_first_ut;
|
||||
t.last_ut = jf->msg_last_ut;
|
||||
t.count = 1;
|
||||
t.size = jf->size;
|
||||
|
||||
dictionary_set(dict, SDJF_SOURCE_ALL_NAME, &t, sizeof(t));
|
||||
|
||||
if(jf->source_type & SDJF_LOCAL_ALL)
|
||||
dictionary_set(dict, SDJF_SOURCE_LOCAL_NAME, &t, sizeof(t));
|
||||
if(jf->source_type & SDJF_LOCAL_SYSTEM)
|
||||
dictionary_set(dict, SDJF_SOURCE_LOCAL_SYSTEM_NAME, &t, sizeof(t));
|
||||
if(jf->source_type & SDJF_LOCAL_USER)
|
||||
dictionary_set(dict, SDJF_SOURCE_LOCAL_USERS_NAME, &t, sizeof(t));
|
||||
if(jf->source_type & SDJF_LOCAL_OTHER)
|
||||
dictionary_set(dict, SDJF_SOURCE_LOCAL_OTHER_NAME, &t, sizeof(t));
|
||||
if(jf->source_type & SDJF_LOCAL_NAMESPACE)
|
||||
dictionary_set(dict, SDJF_SOURCE_NAMESPACES_NAME, &t, sizeof(t));
|
||||
if(jf->source_type & SDJF_REMOTE_ALL)
|
||||
dictionary_set(dict, SDJF_SOURCE_REMOTES_NAME, &t, sizeof(t));
|
||||
if(jf->source)
|
||||
dictionary_set(dict, string2str(jf->source), &t, sizeof(t));
|
||||
}
|
||||
dfe_done(jf);
|
||||
|
||||
dictionary_sorted_walkthrough_read(dict, journal_file_to_json_array_cb, wb);
|
||||
|
||||
dictionary_destroy(dict);
|
||||
}
|
||||
|
||||
static void files_registry_delete_cb(const DICTIONARY_ITEM *item, void *value, void *data __maybe_unused) {
|
||||
struct journal_file *jf = value; (void)jf;
|
||||
const char *filename = dictionary_acquired_item_name(item); (void)filename;
|
||||
|
||||
string_freez(jf->source);
|
||||
internal_error(true, "removed journal file '%s'", filename);
|
||||
}
|
||||
|
||||
void journal_directory_scan(const char *dirname, int depth, usec_t last_scan_ut) {
|
||||
static const char *ext = ".journal";
|
||||
static const size_t ext_len = sizeof(".journal") - 1;
|
||||
|
||||
if (depth > VAR_LOG_JOURNAL_MAX_DEPTH)
|
||||
return;
|
||||
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
struct stat info;
|
||||
char absolute_path[FILENAME_MAX];
|
||||
|
||||
// Open the directory.
|
||||
if ((dir = opendir(dirname)) == NULL) {
|
||||
if(errno != ENOENT && errno != ENOTDIR)
|
||||
netdata_log_error("Cannot opendir() '%s'", dirname);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read each entry in the directory.
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
snprintfz(absolute_path, sizeof(absolute_path), "%s/%s", dirname, entry->d_name);
|
||||
if (stat(absolute_path, &info) != 0) {
|
||||
netdata_log_error("Failed to stat() '%s", absolute_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (S_ISDIR(info.st_mode)) {
|
||||
// If entry is a directory, call traverse recursively.
|
||||
if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0)
|
||||
journal_directory_scan(absolute_path, depth + 1, last_scan_ut);
|
||||
|
||||
}
|
||||
else if (S_ISREG(info.st_mode)) {
|
||||
// If entry is a regular file, check if it ends with .journal.
|
||||
char *filename = entry->d_name;
|
||||
size_t len = strlen(filename);
|
||||
|
||||
if (len > ext_len && strcmp(filename + len - ext_len, ext) == 0) {
|
||||
struct journal_file t = {
|
||||
.file_last_modified_ut = info.st_mtim.tv_sec * USEC_PER_SEC + info.st_mtim.tv_nsec / NSEC_PER_USEC,
|
||||
.last_scan_ut = last_scan_ut,
|
||||
.size = info.st_size,
|
||||
.max_journal_vs_realtime_delta_ut = JOURNAL_VS_REALTIME_DELTA_DEFAULT_UT,
|
||||
};
|
||||
dictionary_set(journal_files_registry, absolute_path, &t, sizeof(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
void journal_files_registry_update(void) {
|
||||
usec_t scan_ut = now_monotonic_usec();
|
||||
|
||||
for(unsigned i = 0; i < MAX_JOURNAL_DIRECTORIES ;i++) {
|
||||
if(!journal_directories[i].path)
|
||||
break;
|
||||
|
||||
journal_directory_scan(journal_directories[i].path, 0, scan_ut);
|
||||
}
|
||||
|
||||
struct journal_file *jf;
|
||||
dfe_start_write(journal_files_registry, jf) {
|
||||
if(jf->last_scan_ut < scan_ut)
|
||||
dictionary_del(journal_files_registry, jf_dfe.name);
|
||||
}
|
||||
dfe_done(jf);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
int journal_file_dict_items_backward_compar(const void *a, const void *b) {
|
||||
const DICTIONARY_ITEM **ad = (const DICTIONARY_ITEM **)a, **bd = (const DICTIONARY_ITEM **)b;
|
||||
struct journal_file *jfa = dictionary_acquired_item_value(*ad);
|
||||
struct journal_file *jfb = dictionary_acquired_item_value(*bd);
|
||||
|
||||
if(jfa->msg_last_ut < jfb->msg_last_ut)
|
||||
return 1;
|
||||
|
||||
if(jfa->msg_last_ut > jfb->msg_last_ut)
|
||||
return -1;
|
||||
|
||||
if(jfa->msg_first_ut < jfb->msg_first_ut)
|
||||
return 1;
|
||||
|
||||
if(jfa->msg_first_ut > jfb->msg_first_ut)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int journal_file_dict_items_forward_compar(const void *a, const void *b) {
|
||||
return -journal_file_dict_items_backward_compar(a, b);
|
||||
}
|
||||
|
||||
void journal_init_files_and_directories(void) {
|
||||
unsigned d = 0;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// setup the journal directories
|
||||
|
||||
journal_directories[d++].path = strdupz("/var/log/journal");
|
||||
journal_directories[d++].path = strdupz("/run/log/journal");
|
||||
|
||||
if(*netdata_configured_host_prefix) {
|
||||
char path[PATH_MAX];
|
||||
snprintfz(path, sizeof(path), "%s/var/log/journal", netdata_configured_host_prefix);
|
||||
journal_directories[d++].path = strdupz(path);
|
||||
snprintfz(path, sizeof(path), "%s/run/log/journal", netdata_configured_host_prefix);
|
||||
journal_directories[d++].path = strdupz(path);
|
||||
}
|
||||
|
||||
// terminate the list
|
||||
journal_directories[d].path = NULL;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// initialize the used hashes files registry
|
||||
|
||||
used_hashes_registry = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
|
||||
|
||||
systemd_journal_session = (now_realtime_usec() / USEC_PER_SEC) * USEC_PER_SEC;
|
||||
|
||||
journal_files_registry = dictionary_create_advanced(
|
||||
DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
|
||||
NULL, sizeof(struct journal_file));
|
||||
|
||||
dictionary_register_insert_callback(journal_files_registry, files_registry_insert_cb, NULL);
|
||||
dictionary_register_delete_callback(journal_files_registry, files_registry_delete_cb, NULL);
|
||||
dictionary_register_conflict_callback(journal_files_registry, files_registry_conflict_cb, NULL);
|
||||
|
||||
boot_ids_to_first_ut = dictionary_create_advanced(
|
||||
DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
|
||||
NULL, sizeof(usec_t));
|
||||
|
||||
journal_files_registry_update();
|
||||
}
|
74
collectors/systemd-journal.plugin/systemd-journal-fstat.c
Normal file
74
collectors/systemd-journal.plugin/systemd-journal-fstat.c
Normal file
|
@ -0,0 +1,74 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "systemd-internals.h"
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// fstat64 overloading to speed up libsystemd
|
||||
// https://github.com/systemd/systemd/pull/29261
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define FSTAT_CACHE_MAX 1024
|
||||
struct fdstat64_cache_entry {
|
||||
bool enabled;
|
||||
bool updated;
|
||||
int err_no;
|
||||
struct stat64 stat;
|
||||
int ret;
|
||||
size_t cached_count;
|
||||
size_t session;
|
||||
};
|
||||
|
||||
struct fdstat64_cache_entry fstat64_cache[FSTAT_CACHE_MAX] = {0 };
|
||||
__thread size_t fstat_thread_calls = 0;
|
||||
__thread size_t fstat_thread_cached_responses = 0;
|
||||
static __thread bool enable_thread_fstat = false;
|
||||
static __thread size_t fstat_caching_thread_session = 0;
|
||||
static size_t fstat_caching_global_session = 0;
|
||||
|
||||
void fstat_cache_enable_on_thread(void) {
|
||||
fstat_caching_thread_session = __atomic_add_fetch(&fstat_caching_global_session, 1, __ATOMIC_ACQUIRE);
|
||||
enable_thread_fstat = true;
|
||||
}
|
||||
|
||||
void fstat_cache_disable_on_thread(void) {
|
||||
fstat_caching_thread_session = __atomic_add_fetch(&fstat_caching_global_session, 1, __ATOMIC_RELEASE);
|
||||
enable_thread_fstat = false;
|
||||
}
|
||||
|
||||
int fstat64(int fd, struct stat64 *buf) {
|
||||
static int (*real_fstat)(int, struct stat64 *) = NULL;
|
||||
if (!real_fstat)
|
||||
real_fstat = dlsym(RTLD_NEXT, "fstat64");
|
||||
|
||||
fstat_thread_calls++;
|
||||
|
||||
if(fd >= 0 && fd < FSTAT_CACHE_MAX) {
|
||||
if(enable_thread_fstat && fstat64_cache[fd].session != fstat_caching_thread_session) {
|
||||
fstat64_cache[fd].session = fstat_caching_thread_session;
|
||||
fstat64_cache[fd].enabled = true;
|
||||
fstat64_cache[fd].updated = false;
|
||||
}
|
||||
|
||||
if(fstat64_cache[fd].enabled && fstat64_cache[fd].updated && fstat64_cache[fd].session == fstat_caching_thread_session) {
|
||||
fstat_thread_cached_responses++;
|
||||
errno = fstat64_cache[fd].err_no;
|
||||
*buf = fstat64_cache[fd].stat;
|
||||
fstat64_cache[fd].cached_count++;
|
||||
return fstat64_cache[fd].ret;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = real_fstat(fd, buf);
|
||||
|
||||
if(fd >= 0 && fd < FSTAT_CACHE_MAX && fstat64_cache[fd].enabled && fstat64_cache[fd].session == fstat_caching_thread_session) {
|
||||
fstat64_cache[fd].ret = ret;
|
||||
fstat64_cache[fd].updated = true;
|
||||
fstat64_cache[fd].err_no = errno;
|
||||
fstat64_cache[fd].stat = *buf;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
File diff suppressed because it is too large
Load diff
103
collectors/systemd-journal.plugin/systemd-main.c
Normal file
103
collectors/systemd-journal.plugin/systemd-main.c
Normal file
|
@ -0,0 +1,103 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "systemd-internals.h"
|
||||
#include "libnetdata/required_dummies.h"
|
||||
|
||||
#define SYSTEMD_JOURNAL_WORKER_THREADS 5
|
||||
|
||||
netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER;
|
||||
static bool plugin_should_exit = false;
|
||||
|
||||
int main(int argc __maybe_unused, char **argv __maybe_unused) {
|
||||
stderror = stderr;
|
||||
clocks_init();
|
||||
|
||||
program_name = "systemd-journal.plugin";
|
||||
|
||||
// disable syslog
|
||||
error_log_syslog = 0;
|
||||
|
||||
// set errors flood protection to 100 logs per hour
|
||||
error_log_errors_per_period = 100;
|
||||
error_log_throttle_period = 3600;
|
||||
|
||||
log_set_global_severity_for_external_plugins();
|
||||
|
||||
netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
|
||||
if(verify_netdata_host_prefix() == -1) exit(1);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// initialization
|
||||
|
||||
journal_init_query_status();
|
||||
journal_init_files_and_directories();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// debug
|
||||
|
||||
if(argc == 2 && strcmp(argv[1], "debug") == 0) {
|
||||
bool cancelled = false;
|
||||
char buf[] = "systemd-journal after:-16000000 before:0 last:1";
|
||||
// char buf[] = "systemd-journal after:1695332964 before:1695937764 direction:backward last:100 slice:true source:all DHKucpqUoe1:PtVoyIuX.MU";
|
||||
// char buf[] = "systemd-journal after:1694511062 before:1694514662 anchor:1694514122024403";
|
||||
function_systemd_journal("123", buf, 600, &cancelled);
|
||||
// function_systemd_units("123", "systemd-units", 600, &cancelled);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// the event loop for functions
|
||||
|
||||
struct functions_evloop_globals *wg =
|
||||
functions_evloop_init(SYSTEMD_JOURNAL_WORKER_THREADS, "SDJ", &stdout_mutex, &plugin_should_exit);
|
||||
|
||||
functions_evloop_add_function(wg, SYSTEMD_JOURNAL_FUNCTION_NAME, function_systemd_journal,
|
||||
SYSTEMD_JOURNAL_DEFAULT_TIMEOUT);
|
||||
|
||||
#ifdef ENABLE_SYSTEMD_DBUS
|
||||
functions_evloop_add_function(wg, SYSTEMD_UNITS_FUNCTION_NAME, function_systemd_units,
|
||||
SYSTEMD_UNITS_DEFAULT_TIMEOUT);
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
time_t started_t = now_monotonic_sec();
|
||||
|
||||
size_t iteration = 0;
|
||||
usec_t step = 1000 * USEC_PER_MS;
|
||||
bool tty = isatty(fileno(stderr)) == 1;
|
||||
|
||||
netdata_mutex_lock(&stdout_mutex);
|
||||
|
||||
fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\"\n",
|
||||
SYSTEMD_JOURNAL_FUNCTION_NAME, SYSTEMD_JOURNAL_DEFAULT_TIMEOUT, SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION);
|
||||
|
||||
#ifdef ENABLE_SYSTEMD_DBUS
|
||||
fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\"\n",
|
||||
SYSTEMD_UNITS_FUNCTION_NAME, SYSTEMD_UNITS_DEFAULT_TIMEOUT, SYSTEMD_UNITS_FUNCTION_DESCRIPTION);
|
||||
#endif
|
||||
|
||||
heartbeat_t hb;
|
||||
heartbeat_init(&hb);
|
||||
while(!plugin_should_exit) {
|
||||
iteration++;
|
||||
|
||||
netdata_mutex_unlock(&stdout_mutex);
|
||||
heartbeat_next(&hb, step);
|
||||
netdata_mutex_lock(&stdout_mutex);
|
||||
|
||||
if(!tty)
|
||||
fprintf(stdout, "\n");
|
||||
|
||||
if(iteration % 60 == 0)
|
||||
journal_files_registry_update();
|
||||
|
||||
fflush(stdout);
|
||||
|
||||
time_t now = now_monotonic_sec();
|
||||
if(now - started_t > 86400)
|
||||
break;
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
1392
collectors/systemd-journal.plugin/systemd-units.c
Normal file
1392
collectors/systemd-journal.plugin/systemd-units.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1184,6 +1184,14 @@ if test "${have_sd_journal_restart_fields}" = "yes"; then
|
|||
AC_DEFINE([HAVE_SD_JOURNAL_RESTART_FIELDS], [1], [sd_journal_restart_fields usability])
|
||||
fi
|
||||
|
||||
AC_CHECK_LIB([systemd], [sd_bus_default_system, sd_bus_call_method, sd_bus_message_enter_container, sd_bus_message_read, sd_bus_message_exit_container],
|
||||
[SYSTEMD_DBUS_FOUND=yes],
|
||||
[SYSTEMD_DBUS_FOUND=no])
|
||||
|
||||
if test "x$SYSTEMD_DBUS_FOUND" = "xyes"; then
|
||||
AC_DEFINE([ENABLE_SYSTEMD_DBUS], [1], [libsystemd dbus usability])
|
||||
fi
|
||||
|
||||
AC_MSG_NOTICE([OPTIONAL_SYSTEMD_LIBS is set to: ${OPTIONAL_SYSTEMD_LIBS}])
|
||||
|
||||
if test "${enable_plugin_systemd_journal}" = "yes"; then
|
||||
|
|
|
@ -991,7 +991,7 @@ int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout, const char *
|
|||
// the caller has to wait
|
||||
|
||||
code = rdcf->execute_cb(result_wb, timeout, sanitized_cmd, rdcf->execute_cb_data,
|
||||
NULL, NULL, // no callback needed, it is synchronous
|
||||
result_cb, result_cb_data,
|
||||
is_cancelled_cb, is_cancelled_cb_data, // it is ok to pass these, we block the caller
|
||||
NULL, NULL); // no need to pass, we will wait
|
||||
|
||||
|
@ -1372,19 +1372,19 @@ int rrdhost_function_streaming(BUFFER *wb, int timeout __maybe_unused, const cha
|
|||
RRDF_FIELD_TYPE_TIMESTAMP, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DATETIME_MS,
|
||||
0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
|
||||
RRDF_FIELD_SUMMARY_MIN, RRDF_FIELD_FILTER_RANGE,
|
||||
RRDF_FIELD_OPTS_VISIBLE, NULL);
|
||||
RRDF_FIELD_OPTS_NONE, NULL);
|
||||
|
||||
buffer_rrdf_table_add_field(wb, field_id++, "dbTo", "DB Data Retention To",
|
||||
RRDF_FIELD_TYPE_TIMESTAMP, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DATETIME_MS,
|
||||
0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
|
||||
RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
|
||||
RRDF_FIELD_OPTS_VISIBLE, NULL);
|
||||
RRDF_FIELD_OPTS_NONE, NULL);
|
||||
|
||||
buffer_rrdf_table_add_field(wb, field_id++, "dbDuration", "DB Data Retention Duration",
|
||||
RRDF_FIELD_TYPE_DURATION, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DURATION_S,
|
||||
0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
|
||||
RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
|
||||
RRDF_FIELD_OPTS_NONE, NULL);
|
||||
RRDF_FIELD_OPTS_VISIBLE, NULL);
|
||||
|
||||
buffer_rrdf_table_add_field(wb, field_id++, "dbMetrics", "Time-series Metrics in the DB",
|
||||
RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
|
||||
|
|
|
@ -959,7 +959,8 @@ typedef enum __attribute__((packed)) {
|
|||
RRDF_FIELD_OPTS_STICKY = (1 << 2), // the field should be sticky
|
||||
RRDF_FIELD_OPTS_FULL_WIDTH = (1 << 3), // the field should get full width
|
||||
RRDF_FIELD_OPTS_WRAP = (1 << 4), // the field should wrap
|
||||
RRDR_FIELD_OPTS_DUMMY = (1 << 5), // not a presentable field
|
||||
RRDF_FIELD_OPTS_DUMMY = (1 << 5), // not a presentable field
|
||||
RRDF_FIELD_OPTS_EXPANDED_FILTER = (1 << 6), // show the filter expanded
|
||||
} RRDF_FIELD_OPTIONS;
|
||||
|
||||
typedef enum __attribute__((packed)) {
|
||||
|
@ -1173,8 +1174,9 @@ buffer_rrdf_table_add_field(BUFFER *wb, size_t field_id, const char *key, const
|
|||
|
||||
buffer_json_member_add_boolean(wb, "full_width", options & RRDF_FIELD_OPTS_FULL_WIDTH);
|
||||
buffer_json_member_add_boolean(wb, "wrap", options & RRDF_FIELD_OPTS_WRAP);
|
||||
buffer_json_member_add_boolean(wb, "default_expanded_filter", options & RRDF_FIELD_OPTS_EXPANDED_FILTER);
|
||||
|
||||
if(options & RRDR_FIELD_OPTS_DUMMY)
|
||||
if(options & RRDF_FIELD_OPTS_DUMMY)
|
||||
buffer_json_member_add_boolean(wb, "dummy", true);
|
||||
}
|
||||
buffer_json_object_close(wb);
|
||||
|
|
|
@ -1948,7 +1948,7 @@ bool facets_row_finished(FACETS *facets, usec_t usec) {
|
|||
// ----------------------------------------------------------------------------
|
||||
// output
|
||||
|
||||
static const char *facets_severity_to_string(FACET_ROW_SEVERITY severity) {
|
||||
const char *facets_severity_to_string(FACET_ROW_SEVERITY severity) {
|
||||
switch(severity) {
|
||||
default:
|
||||
case FACET_ROW_SEVERITY_NORMAL:
|
||||
|
@ -2338,7 +2338,7 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry)
|
|||
NULL,
|
||||
RRDF_FIELD_SUMMARY_COUNT,
|
||||
RRDF_FIELD_FILTER_NONE,
|
||||
RRDR_FIELD_OPTS_DUMMY,
|
||||
RRDF_FIELD_OPTS_DUMMY,
|
||||
NULL);
|
||||
|
||||
FACET_KEY *k;
|
||||
|
@ -2358,6 +2358,9 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry)
|
|||
if (k->options & FACET_KEY_OPTION_MAIN_TEXT)
|
||||
options |= RRDF_FIELD_OPTS_FULL_WIDTH | RRDF_FIELD_OPTS_WRAP;
|
||||
|
||||
if (k->options & FACET_KEY_OPTION_EXPANDED_FILTER)
|
||||
options |= RRDF_FIELD_OPTS_EXPANDED_FILTER;
|
||||
|
||||
const char *hash_str = hash_to_static_string(k->hash);
|
||||
|
||||
buffer_rrdf_table_add_field(
|
||||
|
@ -2369,8 +2372,7 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry)
|
|||
RRDF_FIELD_SORT_FIXED,
|
||||
NULL,
|
||||
RRDF_FIELD_SUMMARY_COUNT,
|
||||
(k->options & FACET_KEY_OPTION_NEVER_FACET) ? RRDF_FIELD_FILTER_NONE
|
||||
: RRDF_FIELD_FILTER_FACET,
|
||||
(k->options & FACET_KEY_OPTION_NEVER_FACET) ? RRDF_FIELD_FILTER_NONE : RRDF_FIELD_FILTER_FACET,
|
||||
options, FACET_VALUE_UNSET);
|
||||
}
|
||||
foreach_key_in_facets_done(k);
|
||||
|
|
|
@ -31,6 +31,7 @@ typedef enum __attribute__((packed)) {
|
|||
FACET_KEY_OPTION_RICH_TEXT = (1 << 7),
|
||||
FACET_KEY_OPTION_REORDER = (1 << 8), // give the key a new order id on first encounter
|
||||
FACET_KEY_OPTION_TRANSFORM_VIEW = (1 << 9), // when registering the transformation, do it only at the view, not on all data
|
||||
FACET_KEY_OPTION_EXPANDED_FILTER = (1 << 10), // the presentation should have this filter expanded by default
|
||||
} FACET_KEY_OPTIONS;
|
||||
|
||||
typedef enum __attribute__((packed)) {
|
||||
|
@ -115,4 +116,6 @@ uint32_t facets_rows(FACETS *facets);
|
|||
|
||||
void facets_table_config(BUFFER *wb);
|
||||
|
||||
const char *facets_severity_to_string(FACET_ROW_SEVERITY severity);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Reference in a new issue