From c4867d34e32a79a76958703ac1b3ae8d5c6e37a0 Mon Sep 17 00:00:00 2001 From: Konrad Kuchciak Date: Fri, 29 Mar 2019 11:41:15 +0100 Subject: [PATCH] Add initial version of stability-monitor Change-Id: Ifdde9fcfce1eeea1debe5720b751061f82f4cb33 --- Makefile | 38 +++ config/10-example.conf | 51 +++ config/default.conf | 32 ++ config/stability-monitor.service | 12 + kernel/Makefile | 12 + kernel/proc-tsm.c | 153 +++++++++ packaging/stability-monitor.spec | 46 +++ src/action.c | 237 +++++++++++++ src/action.h | 29 ++ src/aul.c | 82 +++++ src/aul.h | 24 ++ src/config.c | 190 +++++++++++ src/config.h | 33 ++ src/data_source.c | 124 +++++++ src/data_source.h | 82 +++++ src/data_sources/tsm_cpu.c | 138 ++++++++ src/data_sources/tsm_fd.c | 84 +++++ src/data_sources/tsm_io.c | 135 ++++++++ src/data_sources/tsm_mem.c | 97 ++++++ src/dbus.c | 117 +++++++ src/dbus.h | 43 +++ src/limit.c | 37 ++ src/limit.h | 38 +++ src/list.h | 158 +++++++++ src/log.c | 13 + src/log.h | 57 ++++ src/process.c | 548 ++++++++++++++++++++++++++++++ src/process.h | 85 +++++ src/raw_data.c | 65 ++++ src/raw_data.h | 52 +++ src/raw_data_providers/proc_tsm.c | 127 +++++++ src/raw_data_providers/proc_tsm.h | 36 ++ src/sample.c | 121 +++++++ src/sample.h | 57 ++++ src/stability-monitor.c | 317 +++++++++++++++++ src/utils.c | 128 +++++++ src/utils.h | 47 +++ 37 files changed, 3645 insertions(+) create mode 100644 Makefile create mode 100644 config/10-example.conf create mode 100644 config/default.conf create mode 100644 config/stability-monitor.service create mode 100644 kernel/Makefile create mode 100644 kernel/proc-tsm.c create mode 100644 packaging/stability-monitor.spec create mode 100644 src/action.c create mode 100644 src/action.h create mode 100644 src/aul.c create mode 100644 src/aul.h create mode 100644 src/config.c create mode 100644 src/config.h create mode 100644 src/data_source.c create mode 100644 src/data_source.h create mode 100644 src/data_sources/tsm_cpu.c create mode 100644 src/data_sources/tsm_fd.c create mode 100644 src/data_sources/tsm_io.c create mode 100644 src/data_sources/tsm_mem.c create mode 100644 src/dbus.c create mode 100644 src/dbus.h create mode 100644 src/limit.c create mode 100644 src/limit.h create mode 100644 src/list.h create mode 100644 src/log.c create mode 100644 src/log.h create mode 100644 src/process.c create mode 100644 src/process.h create mode 100644 src/raw_data.c create mode 100644 src/raw_data.h create mode 100644 src/raw_data_providers/proc_tsm.c create mode 100644 src/raw_data_providers/proc_tsm.h create mode 100644 src/sample.c create mode 100644 src/sample.h create mode 100644 src/stability-monitor.c create mode 100644 src/utils.c create mode 100644 src/utils.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bde1162 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +srcdir = ./src +src = \ + src/stability-monitor.c \ + src/config.c \ + src/aul.c \ + src/process.c \ + src/sample.c \ + src/utils.c \ + src/log.c \ + src/dbus.c \ + src/limit.c \ + src/action.c \ + src/raw_data.c \ + src/raw_data_providers/proc_tsm.c \ + src/data_source.c \ + src/data_sources/tsm_cpu.c \ + src/data_sources/tsm_mem.c \ + src/data_sources/tsm_io.c \ + src/data_sources/tsm_fd.c + +obj = $(src:.c=.o) + +libs = systemd json-c aul gio-2.0 glib-2.0 pkgmgr-info + +CFLAGS = \ + -Wall \ + -I$(srcdir) \ + `pkg-config --cflags $(libs)` \ + -DKMOD_PATH=\"$(KMOD_PATH)\" + +LDFLAGS = `pkg-config --libs $(libs)` + + +stability-monitor: $(obj) + $(CC) -o $@ $^ $(LDFLAGS) + +install: + install -D stability-monitor $(INSTALL_PREFIX)/stability-monitor diff --git a/config/10-example.conf b/config/10-example.conf new file mode 100644 index 0000000..9306636 --- /dev/null +++ b/config/10-example.conf @@ -0,0 +1,51 @@ +{ + "global":{ + "report": 1, + }, + + + "foreground_native_app":{ + "cpu_limit_avg": 0.6, + }, + + "background_native_app":{ + "cpu_limit_avg": 0.5, + }, + + + "foreground_web_app":{ + }, + + "background_web_app":{ + }, + + + "foreground_csharp_app":{ + }, + + "background_csharp_app":{ + }, + + + "foreground_ui_widget":{ + }, + + "background_ui_widget":{ + }, + + + "native_service":{ + }, + + "other_service":{ + "cpu_limit_avg": 0.7, + }, + + "com.samsung.voice-trigger":{ + "monitor": 0, + }, + + "voice-trigger":{ + "monitor": 0, + }, +} diff --git a/config/default.conf b/config/default.conf new file mode 100644 index 0000000..0c4c01e --- /dev/null +++ b/config/default.conf @@ -0,0 +1,32 @@ +{ + "global":{ + "monitor": 1, + "print_current": 0, + "kill": 0, + "report": 0, + "sampling_rate": 1, + + "cpu_limit_avg": 0.5, + "cpu_avg_period": 10, + "cpu_limit_peak": 0.95, + + "mem_limit_avg": 0.3, + "mem_avg_period": 10, + "mem_limit_peak": 0.3, + + "io_limit_avg": 30.0, + "io_avg_period": 10, + "io_limit_peak": 60.0, + + "fd_limit": 300, + }, + + "stability-monitor":{ + "monitor": 0, + }, + + "crash-manager": { + "monitor": 0, + "apply_to_children": 1, + }, +} diff --git a/config/stability-monitor.service b/config/stability-monitor.service new file mode 100644 index 0000000..a67b7ca --- /dev/null +++ b/config/stability-monitor.service @@ -0,0 +1,12 @@ +[Unit] +Description=Stability monitoring daemon + +[Service] +Type=simple +SmackProcessLabel=System +ExecStart=/usr/sbin/stability-monitor +Restart=on-failure +RestartSec=0 + +[Install] +WantedBy=multi-user.target diff --git a/kernel/Makefile b/kernel/Makefile new file mode 100644 index 0000000..2060cda --- /dev/null +++ b/kernel/Makefile @@ -0,0 +1,12 @@ +KERNELDIR = /boot/kernel_header/debug/linux/ +PWD := $(shell pwd) +LD = ld + +obj-m += proc-tsm.o +proc-tsm_SOURCES = proc-tsm.c + +all: + make -C $(KERNELDIR) M=$(PWD) modules + +clean: + make -C $(KERNELDIR) M=$(PWD) clean diff --git a/kernel/proc-tsm.c b/kernel/proc-tsm.c new file mode 100644 index 0000000..5dad1aa --- /dev/null +++ b/kernel/proc-tsm.c @@ -0,0 +1,153 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG 0 +#define PAGE_TO_KB(x) ((x) << (PAGE_SHIFT - 10)) + +static struct proc_dir_entry* proc_file; + + +static u64 time_now(void) +{ + return div64_ul(ktime_get_boot_ns(), NSEC_PER_USEC); +} + +static int stability_monitor_show(struct seq_file *m, void *v) +{ + struct sysinfo info; + struct task_struct *task; + struct pid *pid = NULL; + pid_t ppid = 0; + cputime_t utime, stime; + unsigned long long vm_rss, total_ram, task_io; + unsigned int open_fds; + u64 uptime; + + + rcu_read_lock(); + + /* Uptime in us */ + uptime = time_now(); + + /* Memory info */ + si_meminfo(&info); + total_ram = PAGE_TO_KB(info.totalram); + + seq_put_decimal_ull(m, 0, uptime); + seq_put_decimal_ull(m, ' ', total_ram); + seq_printf(m, "\n"); + + for_each_process(task) { + ppid = task->real_parent->pid; + pid = task_pgrp(task); + if (!pid) + continue; + + task_lock(task); + + if (!task->mm) { + task_unlock(task); + continue; + } + + if (task->flags & (PF_DUMPCORE|PF_SIGNALED|PF_EXITING)) { + task_unlock(task); + continue; + } + + + /* Memory */ + vm_rss = get_mm_rss(task->mm); + + /* Open FDs - this needs CONFIG_KDEBUGD_FD_DEBUG */ + open_fds = task->files ? files_fdtable(task->files)->cur_fdn : 0; + + /* CPU time */ + thread_group_cputime_adjusted(task, &utime, &stime); + + /* IO */ + task_io = (unsigned long long)task->ioac.read_bytes + (unsigned long long)task->ioac.write_bytes; + + task_unlock(task); + + seq_put_decimal_ll(m, 0, task->pid); + seq_put_decimal_ll(m, ' ', ppid); + seq_put_decimal_ull(m, ' ', cputime_to_usecs(utime + stime)); + seq_put_decimal_ull(m, ' ', PAGE_TO_KB(vm_rss)); // kB + seq_put_decimal_ll(m, ' ', open_fds); + seq_put_decimal_ull(m, ' ', task_io); + seq_printf(m, "\n"); + } + + rcu_read_unlock(); + +#if DEBUG + printk(KERN_DEBUG "stability_monitor_show() took %llu us\n", time_now() - uptime); +#endif + + return 0; +} + +static int stability_monitor_open(struct inode *inode, struct file *file) +{ + return single_open_size(file, stability_monitor_show, NULL, 8192); +} + +static const struct file_operations stability_monitor_fops = { + .owner = THIS_MODULE, + .open = stability_monitor_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init stability_monitor_init(void) +{ + proc_file = proc_create("tsm", 0, NULL, &stability_monitor_fops); + if (!proc_file) + return -ENOMEM; + + return 0; +} + +static void __exit stability_monitor_exit(void) +{ + remove_proc_entry("tsm", NULL); +} + +module_init(stability_monitor_init); +module_exit(stability_monitor_exit); + +MODULE_LICENSE("GPL"); diff --git a/packaging/stability-monitor.spec b/packaging/stability-monitor.spec new file mode 100644 index 0000000..0107538 --- /dev/null +++ b/packaging/stability-monitor.spec @@ -0,0 +1,46 @@ +Name: stability-monitor +Version: 0.0.1 +Release: 0 +License: Apache-2.0 +Source0: %{name}-%{version}.tar.xz +Summary: Stability monitoring daemon +Group: System/Monitoring +BuildRequires: pkgconfig(libsystemd) +BuildRequires: pkgconfig(json-c) +BuildRequires: pkgconfig(aul) +BuildRequires: pkgconfig(glib-2.0) +BuildRequires: pkgconfig(gio-2.0) +BuildRequires: pkgconfig(pkgmgr-info) +BuildRequires: linux-kantm-devel + +%description +This package provides stability monitoring daemon. + +%prep +%setup -q +%define KMOD_PATH /%{_libdir}/modules/linux/kernel/drivers/misc/stability-monitor/proc-tsm.ko + +%build +make stability-monitor KMOD_PATH=%KMOD_PATH + +cd kernel +make clean +make all + +%install +make install INSTALL_PREFIX=%{buildroot}/%{_sbindir} +install -D config/default.conf %{buildroot}/%{_libdir}/stability-monitor/default.conf +install -D config/10-example.conf %{buildroot}/%{_sysconfdir}/stability-monitor.d/10-example.conf +install -D kernel/proc-tsm.ko %{buildroot}/%KMOD_PATH + +install -D config/stability-monitor.service %{buildroot}/%{_unitdir}/stability-monitor.service +mkdir -p %{buildroot}/%{_unitdir}/multi-user.target.wants +ln -s ../stability-monitor.service %{buildroot}/%{_unitdir}/multi-user.target.wants/stability-monitor.service + +%files +%{_sbindir}/stability-monitor +%{_libdir}/stability-monitor/default.conf +%{_sysconfdir}/stability-monitor.d/10-example.conf +%KMOD_PATH +%{_unitdir}/stability-monitor.service +%{_unitdir}/multi-user.target.wants/stability-monitor.service diff --git a/src/action.c b/src/action.c new file mode 100644 index 0000000..045ba38 --- /dev/null +++ b/src/action.c @@ -0,0 +1,237 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "log.h" +#include "action.h" +#include "dbus.h" +#include "limit.h" + + +struct action_data { + int stdout_fd; + int stderr_fd; + + struct data_source *ds; + enum limit_type lt; + GVariant *actual_value; + GVariant *allowed_value; + + char *report_path; +}; + +static void action_finish(struct action_data *ad) +{ + char objpath[PATH_MAX]; + char interface[PATH_MAX]; + char format[20]; + GVariantBuilder *builder; + GVariant *signal_params; + + snprintf(format, 20, "(s@%s@%s@a{sv})", g_variant_get_type_string(ad->actual_value), + g_variant_get_type_string(ad->allowed_value)); + + snprintf(objpath, PATH_MAX, "%s/%s", TSM_DBUS_PATH, ad->ds->name); + snprintf(interface, PATH_MAX, "%s.%s", TSM_DBUS_INTERFACE, ad->ds->param_name); + + + builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(builder, "{sv}", + "report_path", + g_variant_new_string(ad->report_path ? ad->report_path : "")); + + g_variant_builder_add(builder, "{sv}", + "killed", + g_variant_new_boolean(ad->ds->process->kill)); + + signal_params = g_variant_new(format, + limit_type_to_string(ad->lt), + ad->actual_value, + ad->allowed_value, + g_variant_builder_end(builder)); + + g_variant_builder_unref(builder); + + /* Kill the process */ + if (ad->ds->process->kill) { + _D_PROC(ad->ds->process, "Killing process"); + if (kill(ad->ds->process->pid, SIGKILL)) + _E("Unable to kill process %s(%d): %m", + ad->ds->process->name, + ad->ds->process->pid); + } + + /* Send signal */ + dbus_send_signal(objpath, interface, "AbnormalityDetected", signal_params); + + _D_PROC(ad->ds->process, "Sent D-Bus signal (%s, %s)", + limit_type_to_string(ad->lt), + ad->ds->param_name); + + ad->ds->process->action_in_progress = 0; + + close(ad->stdout_fd); + close(ad->stderr_fd); + free(ad->report_path); + free(ad); +} + +static void crash_manager_exit_cb(GPid pid, gint status, gpointer user_data) +{ + struct action_data *ad = user_data; + FILE *stream = NULL; + char *line = NULL; + size_t len = 0; + ssize_t n; + + if (!g_spawn_check_exit_status (status, NULL)) { + _E("Crash-manager failed with code: %d", status); + + stream = fdopen(ad->stderr_fd, "r"); + if (!stream) { + _E("Unable to open stderr stream: %m"); + goto finish; + } + + while ((n = getline(&line, &len, stream)) != -1) + _D("%s", line); + + goto finish; + } + + stream = fdopen(ad->stdout_fd, "r"); + if (!stream) { + _E("Unable to open stdout stream: %m"); + goto finish; + } + + /* Get report path from stdout */ + while ((n = getline(&line, &len, stream)) != -1) { + if (strncmp(line, "REPORT_PATH=", 12) == 0) { + line[n-1] = 0; + + ad->report_path = strndup(line + 12, n - 12); + if (!ad->report_path) + _E("Unable to allocate memory"); + else + _D_PROC(ad->ds->process, "Received report: %s", ad->report_path); + + goto finish; + } + } + + _E("Crash-worker ended without error but didn't provide report path"); + +finish: + action_finish(ad); + + free(line); + if (stream) + fclose(stream); + + g_spawn_close_pid(pid); +} + +static GPid spawn_crash_manager(struct action_data *ad) +{ + GPid child_pid = 0; + GError *error = NULL; + gchar **argv; + + argv = g_new(gchar *, 5); + argv[0] = g_strdup(CRASH_MANAGER_BIN); + argv[1] = g_strdup_printf("-p%d", ad->ds->process->pid); + argv[2] = g_strdup("-l"); + argv[3] = g_strdup("-r"); + argv[4] = NULL; + + _D_PROC(ad->ds->process, "Generating report..."); + + /* Spawn child */ + if (!g_spawn_async_with_pipes(NULL, argv, NULL, + G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, NULL, + NULL, &child_pid, NULL, &ad->stdout_fd, &ad->stderr_fd, &error)) { + + _E("Unable to spawn child process: %s", error->message); + g_error_free(error); + } + + g_strfreev(argv); + + return child_pid; +} + +static gboolean action_run(gpointer data) +{ + struct action_data *ad = data; + GPid child_pid; + + if (ad->ds->process->report) { + child_pid = spawn_crash_manager(ad); + if (child_pid != 0) + g_child_watch_add(child_pid, crash_manager_exit_cb, ad); + } else { + action_finish(ad); + } + + return FALSE; +} + +int action_run_default(struct data_source *ds, enum limit_type lt) +{ + struct action_data *ad; + struct smpl_container *sc = process_is_foreground(ds->process) ? &ds->data_fg : &ds->data_bg; + + if (ds->process->action_in_progress) + return 0; + ds->process->action_in_progress = 1; + + _D_PROC(ds->process, "Running default action"); + + ad = calloc(1, sizeof(*ad)); + if (!ad) { + _E("Unable to allocate memory"); + return -ENOMEM; + } + + ad->ds = ds; + ad->report_path = NULL; + ad->lt = lt; + + if (lt == LIMIT_TYPE_AVG) { + ad->actual_value = g_variant_new_double(sc->average); + ad->allowed_value = g_variant_new_double(ds->limit.avg); + } else { + ad->actual_value = sample_to_gvariant(list_first_entry(&sc->samples, struct sample_s, node)); + ad->allowed_value = sample_to_gvariant(&ds->limit.peak); + } + + g_timeout_add(0, action_run, ad); + + return 0; +} diff --git a/src/action.h b/src/action.h new file mode 100644 index 0000000..87a2aa8 --- /dev/null +++ b/src/action.h @@ -0,0 +1,29 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ACTION_H +#define ACTION_H + +#include "data_source.h" +#include "limit.h" + +#define CRASH_MANAGER_BIN "crash-manager" + +int action_run_default(struct data_source *ds, enum limit_type lt); + +#endif /* ACTION_H */ \ No newline at end of file diff --git a/src/aul.c b/src/aul.c new file mode 100644 index 0000000..c169c24 --- /dev/null +++ b/src/aul.c @@ -0,0 +1,82 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "log.h" +#include "process.h" +#include "config.h" +#include "dbus.h" + +#include "aul.h" + + +static void app_status_change_handler(GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + const char *appid, *pkgid, *status, *type; + int pid, aul_app_status; + struct proc_info *process; + unsigned int old_class; + int ret; + + g_variant_get(parameters, "(issss)", &pid, &appid, &pkgid, &status, &type); + + process = process_get_by_pid(pid); + if (process) { + old_class = process->class; + + if (0 == strcmp(status, "fg")) + aul_app_status = STATUS_VISIBLE; + else + aul_app_status = STATUS_BG; + + ret = process_update_info(process, &aul_app_status); + if (ret) + return; + + if (process->class != old_class) + _D_PROC(process, "Class changed"); + + } else { + _W("Received AUL status update for PID %d, but it wasn't found in internal state - ignoring.", pid); + } +} + +int aul_listener_init(void) +{ + int ret; + + ret = dbus_subscribe("/Org/Tizen/Aul/AppStatus", + "org.tizen.aul.AppStatus", + "AppStatusChange", + app_status_change_handler); + if (ret) { + _E("Unable to subscribe to signal \"AppStatusChange\""); + return -1; + } + + return 0; +} diff --git a/src/aul.h b/src/aul.h new file mode 100644 index 0000000..cad7419 --- /dev/null +++ b/src/aul.h @@ -0,0 +1,24 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AUL_H +#define AUL_H + +int aul_listener_init(void); + +#endif /* AUL_H */ \ No newline at end of file diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..d792c17 --- /dev/null +++ b/src/config.c @@ -0,0 +1,190 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "process.h" +#include "utils.h" +#include "data_source.h" +#include "config.h" + +static struct json_object *config = NULL; + + +int config_is_empty(void) { + return config == NULL; +} + +struct json_object *config_get(void) +{ + return config; +} + +int config_load(const char *config_file) +{ + config = json_object_from_file(config_file); + if (!config) + return -EINVAL; + + _D("Loaded config %s\n", config_file); + + return 0; +} + +static int config_load_merge(const char *config_file) +{ + struct json_object *obj; + int ret; + + obj = json_object_from_file(config_file); + if (!obj) { + _E("Unable to parse config %s", config_file); + return -EINVAL; + } + + json_object_object_foreach(obj, key, val) { + ret = _json_object_object_merge(config, key, val); + if (ret) { + json_object_put(obj); + return ret; + } + } + + _D("Loaded config %s\n", config_file); + + return 0; +} + +static int is_regular_file(const char *path) +{ + struct stat st; + + stat(path, &st); + + return S_ISREG(st.st_mode); +} + +int config_load_dir(const char *config_dir) +{ + int n; + struct dirent **filelist; + char full_path[PATH_MAX]; + + n = scandir(config_dir, &filelist, NULL, alphasort); + if (n < 0) { + _W("Unable to load user config from %s: %m", config_dir); + return n; + } + + while (n--) { + snprintf(full_path, PATH_MAX, "%s/%s", config_dir, filelist[n]->d_name); + + if (is_regular_file(full_path)) + config_load_merge(full_path); + + free(filelist[n]); + } + + free(filelist); + return 0; +} + +void config_print(void) +{ + _D("Config tree:\n\n%s\n", + json_object_to_json_string_ext(config, JSON_C_TO_STRING_PRETTY)); +} + +static void _read_params(json_object *obj, struct proc_info *process) +{ + struct data_source *ds; + + json_object_object_foreach(obj, key, val) { + if (strcmp(key, "monitor") == 0) { + process->monitor = json_object_get_int(val) ? 1 : 0; + continue; + } + + if (strcmp(key, "kill") == 0) { + process->kill = json_object_get_int(val) ? 1 : 0; + continue; + } + + if (strcmp(key, "report") == 0) { + process->report = json_object_get_int(val) ? 1 : 0; + continue; + } + + if (strcmp(key, "print_current") == 0) { + process->print_current = json_object_get_int(val) ? 1 : 0; + continue; + } + + if (strcmp(key, "sampling_rate") == 0) { + process->sampling_rate = json_object_get_double(val); + continue; + } + + if (strcmp(key, "apply_to_children") == 0) { + process->apply_to_children = json_object_get_int(val) ? 1 : 0; + continue; + } + + for (int i = 0; i < data_sources_get_n(); i++) { + ds = &(process->data_sources[i]); + if (ds->apply_config(ds, key, val) == 0) + break; + } + } +} + +int config_read_per_process(struct proc_info *process) +{ + const char *class_str = process_class_to_string(process->class); + struct json_object *obj; + + if (!config) { + _E("There is no config"); + return -EINVAL; + } + + /* Global */ + if (!json_object_object_get_ex(config, "global", &obj)) + _E("There is no 'global' section in config"); + else + _read_params(obj, process); + + /* Class related config */ + if (!json_object_object_get_ex(config, class_str, &obj)) + _W("There is no '%s' section in config", class_str); + else + _read_params(obj, process); + + /* Process by name */ + if (json_object_object_get_ex(config, process->name, &obj)) { + _read_params(obj, process); + } + + return 0; +} \ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..99b9d79 --- /dev/null +++ b/src/config.h @@ -0,0 +1,33 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#include +#include "process.h" + + +int config_is_empty(void); +struct json_object *config_get(void); +int config_load(const char *config_file); +int config_load_dir(const char *config_dir); +void config_print(void); +int config_read_per_process(struct proc_info *process); + +#endif /* CONFIG_H */ \ No newline at end of file diff --git a/src/data_source.c b/src/data_source.c new file mode 100644 index 0000000..e34ece6 --- /dev/null +++ b/src/data_source.c @@ -0,0 +1,124 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "list.h" +#include "log.h" + +#include "data_source.h" + +static struct data_source_mod **data_sources = NULL; +static int data_sources_n = 0; + + +int data_source_add_sample(struct data_source *ds, enum sample_type type, + void *value, unsigned long long measure_time) +{ + struct sample_s *last_sample, *sample, *prev; + struct smpl_container *sc = process_is_foreground(ds->process) ? &ds->data_fg : &ds->data_bg; + + last_sample = sample_add(type, value, &sc->samples, measure_time); + if (!last_sample){ + _E("Unable to add sample"); + return -ENOMEM; + } + + if (last_sample->type == ST_INT) + sc->average = (sc->average * sc->samples_n + last_sample->value.i) / (sc->samples_n + 1); + else if (last_sample->type == ST_DOUBLE) + sc->average = (sc->average * sc->samples_n + last_sample->value.d) / (sc->samples_n + 1); + else if (last_sample->type == ST_ULL) + sc->average = (sc->average * sc->samples_n + last_sample->value.ull) / (sc->samples_n + 1); + + sc->samples_n++; + + /* Sample retency */ + + if (ds->limit.avg_period == 0) { + /* Don't count average, leave only last sample */ + + sample_remove_all_but_n(1, &sc->samples); + sc->average = 0; + sc->samples_n = 0; + + } else { + /* Remove old samples, no longer needed */ + list_for_each_entry_reverse_safe(sample, prev, &sc->samples, node) { + if ((long long) sample->measure_time >= (long long) (last_sample->measure_time - (USEC_PER_SEC * ds->limit.avg_period))) + break; + + assert(sc->samples_n > 1); + + /* Recalculate average */ + if (sample->type == ST_INT) + sc->average = (sc->average * sc->samples_n - sample->value.i) / (sc->samples_n - 1); + else if (sample->type == ST_DOUBLE) + sc->average = (sc->average * sc->samples_n - sample->value.d) / (sc->samples_n - 1); + else if (sample->type == ST_ULL) + sc->average = (sc->average * sc->samples_n - sample->value.ull) / (sc->samples_n - 1); + + sc->samples_n--; + + list_del(&sample->node); + free(sample); + } + } + + return 0; +} + +int data_source_register(struct data_source_mod *mod) +{ + if (!mod->name) { + _E("Data source name must not be empty"); + goto error; + } + + data_sources = realloc(data_sources, (data_sources_n + 1) * sizeof(struct data_source_mod *)); + if (!data_sources) { + _E("Unable to allocate memory"); + goto error; + } + + data_sources[data_sources_n++] = mod; + _D("Registered data source: %s\n", mod->name); + + return 0; + +error: + _E("Unable to register data source: %s", mod->name); + return -1; +} + +void data_source_unregister(struct data_source_mod *mod) +{ + if (--data_sources_n == 0) + free(data_sources); +} + +struct data_source_mod **data_sources_get(void) +{ + return data_sources; +} + +int data_sources_get_n(void) +{ + return data_sources_n; +} diff --git a/src/data_source.h b/src/data_source.h new file mode 100644 index 0000000..be9a323 --- /dev/null +++ b/src/data_source.h @@ -0,0 +1,82 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DATA_SOURCE_H +#define DATA_SOURCE_H + +#include + +#include "list.h" +#include "limit.h" +#include "process.h" +#include "sample.h" +#include "raw_data.h" + +struct smpl_container { + struct list_head samples; + int samples_n; + double average; +}; + +struct data_source { + char *name; + char *param_name; + + struct limit_s limit; + struct smpl_container data_fg; + struct smpl_container data_bg; + void *extra_data; + + int (*update)(struct data_source *ds, + struct raw_data_item *rd_global, + struct raw_data_item *rd_pid); + int (*apply_config)(struct data_source *ds, + char *key, struct json_object *val); + int (*action)(struct data_source *ds, enum limit_type lt); + void (*cleanup)(struct data_source *ds); + + struct proc_info *process; +}; + +struct data_source_mod { + char *name; + + int (*init)(struct data_source *ds); +}; + +int data_source_add_sample(struct data_source *ds, enum sample_type type, + void *value, unsigned long long measure_time); + +int data_source_register(struct data_source_mod *mod); +void data_source_unregister(struct data_source_mod *mod); + +struct data_source_mod **data_sources_get(void); +int data_sources_get_n(void); + +#define DATA_SOURCE_REGISTER(mod) \ + static void __attribute__((constructor)) _ds_register(void) \ + { \ + data_source_register(mod); \ + } \ + \ + static void __attribute__((destructor)) _ds_unregister(void) \ + { \ + data_source_unregister(mod); \ + } + +#endif /* DATA_SOURCE_H */ \ No newline at end of file diff --git a/src/data_sources/tsm_cpu.c b/src/data_sources/tsm_cpu.c new file mode 100644 index 0000000..528a218 --- /dev/null +++ b/src/data_sources/tsm_cpu.c @@ -0,0 +1,138 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "log.h" +#include "data_source.h" +#include "utils.h" +#include "raw_data.h" +#include "limit.h" +#include "action.h" +#include "raw_data_providers/proc_tsm.h" + +#define TSM_CPU_MODULE_NAME "tsm_cpu" +#define TSM_CPU_PARAM_NAME "cpu.relative" + + +struct cpu_extra_data { + unsigned long long cpu_time, cpu_time_last; + unsigned long long measure_time, measure_time_last; + int next_run; +}; + +static int update(struct data_source *ds, + struct raw_data_item *rd_global, + struct raw_data_item *rd_pid) +{ + struct cpu_extra_data *ced = (struct cpu_extra_data *) ds->extra_data; + double cpu_usage; + int ret; + + ced->cpu_time_last = ced->cpu_time; + ced->measure_time_last = ced->measure_time; + + ced->cpu_time = rd_pid->v[ITEM_CPU]; + ced->measure_time = rd_global->v[ITEM_UPTIME]; + + if (!ced->next_run) { + /* + * End here if this is the first run of this function, + * because we need at least two values of cpu_time to calculate the cpu usage + */ + ced->next_run = 1; + return 0; + } + + cpu_usage = (double) (ced->cpu_time - ced->cpu_time_last) / (ced->measure_time - ced->measure_time_last); + + ret = data_source_add_sample(ds, ST_DOUBLE, &cpu_usage, rd_global->v[0]); + if (ret) { + _E("Unable to add sample: %d", ret); + return ret; + } + + return 0; +} + +static int apply_config(struct data_source *ds, char *key, struct json_object *val) +{ + if (strcmp(key, "cpu_limit_avg") == 0) { + ds->limit.avg = json_object_get_double(val); + return 0; + } + + if (strcmp(key, "cpu_avg_period") == 0) { + ds->limit.avg_period = json_object_get_int(val); + return 0; + } + + if (strcmp(key, "cpu_limit_peak") == 0) { + ds->limit.peak.value.d = json_object_get_double(val); + ds->limit.peak.type = ST_DOUBLE; + return 0; + } + + return -1; +} + +static int action(struct data_source *ds, enum limit_type lt) +{ + return action_run_default(ds, lt); +} + +static void cleanup(struct data_source *ds) +{ + struct cpu_extra_data *ced = (struct cpu_extra_data*) ds->extra_data; + + free(ced); +} + +static int init(struct data_source *ds) +{ + struct cpu_extra_data *ced; + + ds->name = TSM_CPU_MODULE_NAME; + ds->param_name = TSM_CPU_PARAM_NAME; + + ds->update = update; + ds->apply_config = apply_config; + ds->action = action; + ds->cleanup = cleanup; + + /* Extra data - for cpu usage calculation */ + ced = calloc(1, sizeof(*ced)); + if (!ced) { + _E("Unable to allocate memory"); + return -ENOMEM; + } + + ds->extra_data = ced; + + return 0; +} + +struct data_source_mod cpu_module = { + .name = TSM_CPU_MODULE_NAME, + .init = init, +}; + +DATA_SOURCE_REGISTER(&cpu_module); diff --git a/src/data_sources/tsm_fd.c b/src/data_sources/tsm_fd.c new file mode 100644 index 0000000..665b587 --- /dev/null +++ b/src/data_sources/tsm_fd.c @@ -0,0 +1,84 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "log.h" +#include "data_source.h" +#include "utils.h" +#include "raw_data.h" +#include "limit.h" +#include "action.h" +#include "raw_data_providers/proc_tsm.h" + +#define TSM_FD_MODULE_NAME "tsm_fd" +#define TSM_FD_PARAM_NAME "fd.absolute" + + +static int update(struct data_source *ds, + struct raw_data_item *rd_global, + struct raw_data_item *rd_pid) +{ + int ret; + + ret = data_source_add_sample(ds, ST_INT, &rd_pid->v[ITEM_FDS], rd_global->v[ITEM_UPTIME]); + if (ret) { + _E("Unable to add sample: %d", ret); + return ret; + } + + return 0; +} + +static int apply_config(struct data_source *ds, char *key, struct json_object *val) +{ + if (strcmp(key, "fd_limit") == 0) { + ds->limit.peak.value.i = json_object_get_int(val); + ds->limit.peak.type = ST_INT; + return 0; + } + + return -1; +} + +static int action(struct data_source *ds, enum limit_type lt) +{ + return action_run_default(ds, lt); +} + +static int init(struct data_source *ds) +{ + ds->name = TSM_FD_MODULE_NAME; + ds->param_name = TSM_FD_PARAM_NAME; + + ds->update = update; + ds->apply_config = apply_config; + ds->action = action; + ds->cleanup = NULL; + + return 0; +} + +struct data_source_mod fd_module = { + .name = TSM_FD_MODULE_NAME, + .init = init, +}; + +DATA_SOURCE_REGISTER(&fd_module); diff --git a/src/data_sources/tsm_io.c b/src/data_sources/tsm_io.c new file mode 100644 index 0000000..827add6 --- /dev/null +++ b/src/data_sources/tsm_io.c @@ -0,0 +1,135 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "log.h" +#include "data_source.h" +#include "utils.h" +#include "raw_data.h" +#include "limit.h" +#include "action.h" +#include "raw_data_providers/proc_tsm.h" + +#define TSM_IO_MODULE_NAME "tsm_io" +#define TSM_IO_PARAM_NAME "io.absolute" + + +struct io_extra_data { + unsigned long long io_bytes, io_bytes_last; + unsigned long long measure_time, measure_time_last; + int next_run; +}; + +static int update(struct data_source *ds, + struct raw_data_item *rd_global, + struct raw_data_item *rd_pid) +{ + struct io_extra_data *ied = (struct io_extra_data *) ds->extra_data; + double io_rate; + int ret; + + ied->io_bytes_last = ied->io_bytes; + ied->measure_time_last = ied->measure_time; + + ied->io_bytes = rd_pid->v[ITEM_IO]; + ied->measure_time = rd_global->v[ITEM_UPTIME]; + + if (!ied->next_run) { + ied->next_run = 1; + return 0; + } + + io_rate = (double) (ied->io_bytes - ied->io_bytes_last) / + (ied->measure_time - ied->measure_time_last); + + ret = data_source_add_sample(ds, ST_DOUBLE, &io_rate, rd_global->v[0]); + if (ret) { + _E("Unable to add sample: %d", ret); + return ret; + } + + return 0; +} + +static int apply_config(struct data_source *ds, char *key, struct json_object *val) +{ + if (strcmp(key, "io_limit_avg") == 0) { + ds->limit.avg = json_object_get_double(val); + return 0; + } + + if (strcmp(key, "io_avg_period") == 0) { + ds->limit.avg_period = json_object_get_int(val); + return 0; + } + + if (strcmp(key, "io_limit_peak") == 0) { + ds->limit.peak.value.d = json_object_get_double(val); + ds->limit.peak.type = ST_DOUBLE; + return 0; + } + + return -1; +} + +static int action(struct data_source *ds, enum limit_type lt) +{ + return action_run_default(ds, lt); +} + +static void cleanup(struct data_source *ds) +{ + struct io_extra_data *ied = (struct io_extra_data*) ds->extra_data; + + free(ied); +} + +static int init(struct data_source *ds) +{ + struct io_extra_data *ied; + + ds->name = TSM_IO_MODULE_NAME; + ds->param_name = TSM_IO_PARAM_NAME; + + ds->update = update; + ds->apply_config = apply_config; + ds->action = action; + ds->cleanup = cleanup; + + /* Extra data - for io_rate calculation */ + ied = calloc(1, sizeof(*ied)); + if (!ied) { + _E("Unable to allocate memory"); + return -ENOMEM; + } + + ds->extra_data = ied; + + return 0; +} + +struct data_source_mod io_module = { + .name = TSM_IO_MODULE_NAME, + .init = init, +}; + +DATA_SOURCE_REGISTER(&io_module); diff --git a/src/data_sources/tsm_mem.c b/src/data_sources/tsm_mem.c new file mode 100644 index 0000000..287d23f --- /dev/null +++ b/src/data_sources/tsm_mem.c @@ -0,0 +1,97 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "log.h" +#include "data_source.h" +#include "utils.h" +#include "raw_data.h" +#include "limit.h" +#include "action.h" +#include "raw_data_providers/proc_tsm.h" + +#define TSM_MEM_MODULE_NAME "tsm_mem" +#define TSM_MEM_PARAM_NAME "mem.relative" + + +static int update(struct data_source *ds, + struct raw_data_item *rd_global, + struct raw_data_item *rd_pid) +{ + double mem_usage; + int ret; + + mem_usage = (double) rd_pid->v[ITEM_RSS] / rd_global->v[ITEM_TOTAL_MEM]; + + ret = data_source_add_sample(ds, ST_DOUBLE, &mem_usage, rd_global->v[ITEM_UPTIME]); + if (ret) { + _E("Unable to add sample: %d", ret); + return ret; + } + + return 0; +} + +static int apply_config(struct data_source *ds, char *key, struct json_object *val) +{ + if (strcmp(key, "mem_limit_avg") == 0) { + ds->limit.avg = json_object_get_double(val); + return 0; + } + + if (strcmp(key, "mem_avg_period") == 0) { + ds->limit.avg_period = json_object_get_int(val); + return 0; + } + + if (strcmp(key, "mem_limit_peak") == 0) { + ds->limit.peak.value.d = json_object_get_double(val); + ds->limit.peak.type = ST_DOUBLE; + return 0; + } + + return -1; +} + +static int action(struct data_source *ds, enum limit_type lt) +{ + return action_run_default(ds, lt); +} + +static int init(struct data_source *ds) +{ + ds->name = TSM_MEM_MODULE_NAME; + ds->param_name = TSM_MEM_PARAM_NAME; + + ds->update = update; + ds->apply_config = apply_config; + ds->action = action; + ds->cleanup = NULL; + + return 0; +} + +struct data_source_mod mem_module = { + .name = TSM_MEM_MODULE_NAME, + .init = init, +}; + +DATA_SOURCE_REGISTER(&mem_module); diff --git a/src/dbus.c b/src/dbus.c new file mode 100644 index 0000000..4eb0036 --- /dev/null +++ b/src/dbus.c @@ -0,0 +1,117 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "log.h" +#include "dbus.h" + +static GDBusConnection *system_bus = NULL; + + +static int dbus_connect() +{ + GError *err = NULL; + + if (system_bus) + return 0; + + system_bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &err); + if (!system_bus) { + _E("Unable to connect to system bus: %s", err->message); + g_error_free(err); + return -1; + } + + return 0; +} + +int dbus_send_signal(const char *object_path, + const char *interface_name, + const char *signal_name, + GVariant *parameters) +{ + GError *err = NULL; + gchar *parameters_str = g_variant_print(parameters, TRUE); + int ret; + + ret = dbus_connect(); + if (ret) + goto cleanup; + + ret = g_dbus_connection_emit_signal(system_bus, /* connection */ + NULL, /* destination_bus_name */ + object_path, /* object_path */ + interface_name, /* interface_name */ + signal_name, /* signal_name */ + parameters, /* parameters */ + &err); /* error */ + if (ret == FALSE) { + _E("Unable to emit dbus signal: %s", err->message); + g_error_free(err); + ret = -1; + goto cleanup; + } + + _DD("Emitted signal\n" + " objpath: %s\n" + " interface: %s\n" + " member: %s\n" + " parameters: %s\n", + object_path, interface_name, signal_name, parameters_str); + +cleanup: + g_free(parameters_str); + return ret; +} + +int dbus_subscribe(const char *object_path, + const char *interface_name, + const char *signal_name, + void (*handler)(GDBusConnection *, + const gchar *, + const gchar *, + const gchar *, + const gchar *, + GVariant *, + gpointer)) +{ + int ret; + + ret = dbus_connect(); + if (ret) + return ret; + + ret = g_dbus_connection_signal_subscribe(system_bus, + NULL, + interface_name, + signal_name, + object_path, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + handler, + NULL, + NULL); + if (ret == FALSE) { + _E("g_dbus_connection_signal_subscribe() failed"); + return -1; + } + + return 0; +} \ No newline at end of file diff --git a/src/dbus.h b/src/dbus.h new file mode 100644 index 0000000..23969a9 --- /dev/null +++ b/src/dbus.h @@ -0,0 +1,43 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DBUS_H +#define DBUS_H + +#include +#include + +#define TSM_DBUS_PATH "/Org/Tizen/StabilityMonitor" +#define TSM_DBUS_INTERFACE "org.tizen.abnormality" + +int dbus_send_signal(const char *object_path, + const char *interface_name, + const char *signal_name, + GVariant *parameters); + +int dbus_subscribe(const char *object_path, + const char *interface_name, + const char *signal_name, + void (*handler)(GDBusConnection *, + const gchar *, + const gchar *, + const gchar *, + const gchar *, + GVariant *, + gpointer)); +#endif /* DBUS_H */ diff --git a/src/limit.c b/src/limit.c new file mode 100644 index 0000000..d0a11f4 --- /dev/null +++ b/src/limit.c @@ -0,0 +1,37 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "limit.h" +#include "log.h" + + +const char *limit_type_to_string(enum limit_type lt) +{ + assert(lt == LIMIT_TYPE_PEAK || lt == LIMIT_TYPE_AVG); + + switch (lt) { + case LIMIT_TYPE_PEAK: + return "peak"; + case LIMIT_TYPE_AVG: + return "avg"; + } + + return NULL; +} diff --git a/src/limit.h b/src/limit.h new file mode 100644 index 0000000..de44e79 --- /dev/null +++ b/src/limit.h @@ -0,0 +1,38 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIMIT_H +#define LIMIT_H + +#include "list.h" +#include "sample.h" + +enum limit_type { + LIMIT_TYPE_PEAK, + LIMIT_TYPE_AVG, +}; + +struct limit_s { + double avg; + int avg_period; /* seconds */ + struct sample_s peak; +}; + +const char *limit_type_to_string(enum limit_type); + +#endif /* LIMIT_H */ \ No newline at end of file diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..861ee30 --- /dev/null +++ b/src/list.h @@ -0,0 +1,158 @@ +/* + * This file is based on list.h from faultd. + * https://review.tizen.org/gerrit/a/platform/core/system/faultd.git + * + * Copyright © 2017,2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIST_H +#define LIST_H + + +#ifndef offsetof +#define offsetof(type, member) __builtin_offsetof(type, member) +#endif /* offsetof */ + +#ifndef container_of +#define container_of(ptr, type, field) ({ \ + const typeof(((type *)0)->field) *member = (ptr); \ + (type *)((char *)member - offsetof(type, field)); \ +}) +#endif /* container_of */ + + +/* + * Simple doubly linked list based on list from linux kernel + */ +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +static inline void INIT_LIST_HEAD(struct list_head *head) +{ + head->next = head; + head->prev = head; +} + +static inline void list_add_helper(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + new->prev = prev; + new->next = next; + next->prev = new; + prev->next = new; +} + +/* + * Add a new entry after the specified head + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + list_add_helper(new, head, head->next); +} + +/* + * Add a new entry before the specified head + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + list_add_helper(new, head->prev, head); +} + +/* + * Remove given element from the list + */ +static inline void list_del(struct list_head *entry) +{ + entry->next->prev = entry->prev; + entry->prev->next = entry->next; +} + +/* + * Remove given element from the list and reinitialize it + */ +static inline void list_del_init(struct list_head *entry) +{ + list_del(entry); + INIT_LIST_HEAD(entry); +} + +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +static inline int list_is_singular(const struct list_head *head) +{ + return (!list_empty(head) && head->prev == head->next); +} + +#define list_entry(ptr, type, member) \ + container_of((ptr), type, member) + +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +#define list_first_entry_or_null(ptr, type, member) \ + (list_empty(ptr) ? NULL : list_first_entry((ptr), type, member)) + +#define list_last_entry(ptr, type, member) \ + list_entry((ptr)->prev, type, member) + +#define list_next_entry(pos, member) \ + list_entry((pos)->member.next, typeof(*pos), member) + +#define list_prev_entry(pos, member) \ + list_entry((pos)->member.prev, typeof(*pos), member) + +#define list_for_each(pos, head) for ( \ + (pos) = (head)->next; \ + (pos) != (head); \ + (pos) = (pos)->next) + +#define list_for_each_safe(pos, n, head) for ( \ + (pos) = (head)->next, (n) = (pos)->next; \ + (pos) != (head); \ + (pos) = (n), (n) = (pos)->next) + +#define list_for_each_entry(pos, head, member) for ( \ + (pos) = list_first_entry((head), typeof(*pos), member); \ + &(pos)->member != (head); \ + (pos) = list_next_entry((pos), member)) + +#define list_for_each_entry_reverse(pos, head, member) for ( \ + (pos) = list_last_entry((head), typeof(*pos), member); \ + &(pos)->member != (head); \ + (pos) = list_prev_entry((pos), member)) + +#define list_for_each_entry_safe(pos, n, head, member) for ( \ + (pos) = list_first_entry((head), typeof(*pos), member), \ + (n) = list_next_entry((pos), member); \ + &(pos)->member != (head); \ + (pos) = (n), \ + (n) = list_next_entry((pos), member)) + +#define list_for_each_entry_reverse_safe(pos, n, head, member) for ( \ + (pos) = list_last_entry((head), typeof(*pos), member), \ + (n) = list_prev_entry((pos), member); \ + &(pos)->member != (head); \ + (pos) = (n), \ + (n) = list_prev_entry((pos), member)) + +#endif /* LIST_H */ diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..6e256af --- /dev/null +++ b/src/log.c @@ -0,0 +1,13 @@ +#include "log.h" + +static unsigned long long msg_count = 0; + +void log_inc_msg_count() +{ + msg_count++; +} + +unsigned long long log_get_msg_count() +{ + return msg_count; +} \ No newline at end of file diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..016245b --- /dev/null +++ b/src/log.h @@ -0,0 +1,57 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LOG_H +#define LOG_H + +#include + +#include "utils.h" +#include "process.h" + +#define print(fmt, arg...) \ + printf(fmt, ##arg); \ + log_inc_msg_count() + +#define _E(fmt, arg...) do { \ + print("ERROR: %s::%s(%d): " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ##arg); \ +} while (0) + +#define _W(fmt, arg...) do { \ + print("WARN: %s::%s(%d): " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ##arg); \ +} while (0) + +#define _D(fmt, arg...) do { \ + print(fmt, ##arg); \ +} while (0) + +#define _DD(fmt, arg...) do { \ + /* TODO */ \ +} while (0) + +#define _D_PROC(process, fmt, arg...) \ + _D("(%d)\t[%-25.25s | %-21s ] " fmt "\n", \ + process->pid, \ + process->name, \ + process_class_to_string(process->class), \ + ##arg) + +void log_inc_msg_count(); +unsigned long long log_get_msg_count(); + +#endif /* LOG_H */ diff --git a/src/process.c b/src/process.c new file mode 100644 index 0000000..a9c5bdb --- /dev/null +++ b/src/process.c @@ -0,0 +1,548 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "config.h" +#include "log.h" +#include "sample.h" +#include "dbus.h" +#include "data_source.h" + +#include "process.h" + +#define HASH_TABLE_SIZE 200 + +/* List of all processes and their current state */ +static struct list_head process_table[HASH_TABLE_SIZE]; + + +static int hash(int pid) +{ + return pid % HASH_TABLE_SIZE; +} + +void process_table_init() +{ + for (int i = 0; i < HASH_TABLE_SIZE; i++) + INIT_LIST_HEAD(&process_table[i]); +} + +static void process_table_add(struct proc_info *process) +{ + list_add(&process->node, &process_table[hash(process->pid)]); +} + +int process_create_info(int pid, int ppid, struct proc_info **process) +{ + struct proc_info *p; + struct data_source_mod *ds_mod; + struct data_source *ds; + int ret; + + p = calloc(1, sizeof(struct proc_info)); + if (!p) { + _E("Unable to allocate memory"); + return -ENOMEM; + } + + /* Init and set default values in case of not being specified in config */ + p->pid = pid; + p->ppid = ppid; + p->parent = process_get_by_pid(ppid); + p->name = NULL; + p->appid = NULL; + p->apptype = NULL; + p->component = -1; + p->status = -1; + + p->monitor = 1; + p->kill = 0; + p->print_current = 0; + p->sampling_rate = 1; + p->apply_to_children = 0; + + /* Create data sources */ + p->data_sources = calloc(data_sources_get_n(), sizeof(struct data_source)); + if (!p->data_sources) { + _E("Unable to allocate memory"); + return -ENOMEM; + } + + for (int i = 0; i < data_sources_get_n(); i++) { + ds_mod = data_sources_get()[i]; + ds = &(p->data_sources[i]); + + ds->process = p; + INIT_LIST_HEAD(&ds->data_fg.samples); + INIT_LIST_HEAD(&ds->data_bg.samples); + + ret = ds_mod->init(ds); + if (ret) { + _E("Unable to initialize new %s data source: %d", ds_mod->name, ret); + return ret; + } + } + + INIT_LIST_HEAD(&p->node); + + ret = process_update_info(p, NULL); + if (ret) + return ret; + + process_table_add(p); + *process = p; + + if (p->monitor) + _D_PROC(p, "Created process"); + + return 0; +} + +static int process_get_appid(struct proc_info *process) +{ + char buf[NAME_MAX]; + int ret; + + free(process->appid); + process->appid = NULL; + + ret = aul_app_get_appid_bypid(process->pid, buf, sizeof(buf)); + if (ret != AUL_R_OK) { + /* process is not an application */ + process->appid = NULL; + return -1; + } + + process->appid = strndup(buf, NAME_MAX); + if (!process->appid) { + _E("Unable to allocate memory"); + return -ENOMEM; + } + + return 0; +} + +static int process_get_exe(struct proc_info *process) +{ + char buf[NAME_MAX]; + int ret; + + free(process->exe); + process->exe = NULL; + + ret = pid_to_basename(process->pid, buf); + if (ret) { + _E("Unable to get process basename: %d", ret); + return -1; + } + + process->exe = strndup(buf, NAME_MAX); + if (!process->exe) { + _E("Unable to allocate memory"); + return -ENOMEM; + } + + return 0; +} + +static int process_get_component(struct proc_info *process) +{ + pkgmgrinfo_appinfo_h handle; + pkgmgrinfo_app_component component; + int ret; + + process->component = -1; + + ret = pkgmgrinfo_appinfo_get_appinfo(process->appid, &handle); + if (ret != PMINFO_R_OK) { + _E("pkgmgrinfo_appinfo_get_appinfo() failed: %d", ret); + return -1; + } + + ret = pkgmgrinfo_appinfo_get_component(handle, &component); + if (ret != PMINFO_R_OK) { + _E("pkgmgrinfo_appinfo_get_component() failed: %d", ret); + pkgmgrinfo_appinfo_destroy_appinfo(handle); + return -1; + } + + process->component = component; + + pkgmgrinfo_appinfo_destroy_appinfo(handle); + return 0; +} + +static int process_get_apptype(struct proc_info *process) +{ + pkgmgrinfo_appinfo_h handle; + char *apptype = NULL; + int ret; + + if (process->apptype) { + free(process->apptype); + process->apptype = NULL; + } + + ret = pkgmgrinfo_appinfo_get_appinfo(process->appid, &handle); + if (ret != PMINFO_R_OK) { + _E("pkgmgrinfo_appinfo_get_appinfo() failed: %d", ret); + return -1; + } + + ret = pkgmgrinfo_appinfo_get_apptype(handle, &apptype); + if (ret != PMINFO_R_OK) { + _E("pkgmgrinfo_appinfo_get_apptype() failed: %d", ret); + pkgmgrinfo_appinfo_destroy_appinfo(handle); + return -1; + } + + process->apptype = strdup(apptype); + if (!process->apptype) { + _E("Unable to allocate memory"); + return -ENOMEM; + } + + pkgmgrinfo_appinfo_destroy_appinfo(handle); + return 0; +} + +int process_is_foreground(struct proc_info *process) +{ + return (process->status == STATUS_FOCUS || + process->status == STATUS_VISIBLE || + process->status == STATUS_HOME); +} + +static void process_classify(struct proc_info *process) +{ + int webapp, dotnet; + + if (process->component == PMINFO_UI_APP) { + webapp = (0 == strcmp(process->apptype, "webapp")); + dotnet = (0 == strcmp(process->apptype, "dotnet")); + + if (process_is_foreground(process)) { + if (webapp) + process->class = PROC_CLASS_FG_WEB_APP; + else if (dotnet) + process->class = PROC_CLASS_FG_CSHARP_APP; + else + process->class = PROC_CLASS_FG_NATIVE_APP; + } else { + if (webapp) + process->class = PROC_CLASS_BG_WEB_APP; + else if (dotnet) + process->class = PROC_CLASS_BG_CSHARP_APP; + else + process->class = PROC_CLASS_BG_NATIVE_APP; + } + + return; + } + + if (process->component == PMINFO_WIDGET_APP) { + if (process_is_foreground(process)) + process->class = PROC_CLASS_FG_UI_WIDGET; + else + process->class = PROC_CLASS_BG_UI_WIDGET; + return; + } + + if (process->component == PMINFO_SVC_APP) { + process->class = PROC_CLASS_NATIVE_SERVICE; + return; + } + + process->class = PROC_CLASS_OTHER_SERVICE; +} + +int process_update_info(struct proc_info *process, int *status) +{ + int ret; + + if (!process->monitor) + return 0; + + ret = process_get_appid(process); + if (ret == -ENOMEM) { + goto cleanup; + } + + ret = process_get_exe(process); + if (ret) { + goto cleanup; + } + + if (process->appid) { + process->name = process->appid; + + ret = process_get_apptype(process); + if (ret) { + _E("Unable to get process' app type"); + goto cleanup; + } + + ret = process_get_component(process); + if (ret) { + _E("Unable to get process' component type"); + goto cleanup; + } + + } else { + process->name = process->exe; + } + + process->status = status ? *status : aul_app_get_status_bypid(process->pid); + + process_classify(process); + + if (process->parent && process->parent->apply_to_children) { + process_copy_settings(process->parent, process); + } else { + ret = config_read_per_process(process); + if (ret) { + _E("Unable to read limits for process"); + goto cleanup; + } + } + + return 0; + +cleanup: + process_cleanup(process); + return ret; +} + +void process_copy_settings(struct proc_info *src, struct proc_info *dst) +{ + dst->monitor = src->monitor; + dst->kill = src->kill; + dst->sampling_rate = src->sampling_rate; + dst->apply_to_children = src->apply_to_children; + + for (int i = 0; i < data_sources_get_n(); i++) { + dst->data_sources[i].limit = src->data_sources[i].limit; + } +} + +struct proc_info *process_get_by_pid(int pid) +{ + struct proc_info *process; + int h = hash(pid); + + list_for_each_entry(process, &process_table[h], node) + if (process->pid == pid) + return process; + + return NULL; +} + +const char *process_class_to_string(int class) +{ + switch (class) { + case PROC_CLASS_FG_NATIVE_APP: + return "foreground_native_app"; + case PROC_CLASS_BG_NATIVE_APP: + return "background_native_app"; + case PROC_CLASS_FG_WEB_APP: + return "foreground_web_app"; + case PROC_CLASS_BG_WEB_APP: + return "background_web_app"; + case PROC_CLASS_FG_CSHARP_APP: + return "foreground_csharp_app"; + case PROC_CLASS_BG_CSHARP_APP: + return "background_csharp_app"; + case PROC_CLASS_FG_UI_WIDGET: + return "foreground_ui_widget"; + case PROC_CLASS_BG_UI_WIDGET: + return "background_ui_widget"; + case PROC_CLASS_NATIVE_SERVICE: + return "native_service"; + case PROC_CLASS_OTHER_SERVICE: + return "other_service"; + default: + return "unknown class"; + } +} + +void process_check_limits(struct proc_info *process) +{ + struct data_source *ds; + struct smpl_container *sc; + struct sample_s *last_sample; + + for (int i = 0; i < data_sources_get_n(); i++) { + ds = &(process->data_sources[i]); + + sc = process_is_foreground(ds->process) ? &ds->data_fg : &ds->data_bg; + if (list_empty(&sc->samples)) + continue; + + last_sample = list_first_entry(&sc->samples, struct sample_s, node); + + /* Peak limit */ + if (sample_is_greater(last_sample, &ds->limit.peak)) { + _D_PROC(process, "%s: PEAK limit exceeded (%s)", + ds->name, smpl2str(last_sample)); + + ds->action(ds, LIMIT_TYPE_PEAK); + } + + /* Avg limit */ + if (sc->average > ds->limit.avg) { + _D_PROC(process, "%s: AVG limit exceeded (%2.3lf)", + ds->name, sc->average); + + ds->action(ds, LIMIT_TYPE_AVG); + } + } +} + +static void _print_header(struct proc_info *process) +{ + struct data_source *ds; + int ds_n = data_sources_get_n(); + int header_lines = 2; + gchar **header_items = g_new(gchar *, (ds_n + 1) * header_lines); + gchar *str = NULL; + + for (int i = 0; i < ds_n; i++) { + ds = &(process->data_sources[i]); + header_items[(ds_n + 1) * 0 + i] = g_strdup_printf("%s%-20s", i > 0 ? " | " : "", ds->param_name); + header_items[(ds_n + 1) * 1 + i] = g_strdup_printf("%s%-10s%-10s", i > 0 ? " | " : "", "peak ---- ", "avg ------"); + } + + /* Print the header line by line */ + for (int i = 0; i < header_lines; i++) { + header_items[(ds_n + 1) * i + ds_n] = NULL; + str = g_strjoinv(NULL, &header_items[(ds_n + 1) * i]); + _D_PROC(process, "%s", str); + g_free(str); + } + + g_strfreev(header_items); +} + +void process_print_current_state(struct proc_info *process) +{ + struct data_source *ds; + struct smpl_container *sc; + struct sample_s *last_sample; + int ds_n = data_sources_get_n(); + gchar **state_items = g_new(gchar *, ds_n); + gchar *avg_item; + gchar *state_msg = NULL; + static unsigned long long last_msg_count = 0; + + /* Print header every n log messages to keep it always visible */ + if (log_get_msg_count() - last_msg_count > STATE_HEADER_EVERY_N_MSG) { + last_msg_count = log_get_msg_count(); + _print_header(process); + } + + for (int i = 0; i < ds_n; i++) { + ds = &(process->data_sources[i]); + sc = process_is_foreground(ds->process) ? &ds->data_fg : &ds->data_bg; + + last_sample = list_empty(&sc->samples) ? + NULL : list_first_entry(&sc->samples, struct sample_s, node); + + avg_item = g_strdup_printf("%2.3lf", sc->average); + state_items[i] = g_strdup_printf("%s%-10s%-10s", + i > 0 ? " | " : "", + last_sample ? smpl2str(last_sample) : "n/a", + ds->limit.avg_period > 0 ? avg_item : "n/a"); + g_free(avg_item); + } + + state_items[ds_n] = NULL; + + state_msg = g_strjoinv(NULL, state_items); + _D_PROC(process, "%s", state_msg); + + g_strfreev(state_items); + g_free(state_msg); +} + +void process_remove_deads(void) +{ + struct proc_info *process, *next; + + for (int i = 0; i < HASH_TABLE_SIZE; i++) { + list_for_each_entry_safe(process, next, &process_table[i], node) { + if (!process->still_alive && !process->action_in_progress) { + process_cleanup(process); + continue; + } + + process->still_alive = 0; + } + } +} + +void process_cleanup(struct proc_info *process) +{ + struct data_source *ds; + struct sample_s *sample, *sample_next; + + if (process->monitor) + _D_PROC(process, "Removed process"); + + free(process->exe); + free(process->appid); + free(process->apptype); + + /* Remove data sources */ + for (int i = 0; i < data_sources_get_n(); i++) { + ds = &(process->data_sources[i]); + + /* Remove samples */ + list_for_each_entry_safe(sample, sample_next, &ds->data_fg.samples, node) { + list_del(&sample->node); + free(sample); + } + list_for_each_entry_safe(sample, sample_next, &ds->data_bg.samples, node) { + list_del(&sample->node); + free(sample); + } + + if (ds->cleanup) + ds->cleanup(ds); + } + free(process->data_sources); + + list_del(&process->node); + free(process); +} + +void process_cleanup_all(void) +{ + struct proc_info *process, *next; + + for (int i = 0; i < HASH_TABLE_SIZE; i++) { + list_for_each_entry_safe(process, next, &process_table[i], node) + process_cleanup(process); + } +} diff --git a/src/process.h b/src/process.h new file mode 100644 index 0000000..a0a160c --- /dev/null +++ b/src/process.h @@ -0,0 +1,85 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PROCESS_H +#define PROCESS_H + +#include + +#include "list.h" +#include "limit.h" + +#define STATE_HEADER_EVERY_N_MSG 20 + +enum proc_class { + PROC_CLASS_FG_NATIVE_APP, + PROC_CLASS_BG_NATIVE_APP, + PROC_CLASS_FG_WEB_APP, + PROC_CLASS_BG_WEB_APP, + PROC_CLASS_FG_CSHARP_APP, + PROC_CLASS_BG_CSHARP_APP, + PROC_CLASS_FG_UI_WIDGET, + PROC_CLASS_BG_UI_WIDGET, + PROC_CLASS_NATIVE_SERVICE, + PROC_CLASS_OTHER_SERVICE, +}; + +/* Main structure storing current process state */ +struct proc_info { + int pid; + int ppid; /* pid of the parent */ + struct proc_info *parent; /* pointer to the parent */ + char* name; /* either exe or appid */ + char* exe; /* basename of /proc/pid/exe */ + char* appid; /* from aul */ + char* apptype; /* from pkgmgrinfo */ + int component; /* pkgmgrinfo_app_component */ + int status; /* aul_app_status */ + enum proc_class class; + + int still_alive; + int action_in_progress; + + int monitor; + int kill; + int report; + int print_current; + double sampling_rate; + unsigned long long last_update; + int apply_to_children; + + struct data_source *data_sources; + + struct list_head node; +}; + +void process_table_init(); +int process_create_info(int pid, int ppid, struct proc_info **process); +int process_update_info(struct proc_info *process, int *status); +void process_copy_settings(struct proc_info *src, struct proc_info *dst); +struct proc_info *process_get_by_pid(int pid); +int process_is_foreground(struct proc_info *process); +const char *process_class_to_string(int class); +void process_check_limits(struct proc_info *process); +void process_print_current_state(struct proc_info *process); +void process_remove_deads(void); +void process_cleanup(struct proc_info *process); +void process_cleanup_all(void); + + +#endif /* PROCESS_H */ \ No newline at end of file diff --git a/src/raw_data.c b/src/raw_data.c new file mode 100644 index 0000000..9e8d455 --- /dev/null +++ b/src/raw_data.c @@ -0,0 +1,65 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "list.h" +#include "log.h" + +#include "raw_data.h" + +static struct raw_data_provider *rdp = NULL; + +int raw_data_new_item(struct list_head *rd, struct raw_data_item **rd_i) +{ + *rd_i = calloc(1, sizeof(struct raw_data_item)); + if (!*rd_i) { + _E("Unable to allocate memory"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&(*rd_i)->node); + list_add_tail(&(*rd_i)->node, rd); + + return 0; +} + +void raw_data_cleanup(struct list_head *rd) +{ + struct raw_data_item *rd_i, *next; + + list_for_each_entry_safe(rd_i, next, rd, node) { + list_del(&rd_i->node); + free(rd_i); + } +} + +int raw_data_provider_register(struct raw_data_provider *mod) +{ + /* TODO: Check name */ + + rdp = mod; + _D("Registered raw data provider: %s\n", mod->name); + + return 0; +} + +struct raw_data_provider *raw_data_provider_get(void) +{ + return rdp; +} \ No newline at end of file diff --git a/src/raw_data.h b/src/raw_data.h new file mode 100644 index 0000000..e54f115 --- /dev/null +++ b/src/raw_data.h @@ -0,0 +1,52 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RAW_DATA_H +#define RAW_DATA_H + +#include "list.h" + +#define MAX_PARAMS_PER_LINE 10 + + +struct raw_data_item { + unsigned long long v[MAX_PARAMS_PER_LINE]; + struct list_head node; +}; + +struct raw_data_provider { + char *name; + + int (*init)(void); + int (*get_raw_data)(struct list_head *rd); + void (*cleanup)(void); +}; + +int raw_data_new_item(struct list_head *rd, struct raw_data_item **rd_i); +void raw_data_cleanup(struct list_head *rd); + +int raw_data_provider_register(struct raw_data_provider *mod); +struct raw_data_provider *raw_data_provider_get(void); + +#define RAW_DATA_PROVIDER_REGISTER(mod) \ + static void __attribute__((constructor)) _rdp_register(void) \ + { \ + raw_data_provider_register(mod); \ + } + +#endif /* RAW_DATA_H */ diff --git a/src/raw_data_providers/proc_tsm.c b/src/raw_data_providers/proc_tsm.c new file mode 100644 index 0000000..7656564 --- /dev/null +++ b/src/raw_data_providers/proc_tsm.c @@ -0,0 +1,127 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "raw_data.h" + +#define PROC_TSM_MODULE_NAME "proc_tsm" +#define PROC_TSM_PATH "/proc/tsm" +#define MAX_LINE 100 +#define finit_module(fd, params, flags) syscall(__NR_finit_module, fd, params, flags) + + +static int str_to_ullarray(char *str, unsigned long long *array) +{ + char *ptr = str; + int n = 0; + + while (*ptr != '\n' && *ptr != 0) + if (n < MAX_PARAMS_PER_LINE) { + array[n++] = strtoull(ptr, &ptr, 10); + } else { + _W("Reached maximum number of parameters per line (%d), discarding others", + MAX_PARAMS_PER_LINE); + break; + } + + return n; +} + +/* Read data prepared by kernel module */ +static int get_raw_data(struct list_head *rd) +{ + char buf[MAX_LINE] = {0,}; + struct raw_data_item *rd_i; + int ret; + FILE *fp; + + fp = fopen(PROC_TSM_PATH, "r"); + if (!fp) { + _E("Unable to open " PROC_TSM_PATH ": %m"); + return -EINVAL; + } + + while (fgets(buf, MAX_LINE, fp) != NULL) { + ret = raw_data_new_item(rd, &rd_i); + if (ret) + goto error; + + ret = str_to_ullarray(buf, rd_i->v); + if (ret <= 0) { + _E("Unable to parse raw data from kernel: \"%s\"\n", buf); + goto error; + } + } + + fclose(fp); + return 0; + +error: + fclose(fp); + raw_data_cleanup(rd); + return ret; +} + +/* Load kernel module if not loaded */ +static int init(void) +{ + int fd, ret = 0; + + if (access(PROC_TSM_PATH, O_RDONLY) == 0) { + _D("Kernel module already loaded\n"); + return 0; + } + + fd = open(KMOD_PATH, O_RDONLY); + if (fd == -1) { + _E("Unable to open file descriptor"); + return -1; + } + + ret = finit_module(fd, "", 0); + close(fd); + if (ret) { + _E("Unable to load kernel module: %m"); + return ret; + } + + return 0; +} + +static void cleanup(void) +{ + return; +} + +struct raw_data_provider proc_tsm_module = { + .name = PROC_TSM_MODULE_NAME, + .init = init, + .get_raw_data = get_raw_data, + .cleanup = cleanup, +}; + +RAW_DATA_PROVIDER_REGISTER(&proc_tsm_module); diff --git a/src/raw_data_providers/proc_tsm.h b/src/raw_data_providers/proc_tsm.h new file mode 100644 index 0000000..7a642cf --- /dev/null +++ b/src/raw_data_providers/proc_tsm.h @@ -0,0 +1,36 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PROC_TSM +#define PROC_TSM + +enum proc_tsm_glob_items { + ITEM_UPTIME, + ITEM_TOTAL_MEM, +}; + +enum proc_tsm_pid_items { + ITEM_PID, + ITEM_PPID, + ITEM_CPU, + ITEM_RSS, + ITEM_FDS, + ITEM_IO, +}; + +#endif /* PROC_TSM */ \ No newline at end of file diff --git a/src/sample.c b/src/sample.c new file mode 100644 index 0000000..f4a9213 --- /dev/null +++ b/src/sample.c @@ -0,0 +1,121 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "log.h" +#include "list.h" +#include "sample.h" + + +struct sample_s *sample_add(enum sample_type type, void *value, + struct list_head *list, unsigned long long measure_time) +{ + struct sample_s *sample; + + sample = calloc(1, sizeof(*sample)); + if (!sample) + return NULL; + + INIT_LIST_HEAD(&sample->node); + sample->type = type; + sample->measure_time = measure_time; + + if (type == ST_INT) + sample->value.i = *(int*) value; + else if (type == ST_DOUBLE) + sample->value.d = *(double*) value; + else if (type == ST_ULL) + sample->value.ull = *(unsigned long long*) value; + + list_add(&sample->node, list); + + return sample; +} + +void sample_remove_older_than(unsigned long long time, struct list_head *list) +{ + struct sample_s *sample, *prev; + + list_for_each_entry_reverse_safe(sample, prev, list, node) { + if (sample->measure_time >= time) + break; + + list_del(&sample->node); + free(sample); + } +} + +void sample_remove_all_but_n(int n, struct list_head *list) +{ + struct sample_s *sample, *next; + + /* Skip n samples */ + sample = list_first_entry(list, struct sample_s, node); + for (int i = 0; i < n; i++) { + sample = list_next_entry(sample, node); + if (&sample->node == list) + return; + } + + /* Remove the rest */ + for (next = list_next_entry(sample, node); + &sample->node != list; + sample = next, next = list_next_entry(sample, node)) { + + list_del(&sample->node); + free(sample); + } +} + +void sample_print_list(struct list_head *list) +{ + struct sample_s *sample; + + list_for_each_entry(sample, list, node) { + _D("sample %p\n", sample); + } +} + +int sample_is_greater(struct sample_s *s1, struct sample_s *s2) +{ + if (s1->type != s2->type) + _W("Comparing samples of different types"); + + if (s1->type == ST_INT) + return s1->value.i > s2->value.i; + else if (s1->type == ST_DOUBLE) + return s1->value.d > s2->value.d; + else if (s1->type == ST_ULL) + return s1->value.ull > s2->value.ull; + + return 0; +} + +GVariant *sample_to_gvariant(struct sample_s *s) +{ + if (s->type == ST_INT) + return g_variant_new_int32(s->value.i); + else if (s->type == ST_DOUBLE) + return g_variant_new_double(s->value.d); + else if (s->type == ST_ULL) + return g_variant_new_uint64(s->value.ull); + + return NULL; +} diff --git a/src/sample.h b/src/sample.h new file mode 100644 index 0000000..a28e9a6 --- /dev/null +++ b/src/sample.h @@ -0,0 +1,57 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SAMPLE_H +#define SAMPLE_H + +#include +#include + +#include "list.h" + + +enum sample_type { + ST_INT, + ST_ULL, + ST_DOUBLE, +}; + +/* Single sample of data */ +struct sample_s { + union { + int i; + double d; + unsigned long long ull; + } value; + + enum sample_type type; + unsigned long long measure_time; + + struct list_head node; +}; + +struct sample_s *sample_add(enum sample_type type, void *value, + struct list_head *list, unsigned long long measure_time); + +void sample_remove_older_than(unsigned long long time, struct list_head *list); +void sample_remove_all_but_n(int n, struct list_head *list); +void sample_print_list(struct list_head *list); +int sample_is_greater(struct sample_s *s1, struct sample_s *s2); +GVariant *sample_to_gvariant(struct sample_s *s); + +#endif /* SAMPLE_H */ \ No newline at end of file diff --git a/src/stability-monitor.c b/src/stability-monitor.c new file mode 100644 index 0000000..3fe0f46 --- /dev/null +++ b/src/stability-monitor.c @@ -0,0 +1,317 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "config.h" +#include "aul.h" +#include "process.h" +#include "dbus.h" +#include "raw_data.h" +#include "data_source.h" + +#define DEFAULT_CONFIG_FILE "/usr/lib/stability-monitor/default.conf" +#define USER_CONFIG_DIR "/etc/stability-monitor.d" + + +static void print_help(const char *name) +{ + printf("Usage: %s [OPTIONS]\n" + "Options: \n" + " -c, --config Read configuration only from \n" + " -h, --help Display this help and exit\n", + name); +} + +static int parse_argv(int argc, char *argv[]) +{ + static struct option long_options[] = { + {"config", required_argument, NULL, 'c'}, + {"help", no_argument, NULL, 'h'}, + {} + }; + int c, ret; + + + while ((c = getopt_long(argc, argv, "c:h", long_options, NULL)) >= 0) { + switch (c) { + case 'c': + ret = config_load(optarg); + if (ret) { + _E("Unable to parse config: %d", ret); + return -1; + } + break; + + case 'h': + print_help(argv[0]); + exit(0); + break; + + default: + print_help(argv[0]); + return -EINVAL; + } + } + + return 0; +} + +static int load_config(void) +{ + int ret; + + /* Return if custom config is already loaded */ + if (!config_is_empty()) + return 0; + + /* Load main config */ + ret = config_load(DEFAULT_CONFIG_FILE); + if (ret) { + _E("Unable to load config: %d", ret); + return -1; + } + + /* Load additional configs */ + config_load_dir(USER_CONFIG_DIR); + + return 0; +} + +static gboolean signal_handler(GMainLoop *loop, const char *sig_name) +{ + _D("\nReceived %s, exit\n", sig_name); + g_main_loop_quit(loop); + return FALSE; +} +static gboolean sigterm_handler(gpointer data) +{ + return signal_handler(data, "SIGTERM"); +} + +static gboolean sigint_handler(gpointer data) +{ + return signal_handler(data, "SIGINT"); +} + +static int get_global_sampling_rate(double *sr) +{ + struct json_object *config, *obj; + int ret; + + config = config_get(); + + ret = json_object_object_get_ex(config, "global", &obj); + if (!ret) { + _E("There is no 'global' section in config"); + return -ENOENT; + } + + ret = json_object_object_get_ex(obj, "sampling_rate", &obj); + if (!ret) { + _E("There is no 'sampling_rate' parameter in global config"); + return -ENOENT; + } + + *sr = json_object_get_double(obj); + if (*sr == 0) { + _E("Sampling rate should be of type double and greater than 0"); + return -EINVAL; + } + + return 0; +} + +static gboolean run(gpointer data) +{ + struct list_head rd; + struct raw_data_item *rd_i, *next, *rd_global; + struct proc_info *process; + struct data_source *ds; + unsigned long long current_time; + // unsigned long long _t = time_now(); + int ret, first_item = 1; + + INIT_LIST_HEAD(&rd); + + ret = raw_data_provider_get()->get_raw_data(&rd); + if (ret) { + _E("Unable to get raw data"); + goto cleanup; + } + + list_for_each_entry_safe(rd_i, next, &rd, node) { + if (first_item) { + rd_global = rd_i; + current_time = rd_global->v[0]; + + first_item = 0; + continue; + } + + /* Get proc info */ + process = process_get_by_pid(rd_i->v[0]); + if (!process) { + /* Create process info */ + ret = process_create_info(rd_i->v[0], rd_i->v[1], &process); + if (ret) { + _E("Unable to create process info: %d", ret); + continue; + } + } + + process->still_alive = 1; + + /* End here if not monitoring the process */ + if (!process->monitor) + continue; + + if (process->sampling_rate != 0 && + current_time - process->last_update >= USEC_PER_SEC / process->sampling_rate) { + + /* Update process state */ + process->last_update = current_time; + for (int i = 0; i < data_sources_get_n(); i++) { + ds = &(process->data_sources[i]); + + ret = ds->update(ds, rd_global, rd_i); + if (ret) { + _E("Update() failed in data source %s", ds->name); + continue; + } + } + + /* Check whether limits are exceeded */ + process_check_limits(process); + + if (process->print_current) + process_print_current_state(process); + } + } + + /* Remove processes that were not present in raw data */ + process_remove_deads(); + +cleanup: + raw_data_cleanup(&rd); + + // _D("Run took %lf s\n", (double) 1. * (time_now() - _t) / USEC_PER_SEC); + return TRUE; +} + +static int setup(GMainLoop *loop) +{ + struct raw_data_provider *rdp = raw_data_provider_get(); + double sr; + int ret; + + /* Register signal handlers */ + ret = g_unix_signal_add(SIGINT, sigint_handler, loop); + if (ret < 0) { + _E("Failed to register SIGINT handler: %d", ret); + return -1; + } + + ret = g_unix_signal_add(SIGTERM, sigterm_handler, loop); + if (ret < 0) { + _E("Failed to register SIGTERM handler: %d", ret); + return -1; + } + + /* Init raw data provider */ + if (!rdp) { + _E("Unable to find any raw data providers"); + return -1; + } + + ret = rdp->init(); + if (ret) { + _E("Unable to initialize %s raw data provider: %d", rdp->name, ret); + return -1; + } + + /* Init other modules */ + ret = aul_listener_init(); + if (ret) { + _E("Unable to initialize aul listener: %d", ret); + return -1; + } + + process_table_init(); + + /* Schedule periodic processing */ + ret = get_global_sampling_rate(&sr); + if (ret) + return ret; + + g_timeout_add(MSEC_PER_SEC / sr, run, NULL); + run(NULL); + + return 0; +} + +static void cleanup(void) +{ + struct raw_data_provider *rdp = raw_data_provider_get(); + + if (rdp) + rdp->cleanup(); + + process_cleanup_all(); +} + +int main(int argc, char *argv[]) +{ + GMainLoop *loop = NULL; + int ret; + + ret = parse_argv(argc, argv); + if (ret) + return -1; + + ret = load_config(); + if (ret) + return -1; + + /* Create event loop */ + loop = g_main_loop_new(NULL, FALSE); + if (!loop) { + _E("Failed to create an event loop"); + return -1; + } + + ret = setup(loop); + if (ret) + return -1; + + + /* Run main loop */ + g_main_loop_run(loop); + g_main_loop_unref(loop); + + cleanup(); + + return 0; +} \ No newline at end of file diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..25cde1b --- /dev/null +++ b/src/utils.c @@ -0,0 +1,128 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" + + +unsigned long long time_now(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_BOOTTIME, &ts); + return ts.tv_sec * USEC_PER_SEC + ts.tv_nsec / NSEC_PER_USEC; // us +} + +char *get_basename(char *path) +{ + char *base = strrchr(path, '/'); + return base ? base+1 : path; +} + +int pid_to_basename(int pid, char *basename) +{ + char path[PATH_MAX]; + char link_name[NAME_MAX]; + char *basename_ptr; + int basename_len; + int ret; + + /* Assembly /proc/pid/exe */ + ret = snprintf(link_name, sizeof(link_name), PROC_EXE_PATH, pid); + if (ret <= 0) { + return -1; + } + + /* Readlink /proc/pid/exe */ + ret = readlink(link_name, path, PATH_MAX); + if (ret <= 0) { + _E("readlink() failed, probably the process(%d) has just died: %d", pid, ret); + return -1; + } + + path[ret] = 0; + + /* Return basename */ + basename_ptr = get_basename(path); + basename_len = strlen(basename_ptr); + + strncpy(basename, basename_ptr, basename_len); + basename[basename_len] = 0; + + return 0; +} + +const char *smpl2str(struct sample_s *s) +{ + static char str[MAX_SAMPLE_STR]; + + if (s->type == ST_DOUBLE) + snprintf(str, MAX_SAMPLE_STR, "%2.3lf", s->value.d); + else if (s->type == ST_INT) + snprintf(str, MAX_SAMPLE_STR, "%d", s->value.i); + else if (s->type == ST_ULL) + snprintf(str, MAX_SAMPLE_STR, "%llu", s->value.ull); + + return str; +} + +int _json_object_object_merge(json_object *obj1, char *key, json_object *obj2_node) +{ + struct json_object *obj1_node = NULL; + int ret; + + ret = json_object_object_get_ex(obj1, key, &obj1_node); + + /* if there is no such object in obj1 - add it */ + if (ret == 0) + goto json_add; + + if (obj1_node == NULL) { + _E("Unable to get json object"); + return -ENOMEM; + } + + /* if there is, but obj1 or obj2 is not json_type_object - replace it */ + if (!json_object_is_type(obj1_node, json_type_object) || + !json_object_is_type(obj2_node, json_type_object)) { + + json_object_object_del(obj1, key); + goto json_add; + } + + /* if obj1 and obj2 is json_object_type */ + json_object_object_foreach(obj2_node, k, v) { + ret = _json_object_object_merge(obj1_node, k, v); + if (ret) + return ret; + } + + return 0; + +json_add: + json_object_get(obj2_node); + json_object_object_add(obj1, key, obj2_node); + return 0; +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..59d2470 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,47 @@ +/* + * This file is part of stability-monitor + * + * Copyright © 2019 Samsung Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UTILS_H +#define UTILS_H + +#include +#include + +#include "sample.h" + +#define PROC_EXE_PATH "/proc/%d/exe" + +#define MSEC_PER_SEC 1000ULL +#define USEC_PER_MSEC 1000ULL +#define NSEC_PER_USEC 1000ULL +#define NSEC_PER_MSEC 1000000ULL +#define USEC_PER_SEC 1000000ULL +#define NSEC_PER_SEC 1000000000ULL +#define FSEC_PER_SEC 1000000000000000ULL + +#define MAX_SAMPLE_STR 20 + + +unsigned long long time_now(void); +char *get_basename(char *path); +int pid_to_basename(int pid, char *basename); +const char *smpl2str(struct sample_s *s); + +int _json_object_object_merge(json_object *obj1, char *key, json_object *obj2_node); + +#endif /* UTILS_H */ -- 2.34.1