diff --git a/meson_options.txt b/meson_options.txt index faedfeada..64f0be347 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -301,3 +301,7 @@ option('gesture-recognition', type: 'boolean', value: true, description: 'Enable gesture recognition using libinput, needed to get swipe bindings beeing detected.') +option('procstats', + type: 'boolean', + value: true, + description: 'enable procstats module: (default=true)') diff --git a/src/bin/e_client.h b/src/bin/e_client.h index 6c178819c..4725ba53c 100644 --- a/src/bin/e_client.h +++ b/src/bin/e_client.h @@ -236,6 +236,7 @@ struct E_Client } pre_cb; Eina_Rectangle client; //client geom Evas_Object *frame; //comp object + Evas_Object *frame_object; //frame border comp object Evas_Object *agent; //resize agent; E_Zone *zone; E_Desk *desk; diff --git a/src/bin/e_comp_object.c b/src/bin/e_comp_object.c index 78903c7cf..76e2d7032 100644 --- a/src/bin/e_comp_object.c +++ b/src/bin/e_comp_object.c @@ -3553,7 +3553,7 @@ e_comp_object_frame_theme_set(Evas_Object *obj, const char *name) if (ok) { - cw->frame_object = o; + cw->frame_object = cw->ec->frame_object = o; edje_object_signal_emit(o, "e,version,22", "e"); eina_stringshare_del(cw->frame_theme); cw->frame_theme = theme; diff --git a/src/modules/meson.build b/src/modules/meson.build index 735cd0e77..358824631 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -46,6 +46,7 @@ mods = [ 'packagekit', 'vkbd', 'gesture_recognition', + 'procstats', # modules have a custom binary as well 'battery', 'cpufreq', diff --git a/src/modules/procstats/e-module-procstats.edj b/src/modules/procstats/e-module-procstats.edj new file mode 100644 index 000000000..75aa3dc0b Binary files /dev/null and b/src/modules/procstats/e-module-procstats.edj differ diff --git a/src/modules/procstats/e_mod_main.c b/src/modules/procstats/e_mod_main.c new file mode 100644 index 000000000..c58eaaa9d --- /dev/null +++ b/src/modules/procstats/e_mod_main.c @@ -0,0 +1,233 @@ +#include +#include "process.h" + +static Eina_List *_clients = NULL; +static Ecore_Timer *_clients_timer = NULL; + +#define _TIMER_FREQ 3.0 + +typedef struct _Proc_Stats Proc_Stats; +struct _Proc_Stats +{ + E_Client *client; + Evas_Object *obj; + pid_t pid; + uint64_t mem_size; + uint64_t cpu_time; + uint64_t cpu_time_prev; +}; + +static Eina_Bool +_proc_stats_item_exists(E_Client *ec) +{ + Proc_Stats *item; + Eina_List *l; + + EINA_LIST_FOREACH(_clients, l, item) + { + if (item->pid == ec->netwm.pid) return 1; + } + return 0; +} + +static void +_proc_stats_client_del_cb(void *data EINA_UNUSED, Evas *evas EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + edje_object_signal_emit(obj, "e,state,procstats,off", "e"); +} + +static void +_proc_stats_item_add(E_Client *ec) +{ + Evas_Object *o; + Proc_Stats *item; + + if (ec->internal || ec->netwm.pid == -1) return; + if (!ec->frame_object) return; + if (_proc_stats_item_exists(ec)) return; + + o = edje_object_add(evas_object_evas_get(ec->frame)); + + e_theme_edje_object_set(o, "base/theme/borders", + "e/widgets/border/default/border"); + + if (!edje_object_part_exists(o, "e.procstats.text")) return; + + item = calloc(1, sizeof(Proc_Stats)); + EINA_SAFETY_ON_NULL_RETURN(item); + item->pid = ec->netwm.pid; + item->client = ec; + item->obj = ec->frame_object; + + edje_object_signal_emit(item->obj, "e,state,procstats,on", "e"); + evas_object_event_callback_add(item->obj, EVAS_CALLBACK_DEL, _proc_stats_client_del_cb, NULL); + + _clients = eina_list_append(_clients, item); +} + +static void +_proc_stats_item_del(Proc_Stats *item) +{ + edje_object_signal_emit(item->obj, "e,state,procstats,off", "e"); + free(item); +} + +static void +_proc_stats_item_remove(Proc_Stats *item) +{ + Eina_List *l, *l_next; + Proc_Stats *it; + + EINA_LIST_FOREACH_SAFE(_clients, l, l_next, it) + { + if (it == item) + { + _proc_stats_item_del(item); + _clients = eina_list_remove_list(_clients, l); + return; + } + } +} + +static Eina_Bool +_proc_stats_item_gone(Proc_Stats *item) +{ + Eina_List *l; + E_Client *ec; + + EINA_LIST_FOREACH(e_comp->clients, l, ec) + { + if (item->client == ec) return 0; + } + + return 1; +} + +static void +_proc_stats_item_children_update(Eina_List *children, Proc_Stats *item) +{ + Eina_List *l; + Proc_Info *child; + + EINA_LIST_FOREACH(children, l, child) + { + item->mem_size += child->mem_size; + item->cpu_time += child->cpu_time; + if (child->children) + _proc_stats_item_children_update(child->children, item); + } +} + +static void +_proc_stats_item_display(Proc_Stats *item) +{ + Edje_Message_Int_Set *msg; + int mem_size; + + if (item->cpu_time_prev > item->cpu_time) + return; + + if (!item->cpu_time_prev) item->cpu_time_prev = item->cpu_time; + + mem_size = item->mem_size >> 10; + msg = malloc(sizeof(Edje_Message_Int_Set) + (sizeof(int) * 3)); + EINA_SAFETY_ON_NULL_RETURN(msg); + msg->count = 4; + msg->val[0] = eina_cpu_count(); + msg->val[1] = (item->cpu_time - item->cpu_time_prev) / _TIMER_FREQ; + msg->val[2] = mem_size; + msg->val[3] = 0; + edje_object_message_send(item->obj, EDJE_MESSAGE_INT_SET, 1, msg); + free(msg); +} + +static void +_proc_stats_item_update(Eina_List *procs, Proc_Stats *item) +{ + Proc_Info *proc; + Eina_List *l; + + EINA_LIST_FOREACH(procs, l, proc) + { + if (proc->pid == item->pid) + { + item->cpu_time = item->mem_size = 0; + item->mem_size += proc->mem_size; + item->cpu_time += proc->cpu_time; + _proc_stats_item_children_update(proc->children, item); + break; + } + } + _proc_stats_item_display(item); + item->cpu_time_prev = item->cpu_time; +} + +static Eina_Bool +_proc_stats_timer_cb(void *data EINA_UNUSED) +{ + Eina_List *procs, *l; + E_Client *ec; + Proc_Info *proc; + Proc_Stats *item; + + EINA_LIST_FOREACH(e_comp->clients, l, ec) + { + if (!_proc_stats_item_exists(ec)) + _proc_stats_item_add(ec); + } + + procs = proc_info_all_children_get(); + + EINA_LIST_FOREACH(_clients, l, item) + { + if (_proc_stats_item_gone(item)) + _proc_stats_item_remove(item); + else + _proc_stats_item_update(procs, item); + } + + EINA_LIST_FREE(procs, proc) + proc_info_free(proc); + + return 1; +} + +E_API E_Module_Api e_modapi = +{ + E_MODULE_API_VERSION, + "Procstats" +}; + +E_API int +e_modapi_init(E_Module *m EINA_UNUSED) +{ + _proc_stats_timer_cb(NULL); + + _clients_timer = ecore_timer_add(_TIMER_FREQ, _proc_stats_timer_cb, NULL); + + return 1; +} + +E_API int +e_modapi_shutdown(E_Module *m EINA_UNUSED) +{ + Proc_Stats *item; + + if (_clients_timer) + ecore_timer_del(_clients_timer); + + _clients_timer = NULL; + + EINA_LIST_FREE(_clients, item) + _proc_stats_item_del(item); + + _clients = NULL; + + return 1; +} + +E_API int +e_modapi_save(E_Module *m EINA_UNUSED) +{ + return 1; +} diff --git a/src/modules/procstats/meson.build b/src/modules/procstats/meson.build new file mode 100644 index 000000000..ddd429527 --- /dev/null +++ b/src/modules/procstats/meson.build @@ -0,0 +1,12 @@ +deps_custom = declare_dependency(link_args: [] ) +if host_os == 'openbsd' or host_os == 'freebsd' or host_os == 'dragonfly' + deps_custom = declare_dependency(link_args : [ '-lkvm' ]) +endif + +module_deps += deps_custom + +src = files( + 'e_mod_main.c', + 'process.c', + 'process.h', + ) diff --git a/src/modules/procstats/module.desktop b/src/modules/procstats/module.desktop new file mode 100644 index 000000000..844b80324 --- /dev/null +++ b/src/modules/procstats/module.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Link +Name=Procstats +Comment=Monitor window process resources. +Icon=e-module-procstats +X-Enlightenment-ModuleType=system diff --git a/src/modules/procstats/process.c b/src/modules/procstats/process.c new file mode 100644 index 000000000..c47415e3e --- /dev/null +++ b/src/modules/procstats/process.c @@ -0,0 +1,970 @@ +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) +# include +# include +# include +# include +# include +#endif + +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include "process.h" +#include +#include +#include + +#if defined(__linux__) && !defined(PF_KTHREAD) +# define PF_KTHREAD 0x00200000 +#endif + +#define U64(n) (uint64_t) n +#define MEMSIZE U64 + +static Eina_Bool _show_kthreads = EINA_FALSE; + +#define DO_WE_REALLY_CARE 0 + +void +proc_info_kthreads_show_set(Eina_Bool enabled) +{ + _show_kthreads = enabled; +} + +Eina_Bool +proc_info_kthreads_show_get(void) +{ + return _show_kthreads; +} + +static const char * +_process_state_name(char state) +{ + const char *statename = NULL; +#if defined(__linux__) + + switch (state) + { + case 'D': + statename = "dsleep"; + break; + + case 'I': + statename = "idle"; + break; + + case 'R': + statename = "run"; + break; + + case 'S': + statename = "sleep"; + break; + + case 'T': + case 't': + statename = "stop"; + break; + + case 'X': + statename = "dead"; + break; + + case 'Z': + statename = "zomb"; + break; + } +#else + switch (state) + { + case SIDL: + statename = "idle"; + break; + + case SRUN: + statename = "run"; + break; + + case SSLEEP: + statename = "sleep"; + break; + + case SSTOP: + statename = "stop"; + break; + +#if !defined(__OpenBSD__) + case SWAIT: + statename = "wait"; + break; + + case SLOCK: + statename = "lock"; + break; + +#endif + case SZOMB: + statename = "zomb"; + break; + +#if defined(__OpenBSD__) + case SDEAD: + statename = "dead"; + break; + + case SONPROC: + statename = "onproc"; + break; +#endif + } +#endif + return statename; +} + +#if defined(__linux__) + +static unsigned long +_parse_line(const char *line) +{ + char *p, *tok; + + p = strchr(line, ':') + 1; + while (isspace(*p)) + p++; + tok = strtok(p, " "); + + return atol(tok); +} + +static void +_mem_size(Proc_Info *proc) +{ + FILE *f; + char buf[1024]; + unsigned int dummy, size, shared, resident, data, text; + static int pagesize = 0; + + if (!pagesize) pagesize = getpagesize(); + + f = fopen(eina_slstr_printf("/proc/%d/statm", proc->pid), "r"); + if (!f) return; + + if (fgets(buf, sizeof(buf), f)) + { + if (sscanf(buf, "%u %u %u %u %u %u %u", + &size, &resident, &shared, &text, + &dummy, &data, &dummy) == 7) + { + proc->mem_rss = MEMSIZE(resident) * MEMSIZE(pagesize); + proc->mem_shared = MEMSIZE(shared) * MEMSIZE(pagesize); + proc->mem_size = proc->mem_rss - proc->mem_shared; + proc->mem_virt = MEMSIZE(size) * MEMSIZE(pagesize); + } + } + + fclose(f); +} + +static void +_cmd_args(Proc_Info *p, char *name, size_t len) +{ + char line[4096]; + int pid = p->pid; + + char *link = ecore_file_readlink(eina_slstr_printf("/proc/%d/exe", pid)); + if (link) + { + snprintf(name, len, "%s", ecore_file_file_get(link)); + free(link); + } + + FILE *f = fopen(eina_slstr_printf("/proc/%d/cmdline", pid), "r"); + if (f) + { + if (fgets(line, sizeof(line), f)) + { + Eina_Strbuf *buf = eina_strbuf_new(); + const char *n; + + if (ecore_file_exists(line)) + snprintf(name, len, "%s", ecore_file_file_get(line)); + + n = line; + while (*n && (*n + 1)) + { + eina_strbuf_append(buf, n); + n = strchr(n, '\0') + 1; + if (*n && (*n + 1)) eina_strbuf_append(buf, " "); + } + p->arguments = eina_strbuf_release(buf); + } + fclose(f); + } + + char *end = strchr(name, ' '); + if (end) *end = '\0'; + + p->command = strdup(name); +} + +static int +_uid(int pid) +{ + FILE *f; + int uid = -1; + char line[1024]; + + f = fopen(eina_slstr_printf("/proc/%d/status", pid), "r"); + if (!f) return -1; + + while ((fgets(line, sizeof(line), f)) != NULL) + { + if (!strncmp(line, "Uid:", 4)) + { + uid = _parse_line(line); + break; + } + } + + fclose(f); + + return uid; +} + +static int64_t +_boot_time(void) +{ + FILE *f; + int64_t boot_time; + char buf[4096]; + double uptime = 0.0; + + f = fopen("/proc/uptime", "r"); + if (!f) return 0; + + if (fgets(buf, sizeof(buf), f)) + sscanf(buf, "%lf", &uptime); + else boot_time = 0; + + fclose(f); + + if (uptime > 0.0) + boot_time = time(NULL) - (time_t) uptime; + + return boot_time; +} + +typedef struct { + int pid, ppid, utime, stime, cutime, cstime; + int psr, pri, nice, numthreads; + long long int start_time; + char state; + unsigned int mem_rss, flags; + unsigned long mem_virt; + char name[1024]; +} Stat; + +static Eina_Bool +_stat(const char *path, Stat *st) +{ + FILE *f; + char line[4096]; + int dummy, res = 0; + static int64_t boot_time = 0; + + if (!boot_time) boot_time = _boot_time(); + + memset(st, 0, sizeof(Stat)); + + f = fopen(path, "r"); + if (!f) return EINA_FALSE; + + if (fgets(line, sizeof(line), f)) + { + char *end, *start = strchr(line, '(') + 1; + end = strchr(line, ')'); + + strncpy(st->name, start, end - start); + st->name[end - start] = '\0'; + res = sscanf(end + 2, "%c %d %d %d %d %d %u %u %u %u %u %d %d %d" + " %d %d %d %u %u %lld %lu %u %u %u %u %u %u %u %d %d %d %d %u" + " %d %d %d %d %d %d %d %d %d", + &st->state, &st->ppid, &dummy, &dummy, &dummy, &dummy, &st->flags, + &dummy, &dummy, &dummy, &dummy, &st->utime, &st->stime, &st->cutime, + &st->cstime, &st->pri, &st->nice, &st->numthreads, &dummy, &st->start_time, + &st->mem_virt, &st->mem_rss, &dummy, &dummy, &dummy, &dummy, &dummy, + &dummy, &dummy, &dummy, &dummy, &dummy, &dummy, &dummy, + &dummy, &dummy, &st->psr, &dummy, &dummy, &dummy, &dummy, &dummy); + } + fclose(f); + + if (res != 42) return EINA_FALSE; + + st->start_time /= sysconf(_SC_CLK_TCK); + st->start_time += boot_time; + + return EINA_TRUE; +} + +static Eina_List * +_process_list_linux_get(void) +{ + Eina_List *files, *list; + char *n; + Stat st; + + list = NULL; + + files = ecore_file_ls("/proc"); + EINA_LIST_FREE(files, n) + { + int pid = atoi(n); + free(n); + + if (!pid) continue; + + if (!_stat(eina_slstr_printf("/proc/%d/stat", pid), &st)) + continue; + + if (st.flags & PF_KTHREAD && !proc_info_kthreads_show_get()) + continue; + + Proc_Info *p = calloc(1, sizeof(Proc_Info)); + if (!p) return NULL; + + p->pid = pid; + p->ppid = st.ppid; + p->uid = _uid(pid); + p->cpu_id = st.psr; + p->start = st.start_time; + p->state = _process_state_name(st.state); + p->cpu_time = st.utime + st.stime; + p->nice = st.nice; + p->priority = st.pri; + p->numthreads = st.numthreads; + _mem_size(p); + _cmd_args(p, st.name, sizeof(st.name)); + + list = eina_list_append(list, p); + } + + return list; +} + +static void +_proc_thread_info(Proc_Info *p) +{ + Eina_List *files; + char *n; + Stat st; + + files = ecore_file_ls(eina_slstr_printf("/proc/%d/task", p->pid)); + EINA_LIST_FREE(files, n) + { + int tid = atoi(n); + free(n); + if (!_stat(eina_slstr_printf("/proc/%d/task/%d/stat", p->pid, tid), &st)) + continue; + + Proc_Info *t = calloc(1, sizeof(Proc_Info)); + if (!t) continue; + t->cpu_id = st.psr; + t->state = _process_state_name(st.state); + t->cpu_time = st.utime + st.stime; + t->nice = st.nice; + t->priority = st.pri; + t->numthreads = st.numthreads; + t->mem_virt = st.mem_virt; + t->mem_rss = st.mem_rss; + + t->tid = tid; + t->thread_name = strdup(st.name); + + p->threads = eina_list_append(p->threads, t); + } +} + +Proc_Info * +proc_info_by_pid(int pid) +{ + Stat st; + + if (!_stat(eina_slstr_printf("/proc/%d/stat", pid), &st)) + return NULL; + + Proc_Info *p = calloc(1, sizeof(Proc_Info)); + if (!p) return NULL; + + p->pid = pid; + p->ppid = st.ppid; + p->uid = _uid(pid); + p->cpu_id = st.psr; + p->start = st.start_time; + p->state = _process_state_name(st.state); + p->cpu_time = st.utime + st.stime; + p->priority = st.pri; + p->nice = st.nice; + p->numthreads = st.numthreads; + _mem_size(p); + _cmd_args(p, st.name, sizeof(st.name)); + + _proc_thread_info(p); + + return p; +} + +#endif + +#if defined(__OpenBSD__) + +static void +_proc_get(Proc_Info *p, struct kinfo_proc *kp) +{ + static int pagesize = 0; + + if (!pagesize) pagesize = getpagesize(); + + p->pid = kp->p_pid; + p->ppid = kp->p_ppid; + p->uid = kp->p_uid; + p->cpu_id = kp->p_cpuid; + p->start = kp->p_ustart_sec; + p->state = _process_state_name(kp->p_stat); + p->cpu_time = kp->p_uticks + kp->p_sticks + kp->p_iticks; + p->mem_virt = p->mem_size = (MEMSIZE(kp->p_vm_tsize) * MEMSIZE(pagesize)) + + (MEMSIZE(kp->p_vm_dsize) * MEMSIZE(pagesize)) + (MEMSIZE(kp->p_vm_ssize) * MEMSIZE(pagesize)); + p->mem_rss = MEMSIZE(kp->p_vm_rssize) * MEMSIZE(pagesize); + p->mem_size = p->mem_rss; + p->priority = kp->p_priority - PZERO; + p->nice = kp->p_nice - NZERO; + p->tid = kp->p_tid; +} + +static void +_cmd_get(Proc_Info *p, kvm_t *kern, struct kinfo_proc *kp) +{ + char **args; + char name[1024]; + + if ((args = kvm_getargv(kern, kp, sizeof(name)-1))) + { + Eina_Strbuf *buf = eina_strbuf_new(); + for (int i = 0; args[i]; i++) + { + eina_strbuf_append(buf, args[i]); + if (args[i + 1]) + eina_strbuf_append(buf, " "); + } + p->arguments = eina_strbuf_string_steal(buf); + eina_strbuf_free(buf); + + if (args[0] && ecore_file_exists(args[0])) + p->command = strdup(ecore_file_file_get(args[0])); + } + + if (!p->command) + p->command = strdup(kp->p_comm); +} + +Proc_Info * +proc_info_by_pid(int pid) +{ + struct kinfo_proc *kp, *kpt; + kvm_t *kern; + char errbuf[_POSIX2_LINE_MAX]; + int count, pid_count; + + kern = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); + if (!kern) return NULL; + + kp = kvm_getprocs(kern, KERN_PROC_PID, pid, sizeof(*kp), &count); + if (!kp) return NULL; + + if (count == 0) return NULL; + + Proc_Info *p = calloc(1, sizeof(Proc_Info)); + if (!p) return NULL; + + _proc_get(p, kp); + _cmd_get(p, kern, kp); + + kp = kvm_getprocs(kern, KERN_PROC_SHOW_THREADS, 0, sizeof(*kp), &pid_count); + + for (int i = 0; i < pid_count; i++) + { + if (kp[i].p_pid != p->pid) continue; + + kpt = &kp[i]; + + if (kpt->p_tid <= 0) continue; + + Proc_Info *t = calloc(1, sizeof(Proc_Info)); + if (!t) continue; + + _proc_get(t, kpt); + + t->tid = kpt->p_tid; + t->thread_name = strdup(kpt->p_comm); + + p->threads = eina_list_append(p->threads, t); + } + + p->numthreads = eina_list_count(p->threads); + + kvm_close(kern); + + return p; +} + +static Eina_List * +_process_list_openbsd_get(void) +{ + struct kinfo_proc *kps, *kp; + Proc_Info *p; + char errbuf[4096]; + kvm_t *kern; + int pid_count; + Eina_List *list = NULL; + + kern = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); + if (!kern) return NULL; + + kps = kvm_getprocs(kern, KERN_PROC_ALL, 0, sizeof(*kps), &pid_count); + if (!kps) return NULL; + + for (int i = 0; i < pid_count; i++) + { + p = calloc(1, sizeof(Proc_Info)); + if (!p) return NULL; + + kp = &kps[i]; + + _proc_get(p, kp); + _cmd_get(p, kern, kp); + + list = eina_list_append(list, p); + } + + kvm_close(kern); + + return list; +} + +#endif + +#if defined(__FreeBSD__) || defined(__DragonFly__) + +static int +_pid_max(void) +{ + size_t len; + static int pid_max = 0; + + if (pid_max != 0) return pid_max; + + len = sizeof(pid_max); + if (sysctlbyname("kern.pid_max", &pid_max, &len, NULL, 0) == -1) + { +#if defined(__FreeBSD__) + pid_max = 99999; +#elif defined(__DragonFly__) + pid_max = 999999; +#else + pid_max = PID_MAX; +#endif + } + + return pid_max; +} + +static void +_cmd_get(Proc_Info *p, struct kinfo_proc *kp) +{ + kvm_t * kern; + char **args; + char name[1024]; + Eina_Bool have_command = EINA_FALSE; + + kern = kvm_open(NULL, "/dev/null", NULL, O_RDONLY, "kvm_open"); + if (kern) + { + if ((args = kvm_getargv(kern, kp, sizeof(name)-1)) && (args[0])) + { + char *base = strdup(args[0]); + if (base) + { + char *spc = strchr(base, ' '); + if (spc) *spc = '\0'; + + if (ecore_file_exists(base)) + { + snprintf(name, sizeof(name), "%s", basename(base)); + have_command = EINA_TRUE; + } + free(base); + } + Eina_Strbuf *buf = eina_strbuf_new(); + for (int i = 0; args[i] != NULL; i++) + { + eina_strbuf_append(buf, args[i]); + if (args[i + 1]) + eina_strbuf_append(buf, " "); + } + p->arguments = eina_strbuf_string_steal(buf); + eina_strbuf_free(buf); + } + kvm_close(kern); + } + + if (!have_command) + snprintf(name, sizeof(name), "%s", kp->ki_comm); + + p->command = strdup(name); +} + +static Proc_Info * +_proc_thread_info(struct kinfo_proc *kp, Eina_Bool is_thread) +{ + struct rusage *usage; + Proc_Info *p; + static int pagesize = 0; + + if (!pagesize) pagesize = getpagesize(); + + p = calloc(1, sizeof(Proc_Info)); + if (!p) return NULL; + + p->pid = kp->ki_pid; + p->ppid = kp->ki_ppid; + p->uid = kp->ki_uid; + + if (!is_thread) + _cmd_get(p, kp); + + p->cpu_id = kp->ki_oncpu; + if (p->cpu_id == -1) + p->cpu_id = kp->ki_lastcpu; + + usage = &kp->ki_rusage; + + p->cpu_time = (usage->ru_utime.tv_sec * 1000000) + usage->ru_utime.tv_usec + + (usage->ru_stime.tv_sec * 1000000) + usage->ru_stime.tv_usec; + // XXX: See kern.sched.idlespins + p->cpu_time /= 10000; + p->state = _process_state_name(kp->ki_stat); + p->mem_virt = kp->ki_size; + p->mem_rss = MEMSIZE(kp->ki_rssize) * MEMSIZE(pagesize); + p->start = kp->ki_start.tv_sec; + p->mem_size = p->mem_rss; + p->nice = kp->ki_nice - NZERO; + p->priority = kp->ki_pri.pri_level - PZERO; + p->numthreads = kp->ki_numthreads; + + p->tid = kp->ki_tid; + p->thread_name = strdup(kp->ki_tdname); + + return p; +} + +static Eina_List * +_process_list_freebsd_fallback_get(void) +{ + Eina_List *list; + struct kinfo_proc kp; + int mib[4]; + size_t len; + static int pid_max; + + pid_max = _pid_max(); + + list = NULL; + + len = sizeof(int); + if (sysctlnametomib("kern.proc.pid", mib, &len) == -1) + return NULL; + + for (int i = 1; i <= pid_max; i++) + { + mib[3] = i; + len = sizeof(kp); + if (sysctl(mib, 4, &kp, &len, NULL, 0) == -1) + continue; + + if (kp.ki_flag & P_KPROC && !proc_info_kthreads_show_get()) + continue; + + Proc_Info *p = _proc_thread_info(&kp, EINA_FALSE); + if (p) + list = eina_list_append(list, p); + } + + return list; +} + +static Eina_List * +_process_list_freebsd_get(void) +{ + kvm_t *kern; + Eina_List *list = NULL; + struct kinfo_proc *kps, *kp; + char errbuf[_POSIX2_LINE_MAX]; + int pid_count; + + kern = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf); + if (!kern) + return _process_list_freebsd_fallback_get(); + + kps = kvm_getprocs(kern, KERN_PROC_PROC, 0, &pid_count); + if (!kps) + { + kvm_close(kern); + return _process_list_freebsd_fallback_get(); + } + + for (int i = 0; i < pid_count; i++) + { + if (kps[i].ki_flag & P_KPROC && !proc_info_kthreads_show_get()) + continue; + + kp = &kps[i]; + + Proc_Info *p = _proc_thread_info(kp, EINA_FALSE); + if (p) + list = eina_list_append(list, p); + } + + kvm_close(kern); + + return list; +} + +static Proc_Info * +_proc_info_by_pid_fallback(int pid) +{ + struct kinfo_proc kp; + int mib[4]; + size_t len; + + len = sizeof(int); + if (sysctlnametomib("kern.proc.pid", mib, &len) == -1) + return NULL; + + mib[3] = pid; + + len = sizeof(kp); + if (sysctl(mib, 4, &kp, &len, NULL, 0) == -1) + return NULL; + + Proc_Info *p = _proc_thread_info(&kp, EINA_FALSE); + + return p; +} + +Proc_Info * +proc_info_by_pid(int pid) +{ + kvm_t *kern; + struct kinfo_proc *kps, *kp; + char errbuf[_POSIX2_LINE_MAX]; + int pid_count; + + kern = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf); + if (!kern) + return _proc_info_by_pid_fallback(pid); + + kps = kvm_getprocs(kern, KERN_PROC_ALL, 0, &pid_count); + if (!kps) + { + kvm_close(kern); + return _proc_info_by_pid_fallback(pid); + } + + Proc_Info *p = NULL; + + for (int i = 0; i < pid_count; i++) + { + if (kps[i].ki_flag & P_KPROC && !proc_info_kthreads_show_get()) + continue; + if (kps[i].ki_pid != pid) + continue; + + kp = &kps[i]; + Proc_Info *t = _proc_thread_info(kp, EINA_TRUE); + if (!p) + { + p = _proc_thread_info(kp, EINA_FALSE); + p->cpu_time = 0; + } + + p->cpu_time += t->cpu_time; + p->threads = eina_list_append(p->threads, t); + } + + kvm_close(kern); + + if (!p) return _proc_info_by_pid_fallback(pid); + + return p; +} +#endif + +void +proc_info_free(Proc_Info *proc) +{ + Proc_Info *t; + + EINA_LIST_FREE(proc->threads, t) + { + proc_info_free(t); + } + + if (proc->children) + eina_list_free(proc->children); + + if (proc->command) + free(proc->command); + if (proc->arguments) + free(proc->arguments); + if (proc->thread_name) + free(proc->thread_name); + + free(proc); +} + +Eina_List * +proc_info_all_get(void) +{ + Eina_List *processes; + +#if defined(__linux__) + processes = _process_list_linux_get(); +#elif defined(__FreeBSD__) || defined(__DragonFly__) + processes = _process_list_freebsd_get(); +#elif defined(__OpenBSD__) + processes = _process_list_openbsd_get(); +#else + processes = NULL; +#endif + + return processes; +} + +static Eina_Bool +_child_add(Eina_List *parents, Proc_Info *child) +{ + Eina_List *l; + Proc_Info *parent; + + EINA_LIST_FOREACH(parents, l, parent) + { + if (parent->pid == child->ppid) + { + parent->children = eina_list_append(parent->children, child); + return 1; + } + } + + return 0; +} + +Eina_List * +proc_info_all_children_get() +{ + Proc_Info *proc; + Eina_List *l; + Eina_List *procs; + + procs = proc_info_all_get(); + + EINA_LIST_FOREACH(procs, l, proc) + { + int ok =_child_add(procs, proc); + (void) ok; + } + + return procs; +} + +Eina_List * +_append_wanted(Eina_List *wanted, Eina_List *tree) +{ + Eina_List *l; + Proc_Info *parent; + + EINA_LIST_FOREACH(tree, l, parent) + { + wanted = eina_list_append(wanted, parent); + if (parent->children) + wanted = _append_wanted(wanted, parent->children); + } + return wanted; +} + +Eina_List * +proc_info_pid_children_get(pid_t pid) +{ + Proc_Info *proc; + Eina_List *l, *procs, *wanted = NULL; + + procs = proc_info_all_children_get(); + + EINA_LIST_FOREACH(procs, l, proc) + { + if (!wanted && proc->pid == pid) + { + wanted = eina_list_append(wanted, proc); + if (proc->children) + wanted = _append_wanted(wanted, proc->children); + } + } + + EINA_LIST_FREE(procs, proc) + { + if (!eina_list_data_find(wanted, proc)) + { + proc_info_free(proc); + } + } + + return wanted; +} + +void +proc_info_all_children_free(Eina_List *pstree) +{ + Proc_Info *parent, *child; + + EINA_LIST_FREE(pstree, parent) + { + EINA_LIST_FREE(parent->children, child) + proc_info_pid_children_free(child); + proc_info_free(parent); + } +} + +void +proc_info_pid_children_free(Proc_Info *proc) +{ + Proc_Info *child; + + EINA_LIST_FREE(proc->children, child) + proc_info_free(child); + + proc_info_free(proc); +} + diff --git a/src/modules/procstats/process.h b/src/modules/procstats/process.h new file mode 100644 index 000000000..83da6aec6 --- /dev/null +++ b/src/modules/procstats/process.h @@ -0,0 +1,65 @@ +#ifndef __PROC_H__ +#define __PROC_H__ + +#include +#include +#include + +#if !defined(PID_MAX) +# define PID_MAX 99999 +#endif + +typedef struct _Proc_Info +{ + pid_t pid; + pid_t ppid; + uid_t uid; + int8_t nice; + int8_t priority; + int cpu_id; + int32_t numthreads; + int64_t cpu_time; + double cpu_usage; + int64_t start; + + uint64_t mem_size; + uint64_t mem_virt; + uint64_t mem_rss; + uint64_t mem_shared; + + char *command; + char *arguments; + const char *state; + + int tid; + char *thread_name; + + Eina_List *threads; + Eina_List *children; +} Proc_Info; + +Eina_List * +proc_info_all_get(void); + +Proc_Info * +proc_info_by_pid(int pid); + +void +proc_info_free(Proc_Info *proc); + +void +proc_info_kthreads_show_set(Eina_Bool enabled); + +Eina_Bool +proc_info_kthreads_show_get(void); + +Eina_List * +proc_info_all_children_get(void); + +Eina_List * +proc_info_pid_children_get(pid_t pid); + +void +proc_info_pid_children_free(Proc_Info *procs); + +#endif