├── Makefile.am ├── autogen.sh ├── src ├── hostid.h ├── hostspec_interfaces.c ├── stringutil.h ├── hostspec_hostname.c ├── log.h ├── fileutil.h ├── conf.h ├── Makefile.am ├── metrics_loadavg.c ├── metrics.h ├── stringutil.c ├── fileutil.c ├── metrics.c ├── hostspec.h ├── hostspec_kernel.c ├── api_internal.h ├── api.h ├── hostspec_cpu.c ├── hostspec_block_device.c ├── hostspec_filesystem.c ├── hostspec_memory.c ├── hostid.c ├── conf.c ├── metrics_custom.c ├── api.c ├── api_request.c ├── main.c └── api_client.c ├── .editorconfig ├── .clang-format ├── etc └── init.d │ └── umackereld ├── .gitignore ├── configure.ac └── README.md /Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS=src 2 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | autoreconf --install 3 | -------------------------------------------------------------------------------- /src/hostid.h: -------------------------------------------------------------------------------- 1 | #ifndef UMACKERELD_HOSTID_H 2 | #define UMACKERELD_HOSTID_H 3 | 4 | extern char *hostid; 5 | 6 | int hostid_set(char const *id); 7 | int hostid_get(); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*] 4 | end_of_line=lf 5 | insert_final_newline=true 6 | trim_trailing_whitespace=true 7 | 8 | [*.{c,h}] 9 | charset=utf-8 10 | indent_style=space 11 | indent_size=2 12 | -------------------------------------------------------------------------------- /src/hostspec_interfaces.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "hostspec.h" 4 | 5 | json_object *hostspec_collect_block_device() { 6 | json_object *obj = json_object_new_object(); 7 | assert(obj); 8 | } 9 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # -*- yaml -*- 2 | BasedOnStyle: LLVM 3 | AlignEscapedNewlinesLeft: true 4 | ColumnLimit: 120 5 | IndentCaseLabels: true 6 | KeepEmptyLinesAtTheStartOfBlocks: false 7 | SpacesBeforeTrailingComments: 2 8 | -------------------------------------------------------------------------------- /src/stringutil.h: -------------------------------------------------------------------------------- 1 | #ifndef UMACKERELD_STRINGUTIL_H 2 | #define UMACKERELD_STRINGUTIL_H 3 | 4 | int begin_with(char const *str, char const *prefix); 5 | char const *after_colon(char const *str); 6 | char *chomp(char *str); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /etc/init.d/umackereld: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | 3 | START=99 4 | 5 | USE_PROCD=1 6 | 7 | start_service() { 8 | procd_open_instance 9 | procd_set_param command /usr/sbin/umackereld 10 | procd_set_param respawn 11 | procd_close_instance 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .envrc 2 | 3 | *.o 4 | *.a 5 | /src/umackereld 6 | 7 | # autotools 8 | *.log 9 | *.status 10 | Makefile 11 | Makefile.in 12 | aclocal.m4 13 | stamp-* 14 | *.cache/ 15 | /config.h 16 | /config.h.in 17 | /configure 18 | /compile 19 | /depcomp 20 | /install-sh 21 | /missing 22 | .deps/ 23 | -------------------------------------------------------------------------------- /src/hostspec_hostname.c: -------------------------------------------------------------------------------- 1 | #include "fileutil.h" 2 | #include "hostspec.h" 3 | 4 | json_object *hostspec_collect_hostname() { 5 | char buf[1024], *p; 6 | if ((p = fgets_close(buf, sizeof buf, fopen("/proc/sys/kernel/hostname", "r")))) { 7 | return json_object_new_string(p); 8 | } 9 | return NULL; 10 | } 11 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #ifndef UMACKERELD_LOG_H 2 | #define UMACKERELD_LOG_H 3 | #include 4 | 5 | #define DEBUG(fmt, ...) ulog(LOG_DEBUG, "%s:%d(%s) " fmt "\n", __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__) 6 | 7 | #define DEBUG_ENTER DEBUG("enter") 8 | #define DEBUG_EXIT DEBUG("exit") 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/fileutil.h: -------------------------------------------------------------------------------- 1 | #ifndef UMACKERELD_FILEUTIL_H 2 | #define UMACKERELD_FILEUTIL_H 3 | #include 4 | #include 5 | 6 | FILE *fopenf(char const *mode, char const *fmt, ...) __attribute__((format(printf, 2, 3))); 7 | 8 | FILE *vfopenf(char const *mode, char const *fmt, va_list ap); 9 | 10 | char *fgets_close(char *s, size_t size, FILE *fp); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/conf.h: -------------------------------------------------------------------------------- 1 | #ifndef UMACKERELD_CONF_H 2 | #define UMACKERELD_CONF_H 3 | 4 | struct global_options { 5 | const char *apikey; 6 | const char *hostname; 7 | }; 8 | 9 | struct metric_options { 10 | const char *command; 11 | }; 12 | 13 | extern struct global_options global_options; 14 | 15 | int load_config(); 16 | int foreach_metric_config(void (*)(struct metric_options options)); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | export STAGING_DIR=@STAGING_DIR@ 2 | 3 | bin_PROGRAMS=umackereld 4 | umackereld_SOURCES=main.c conf.c hostid.c fileutil.c stringutil.c \ 5 | api.c api_client.c api_request.c \ 6 | hostspec_hostname.c hostspec_block_device.c hostspec_kernel.c hostspec_cpu.c hostspec_memory.c hostspec_filesystem.c \ 7 | metrics.c metrics_loadavg.c metrics_custom.c 8 | umackereld_CFLAGS=-Wall -Wextra -Werror -D_GNU_SOURCE -std=gnu11 9 | -------------------------------------------------------------------------------- /src/metrics_loadavg.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "metrics.h" 6 | #include "log.h" 7 | 8 | void metrics_collect_loadavg5(collector_callback yield) { 9 | time_t now = time(NULL); 10 | 11 | struct sysinfo info; 12 | if (sysinfo(&info) == 0) { 13 | double load = info.loads[1] / (double)(1 << SI_LOAD_SHIFT); 14 | yield(now, json_object_new_string("loadavg5"), json_object_new_double(load)); 15 | } else { 16 | ULOG_ERR("sysinfo failed.\n"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/metrics.h: -------------------------------------------------------------------------------- 1 | #ifndef UMACKERELD_METRICS_H 2 | #define UMACKERELD_METRICS_H 3 | #include 4 | #include 5 | 6 | // name is expected to be a string object 7 | // value is expected to be a double or int object 8 | typedef void (*collector_callback)(time_t time, json_object *name, json_object *value); 9 | 10 | void metrics_add(time_t time, json_object *name, json_object *value); 11 | json_object *metrics_flush(); 12 | 13 | void metrics_collect_loadavg5(collector_callback yield); 14 | void metrics_collect_custom(collector_callback yield, char const *command); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/stringutil.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int begin_with(char const *str, char const *prefix) { 4 | size_t prefix_len = strlen(prefix); 5 | return strlen(str) >= prefix_len && strncasecmp(str, prefix, prefix_len) == 0; 6 | } 7 | 8 | char const *after_colon(char const *str) { 9 | while (*str && *str != ':') 10 | ++str; 11 | while (*str && *str == ':') 12 | ++str; 13 | while (*str && *str == ' ') 14 | ++str; 15 | return str; 16 | } 17 | 18 | char *chomp(char *str) { 19 | size_t len = strlen(str); 20 | if (len > 0 && str[len - 1] == '\n') { 21 | str[len - 1] = '\0'; 22 | } 23 | return str; 24 | } 25 | -------------------------------------------------------------------------------- /src/fileutil.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "fileutil.h" 3 | 4 | FILE *vfopenf(char const *mode, char const *fmt, va_list ap) { 5 | char *path; 6 | if (vasprintf(&path, fmt, ap) == -1) { 7 | return NULL; 8 | } 9 | FILE *fp = fopen(path, mode); 10 | free(path); 11 | return fp; 12 | } 13 | 14 | FILE *fopenf(char const *mode, char const *fmt, ...) { 15 | va_list ap; 16 | va_start(ap, fmt); 17 | FILE *fp = vfopenf(mode, fmt, ap); 18 | va_end(ap); 19 | return fp; 20 | } 21 | 22 | char *fgets_close(char *s, size_t size, FILE *fp) { 23 | if (!fp) { 24 | return NULL; 25 | } 26 | char *p = fgets(s, size, fp); 27 | fclose(fp); 28 | return p; 29 | } 30 | -------------------------------------------------------------------------------- /src/metrics.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "metrics.h" 4 | 5 | static json_object *buffer; 6 | 7 | void metrics_add(time_t time, json_object *name, json_object *value) { 8 | assert(name); 9 | assert(value); 10 | 11 | if (!buffer) { 12 | buffer = json_object_new_array(); 13 | } 14 | 15 | json_object *obj = json_object_new_object(); 16 | json_object_array_add(buffer, obj); 17 | json_object_object_add(obj, "time", json_object_new_int64(time)); 18 | json_object_object_add(obj, "name", name); 19 | json_object_object_add(obj, "value", value); 20 | } 21 | 22 | json_object *metrics_flush() { 23 | json_object *values = buffer; 24 | buffer = NULL; 25 | return values; 26 | } 27 | -------------------------------------------------------------------------------- /src/hostspec.h: -------------------------------------------------------------------------------- 1 | #ifndef UMACKERELD_HOSTSPEC_H 2 | #define UMACKERELD_HOSTSPEC_H 3 | 4 | #include 5 | 6 | typedef struct hostspec_meta { 7 | const char *agent_revision; 8 | const char *agent_version; 9 | json_object *block_device; 10 | json_object *cpu; 11 | json_object *filesystem; 12 | json_object *kernel; 13 | json_object *memory; 14 | } hostspec_meta; 15 | 16 | typedef struct hostspec { 17 | json_object *name; 18 | json_object *interfaces; 19 | struct hostspec_meta meta; 20 | } hostspec; 21 | 22 | json_object *hostspec_collect_hostname(); 23 | json_object *hostspec_collect_cpu(); 24 | json_object *hostspec_collect_block_device(); 25 | json_object *hostspec_collect_kernel(); 26 | json_object *hostspec_collect_memory(); 27 | json_object *hostspec_collect_filesystem(); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/hostspec_kernel.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "fileutil.h" 5 | #include "hostspec.h" 6 | 7 | json_object *hostspec_collect_kernel() { 8 | json_object *obj = json_object_new_object(); 9 | assert(obj); 10 | 11 | struct utsname nm; 12 | if (uname(&nm) == -1) { 13 | goto error; 14 | } 15 | 16 | json_object_object_add(obj, "name", json_object_new_string(nm.sysname)); 17 | json_object_object_add(obj, "release", json_object_new_string(nm.release)); 18 | json_object_object_add(obj, "version", json_object_new_string(nm.version)); 19 | json_object_object_add(obj, "machine", json_object_new_string(nm.machine)); 20 | 21 | // Are you serious porting this to other platform?? 22 | json_object_object_add(obj, "os", json_object_new_string("GNU/Linux")); 23 | 24 | return obj; 25 | 26 | error: 27 | json_object_put(obj); 28 | return NULL; 29 | } 30 | -------------------------------------------------------------------------------- /src/api_internal.h: -------------------------------------------------------------------------------- 1 | #ifndef UMACKERELD_API_INTERNAL_H 2 | #define UMACKERELD_API_INTERNAL_H 3 | #include 4 | #include 5 | 6 | #include "api.h" 7 | 8 | int mackerel_client_invoke(struct mackerel_client *client, char const *method, char const *path, 9 | struct json_object *payload, mackerel_request_callback cb, void *pdata); 10 | 11 | struct json_request; 12 | void json_request_free(struct json_request *req); 13 | struct json_request *json_request_alloc(json_object *payload, mackerel_request_callback cb, void *pdata); 14 | void json_request_headers_append(struct json_request *req, char const *header); 15 | CURL *json_request_handle(struct json_request *req); 16 | void json_request_setopts(struct json_request *req, char const *method, char const *url); 17 | void json_request_invoke_callback(struct json_request *req, CURLcode code); 18 | struct json_request *json_request_from_handle(CURL *curl); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # -*- Autoconf -*- 2 | 3 | AC_PREREQ([2.69]) 4 | AC_INIT([umackereld], [0.0.0], [kasumi@rollingapple.net]) 5 | AM_INIT_AUTOMAKE([foreign 1.15]) 6 | AC_CONFIG_SRCDIR([src/main.c]) 7 | AC_CONFIG_HEADERS([config.h]) 8 | 9 | AC_ARG_WITH([sysroot], 10 | AS_HELP_STRING([--with-sysroot], [Set system root path])) 11 | 12 | AC_ARG_VAR(STAGING_DIR, [Path to the target directory in OpenWrt buildroot]) 13 | 14 | AC_PROG_CC 15 | 16 | AC_CHECK_LIB([mbedtls], [ssl_init]) 17 | AC_CHECK_LIB([ubox], [uloop_init]) 18 | AC_CHECK_LIB([uci], [uci_alloc_context]) 19 | AC_CHECK_LIB([json-c], [json_object_new_object]) 20 | AC_CHECK_LIB([curl], [curl_global_init]) 21 | 22 | AC_CHECK_HEADERS([libubox/uloop.h libubox/ulog.h], [], [AC_MSG_ERROR("libubox is not found in include path.")]) 23 | AC_CHECK_HEADERS([uci.h], [], [AC_MSG_ERROR("libuci is not found in include path.")]) 24 | AC_CHECK_HEADERS([json-c/json.h], [], [AC_MSG_ERROR("libjson-c is not found in include path.")]) 25 | AC_CHECK_HEADERS([curl/curl.h], [], [AC_MSG_ERROR("libcurl is not found in include path.")]) 26 | 27 | AC_C_CONST 28 | 29 | AC_CONFIG_FILES([Makefile src/Makefile]) 30 | AC_OUTPUT 31 | -------------------------------------------------------------------------------- /src/api.h: -------------------------------------------------------------------------------- 1 | #ifndef UMACKERELD_API_H 2 | #define UMACKERELD_API_H 3 | #include 4 | #include 5 | 6 | #include "hostspec.h" 7 | 8 | struct mackerel_params { 9 | const char *base_uri; 10 | const char *apikey; 11 | }; 12 | 13 | struct mackerel_client; 14 | 15 | struct mackerel_client *mackerel_client_alloc(struct mackerel_params params); 16 | 17 | void mackerel_client_free(struct mackerel_client *client); 18 | 19 | typedef void (*mackerel_request_callback)(CURLcode result, json_object *data, void *pdata); 20 | 21 | int mackerel_get_services(struct mackerel_client *client, mackerel_request_callback cb, void *pdata); 22 | 23 | int mackerel_create_host(struct mackerel_client *client, struct hostspec const *hostspec, mackerel_request_callback cb, 24 | void *pdata); 25 | 26 | int mackerel_update_host(struct mackerel_client *client, char const *hostid, struct hostspec const *hostspec, 27 | mackerel_request_callback cb, void *pdata); 28 | 29 | int mackerel_update_metrics(struct mackerel_client *client, char const *hostid, struct json_object *metric_values, 30 | mackerel_request_callback cb, void *pdata); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/hostspec_cpu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "hostspec.h" 8 | #include "stringutil.h" 9 | 10 | json_object *hostspec_collect_cpu() { 11 | json_object *obj = json_object_new_array(); 12 | assert(obj); 13 | 14 | FILE *fp = fopen("/proc/cpuinfo", "r"); 15 | if (!fp) { 16 | ULOG_ERR("Unable to open /proc/cpuinfo: %s\n", strerror(errno)); 17 | goto error; 18 | } 19 | 20 | char buf[1024], *p; 21 | json_object *obj_proc = NULL; 22 | while ((p = fgets(buf, sizeof buf, fp))) { 23 | chomp(p); 24 | 25 | if (begin_with(p, "processor\t")) { 26 | obj_proc = json_object_new_object(); 27 | json_object_array_add(obj, obj_proc); 28 | } 29 | 30 | // Only send the model name for now: this should work for x86/amd64/arm/mips 31 | if (obj_proc) { 32 | if (begin_with(p, "cpu model\t") || begin_with(p, "model name\t")) { 33 | json_object_object_add(obj_proc, "model_name", json_object_new_string(after_colon(p))); 34 | } 35 | } 36 | } 37 | 38 | fclose(fp); 39 | return obj; 40 | 41 | error: 42 | if (fp) { 43 | fclose(fp); 44 | } 45 | 46 | json_object_put(obj); 47 | return NULL; 48 | } 49 | -------------------------------------------------------------------------------- /src/hostspec_block_device.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "fileutil.h" 9 | #include "hostspec.h" 10 | 11 | json_object *hostspec_collect_block_device() { 12 | json_object *obj = json_object_new_object(); 13 | assert(obj); 14 | 15 | DIR *dir = opendir("/sys/block"); 16 | if (!dir) { 17 | ULOG_ERR("Unable to open /sys/block: %s\n", strerror(errno)); 18 | goto error; 19 | } 20 | 21 | struct dirent *ent; 22 | while ((ent = readdir(dir))) { 23 | char const *name = ent->d_name; 24 | 25 | if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { 26 | continue; 27 | } 28 | 29 | json_object *obj_dev = json_object_new_object(); 30 | assert(obj_dev); 31 | json_object_object_add(obj, name, obj_dev); 32 | 33 | char buf[1024], *p; 34 | if ((p = fgets_close(buf, sizeof buf, fopenf("r", "/sys/block/%s/size", name)))) { 35 | int64_t size; 36 | if (json_parse_int64(p, &size) == 0) { 37 | json_object_object_add(obj_dev, "size", json_object_new_int64(size)); 38 | } 39 | } 40 | 41 | if ((p = fgets_close(buf, sizeof buf, fopenf("r", "/sys/block/%s/removable", name)))) { 42 | int64_t removable; 43 | if (json_parse_int64(p, &removable) == 0) { 44 | json_object_object_add(obj_dev, "removable", json_object_new_int64(removable)); 45 | } 46 | } 47 | } 48 | 49 | closedir(dir); 50 | return obj; 51 | 52 | error: 53 | if (dir) { 54 | closedir(dir); 55 | } 56 | 57 | json_object_put(obj); 58 | return NULL; 59 | } 60 | -------------------------------------------------------------------------------- /src/hostspec_filesystem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "hostspec.h" 9 | #include "stringutil.h" 10 | #include "log.h" 11 | 12 | json_object *hostspec_collect_filesystem() { 13 | json_object *obj = json_object_new_array(); 14 | assert(obj); 15 | 16 | FILE *fp = setmntent("/proc/mounts", "r"); 17 | if (!fp) { 18 | ULOG_ERR("Unable to open /proc/mounts: %s\n", strerror(errno)); 19 | goto error; 20 | } 21 | 22 | struct mntent *ent; 23 | while ((ent = getmntent(fp))) { 24 | struct statfs s; 25 | if (strcmp(ent->mnt_fsname, "rootfs") == 0 || // skip rootfs 26 | statfs(ent->mnt_dir, &s) == -1 || // failed to stat 27 | s.f_blocks == 0 || // virtual file system 28 | s.f_blocks < s.f_bfree) { // something strange happens 29 | continue; 30 | } 31 | 32 | fsblkcnt_t total = s.f_blocks; 33 | fsblkcnt_t used = total - s.f_bfree; 34 | fsblkcnt_t available = s.f_bavail; 35 | fsblkcnt_t usable_total = used + available; 36 | 37 | if (usable_total <= 0) { // every block is reserved for root? -- avoid div_by_zero 38 | continue; 39 | } 40 | 41 | char percent_used[50]; 42 | if (sprintf(percent_used, "%d%%", (int)(used * 100.0 / usable_total)) == EOF) { 43 | continue; 44 | } 45 | 46 | json_object *obj_fs = json_object_new_object(); 47 | json_object_array_add(obj, obj_fs); 48 | json_object_object_add(obj_fs, "kb_size", json_object_new_int64(total * s.f_bsize / 1024)); 49 | json_object_object_add(obj_fs, "kb_used", json_object_new_int64(used * s.f_bsize / 1024)); 50 | json_object_object_add(obj_fs, "kb_available", json_object_new_int64(available * s.f_bsize / 1024)); 51 | json_object_object_add(obj_fs, "percent_used", json_object_new_string(percent_used)); 52 | json_object_object_add(obj_fs, "mount", json_object_new_string(ent->mnt_dir)); 53 | } 54 | 55 | endmntent(fp); 56 | return obj; 57 | 58 | error: 59 | if (fp) { 60 | endmntent(fp); 61 | } 62 | 63 | json_object_put(obj); 64 | return NULL; 65 | } 66 | -------------------------------------------------------------------------------- /src/hostspec_memory.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "hostspec.h" 9 | #include "stringutil.h" 10 | #include "log.h" 11 | 12 | #define FIELD(Fjson, Fmeminfo) \ 13 | if (begin_with(p, Fmeminfo ":")) { \ 14 | json_object_object_add(obj, Fjson, json_object_new_string(after_colon(p))); \ 15 | continue; \ 16 | } 17 | 18 | json_object *hostspec_collect_memory() { 19 | json_object *obj = json_object_new_object(); 20 | assert(obj); 21 | 22 | FILE *fp = fopen("/proc/meminfo", "r"); 23 | if (!fp) { 24 | ULOG_ERR("Unable to open /proc/meminfo: %s\n", strerror(errno)); 25 | goto error; 26 | } 27 | 28 | char buf[1024], *p; 29 | while ((p = fgets(buf, sizeof buf, fp))) { 30 | chomp(p); 31 | FIELD("total", "MemTotal"); 32 | FIELD("free", "MemFree"); 33 | FIELD("buffers", "Buffers"); 34 | FIELD("cached", "Cached"); 35 | FIELD("active", "Active"); 36 | FIELD("inactive", "Inactive"); 37 | FIELD("high_total", "HighTotal"); 38 | FIELD("high_free", "HighFree"); 39 | FIELD("low_total", "LowTotal"); 40 | FIELD("low_free", "LowFree"); 41 | FIELD("dirty", "Dirty"); 42 | FIELD("writeback", "Writeback"); 43 | FIELD("anon_pages", "AnonPages"); 44 | FIELD("mapped", "Mapped"); 45 | FIELD("slab", "Slab"); 46 | FIELD("slab_reclaimable", "SReclaimable"); 47 | FIELD("slab_unreclaim", "SUnreclaim"); 48 | FIELD("page_tables", "PageTables"); 49 | FIELD("nfs_unstable", "NFS_Unstable"); 50 | FIELD("bounce", "Bounce"); 51 | FIELD("commit_limit", "CommitLimit"); 52 | FIELD("committed_as", "Committed_AS"); 53 | FIELD("vmalloc_total", "VmallocTotal"); 54 | FIELD("vmalloc_used", "VmallocUsed"); 55 | FIELD("vmalloc_chunk", "VmallocChunk"); 56 | FIELD("swap_cached", "SwapCached"); 57 | FIELD("swap_total", "SwapTotal"); 58 | FIELD("swap_free", "SwapFree"); 59 | } 60 | 61 | fclose(fp); 62 | return obj; 63 | 64 | error: 65 | if (fp) { 66 | fclose(fp); 67 | } 68 | 69 | json_object_put(obj); 70 | return NULL; 71 | } 72 | -------------------------------------------------------------------------------- /src/hostid.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "hostid.h" 9 | #include "log.h" 10 | 11 | static char const *const vardir = "/etc/mackerel"; 12 | static char const *const hostid_file = "/etc/mackerel/id"; 13 | 14 | char *hostid = NULL; 15 | 16 | int hostid_set(char const *id) { 17 | if (hostid && strcmp(id, hostid) == 0) { 18 | return 0; 19 | } 20 | 21 | free(hostid); 22 | hostid = strdup(id); 23 | ULOG_INFO("HostID assigned: %s\n", hostid); 24 | 25 | (void)mkdir(vardir, 0755); 26 | 27 | int fd = open(hostid_file, O_CREAT | O_RDWR, 0644); 28 | if (fd == -1) { 29 | ULOG_ERR("Unable to open %s. HostID is not saved.\n", hostid_file); 30 | return -1; 31 | } 32 | 33 | if (flock(fd, LOCK_EX) != 0) { 34 | ULOG_ERR("Unable to lock %s. HostID is not saved.\n", hostid_file); 35 | close(fd); 36 | return -1; 37 | } 38 | 39 | ftruncate(fd, 0); 40 | ssize_t len = strlen(hostid); 41 | if (write(fd, hostid, strlen(hostid)) != len) { 42 | ULOG_ERR("Unable to write into %s. HostID is not saved.\n", hostid_file); 43 | ftruncate(fd, 0); 44 | close(fd); 45 | return -1; 46 | } 47 | 48 | ULOG_INFO("HostID is saved to %s.\n", hostid_file); 49 | 50 | return 0; 51 | } 52 | 53 | int hostid_get() { 54 | int fd = open(hostid_file, O_RDONLY); 55 | if (fd == -1) { 56 | ULOG_ERR("Unable to open %s. HostID is not loaded.\n", hostid_file); 57 | return -1; 58 | } 59 | 60 | if (flock(fd, LOCK_SH) != 0) { 61 | ULOG_ERR("Unable to lock %s. HostID is not loaded.\n", hostid_file); 62 | close(fd); 63 | return -1; 64 | } 65 | 66 | char buffer[100]; 67 | ssize_t cnt = read(fd, buffer, sizeof buffer); 68 | if (cnt == -1) { 69 | ULOG_ERR("Unable to read %s. HostID is not loaded.\n", hostid_file); 70 | close(fd); 71 | return -1; 72 | } 73 | 74 | close(fd); 75 | 76 | for (size_t i = 0; i < (size_t)cnt; ++i) { 77 | if (buffer[i] == '\n') { 78 | cnt = i; 79 | break; 80 | } 81 | } 82 | 83 | if (cnt == 0) { 84 | ULOG_ERR("%s is empty. HostID is not loaded.\n", hostid_file); 85 | return -1; 86 | } 87 | 88 | free(hostid); 89 | hostid = strndup(buffer, (size_t)cnt); 90 | ULOG_INFO("HostID loaded: %s\n", hostid); 91 | 92 | return 0; 93 | } 94 | -------------------------------------------------------------------------------- /src/conf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "conf.h" 5 | #include "log.h" 6 | 7 | static char const *const package_name = "mackerel"; 8 | 9 | struct global_options global_options; 10 | struct uci_context *uci_context; 11 | struct uci_package *uci_package; 12 | 13 | static struct uci_section *find_uci_section_by_type(struct uci_package *package, char const *type) { 14 | struct uci_element *p; 15 | uci_foreach_element(&package->sections, p) { 16 | struct uci_section *const ps = uci_to_section(p); 17 | if (strcmp(ps->type, type) == 0) { 18 | return ps; 19 | } 20 | } 21 | 22 | return NULL; 23 | } 24 | 25 | int load_globals(struct uci_context *ctx, struct uci_package *package) { 26 | struct uci_section *section_global = find_uci_section_by_type(package, "mackerel"); 27 | if (!section_global) { 28 | ULOG_ERR("No mackerel section found in config\n"); 29 | return -1; 30 | } 31 | 32 | global_options.apikey = uci_lookup_option_string(ctx, section_global, "apikey"); 33 | if (!global_options.apikey) { 34 | ULOG_ERR("Option mackerel.apikey is required.\n"); 35 | return -1; 36 | } 37 | 38 | global_options.hostname = uci_lookup_option_string(ctx, section_global, "hostname"); 39 | 40 | return 0; 41 | } 42 | 43 | int load_config() { 44 | uci_context = uci_alloc_context(); 45 | if (!uci_context) { 46 | ULOG_ERR("uci_alloc_context failed\n"); 47 | goto error; 48 | } 49 | 50 | if (uci_load(uci_context, package_name, &uci_package) != 0) { 51 | ULOG_ERR("uci_load failed\n"); 52 | goto error; 53 | } 54 | 55 | if (load_globals(uci_context, uci_package) != 0) { 56 | goto error; 57 | } 58 | 59 | return 0; 60 | 61 | error: 62 | if (uci_package) { 63 | uci_unload(uci_context, uci_package); 64 | } 65 | 66 | if (uci_context) { 67 | uci_free_context(uci_context); 68 | } 69 | 70 | return -1; 71 | } 72 | 73 | int foreach_metric_config(void (*cb)(struct metric_options options)) { 74 | if(!uci_package) { 75 | ULOG_ERR("Config package is not loaded.\n"); 76 | return -1; 77 | } 78 | 79 | struct uci_element *p; 80 | uci_foreach_element(&uci_package->sections, p) { 81 | struct uci_section *section = uci_to_section(p); 82 | if(strcmp(section->type, "metric") == 0) { 83 | char const *command = uci_lookup_option_string(uci_context, section, "command"); 84 | cb((struct metric_options){.command=command}); 85 | } 86 | } 87 | 88 | return 0; 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # umackereld 2 | 3 | `umackereld`, which stands for "micro mackerel daemon," is a small [Mackerel](https://mackerel.io) agent replacement written for [OpenWrt](https://openwrt.org/)-based embedded Linux systems. 4 | 5 | ## Dependency 6 | 7 | `umackereld` depends on the following libraries: 8 | 9 | - `libubox` 10 | - `libubus` 11 | - `libuci` 12 | - `libjson-c` 13 | - `libcurl` compiled with a TLS support (only tested with `mbedtls`) 14 | 15 | and the following platform components: 16 | 17 | - `netifd` 18 | - `ubusd` 19 | 20 | In order to run `umackereld` on a standard Chaos Calmer system, you need to install `libcurl` and `ca-certificates` packages with opkg. 21 | 22 | ## Build 23 | 24 | For cross-compiling `umackereld` for your OpenWrt system, you have to prepare OpenWrt Buildroot (or OpenWrt SDK will do, meybe). 25 | Make sure all the libraries listed in the Dependency section is built for your target system. 26 | 27 | TODO: Write a friendly howto here... 28 | 29 | Something as follows should work (replace "mips" with your favorite architecture). 30 | 31 | ``` 32 | export PATH=/path/to/buildroot/staging_dir/host/bin:/path/to/buildroot/staging_dir/toolchain-mips_foo_gcc-bar_uClibc-baz/bin:$PATH 33 | autoreconf --install 34 | ./configure --host=mips-openwrt-linux STAGING_DIR=/path/to/buildroot/staging_dir/target-mips_foo_uClibc-baz 35 | make 36 | ``` 37 | 38 | ## Configuration 39 | 40 | `umackereld` uses UCI configuration system and by default reads the file at `/etc/config/mackerel`. 41 | Only `mackerel.apikey` option is mandatory. 42 | 43 | ``` 44 | # Global configuration 45 | config mackerel 46 | # Your API key, issued by mackerel.io (mandatory) 47 | option apikey 'YOUR_API_KEY' 48 | 49 | # Hostname is by default obtained from the kernel. You can override it. 50 | option hostname 'override.hostname.localdomain' 51 | 52 | # Custom metrics (any number of sections allowed) 53 | config metric 54 | # The command must output lines in the format: "\t\t