stats.c
clock.c
ipc.c
+ process.c
+ proc-scanner.c
)
ADD_EXECUTABLE(${PROJECT_NAME} ${SRCS})
}
remove_app_by_pid(pid);
-}
\ No newline at end of file
+}
+
+int app_info_iterator_get_count(app_info_iterator_t *iter)
+{
+ ON_NULL_RETURN_VAL(iter, -1);
+
+ return iter->data->len;
+}
int app_info_iterator_get_pid(app_info_iterator_t *iter);
/**
+ * @brief Gets total number of apps in iterator
+ * @param[in] iter Iterator struct.
+ *
+ * @return number of apps.
+ */
+int app_info_iterator_get_count(app_info_iterator_t *iter);
+
+/**
* @brief Frees resource used by iterator.
* @param[in] iter Iterator struct.
*/
static void config_array_iterate_func(JsonArray *array, guint index, JsonNode *element, gpointer user_data);
static config_options_e config_parse_options(const char *option);
static int calculate_task_counter(int frequency);
+static config_top_subject_e config_parse_top_subject(const char *option);
static struct _cfg_data
{
const gchar *target = json_object_get_string_member(entry, SCHEMA_TARGET);
configs[index].data.top.options = config_parse_options(target);
+ const gchar *subject = json_object_get_string_member(entry, SCHEMA_SUBJECT);
+ configs[index].data.top.options = config_parse_top_subject(subject);
+
gint64 top = json_object_get_int_member(entry, SCHEMA_TOP);
configs[index].data.top.top = top;
}
static int calculate_task_counter(int frequency)
{
return cfg_data.total_duration / frequency;
-}
\ No newline at end of file
+}
+
+static config_top_subject_e config_parse_top_subject(const char *option)
+{
+ if (g_strcmp0(option, SCHEMA_SUBJECT_APPS) == 0)
+ {
+ return TOP_SUBJECT_APPS;
+ }
+ else if (g_strcmp0(option, SCHEMA_SUBJECT_ALL) == 0)
+ {
+ return TOP_SUBJECT_ALL;
+ }
+
+ return -1;
+}
/**
* @brief Observe memory usage.
*/
- OBSERVE_MEMORY
-} config_options_e;
+ OBSERVE_MEMORY } config_options_e;
/**
* @brief Config app data structure.
char app_id[APP_ID_REGEX_MAX_LEN+1];
} config_data_app_t;
+typedef enum config_top_subject {
+ TOP_SUBJECT_APPS,
+ TOP_SUBJECT_ALL,
+} config_top_subject_e;
+
/**
* @brief Config top data structure.
*/
* @brief Length of top.
*/
long top;
+
+ /**
+ * @brief top Subject
+ */
+ config_top_subject_e subject;
} config_data_top_t;
/**
} data;
} config_t;
-#endif
\ No newline at end of file
+#endif
#define SCHEMA_TARGET_CPU "CPU"
#define SCHEMA_TARGET_MEMORY "MEMORY"
+#define SCHEMA_SUBJECT "subject"
+#define SCHEMA_SUBJECT_APPS "APPS"
+#define SCHEMA_SUBJECT_ALL "ALL"
+
#define SCHEMA_FREQUENCY "frequency"
#define SCHEMA_TOP "top"
#define SCHEMA_ID "id"
--- /dev/null
+/*
+ * Copyright (c) 2018 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Flora License, Version 1.1 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * 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 <stdlib.h>
+
+#include "proc-scanner.h"
+#include "procfs.h"
+#include "process.h"
+#include "err-check.h"
+#include "clock.h"
+
+#define SWAP(a, b, type) do { type tmp = (a); (a) = (b); (b) = tmp; } while(0);
+#define PROCTAB_INITIAL_LEN 64
+
+struct proctab {
+ struct process *processes;
+ int size;
+ int max;
+};
+
+struct proc_scanner {
+ struct proctab current;
+ struct proctab history;
+ int last_history_index;
+};
+
+void proctab_free(struct proctab *tab)
+{
+ if (!tab) return;
+ for (int i = 0; i < tab->size; i++)
+ process_shutdown(&tab->processes[i]);
+ if (tab->processes) free(tab->processes);
+}
+
+void proctab_resize(struct proctab *tab, int new_max)
+{
+ tab->processes = realloc(tab->processes, new_max * sizeof(struct process));
+ if (!tab->processes) abort();
+ tab->max = new_max;
+}
+
+void proctab_append(struct proctab *tab, struct process *proc)
+{
+ if (tab->size >= tab->max)
+ proctab_resize(tab, tab->max *= 2);
+ tab->processes[tab->size++] = *proc;
+}
+
+void proctab_reset(struct proctab *tab)
+{
+ for (int i = 0; i < tab->size; i++)
+ process_shutdown(&tab->processes[i]);
+ tab->size = 0;
+}
+
+proc_scanner_t *proc_scanner_new()
+{
+ proc_scanner_t *ret = calloc(1, sizeof(proc_scanner_t));
+ if (!ret) {
+ return NULL;
+ }
+ proctab_resize(&ret->current, PROCTAB_INITIAL_LEN);
+ proctab_resize(&ret->history, PROCTAB_INITIAL_LEN);
+ return ret;
+}
+
+void proc_scanner_free(proc_scanner_t *scanner)
+{
+ if (!scanner) return;
+ proctab_free(&scanner->current);
+ proctab_free(&scanner->history);
+ free(scanner);
+}
+
+static int _sort_processes_by_pid(const void *a, const void *b)
+{
+ const struct process *proc1 = a;
+ const struct process *proc2 = b;
+ return process_get_pid(proc1) - process_get_pid(proc2);
+}
+
+static int _search_for_pid_key(const void *a, const void *b)
+{
+ const int *key = a;
+ const struct process *proc = b;
+ return *key - proc->pid;
+}
+
+static int _sort_pids(const void *a, const void *b)
+{
+ const int *pid1 = a;
+ const int *pid2 = a;
+ return pid1 - pid2;
+}
+
+// Search for process entry in history
+static struct process *_proc_scanner_find_process_in_history(proc_scanner_t *scanner, int pid)
+{
+ if (scanner->history.size == 0)
+ return NULL;
+
+ // the scanner->history_last_history_index helps for quickly find
+ // history entry matching given pid. We assume that history is sorted ascending by pid
+ // and searching for pid is done in same order.
+ // the speedup gain is about ~15% compared to bsearch only.
+ while (scanner->last_history_index < scanner->history.size)
+ {
+ if (scanner->history.processes[scanner->last_history_index].pid < pid)
+ {
+ scanner->last_history_index++;
+ continue;
+ }
+ if (scanner->history.processes[scanner->last_history_index].pid == pid) {
+ return &scanner->history.processes[scanner->last_history_index++];
+ }
+ break;
+ }
+
+ return bsearch(&pid, scanner->history.processes, scanner->history.size, sizeof(struct process), _search_for_pid_key);
+}
+
+static bool _proc_scanner_read_pid(int pid, void *user_data)
+{
+ proc_scanner_t *scanner = user_data;
+ struct process proc_new;
+
+ struct process *proc = _proc_scanner_find_process_in_history(scanner, pid);
+
+ if (!proc) {
+ process_init(pid, &proc_new);
+ proc = &proc_new;
+ } else {
+ process_move(&proc_new, proc);
+ }
+
+ if (process_update(&proc_new) != 0) {
+ process_shutdown(&proc_new);
+ return true;
+ }
+ proctab_append(&scanner->current, &proc_new);
+
+ return true;
+}
+
+static int _proc_scanner_iterate_array(int *pids, int n_pids, procfs_pid_iterator_cb cb, void *user_data)
+{
+ qsort(pids, n_pids, sizeof(int), _sort_pids);
+
+ for (int i = 0; i < n_pids; ++i) {
+ if (!cb(pids[i], user_data))
+ break;
+ }
+ return 0;
+}
+
+static int _proc_scanner_scan_internal(proc_scanner_t *scanner, int *pids, int n_pids)
+{
+ ON_NULL_RETURN_VAL(scanner, -1);
+
+ // swap processes and history
+ SWAP(scanner->current, scanner->history, struct proctab);
+
+ // reset current tab, so we can start adding results of current scan
+ proctab_reset(&scanner->current);
+
+ // prepare history
+ scanner->last_history_index = 0;
+ qsort(scanner->history.processes, scanner->history.size, sizeof(struct process), _sort_processes_by_pid);
+
+ if (pids)
+ return _proc_scanner_iterate_array(pids, n_pids, _proc_scanner_read_pid, scanner);
+ else
+ return procfs_iterate_pids(_proc_scanner_read_pid, scanner);
+}
+
+int proc_scanner_scan(proc_scanner_t *scanner)
+{
+ return _proc_scanner_scan_internal(scanner, NULL, -1);
+}
+int proc_scanner_scan_pids(proc_scanner_t *scanner, int *pids, int n_pids)
+{
+ return _proc_scanner_scan_internal(scanner, pids, n_pids);
+}
+
+int proc_scanner_foreach_process(proc_scanner_t *scanner, foreach_t cb, void *data)
+{
+ ON_NULL_RETURN_VAL(scanner, -1);
+ ON_NULL_RETURN_VAL(cb, -1);
+
+ for (int i = 0; i < scanner->current.size; ++i) {
+ if (!cb(&scanner->current.processes[i], data))
+ break;
+ }
+ return 0;
+}
+
+int proc_scanner_sort_processes(proc_scanner_t *scanner, sort_t cb)
+{
+ ON_NULL_RETURN_VAL(scanner, -1);
+ ON_NULL_RETURN_VAL(cb, -1);
+
+ qsort(scanner->current.processes, scanner->current.size, sizeof(struct process), (__compar_fn_t)cb);
+
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Flora License, Version 1.1 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * 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_SCANNER_H
+#define __PROC_SCANNER_H
+
+#include <stdbool.h>
+
+#include "process.h"
+
+/**
+ * @brief Scanner object
+ */
+typedef struct proc_scanner proc_scanner_t;
+
+/**
+ * @brief Iterator callback
+ *
+ * @param[in]: proc the proccess structure
+ * @param[in]: data passed to @proc_scanner_foreach_process
+ *
+ * @return: true to continue iterating, false otherwise.
+*/
+typedef bool (*foreach_t)(struct process *proc, void *data);
+
+/**
+ * @brief Sort callback
+ *
+ * @param[in]: proc the process
+ * @param[in]: proc the process
+ *
+ * @return value < 0 if @proc1 goes before @proc2, value > 0 othersize, 0 if
+ * equal
+ */
+typedef int (*sort_t)(struct process *proc1, struct process *proc2);
+
+/**
+ * @brief Creates new proc_scanner object
+ *
+ * @return scanner object poiter or NULL in case of error
+ *
+ * @note the scanner should be released with @proc_scanner_free
+ */
+proc_scanner_t *proc_scanner_new();
+
+/**
+ * @brief Releases resources allocated for scanner_object
+ *
+ * @param[in] scanner
+ */
+void proc_scanner_free(proc_scanner_t *scanner);
+
+/**
+ * @brief Performs scan of processes scanners contained in /proc/ scannerectory
+ *
+ * @param[in] scanner
+ *
+ * @return 0 on success, other value on failure.
+ */
+int proc_scanner_scan(proc_scanner_t *scanner);
+
+/**
+ * @brief Performs scan of processes scanners contained in /proc/ scannerectory
+ *
+ * @param[in] scanner
+ *
+ * @return 0 on success, other value on failure.
+ */
+int proc_scanner_scan_pids(proc_scanner_t *scanner, int *pids, int n_pids);
+
+/**
+ * @brief Iterate over processes scanned by @proc_scanner_scan_pids or
+ * @proc_scanner_scan methods.
+ *
+ * @param[in] scanner
+ * @param[in] cb iterator callback
+ * @param[in] data user data passed to callback @cb
+ *
+ * @return 0 on success, other value on failure.
+ */
+int proc_scanner_foreach_process(proc_scanner_t *scanner, foreach_t cb, void *data);
+
+/**
+ * @brief Sort processes scanned by @proc_scanner_scan_pids or
+ * @proc_scanner_scan methods. The sorting will affect process iteration
+ * with @proc_scanner_foreach_process.
+ *
+ * @param[in] scanner
+ * @param[in] cb sort callback
+ *
+ * @return 0 on success, other value on failure.
+ */
+int proc_scanner_sort_processes(proc_scanner_t *scanner, sort_t cb);
+
+#endif
--- /dev/null
+#include <string.h>
+#include <stdlib.h>
+
+#include "process.h"
+#include "err-check.h"
+#include "procfs.h"
+
+static int _process_update_cpu_usage(struct process *proc)
+{
+ struct procfs_process_cpu_usage_info info;
+ unsigned long long ticks;
+
+ if (procfs_read_process_cpu_usage(proc->pid, &info) != 0) {
+ return -1;
+ }
+ ticks = info.stime + info.utime;
+ proc->frame_ticks_used = ticks - proc->total_ticks_used;
+ proc->total_ticks_used = ticks;
+ return 0;
+}
+
+static int _process_update_memory_usage(struct process *proc)
+{
+ struct procfs_process_memory_usage_info info;
+
+ if (procfs_read_process_memory_usage(proc->pid, &info) != 0) {
+ return -1;
+ }
+ proc->memory_used = info.rss;
+ return 0;
+}
+
+int process_get_memory_usage(struct process *proc, int *usage)
+{
+ ON_NULL_RETURN_VAL(proc, -1);
+ ON_NULL_RETURN_VAL(usage, -1);
+
+ *usage = proc->memory_used;
+ return 0;
+}
+
+int process_get_cpu_usage(struct process *proc, unsigned long long *usage)
+{
+ ON_NULL_RETURN_VAL(proc, -1);
+ ON_NULL_RETURN_VAL(usage, -1);
+
+ *usage = proc->frame_ticks_used;
+ return 0;
+}
+
+int process_update(struct process *proc)
+{
+ if (_process_update_cpu_usage(proc) != 0) {
+ return -1;
+ }
+ if (_process_update_memory_usage(proc) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+void process_init(int pid, struct process *proc)
+{
+ memset(proc, 0x0, sizeof(struct process));
+ proc->pid = pid;
+}
+
+int process_get_pid(const struct process *proc)
+{
+ ON_NULL_RETURN_VAL(proc, -1);
+ return proc->pid;
+}
+
+const char *process_get_appid(struct process *proc)
+{
+ ON_NULL_RETURN_VAL(proc, NULL);
+
+ if (!proc->appid) {
+ }
+ return proc->appid;
+}
+
+const char *process_get_exe(struct process *proc)
+{
+ ON_NULL_RETURN_VAL(proc, NULL);
+
+ if (!proc->exe) {
+ if (procfs_read_exe(proc->pid, &proc->exe) != 0)
+ return NULL;
+ }
+ return proc->exe;
+}
+
+void process_shutdown(struct process *proc)
+{
+ if (!proc) return;
+ if (proc->appid) free(proc->appid);
+ if (proc->exe) free(proc->exe);
+}
+
+void process_move(struct process *dst, struct process *src)
+{
+ process_shutdown(dst);
+ memcpy(dst, src, sizeof(struct process));
+ src->appid = NULL;
+ src->exe = NULL;
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Flora License, Version 1.1 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * 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 <stdbool.h>
+
+#include "stats.h"
+
+/**
+ * @brief The process structure.
+ *
+ * @note Do not read fields directly, the struct
+ * is only available publicly to allow easy copy semantic
+ */
+struct process
+{
+ int pid;
+ char *appid;
+ char *exe;
+ unsigned long long total_ticks_used;
+ unsigned long long frame_ticks_used;
+ int memory_used;
+};
+
+/**
+ * @brief Gets last read process memory usage
+ *
+ * @param[in]: proc process
+ * @param[out]: used the memory usage in KiB
+ *
+ * @return 0 on success, other value on error.
+ */
+int process_get_memory_usage(struct process *proc, int *used);
+
+/**
+ * @brief Gets last read process CPU usage
+ *
+ * @param[in]: proc process
+ * @param[out]: usage CPU usage in clock ticks
+ *
+ * @return 0 on success, other value on error.
+ */
+int process_get_cpu_usage(struct process *proc, unsigned long long *usage);
+
+/**
+ * @brief Updates process memory & CPU usage data.
+ *
+ * @param[in]: proc process
+ *
+ * @return 0 on success, other value on error.
+ */
+int process_update(struct process *proc);
+
+/**
+ * @brief Initializes process structure
+ *
+ * @param[in]: pid
+ * @param[in]: proc process
+ *
+ * @return 0 on success, other value on error.
+ */
+void process_init(int pid, struct process *proc);
+
+/**
+ * @brief Shutdown process structure
+ *
+ * @param[in]: proc process
+ *
+ * @return 0 on success, other value on error.
+ *
+ * @note the structure should be initialized with @process_init, @process_move
+ */
+void process_shutdown(struct process *proc);
+
+/**
+ * @brief Get process pid
+ *
+ * @param[in]: proc process
+ *
+ * @return: pid
+ */
+int process_get_pid(const struct process *proc);
+
+/**
+ * @brief Get process application id.
+ *
+ * @param[in]: proc process
+ *
+ * @return: appid or NULL if process is not an Tizen Application.
+ */
+const char *process_get_appid(struct process *proc);
+
+/**
+ * @brief Pathname of executed command.
+ *
+ * @param[in]: proc process
+ *
+ * @return: exe file path or NULL in case of error.
+ */
+const char *process_get_exe(struct process *proc);
+
+/**
+ * @brief Moves process data from one struct to another.
+ *
+ * @param[in]: dst destination process
+ * @param[in]: src source process
+ */
+void process_move(struct process *dst, struct process *src);
+
+#endif
#include <dirent.h>
#include <stdlib.h>
#include <unistd.h>
+#include <ctype.h>
#include "procfs.h"
#include "log.h"
#define PROC_PID_EXE_PATH "/proc/%d/exe"
#define PROC_PID_CMDLINE_PATH "/proc/%d/cmdline"
-struct procfs_pid_iterator
-{
- DIR *dir;
- int current_pid;
-};
-
int procfs_read_system_load_average(struct procfs_load_average_info *info)
{
float a1, a5, a15;
usage->utime = utime;
usage->stime = stime;
+ fclose(stat_fp);
return 0;
}
{
int parsed_pid;
+ if (!isdigit(*dirname))
+ return false;
+
if (sscanf(dirname, "%d", &parsed_pid) != 1) {
return false;
}
return true;
}
-/**
- * @brief returns true if pid_iterator could successfully read
- * next pid from /proc directory, false otherwise
- */
-bool _procfs_pid_iterator_next_internal(procfs_pid_iterator_t *iter)
+int procfs_iterate_pids(procfs_pid_iterator_cb iterator, void *user_data)
{
+ ON_NULL_RETURN_VAL(iterator, -1);
+
struct dirent *entry;
int pid;
- bool ret = false;
+
+ DIR *dir = opendir(PROC_DIR_PATH);
+ if (!dir) {
+ ERR("opendir failed.");
+ return -1;
+ }
// According to POSIX docs readdir is not-thread safe.
// however in glib recent implementations readdir
// is thread safe, so we can avoid using locks here.
- while ((entry = readdir(iter->dir)) != NULL) {
- if (_procfs_dirname_parse_pid(entry->d_name, &pid)) {
- iter->current_pid = pid;
- ret = true;
- break;
- } else {
+ while ((entry = readdir(dir)) != NULL) {
+ if (!_procfs_dirname_parse_pid(entry->d_name, &pid))
continue;
- }
- }
-
- return ret;
-}
-procfs_pid_iterator_t *procfs_get_pid_iterator()
-{
- procfs_pid_iterator_t *ret = calloc(1, sizeof(struct procfs_pid_iterator));
- if (!ret) {
- ERR("calloc failed.");
- return NULL;
- }
- ret->dir = opendir(PROC_DIR_PATH);
- if (!ret->dir) {
- ERR("opendir failed.");
- procfs_pid_iterator_free(ret);
- return NULL;
- }
- if (!_procfs_pid_iterator_next_internal(ret)) {
- ERR("_procfs_pid_iterator_next_internal failed");
- procfs_pid_iterator_free(ret);
- return NULL;
+ if (iterator(pid, user_data))
+ continue;
+ else
+ break;;
}
- return ret;
-}
-
-bool procfs_pid_iterator_next(procfs_pid_iterator_t *iterator)
-{
- return _procfs_pid_iterator_next_internal(iterator);
-}
-int procfs_pid_iterator_get_pid(procfs_pid_iterator_t *iterator)
-{
- return iterator->current_pid;
-}
-
-void procfs_pid_iterator_free(procfs_pid_iterator_t *iterator)
-{
- if (!iterator) return;
- if (iterator->dir) closedir(iterator->dir);
- free(iterator);
+ closedir(dir);
+ return 0;
}
int procfs_read_exe(int pid, char **exe)
int procfs_read_cpu_count(int *cpu_count);
/**
- * @brief Iterator over pids in /proc/ directory.
- */
-typedef struct procfs_pid_iterator procfs_pid_iterator_t;
-
-/**
- * @brief Move iterator to next entry.
- *
- * @param[in]: itearator
- *
- * @return returns true if there are more entries available, false otherwise
- */
-bool procfs_pid_iterator_next(procfs_pid_iterator_t *iterator);
-
-/**
- * @brief Gets current pid from iterator
- *
- * @param[in]: itearator
- *
- * @not User should always check validity of pid, since it not guaranteed that
- * returned pid is still valid after function returns.
- */
-int procfs_pid_iterator_get_pid(procfs_pid_iterator_t *iterator);
-
-/**
- * @brief Frees procfs_pid_iterator_t
+ * @brief Callback func for @procfs_iterate_pids
*
- * @param[in]: itearator
+ * @return true if continue interation, false otherwise
*/
-void procfs_pid_iterator_free(procfs_pid_iterator_t *iterator);
+typedef bool (*procfs_pid_iterator_cb)(int pid, void *user_data);
/**
* @brief Gets pid iterator
*
- * @return new procfs_pid_iterator_t or NULL or error or no pids is available.
+ * @param[in] iterator interator function
+ * @param[in] user_data data passed to @iterator
*
- * @note the returned value should be released wiht procfs_pid_iterator_free
+ * @return 0 on success, other value of failure
*/
-procfs_pid_iterator_t *procfs_get_pid_iterator();
+int procfs_iterate_pids(procfs_pid_iterator_cb iterator, void *user_data);
/**
* @brief Reads cmdline file for given pid
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
+#include <math.h>
#include "report-generator.h"
#include "log.h"
#include "appinfo-provider.h"
#include "stats.h"
#include "clock.h"
+#include "proc-scanner.h"
struct report_generator_system {
/** system cpu usage statistics */
report_generator_process_t *process_gen;
};
+struct report_generator_top
+{
+ struct stats_system sys_stats;
+ proc_scanner_t *scanner;
+ report_generator_top_type_e type;
+ int limit;
+};
+
+struct report_generator_top_closure {
+ struct stats_system sys_stats;
+ int current_index;
+ int max_index;
+ struct app_cpu_usage_report *cpu_report;
+ struct app_memory_usage_report *mem_report;
+};
+
int _app_report_generator_setup_process_generator(report_generator_app_t *generator);
+static int _report_generator_top_report_generator_scan(report_generator_top_t *generator);
report_generator_system_t *report_generator_new_system_report_generator()
{
float usage;
- if (stats_get_system_memory_usage(&usage) != 0) {
+ if (stats_update_system_stats(&generator->previous) != 0) {
+ ERR("stats_update_system_stats failed.");
+ return -1;
+ }
+
+ if (stats_get_system_memory_usage(&generator->previous, &usage) != 0) {
ERR("stats_get_system_memory_usage failed.");
return -1;
}
return 0;
}
+report_generator_top_t *report_generator_new_top_report_generator(report_generator_top_type_e type, int limit)
+{
+ report_generator_top_t *gen = calloc(1, sizeof(report_generator_top_t));
+ if (!gen) {
+ ERR("calloc failed.");
+ return NULL;
+ }
+
+ gen->type = type;
+ gen->limit = limit;
+ gen->scanner = proc_scanner_new();
+ if (!gen->scanner) {
+ report_generator_free_top_generator(gen);
+ return NULL;
+ }
+ // run initial scan, so other next report will have valid data.
+ if (_report_generator_top_report_generator_scan(gen) != 0) {
+ report_generator_free_top_generator(gen);
+ return NULL;
+ }
+ if (stats_update_system_stats(&gen->sys_stats) != 0) {
+ report_generator_free_top_generator(gen);
+ return NULL;
+ }
+
+ return gen;
+}
+
+void report_generator_free_top_generator(report_generator_top_t *generator)
+{
+ if (!generator) return;
+ proc_scanner_free(generator->scanner);
+ free(generator);
+}
+
+static bool _append_to_mem_report(struct process *proc, void *data)
+{
+ struct report_generator_top_closure *closure = data;
+ struct app_memory_usage_report report = {0,};
+ const char *appid;
+ int mem;
+
+ if (closure->current_index >= closure->max_index)
+ return false;
+
+ if (process_get_memory_usage(proc, &mem) != 0) {
+ report.process_report.usage = NAN;
+ } else {
+ report.process_report.usage = (float)mem / closure->sys_stats.total_memory;
+ }
+
+ appid = process_get_appid(proc);
+ if (appid) strncpy(report.app_id, appid, sizeof(report.app_id));
+ report.process_report.pid = process_get_pid(proc);
+ report.process_report.time = clock_realtime_get().tv_sec;
+ closure->mem_report[closure->current_index++] = report;
+
+ return true;
+}
+
+static bool _append_to_cpu_report(struct process *proc, void *data)
+{
+ struct report_generator_top_closure *closure = data;
+ struct app_cpu_usage_report report = {0,};
+ const char *appid;
+ unsigned long long ticks = 0;
+
+ if (closure->current_index >= closure->max_index)
+ return false;
+
+ if (process_get_cpu_usage(proc, &ticks) != 0) {
+ report.process_report.usage = NAN;
+ } else {
+ report.process_report.usage = stats_get_cpu_usage_percentage(ticks, closure->sys_stats.frame_time);
+ }
+
+ appid = process_get_appid(proc);
+ if (appid) strncpy(report.app_id, appid, sizeof(report.app_id));
+ report.process_report.pid = process_get_pid(proc);
+ report.process_report.time = clock_realtime_get().tv_sec;
+ closure->cpu_report[closure->current_index++] = report;
+
+ return true;
+}
+
+static int _sort_by_cpu_usage(struct process *proc1, struct process *proc2)
+{
+ unsigned long long usage1, usage2;
+ if (process_get_cpu_usage(proc1, &usage1) != 0) {
+ return 1;
+ }
+ if (process_get_cpu_usage(proc2, &usage2) != 0) {
+ return -1;
+ }
+ return usage2 - usage1;
+}
+
+static int _sort_by_memory_usage(struct process *proc1, struct process *proc2)
+{
+ int usage1, usage2;
+ if (process_get_memory_usage(proc1, &usage1) != 0) {
+ return 1;
+ }
+ if (process_get_memory_usage(proc2, &usage2) != 0) {
+ return -1;
+ }
+ return usage2 - usage1;
+}
+
+static int _report_generator_top_report_generator_scan_apps(
+ report_generator_top_t *generator)
+{
+ app_info_iterator_t *iter = app_info_provider_get_running_applications();
+ int i = 0;
+
+ if (!iter) {
+ return -1;
+ }
+
+ int count = app_info_iterator_get_count(iter);
+ if (count < 1) {
+ app_info_iterator_free(iter);
+ return -1;
+ }
+
+ int *pids = calloc(count, sizeof(int));
+ if (!pids) {
+ app_info_iterator_free(iter);
+ return -1;
+ }
+
+ do {
+ pids[i++] = app_info_iterator_get_pid(iter);
+ }
+ while (app_info_iterator_next(iter));
+
+ app_info_iterator_free(iter);
+
+ return proc_scanner_scan_pids(generator->scanner, pids, count);
+}
+
+static int _report_generator_top_report_generator_scan_all(
+ report_generator_top_t *generator)
+{
+ return proc_scanner_scan(generator->scanner);
+}
+
+static int _report_generator_top_report_generator_scan(
+ report_generator_top_t *generator)
+{
+ switch (generator->type) {
+ case REPORT_GENERATOR_TOP_TYPE_APPS:
+ return _report_generator_top_report_generator_scan_apps(generator);
+ case REPORT_GENERATOR_TOP_TYPE_ALL:
+ return _report_generator_top_report_generator_scan_all(generator);
+ break;
+ }
+
+ return -1;
+}
+
+int report_generator_generate_top_cpu_report(
+ report_generator_top_t *generator,
+ struct app_cpu_usage_report **report,
+ int *n_reports)
+{
+ ON_NULL_RETURN_VAL(generator, -1);
+ ON_NULL_RETURN_VAL(report, -1);
+ ON_NULL_RETURN_VAL(n_reports, -1);
+
+ struct report_generator_top_closure closure = {0,};
+
+ if (stats_update_system_stats(&generator->sys_stats) != 0) {
+ return -1;
+ }
+
+ if (_report_generator_top_report_generator_scan(generator) != 0) {
+ return -1;
+ }
+
+ if (proc_scanner_sort_processes(generator->scanner, _sort_by_cpu_usage) != 0) {
+ return -1;
+ }
+
+ closure.max_index = generator->limit;
+ closure.sys_stats = generator->sys_stats;
+ closure.cpu_report = calloc(generator->limit, sizeof(struct app_cpu_usage_report));
+ if (!closure.cpu_report) {
+ return -1;
+ }
+
+ if (proc_scanner_foreach_process(generator->scanner, _append_to_cpu_report, &closure) != 0) {
+ free(closure.cpu_report);
+ return -1;
+ }
+
+ *report = closure.cpu_report;
+ *n_reports = closure.current_index;
+
+ return 0;
+}
+
+int report_generator_generate_top_memory_report(
+ report_generator_top_t *generator,
+ struct app_memory_usage_report **report,
+ int *n_reports)
+{
+ ON_NULL_RETURN_VAL(generator, -1);
+ ON_NULL_RETURN_VAL(report, -1);
+ ON_NULL_RETURN_VAL(n_reports, -1);
+
+ struct report_generator_top_closure closure = {0,};
+
+ if (stats_update_system_stats(&generator->sys_stats) != 0) {
+ return -1;
+ }
+
+ if (_report_generator_top_report_generator_scan(generator) != 0) {
+ return -1;
+ }
+
+ if (proc_scanner_sort_processes(generator->scanner, _sort_by_memory_usage) != 0) {
+ return -1;
+ }
+
+ closure.max_index = generator->limit;
+ closure.sys_stats = generator->sys_stats;
+ closure.mem_report = calloc(generator->limit, sizeof(struct app_memory_usage_report));
+ if (!closure.mem_report) {
+ return -1;
+ }
+
+ if (proc_scanner_foreach_process(generator->scanner, _append_to_mem_report, &closure) != 0) {
+ free(closure.mem_report);
+ return -1;
+ }
+
+ *report = closure.mem_report;
+ *n_reports = closure.current_index;
+
+ return 0;
+}
/** Generator for app report */
typedef struct report_generator_app report_generator_app_t;
+/** Top report generator yt*/
+typedef enum report_generator_top_type
+{
+ REPORT_GENERATOR_TOP_TYPE_APPS,
+ REPORT_GENERATOR_TOP_TYPE_ALL,
+} report_generator_top_type_e;
+
+/** Generator for top report */
+typedef struct report_generator_top report_generator_top_t;
+
/**
* @brief Creates new instance of report_generator_system_t
* @return new report_generator_system_t object, or NULL on error
void report_generator_free_app_generator(report_generator_app_t *generator);
/**
+ * @brief Creates new instance of report_generator_top_h
+ *
+ * @param[in] type Type of apps to track in generator.
+ * @param[in] limit the maximum number of apps to return in report.
+ *
+ * @return New report_generator_top_h object, or NULL on error
+ *
+ * @remark return value should be released with
+ * @report_generator_free_top_generator
+ */
+report_generator_top_t *report_generator_new_top_report_generator(report_generator_top_type_e type, int limit);
+
+/**
+ * @brief Release report_generator_top_h created with
+ * @report_generator_new_top_report_generator
+ */
+void report_generator_free_top_generator(report_generator_top_t *generator);
+
+/**
* @brief Fills system_cpu_usage_report.
*
* When interval > 0 the function will block for interval seconds and return report with
int report_generator_generate_load_average_report(
struct system_load_average_report *report);
+/**
+ * @brief Creates new CPU top report for apps
+ *
+ * @param[in] generator top generator
+ * @param[out] report
+ * @param[out] number of entries in report
+ *
+ * @note the report should be released with @free
+ */
+int report_generator_generate_top_cpu_report(
+ report_generator_top_t *generator,
+ struct app_cpu_usage_report **report,
+ int *n_reports);
+
+/**
+ * @brief Creates new memory top report for apps
+ *
+ * @param[in] generator top generator
+ * @param[out] report
+ * @param[out] number of entries in report
+ *
+ * @note the report should be released with @free
+ */
+int report_generator_generate_top_memory_report(
+ report_generator_top_t *generator,
+ struct app_memory_usage_report **report,
+ int *n_reports);
+
#endif
#define IMPLEMENT_ARRAY_SERIALIZER_FUNC(func_name, param_type, serializer) \
char* \
-func_name(param_type **reports) \
+func_name(param_type *reports, int len) \
{\
JsonBuilder *builder = json_builder_new(); \
if (!builder) return NULL; \
- serializer(builder, reports); \
+ serializer(builder, reports, len); \
char *ret = _serialize(builder); \
g_object_unref(builder); \
return ret; \
json_builder_end_object(builder);
}
-static void top_cpu_usage_reports_to_json_object(JsonBuilder *builder, struct app_cpu_usage_report **reports)
+static void top_cpu_usage_reports_to_json_object(JsonBuilder *builder, struct app_cpu_usage_report *reports, int len)
{
json_builder_begin_object(builder);
json_builder_set_member_name(builder, SCHEMA_RESULT_DATA_TOP);
json_builder_begin_array(builder);
- int i = 0;
- while(reports[i])
+ for (int i = 0; i < len; ++i)
{
json_builder_begin_object(builder);
json_builder_set_member_name(builder, SCHEMA_ID);
- json_builder_add_string_value(builder, reports[i]->app_id);
+ json_builder_add_string_value(builder, reports[i].app_id);
json_builder_set_member_name(builder, SCHEMA_RESULT_PID);
- json_builder_add_double_value(builder, reports[i]->process_report.pid);
+ json_builder_add_double_value(builder, reports[i].process_report.pid);
json_builder_set_member_name(builder, SCHEMA_RESULT_RESULT);
json_builder_begin_object(builder);
json_builder_set_member_name(builder, SCHEMA_RESULT_TIME);
- json_builder_add_double_value(builder, reports[i]->process_report.time);
+ json_builder_add_double_value(builder, reports[i].process_report.time);
json_builder_set_member_name(builder, SCHEMA_RESULT_USAGE);
- json_builder_add_double_value(builder, reports[i]->process_report.usage);
+ json_builder_add_double_value(builder, reports[i].process_report.usage);
json_builder_end_object(builder);
json_builder_end_object(builder);
- i++;
}
json_builder_end_array(builder);
json_builder_end_object(builder);
}
-static void top_memory_usage_reports_to_json_object(JsonBuilder *builder, struct app_memory_usage_report **reports)
+static void top_memory_usage_reports_to_json_object(JsonBuilder *builder, struct app_memory_usage_report *reports, int len)
{
json_builder_begin_object(builder);
json_builder_set_member_name(builder, SCHEMA_RESULT_DATA_TOP);
json_builder_begin_array(builder);
- int i = 0;
- while(reports[i])
+ for (int i = 0; i < len; ++i)
{
json_builder_begin_object(builder);
json_builder_set_member_name(builder, SCHEMA_ID);
- json_builder_add_string_value(builder, reports[i]->app_id);
+ json_builder_add_string_value(builder, reports[i].app_id);
json_builder_set_member_name(builder, SCHEMA_RESULT_PID);
- json_builder_add_double_value(builder, reports[i]->process_report.pid);
+ json_builder_add_double_value(builder, reports[i].process_report.pid);
json_builder_set_member_name(builder, SCHEMA_RESULT_RESULT);
json_builder_begin_object(builder);
json_builder_set_member_name(builder, SCHEMA_RESULT_TIME);
- json_builder_add_double_value(builder, reports[i]->process_report.time);
+ json_builder_add_double_value(builder, reports[i].process_report.time);
json_builder_set_member_name(builder, SCHEMA_RESULT_USAGE);
- json_builder_add_double_value(builder, reports[i]->process_report.usage);
+ json_builder_add_double_value(builder, reports[i].process_report.usage);
json_builder_end_object(builder);
json_builder_end_object(builder);
- i++;
}
json_builder_end_array(builder);
* @return dynamically allocated string on NULL on error.
* @remark returned value should be released with @free
*/
-char *report_json_serializer_serialize_top_cpu_usage_reports(struct app_cpu_usage_report **reports);
+char *report_json_serializer_serialize_top_cpu_usage_reports(struct app_cpu_usage_report *reports, int len);
/**
* @brief Serializes app_memory_usage_reports to json string
* @return dynamically allocated string on NULL on error.
* @remark returned value should be released with @free
*/
-char *report_json_serializer_serialize_top_memory_usage_reports(struct app_memory_usage_report **reports);
+char *report_json_serializer_serialize_top_memory_usage_reports(struct app_memory_usage_report *reports, int len);
#endif
* limitations under the License.
*/
+#include <unistd.h>
+
#include "stats.h"
#include "procfs.h"
#include "err-check.h"
+#include "clock.h"
static int ncpus;
+static float timescale;
int stats_get_system_cpu_usage_average(struct stats_system *previous, struct stats_system *current, float *usage)
{
{
ON_NULL_RETURN_VAL(sys, -1);
- struct procfs_system_cpu_usage_info info;
+ struct procfs_system_cpu_usage_info cpu_info;
+ struct procfs_system_memory_usage_info mem_info;
+
+ if (procfs_read_system_cpu_usage(&cpu_info) != 0) {
+ return -1;
+ }
- if (procfs_read_system_cpu_usage(&info) != 0) {
+ if (procfs_read_system_memory_usage(&mem_info) != 0) {
+ ERR("procfs_read_system_memory_usage failed.");
return -1;
}
- sys->busy_ticks = info.user + info.system + info.nice + info.irq + info.softirq;
- sys->total_ticks = info.user + info.system + info.nice + info.irq + info.softirq + info.idle + info.iowait;
+ sys->busy_ticks = cpu_info.user + cpu_info.system + cpu_info.nice + cpu_info.irq + cpu_info.softirq;
+ sys->total_ticks = cpu_info.user + cpu_info.system + cpu_info.nice + cpu_info.irq + cpu_info.softirq + cpu_info.idle + cpu_info.iowait;
+ sys->memory_used = mem_info.used;
+ sys->total_memory = mem_info.total;
+
+ struct timespec now = clock_monotonic_get();
+ sys->frame_time = 1.0f / (now.tv_sec - sys->update_time.tv_sec + ((float)now.tv_nsec - sys->update_time.tv_nsec) / 1000000000.0f);
+ sys->update_time = now;
return 0;
}
-int stats_get_system_memory_usage(float *usage)
+int stats_get_system_memory_usage(struct stats_system *sys, float *usage)
{
ON_NULL_RETURN_VAL(usage, -1);
- struct procfs_system_memory_usage_info info;
-
- if (procfs_read_system_memory_usage(&info) != 0) {
- ERR("procfs_read_system_memory_usage failed.");
- return -1;
- }
-
- *usage = (float)info.used / info.total;
+ *usage = (float)sys->memory_used / sys->total_memory;
return 0;
}
stats->process_ticks = proc_info.stime + proc_info.utime;
stats->system_ticks = sys_info.user + sys_info.system + sys_info.nice + sys_info.idle;
-
return 0;
}
return 0;
}
+float stats_get_cpu_usage_percentage(unsigned long long ticks_delta, float ticks_time_inverted)
+{
+ return (float)ticks_delta * ticks_time_inverted * timescale;
+}
+
int stats_init()
{
if (procfs_read_cpu_count(&ncpus) != 0) {
ERR("procfs_read_cpu_count failed.");
return -1;
}
+ timescale = 1.0f / (float)sysconf(_SC_CLK_TCK);
return 0;
}
#ifndef _STATS_H_
#define _STATS_H_
+#include <time.h>
+
/**
* @brief System's statistics snapshot
*/
{
unsigned long long busy_ticks;
unsigned long long total_ticks;
+ unsigned long long memory_used;
+ unsigned long long total_memory;
+ struct timespec update_time;
+ float frame_time;
};
/**
*
* @return: 0 on success, other value on error
*/
-int stats_get_system_memory_usage(float *usage);
+int stats_get_system_memory_usage(struct stats_system *stats, float *usage);
/**
* @brief Takes process statistics snapshot.
*/
int stats_get_load_averages(float *a1, float *a5, float *a15);
+/**
+ * @brief Gets CPU usage percentage
+ *
+ * @param[in] ticks_delta the amount of clock ticks
+ * @param[in] ticks_time_inverted the time of ticks_delete inverted ^-1
+ *
+ * @return: CPU usage perecentage
+ *
+ * @note in case when process has 2 threads which runs tight loop, the function
+ * will report 200% usage (2.0f).
+ */
+float stats_get_cpu_usage_percentage(unsigned long long ticks_delta, float ticks_time_inverted);
+
#endif
static task_t *create_system_report_task(config_options_e options);
static task_t *create_load_avg_report_task();
static task_t *create_app_report_task(config_options_e options, const char *regex);
+static task_t *create_top_report_task(struct config_data_top options);
static void execute_scan_system_memory(task_t *task);
static void execute_scan_system_cpu(task_t *task);
static void execute_scan_load_avg(task_t *task);
static void execute_scan_app_memory(task_t *task);
static void execute_scan_apps_cpu(task_t *task);
+static void execute_scan_top_cpu(task_t *task);
+static void execute_scan_top_memory(task_t *task);
static void release_system_task(task_t *task);
static void release_app_task(task_t *task);
+static void release_top_task(task_t *task);
task_t *task_factory_create_task(const config_t *config)
{
case APPS:
return create_app_report_task(config->data.apps.options, config->data.apps.app_id);
case TOP:
+ return create_top_report_task(config->data.top);
default:
return NULL;
}
report_generator_free_app_generator(_app_task->report_generator);
g_free(_app_task);
}
+
+static task_t *create_top_report_task(struct config_data_top options)
+{
+ top_task_t *_top_task;
+ report_generator_top_type_e type;
+ task_execute_cb execute;
+
+ switch (options.subject) {
+ case TOP_SUBJECT_APPS:
+ type = REPORT_GENERATOR_TOP_TYPE_APPS;
+ break;
+ case TOP_SUBJECT_ALL:
+ type = REPORT_GENERATOR_TOP_TYPE_ALL;
+ break;
+ default:
+ return NULL;
+ }
+
+ switch (options.options) {
+ case OBSERVE_CPU:
+ execute = execute_scan_top_cpu;
+ break;
+ case OBSERVE_MEMORY:
+ execute = execute_scan_top_memory;
+ break;
+ default:
+ return NULL;
+ }
+
+ _top_task = (top_task_t *)g_malloc(sizeof(top_task_t));
+ _top_task->task.execute = execute;
+ _top_task->task.release = release_top_task;
+ _top_task->report_generator = report_generator_new_top_report_generator(type, options.top);
+ if (!_top_task->report_generator) {
+ g_free(_top_task);
+ return NULL;
+ }
+
+ return &_top_task->task;
+}
+
+static void release_top_task(task_t *task)
+{
+ ON_NULL_RETURN(task);
+
+ top_task_t *_top_task = container_of(task, top_task_t, task);
+
+ report_generator_free_top_generator(_top_task->report_generator);
+ g_free(_top_task);
+}
+
+static void execute_scan_top_cpu(task_t *task)
+{
+ ON_NULL_RETURN(task);
+ top_task_t *_top_task = container_of(task, top_task_t, task);
+
+ struct app_cpu_usage_report *reports;
+ int n_reports;
+
+ report_generator_generate_top_cpu_report(_top_task->report_generator, &reports, &n_reports);
+
+ char *json_report = report_json_serializer_serialize_top_cpu_usage_reports(reports, n_reports);
+ ipc_send_report(json_report);
+ g_free(json_report);
+ free(reports);
+}
+
+static void execute_scan_top_memory(task_t *task)
+{
+ ON_NULL_RETURN(task);
+ top_task_t *_top_task = container_of(task, top_task_t, task);
+
+ struct app_memory_usage_report *reports;
+ int n_reports;
+
+ report_generator_generate_top_memory_report(_top_task->report_generator, &reports, &n_reports);
+
+ char *json_report = report_json_serializer_serialize_top_memory_usage_reports(reports, n_reports);
+ ipc_send_report(json_report);
+ g_free(json_report);
+ free(reports);
+}
report_generator_process_t *report_generator;
} process_task_t;
+/**
+ * @brief Top task structure.
+ */
+typedef struct top_task
+{
+ task_t task;
+ report_generator_top_t *report_generator;
+} top_task_t;
/**
* @brief Releases the task.