health-check-0.01.58/0000775000175000017500000000000012317222572012654 5ustar kingkinghealth-check-0.01.58/timeval.c0000664000175000017500000000404512317222554014464 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #define _GNU_SOURCE #include #include #include #include "timeval.h" /* * timeval_to_double() * convert timeval to seconds as a double */ double timeval_to_double(const struct timeval *tv) { return (double)tv->tv_sec + ((double)tv->tv_usec / 1000000.0); } /* * timeval_add() * timeval a + b */ struct timeval timeval_add(const struct timeval *a, const struct timeval *b) { struct timeval ret; ret.tv_sec = a->tv_sec + b->tv_sec; ret.tv_usec = a->tv_usec + b->tv_usec; if (ret.tv_usec > 1000000) { int nsec = (ret.tv_usec / 1000000); ret.tv_sec += nsec; ret.tv_usec -= (1000000 * nsec); } return ret; } /* * timeval_sub() * timeval a - b */ struct timeval timeval_sub( const struct timeval *a, const struct timeval *b) { struct timeval ret, _b; _b.tv_sec = b->tv_sec; _b.tv_usec = b->tv_usec; if (a->tv_usec < _b.tv_usec) { int nsec = ((_b.tv_usec - a->tv_usec) / 1000000) + 1; _b.tv_sec += nsec; _b.tv_usec -= (1000000 * nsec); } if (a->tv_usec - _b.tv_usec > 1000000) { int nsec = (a->tv_usec - _b.tv_usec) / 1000000; _b.tv_sec -= nsec; _b.tv_usec += (1000000 * nsec); } ret.tv_sec = a->tv_sec - _b.tv_sec; ret.tv_usec = a->tv_usec - _b.tv_usec; return ret; } health-check-0.01.58/alloc.c0000664000175000017500000000315512317222554014116 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #include #include #include "alloc.h" #if DEBUG_MALLOC void *__malloc(const size_t size, const char *where, const int line) { void *ptr = malloc(size); printf("malloc(%zu) --> %p @ %s %d\n", size, ptr, where, line); return ptr; } void __free(void *ptr, const char *where, const int line) { printf("free(%p) @ %s %d\n", ptr, where, line); } void *__calloc(const size_t nmemb, const size_t size, const char *where, const int line) { void *ptr = calloc(nmemb, size); printf("calloc(%zu, %zu) --> %p @ %s %d\n", nmemb, size, ptr, where, line); return ptr; } void *__realloc(void *ptr, const size_t size, const char *where, const int line) { void *new = realloc(ptr, size); printf("realloc(%p, %zu) --> %p @ %s %d\n", ptr, size, new, where, line); return new; } #endif health-check-0.01.58/event.h0000664000175000017500000000317112317222554014150 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __EVENT_H__ #define __EVENT_H__ #define _GNU_SOURCE #include "json.h" #include "proc.h" #include "list.h" #define TIMER_STATS "/proc/timer_stats" #if (defined(__x86_64__) || defined(__i386__) || defined(__arm__)) #define EVENT_SUPPORTED 1 #else #define EVENT_SUPPORTED 0 #endif /* wakeup event information per process */ typedef struct { proc_info_t *proc; /* Proc specific info */ char *func; /* Kernel waiting func */ char *callback; /* Kernel timer callback func */ char *ident; /* Unique identity */ uint64_t count; /* Number of events */ } event_info_t; extern int event_get_all_pids(const list_t *pids, proc_state state); extern void event_dump_diff(json_object *j_tests, const double duration); extern void event_stop(void); extern void event_init(void); extern void event_cleanup(void); #endif health-check-0.01.58/fnotify.h0000664000175000017500000000453612317222554014513 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __FNOTIFY_H__ #define __FNOTIFY_H__ #define _GNU_SOURCE #include #include "list.h" #include "json.h" #if defined(__aarch64__) #define FNOTIFY_SUPPORTED 0 #else #define FNOTIFY_SUPPORTED 1 #endif /* fnotify file information per process */ typedef struct { proc_info_t *proc; /* Proc specific info */ char *filename; /* Name of device or filename being accessed */ unsigned int mask; /* fnotify access mask */ uint64_t count; /* Count of accesses */ } fnotify_fileinfo_t; /* fnotify wakelock accounting */ typedef struct { proc_info_t *proc; /* Proc specific info */ uint64_t locked; /* Count of wake locks */ uint64_t unlocked; /* Count of wake unlocks */ uint64_t total; /* Total of wake locks and unlocks */ } fnotify_wakelock_info_t; /* fnotify I/O operations counts per process */ typedef struct { uint64_t open_total; /* open() count */ uint64_t close_total; /* close() count */ uint64_t read_total; /* read() count */ uint64_t write_total; /* write() count */ uint64_t total; /* total count */ proc_info_t *proc; /* process information */ } io_ops_t; extern int fnotify_event_init(void); extern int fnotify_event_add(const list_t *pids, const struct fanotify_event_metadata *metadata); extern void fnotify_dump_events(json_object *j_tests, const double duration, const list_t *pids); extern void fnotify_dump_wakelocks(json_object *j_tests, const double duration); extern char *fnotify_get_filename(const pid_t pid, const int fd); extern void fnotify_init(void); extern void fnotify_cleanup(void); #endif health-check-0.01.58/pid.c0000664000175000017500000000723512317222554013603 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "pid.h" #include "list.h" #include "proc.h" #include "alloc.h" /* * get_pid_comm * */ char *get_pid_comm(const pid_t pid) { char buffer[4096]; int fd; ssize_t ret; snprintf(buffer, sizeof(buffer), "/proc/%i/comm", pid); if ((fd = open(buffer, O_RDONLY)) < 0) return NULL; if ((ret = read(fd, buffer, sizeof(buffer))) <= 0) { close(fd); return NULL; } close(fd); buffer[ret-1] = '\0'; return strdup(buffer); } /* * get_pid_cmdline * get process's /proc/pid/cmdline */ char *get_pid_cmdline(const pid_t pid) { char buffer[4096]; char *ptr; int fd; ssize_t ret; snprintf(buffer, sizeof(buffer), "/proc/%i/cmdline", pid); if ((fd = open(buffer, O_RDONLY)) < 0) return NULL; if ((ret = read(fd, buffer, sizeof(buffer))) <= 0) { close(fd); return NULL; } close(fd); if (ret >= (ssize_t)sizeof(buffer)) ret = sizeof(buffer) - 1; buffer[ret] = '\0'; for (ptr = buffer; *ptr && (ptr < buffer + ret); ptr++) { if (*ptr == ' ') *ptr = '\0'; } return strdup(basename(buffer)); } /* * pid_exists() * true if given process with given pid exists */ bool pid_exists(const pid_t pid) { char path[PATH_MAX]; struct stat statbuf; snprintf(path, sizeof(path), "/proc/%i", pid); return stat(path, &statbuf) == 0; } /* * pid_list_find() * find a pid in the pid list */ bool pid_list_find( const pid_t pid, list_t *list) { link_t *l; for (l = list->head; l; l = l->next) { proc_info_t *p = (proc_info_t*)l->data; if (p->pid == pid) return true; } return false; } /* * pid_get_children() * get all the children from the given pid, add * to children list */ static int pid_get_children( const pid_t pid, list_t *children) { link_t *l; for (l = proc_cache_list.head; l; l = l->next) { proc_info_t *p = (proc_info_t*)l->data; if (p->ppid == pid) { if (list_append(children, p) == NULL) { return -1; } pid_get_children(p->pid, children); } } return 0; } /* * pid_list_get_children() * get all the chindren in the given pid list * and add this to the list */ int pid_list_get_children(list_t *pids) { link_t *l; list_t children; proc_info_t *p; list_init(&children); for (l = pids->head; l; l = l->next) { p = (proc_info_t *)l->data; if (pid_get_children(p->pid, &children) < 0) return -1; } /* Append the children onto the pid list */ for (l = children.head; l; l = l->next) { p = (proc_info_t *)l->data; if (!pid_list_find(p->pid, pids)) if (list_append(pids, p) == NULL) return -1; } /* Free the children list, not the data */ list_free(&children, NULL); for (l = pids->head; l; l = l->next) p = (proc_info_t *)l->data; return 0; } health-check-0.01.58/fnotify.c0000664000175000017500000004662312317222554014511 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "list.h" #include "json.h" #include "proc.h" #include "fnotify.h" #include "health-check.h" static list_t fnotify_files, fnotify_wakelocks; /* * fnotify_event_init() * initialize fnotify */ int fnotify_event_init(void) { int fan_fd; int ret; FILE* mounts; struct mntent* mount; if ((fan_fd = fanotify_init (0, 0)) < 0) { fprintf(stderr, "Cannot initialize fanotify: %s.\n", strerror(errno)); return -1; } ret = fanotify_mark(fan_fd, FAN_MARK_ADD | FAN_MARK_MOUNT, FAN_ACCESS| FAN_MODIFY | FAN_OPEN | FAN_CLOSE | FAN_ONDIR | FAN_EVENT_ON_CHILD, AT_FDCWD, "/"); if (ret < 0) { fprintf(stderr, "Cannot add fanotify watch on /: %s.\n", strerror(errno)); } if ((mounts = setmntent("/proc/self/mounts", "r")) == NULL) { fprintf(stderr, "Cannot get mount points.\n"); return -1; } while ((mount = getmntent (mounts)) != NULL) { /* if (access (mount->mnt_fsname, F_OK) != 0) continue; */ ret = fanotify_mark(fan_fd, FAN_MARK_ADD | FAN_MARK_MOUNT, FAN_ACCESS| FAN_MODIFY | FAN_OPEN | FAN_CLOSE | FAN_ONDIR | FAN_EVENT_ON_CHILD, AT_FDCWD, mount->mnt_dir); if ((ret < 0) && (errno != ENOENT)) { continue; } } endmntent (mounts); /* Track /sys/power ops for wakealarm analysis */ (void)fanotify_mark(fan_fd, FAN_MARK_ADD, FAN_ACCESS | FAN_MODIFY, AT_FDCWD, "/sys/power/wake_lock"); (void)fanotify_mark(fan_fd, FAN_MARK_ADD, FAN_ACCESS | FAN_MODIFY, AT_FDCWD, "/sys/power/wake_unlock"); return fan_fd; } /* * fnotify_event_free() * free event info */ void fnotify_event_free(void *data) { fnotify_fileinfo_t *fileinfo = (fnotify_fileinfo_t *)data; free(fileinfo->filename); free(fileinfo); } /* * fnotify_get_filename() * look up a in-use file descriptor from a given pid * and find the associated filename */ char *fnotify_get_filename(const pid_t pid, const int fd) { char buf[256]; char path[PATH_MAX]; ssize_t len; char *filename; /* * With fnotifies, fd of the file is added to the process * fd array, so we just pick them up from /proc/self. Use * a pid of -1 for self */ if (pid == -1) snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd); else snprintf(buf, sizeof(buf), "/proc/%d/fd/%d", pid, fd); len = readlink(buf, path, sizeof(path)); if (len < 0) { struct stat statbuf; if (fstat(fd, &statbuf) < 0) filename = strdup("(unknown)"); else { snprintf(buf, sizeof(buf), "dev: %i:%i inode %ld", major(statbuf.st_dev), minor(statbuf.st_dev), statbuf.st_ino); filename = strdup(buf); } } else { /* * In an ideal world we should allocate the path * based on a lstat'd size, but because this can be * racey on has to re-check, which involves * re-allocing the buffer. Since we need to be * fast let's just fetch up to PATH_MAX-1 of data. */ path[len >= PATH_MAX ? PATH_MAX - 1 : len] = '\0'; filename = strdup(path); } return filename; } /* * fnotify_event_add() * add a new fnotify event */ int fnotify_event_add( const list_t *pids, const struct fanotify_event_metadata *metadata) { link_t *l; if ((metadata->fd == FAN_NOFD) && (metadata->fd < 0)) return 0; for (l = pids->head; l; l = l->next) { proc_info_t *p = (proc_info_t*)l->data; if (metadata->pid == p->pid) { char *filename = fnotify_get_filename(-1, metadata->fd); if (filename == NULL) { health_check_out_of_memory("allocating fnotify filename"); close(metadata->fd); return -1; } if ((opt_flags & OPT_WAKELOCKS_LIGHT) && (metadata->mask & (FAN_MODIFY | FAN_CLOSE_WRITE)) && (!strcmp(filename, "/sys/power/wake_lock") || !strcmp(filename, "/sys/power/wake_unlock"))) { fnotify_wakelock_info_t *wakelock_info; link_t *l; bool found = false; for (l = fnotify_wakelocks.head; l; l = l->next) { wakelock_info = (fnotify_wakelock_info_t *)l->data; if (wakelock_info->proc == p) { found = true; break; } } if (!found) { if ((wakelock_info = calloc(1, sizeof(*wakelock_info))) == NULL) { health_check_out_of_memory("allocating wakelock information"); free(filename); close(metadata->fd); return -1; } wakelock_info->proc = p; wakelock_info->locked = 0; wakelock_info->unlocked = 0; wakelock_info->total = 0; if (list_append(&fnotify_wakelocks, wakelock_info) == NULL) { free(filename); close(metadata->fd); return -1; } } if (strcmp(filename, "/sys/power/wake_unlock")) wakelock_info->locked++; else wakelock_info->unlocked++; free(filename); wakelock_info->total++; } else { fnotify_fileinfo_t *fileinfo; link_t *l; bool found = false; for (l = fnotify_files.head; l; l = l->next) { fileinfo = (fnotify_fileinfo_t *)l->data; if ((metadata->mask == fileinfo->mask) && (!strcmp(fileinfo->filename, filename))) { found = true; break; } } if (!found) { if ((fileinfo = calloc(1, sizeof(*fileinfo))) == NULL) { health_check_out_of_memory("allocating fnotify file information"); free(filename); close(metadata->fd); return -1; } fileinfo->filename = filename; fileinfo->mask = metadata->mask; fileinfo->proc = p; fileinfo->count = 0; if (list_append(&fnotify_files, fileinfo) == NULL) { free(filename); close(metadata->fd); return -1; } } else { free(filename); } fileinfo->count++; } } } close(metadata->fd); return 0; } /* * fnotify_event_cmp_count() * for list sorting, compare counts */ static int fnotify_event_cmp_count(const void *data1, const void *data2) { fnotify_fileinfo_t *info1 = (fnotify_fileinfo_t *)data1; fnotify_fileinfo_t *info2 = (fnotify_fileinfo_t *)data2; return info2->count - info1->count; } /* * fnotify_event_cmp_io_ops() * for list sorting, compare io op totals */ static int fnotify_event_cmp_io_ops(const void *data1, const void *data2) { io_ops_t *io_ops1 = (io_ops_t *)data1; io_ops_t *io_ops2 = (io_ops_t *)data2; return io_ops2->total - io_ops1->total; } /* * fnotify_wakelock_cmp_count() * for list sorting, compare wakelock totals */ static int fnotify_wakelock_cmp_count(const void *data1, const void *data2) { fnotify_wakelock_info_t *w1 = (fnotify_wakelock_info_t *)data1; fnotify_wakelock_info_t *w2 = (fnotify_wakelock_info_t *)data2; return w2->total - w1->total; } /* * fnotify_mask_to_str() * convert fnotify mask to readable string */ static const char *fnotify_mask_to_str(const int mask) { static char modes[5]; int i = 0; if (mask & FAN_OPEN) modes[i++] = 'O'; if (mask & (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE)) modes[i++] = 'C'; if (mask & FAN_ACCESS) modes[i++] = 'R'; if (mask & (FAN_MODIFY | FAN_CLOSE_WRITE)) modes[i++] = 'W'; modes[i] = '\0'; return modes; } /* * fnotify_dump_files() * dump out fnotify file access stats */ static void fnotify_dump_files( json_object *j_tests, const double duration) { list_t sorted; link_t *l; int count; uint64_t total; #ifndef JSON_OUTPUT (void)j_tests; (void)duration; #endif list_init(&sorted); for (l = fnotify_files.head; l; l = l->next) { fnotify_fileinfo_t *info = (fnotify_fileinfo_t *)l->data; if (list_add_ordered(&sorted, info, fnotify_event_cmp_count) == NULL) goto out; } if (fnotify_files.head) { if (fnotify_files.head && !(opt_flags & OPT_BRIEF)) { printf(" PID Process Count Op Filename\n"); for (count = 0, total = 0, l = sorted.head; l; l = l->next) { fnotify_fileinfo_t *info = (fnotify_fileinfo_t *)l->data; printf(" %5d %-20.20s %6" PRIu64 " %4s %s\n", info->proc->pid, info->proc->cmdline, info->count, fnotify_mask_to_str(info->mask), info->filename); total += info->count; count++; } if (count > 1) printf(" %-25.25s%8" PRIu64 "\n", "Total", total); printf(" Op: O=Open, R=Read, W=Write, C=Close\n\n"); } } #ifdef JSON_OUTPUT if (j_tests) { json_object *j_fnotify_test, *j_accesses, *j_access; if ((j_fnotify_test = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "file-access", j_fnotify_test); if ((j_accesses = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_fnotify_test, "file-access-per-process", j_accesses); for (count = 0, total = 0, l = sorted.head; l; l = l->next) { fnotify_fileinfo_t *info = (fnotify_fileinfo_t *)l->data; if ((j_access = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_access, "pid", info->proc->pid); j_obj_new_int32_add(j_access, "ppid", info->proc->ppid); j_obj_new_int32_add(j_access, "is-thread", info->proc->is_thread); j_obj_new_string_add(j_access, "name", info->proc->cmdline); j_obj_new_string_add(j_access, "access-mode", fnotify_mask_to_str(info->mask)); j_obj_new_string_add(j_access, "filename", info->filename); j_obj_new_int64_add(j_access, "accesses-count", info->count); j_obj_new_double_add(j_access, "accesses-count-rate", (double)info->count / duration); j_obj_array_add(j_accesses, j_access); total += info->count; } if ((j_access = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_fnotify_test, "file-access-total", j_access); j_obj_new_int64_add(j_access, "access-count-total", total); j_obj_new_double_add(j_access, "access-count-total-rate", (double)total / duration); } #endif out: list_free(&sorted, NULL); } /* * fnotify_dump_io_ops() * dump out fnotify I/O operations */ static void fnotify_dump_io_ops( json_object *j_tests, const double duration, const list_t *pids) { link_t *l, *lp; list_t sorted; int count; uint64_t read_total, write_total, open_total, close_total; #ifndef JSON_OUTPUT (void)j_tests; #endif list_init(&sorted); for (lp = pids->head; lp; lp = lp->next) { proc_info_t *p = (proc_info_t*)lp->data; io_ops_t io_ops; memset(&io_ops, 0, sizeof(io_ops)); io_ops.proc = p; for (l = fnotify_files.head; l; l = l->next) { fnotify_fileinfo_t *info = (fnotify_fileinfo_t *)l->data; if (info->proc->pid != p->pid) continue; if (info->mask & FAN_OPEN) io_ops.open_total += info->count; if (info->mask & (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE)) io_ops.close_total += info->count; if (info->mask & FAN_ACCESS) io_ops.read_total += info->count; if (info->mask & (FAN_MODIFY | FAN_CLOSE_WRITE)) io_ops.write_total += info->count; } io_ops.total = io_ops.open_total + io_ops.close_total + io_ops.read_total + io_ops.write_total; if (io_ops.total) { io_ops_t *new_io_ops; if ((new_io_ops = calloc(1, sizeof(*new_io_ops))) == NULL) { health_check_out_of_memory("allocating fnotify I/O ops information"); goto out; } *new_io_ops = io_ops; if (list_add_ordered(&sorted, new_io_ops, fnotify_event_cmp_io_ops) == NULL) { free(new_io_ops); goto out; } } } open_total = close_total = read_total = write_total = 0; if (fnotify_files.head) { if (opt_flags & OPT_BRIEF) { for (l = sorted.head; l; l = l->next) { io_ops_t *io_ops = (io_ops_t *)l->data; open_total += io_ops->open_total; close_total += io_ops->close_total; read_total += io_ops->read_total; write_total += io_ops->write_total; } printf(" I/O Operations per second: %.2f open, %.2f close, %.2f read, %.2f write\n", (double)open_total / duration, (double)close_total / duration, (double)read_total / duration, (double)write_total / duration); printf("\n"); } else { printf("File I/O Operations per second:\n"); printf(" PID Process Open Close Read Write\n"); for (count = 0, l = sorted.head; l; l = l->next) { io_ops_t *io_ops = (io_ops_t *)l->data; printf(" %5d %-20.20s %7.2f %7.2f %7.2f %7.2f\n", io_ops->proc->pid, io_ops->proc->cmdline, (double)io_ops->open_total / duration, (double)io_ops->close_total / duration, (double)io_ops->read_total / duration, (double)io_ops->write_total / duration); open_total += io_ops->open_total; close_total += io_ops->close_total; read_total += io_ops->read_total; write_total += io_ops->write_total; count++; } if (count > 1) { printf(" %-27.27s%7.2f %7.2f %7.2f %7.2f\n", "Total", (double)open_total / duration, (double)close_total / duration, (double)read_total / duration, (double)write_total / duration); } printf("\n"); } } #ifdef JSON_OUTPUT if (j_tests) { json_object *j_fnotify_test, *j_io_ops, *j_io_op; if ((j_fnotify_test = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "file-io-operations", j_fnotify_test); if ((j_io_ops = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_fnotify_test, "file-io-operations-per-process", j_io_ops); for (count = 0, l = sorted.head; l; l = l->next) { io_ops_t *io_ops = (io_ops_t *)l->data; if ((j_io_op = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_io_op, "pid", io_ops->proc->pid); j_obj_new_int32_add(j_io_op, "ppid", io_ops->proc->ppid); j_obj_new_int32_add(j_io_op, "is-thread", io_ops->proc->is_thread); j_obj_new_string_add(j_io_op, "name", io_ops->proc->cmdline); j_obj_new_int64_add(j_io_op, "open-call-count", io_ops->open_total); j_obj_new_int64_add(j_io_op, "close-call-count", io_ops->close_total); j_obj_new_int64_add(j_io_op, "read-call-count", io_ops->read_total); j_obj_new_int64_add(j_io_op, "write-call-count", io_ops->write_total); j_obj_new_double_add(j_io_op, "open-call-rate", (double)io_ops->open_total / duration); j_obj_new_double_add(j_io_op, "close-call-rate", (double)io_ops->close_total / duration); j_obj_new_double_add(j_io_op, "read-call-rate", (double)io_ops->read_total / duration); j_obj_new_double_add(j_io_op, "write-call-rate", (double)io_ops->write_total / duration); j_obj_array_add(j_io_ops, j_io_op); } if ((j_io_op = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_fnotify_test, "file-io-operations-total", j_io_op); j_obj_new_int64_add(j_io_op, "open-call-total", open_total); j_obj_new_int64_add(j_io_op, "close-call-total", close_total); j_obj_new_int64_add(j_io_op, "read-total", read_total); j_obj_new_int64_add(j_io_op, "write-call-total", write_total); j_obj_new_double_add(j_io_op, "open-call-total-rate", (double)open_total / duration); j_obj_new_double_add(j_io_op, "close-call-total-rate", (double)close_total / duration); j_obj_new_double_add(j_io_op, "read-call-total-rate", (double)read_total / duration); j_obj_new_double_add(j_io_op, "write-call-total-rate", (double)write_total / duration); } #endif out: list_free(&sorted, free); } /* * fnotify_dump_wakelocks() * dump out fnotify wakelock operations */ void fnotify_dump_wakelocks( json_object *j_tests, const double duration) { list_t sorted; link_t *l; (void)j_tests; (void)duration; if (!(opt_flags & OPT_WAKELOCKS_LIGHT)) return; printf("Wakelock operations:\n"); list_init(&sorted); for (l = fnotify_wakelocks.head; l; l = l->next) { fnotify_wakelock_info_t *info = (fnotify_wakelock_info_t *)l->data; if (list_add_ordered(&sorted, info, fnotify_wakelock_cmp_count) == NULL) goto out; } if (!fnotify_wakelocks.head) { printf(" None.\n\n"); } else { if (fnotify_wakelocks.head && !(opt_flags & OPT_BRIEF)) { printf(" PID Process Locks Unlocks\n"); for (l = sorted.head; l; l = l->next) { fnotify_wakelock_info_t *info = (fnotify_wakelock_info_t *)l->data; printf(" %5d %-20.20s %8" PRIu64 " %8" PRIu64 "\n", info->proc->pid, info->proc->cmdline, info->locked, info->unlocked); } } printf("\n"); } #ifdef JSON_OUTPUT if (j_tests) { json_object *j_wakelock_test, *j_wakelock_infos, *j_wakelock_info; uint64_t locked_total = 0, unlocked_total = 0; if ((j_wakelock_test = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "wakelock-operations-light", j_wakelock_test); if ((j_wakelock_infos = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_wakelock_test, "wakelock-operations-light-per-process", j_wakelock_infos); for (l = sorted.head; l; l = l->next) { fnotify_wakelock_info_t *info = (fnotify_wakelock_info_t *)l->data; if ((j_wakelock_info = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_wakelock_info, "pid", info->proc->pid); j_obj_new_int32_add(j_wakelock_info, "ppid", info->proc->ppid); j_obj_new_int32_add(j_wakelock_info, "is-thread", info->proc->is_thread); j_obj_new_string_add(j_wakelock_info, "name", info->proc->cmdline); j_obj_new_int64_add(j_wakelock_info, "wakelock-locked", info->locked); j_obj_new_double_add(j_wakelock_info, "wakelock-locked-rate", (double)info->locked / duration); j_obj_new_int64_add(j_wakelock_info, "wakelock-unlocked", info->unlocked); j_obj_new_double_add(j_wakelock_info, "wakelock-unlocked-rate", (double)info->unlocked / duration); j_obj_array_add(j_wakelock_infos, j_wakelock_info); locked_total += info->locked; unlocked_total += info->unlocked; } if ((j_wakelock_info = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_wakelock_test, "wakelock-operations-light-total", j_wakelock_info); j_obj_new_int64_add(j_wakelock_info, "wakelock-locked-total", locked_total); j_obj_new_double_add(j_wakelock_info, "wakelock-locked-total-rate", (double)locked_total / duration); j_obj_new_int64_add(j_wakelock_info, "wakelock-unlocked-total", unlocked_total); j_obj_new_double_add(j_wakelock_info, "wakelock-unlocked-total-rate", (double)unlocked_total / duration); } #endif out: list_free(&sorted, NULL); } /* * fnotify_dump_events() * dump out fnotify file access events */ void fnotify_dump_events( json_object *j_tests, const double duration, const list_t *pids) { printf("File I/O operations:\n"); if (!fnotify_files.head) printf(" No file I/O operations detected.\n\n"); fnotify_dump_files(j_tests, duration); fnotify_dump_io_ops(j_tests, duration, pids); } /* * fnotify_init() * initialize fnotify lists */ void fnotify_init(void) { list_init(&fnotify_files); list_init(&fnotify_wakelocks); } /* * fnotify_cleanup() * free fnotify lists */ void fnotify_cleanup(void) { list_free(&fnotify_files, fnotify_event_free); list_free(&fnotify_wakelocks, free); } health-check-0.01.58/health-check.c0000664000175000017500000004062512317222554015347 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "list.h" #include "json.h" #include "pid.h" #include "proc.h" #include "syscall.h" #include "timeval.h" #include "fnotify.h" #include "event.h" #include "cpustat.h" #include "mem.h" #include "net.h" #include "ctxt-switch.h" #include "health-check.h" #define APP_NAME "health-check" #define DURATION_RUN_FOREVER (0.0) static bool caught_sigint = false; volatile bool keep_running = true; int opt_flags; int opt_max_syscalls = 1000000; /* * handle_sig() * catch signal, stop program */ static void handle_sig(int dummy) { (void)dummy; /* Stop unused parameter warning with -Wextra */ keep_running = false; caught_sigint = true; } /* * health_check_exit() * exit and set timer stat to 0 */ void health_check_exit(const int status) { event_stop(); exit(status); } /* * health_check_out_of_memory(); * report out of memory condition */ void health_check_out_of_memory(const char *msg) { fprintf(stderr, "Out of memory: %s.\n", msg); } /* * show_usage() * show how to use */ static void show_usage(void) { printf("%s, version %s\n\n", APP_NAME, VERSION); printf("Usage: %s [options] [command [options]]\n", APP_NAME); printf("Options are:\n"); printf(" -b brief (terse) output\n"); printf(" -c find all child processes on start-up\n"); printf(" (only useful with -p option)\n"); printf(" -d specify the analysis duration in seconds\n"); printf(" (default is 60 seconds)\n"); printf(" -f follow fork/vfork/clone system calls\n"); printf(" -h show this help\n"); printf(" -p pid[,pid] specify process id(s) or process name(s) to be traced\n"); printf(" -m max specify maximum number of system calls to trace\n"); printf(" (default is 1000000)\n"); #ifdef JSON_OUTPUT printf(" -o file output results to a json data file\n"); #endif printf(" -r resolve IP addresses\n"); printf(" -u user run command as a specified user\n"); printf(" -v verbose output\n"); #if FNOTIFY_SUPPORTED printf(" -w monitor wakelock count\n"); #endif printf(" -W monitor wakelock usage (has overhead)\n"); health_check_exit(EXIT_SUCCESS); } /* * parse_pid_list() * parse list of process IDs or process names, * collect process info in pids list */ static int parse_pid_list(char *arg, list_t *pids) { char *str, *token, *saveptr = NULL; for (str = arg; (token = strtok_r(str, ",", &saveptr)) != NULL; str = NULL) { if (isdigit(token[0])) { proc_info_t *p; pid_t pid; pid = atoi(token); if ((p = proc_cache_find_by_pid(pid)) == NULL) { fprintf(stderr, "Cannot find process with PID %i.\n", pid); return -1; } if (proc_pids_add_proc(pids, p) < 0) return -1; } else { if (proc_cache_find_by_procname(pids, token) < 0) { return -1; } } } return 0; } #ifdef JSON_OUTPUT /* * json_write() * dump out collected JSON data */ static int json_write(json_object *obj, const char *filename) { const char *str; FILE *fp; if (obj == NULL) { fprintf(stderr, "Cannot create JSON log, no JSON data.\n"); return -1; } #ifdef JSON_C_TO_STRING_PRETTY str = json_object_to_json_string_ext( obj, JSON_C_TO_STRING_PRETTY); #else str = json_object_to_json_string(obj); #endif if (str == NULL) { fprintf(stderr, "Cannot turn JSON object to text for JSON output.\n"); return -1; } if ((fp = fopen(filename, "w")) == NULL) { fprintf(stderr, "Cannot create JSON log file %s.\n", filename); return -1; } fprintf(fp, "%s", str); fclose(fp); json_object_put(obj); return 0; } #endif /* * exec_executable() * exec a program */ pid_t exec_executable(const char *opt_username, const char *path, char **argv) { uid_t uid; gid_t gid; pid_t pid; pid = fork(); if (pid < 0) { fprintf(stderr, "Cannot fork to run %s.\n", path); exit(EXIT_FAILURE); } if (pid != 0) return pid; /* We are the tracer, return tracee pid */ /* Traced process starts here */ if (opt_username) { struct passwd *pw; if ((pw = getpwnam(opt_username)) == NULL) { fprintf(stderr, "Username %s does not exist.\n", opt_username); exit(EXIT_FAILURE); } uid = pw->pw_uid; gid = pw->pw_gid; if (initgroups(opt_username, gid) < 0) { fprintf(stderr, "initgroups failed user on %s\n", opt_username); exit(EXIT_FAILURE); } if (setregid(gid, gid) < 0) { fprintf(stderr, "setregid failed\n"); exit(EXIT_FAILURE); } if (setreuid(uid, uid) < 0) { fprintf(stderr, "setreuid failed\n"); exit(EXIT_FAILURE); } } else { if (geteuid() != 0) { uid = getuid(); if (setreuid(uid, uid) < 0) { fprintf(stderr, "setreuid failed\n"); exit(EXIT_FAILURE); } } } /* Suspend ourself waiting for tracer */ kill(getpid(), SIGSTOP); execv(path, argv); printf("Failed to execv %s\n", path); exit(EXIT_FAILURE); } /* * is_executable() * check path to see if it is an executable image */ inline static int is_executable(const char *path) { struct stat buf; return ((stat(path, &buf) == 0) && (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) && S_ISREG(buf.st_mode)); } /* * find_executable() * find executable given a filename */ static const char *find_executable(const char *filename) { static char path[PATH_MAX]; size_t filenamelen = strlen(filename); if (strchr(filename, '/')) { /* Given a full path, try this */ if (strlen(filename) > sizeof(path) - 1) { fprintf(stderr, "executable name too long.\n"); health_check_exit(EXIT_FAILURE); } strncpy(path, filename, sizeof(path) - 1); if (is_executable(path)) return path; else fprintf(stderr, "%s is not a valid executable program\n", filename); } else { /* Try and find it in $PATH */ size_t skiplen; char *p; for (p = getenv("PATH"); p && *p; p += skiplen) { size_t len, pathlen; char *ptr = strchr(p, ':'); if (ptr) { len = ptr - p; skiplen = len + 1; } else { skiplen = len = strlen(p); } if (len) { if (len > sizeof(path) - 1) continue; /* Too long */ else { pathlen = len; strncpy(path, p, pathlen); } } else { if (getcwd(p, PATH_MAX) == NULL) continue; /* Silently ignore */ pathlen = strlen(p); } if (pathlen + filenamelen + 2 > sizeof(path)) continue; if ((pathlen > 0) && (path[pathlen - 1] != '/')) { if (pathlen >= sizeof(path) - 1) continue; /* Too big! */ path[pathlen++] = '/'; } /* is Filename + '/' + pathname + EOS too big? */ if (filenamelen + pathlen >= sizeof(path) - 2) continue; strcpy(path + pathlen, filename); if (is_executable(path)) return path; } fprintf(stderr, "Cannot find %s in $PATH\n", filename); } return NULL; /* No hope */ } int main(int argc, char **argv) { double actual_duration, opt_duration_secs = 60.0; struct timeval tv_start, tv_end, tv_now, duration; int ret, rc = EXIT_SUCCESS; #if FNOTIFY_SUPPORTED int fan_fd = 0; #endif list_t pids; link_t *l; void *buffer = NULL; char *opt_username = NULL; #ifdef JSON_OUTPUT char *opt_json_file = NULL; json_object *json_obj = NULL; #endif json_object *json_tests = NULL; struct sigaction new_action, old_action; list_init(&pids); proc_cache_init(); /* Get a cached view of current process state */ if (proc_cache_get() < 0) goto out; if (proc_cache_get_pthreads() < 0) goto out; sigaction(SIGCHLD, NULL, &old_action); if (old_action.sa_handler != SIG_DFL) { new_action.sa_handler = SIG_DFL; sigemptyset(&new_action.sa_mask); new_action.sa_flags = 0; sigaction(SIGCHLD, &new_action, NULL); } for (;;) { int c = getopt(argc, argv, "+bcd:fhp:m:o:ru:vwW"); if (c == -1) break; switch (c) { case 'b': opt_flags |= OPT_BRIEF; break; case 'c': opt_flags |= OPT_GET_CHILDREN; break; case 'f': opt_flags |= OPT_FOLLOW_NEW_PROCS; break; case 'h': show_usage(); break; case 'p': if (parse_pid_list(optarg, &pids) < 0) health_check_exit(EXIT_FAILURE); break; case 'd': opt_duration_secs = atof(optarg); opt_flags |= OPT_DURATION; break; case 'm': opt_max_syscalls = atoi(optarg); break; #ifdef JSON_OUTPUT case 'o': opt_json_file = optarg; break; #endif case 'r': opt_flags |= OPT_ADDR_RESOLVE; break; case 'u': opt_username = optarg; break; case 'v': opt_flags |= OPT_VERBOSE; break; #if FNOTIFY_SUPPORTED case 'w': opt_flags |= OPT_WAKELOCKS_LIGHT; break; #endif case 'W': opt_flags |= OPT_WAKELOCKS_HEAVY; break; default: show_usage(); } } if ((opt_flags & (OPT_VERBOSE | OPT_BRIEF)) == (OPT_VERBOSE | OPT_BRIEF)) { fprintf(stderr, "Cannot have verbose -v and brief -b flags together.\n"); health_check_exit(EXIT_FAILURE); } if ((getuid() !=0 ) || (geteuid() != 0)) { fprintf(stderr, "%s requires root privileges to write to %s\n", APP_NAME, TIMER_STATS); health_check_exit(EXIT_FAILURE); } if (optind < argc) { const char *path; if (pids.head != NULL) { fprintf(stderr, "Cannot heath-check a program and provide pids to trace at same time\n"); health_check_exit(EXIT_FAILURE); } argv += optind; path = find_executable(argv[0]); if (path) { pid_t pid; proc_info_t *p; /* No duration given, so run until completion */ if (!(opt_flags & OPT_DURATION)) opt_duration_secs = DURATION_RUN_FOREVER; pid = exec_executable(opt_username, path, argv); if ((p = proc_cache_add(pid, 0, false)) == NULL) { fprintf(stderr, "Cannot find process with PID %i\n", pid); goto out; } free(p->cmdline); if ((p->cmdline = strdup(basename(path))) == NULL) { health_check_out_of_memory("cannot allocate process cmdline"); goto out; } if (proc_pids_add_proc(&pids, p) < 0) goto out; } else health_check_exit(EXIT_FAILURE); } if (pids.head == NULL) { fprintf(stderr, "Must provide one or more valid process IDs or name\n"); health_check_exit(EXIT_FAILURE); } for (l = pids.head; l; l = l->next) { proc_info_t *p = (proc_info_t *)l->data; if (!pid_exists(p->pid)) { fprintf(stderr, "Cannot check process %i, no such process pid\n", p->pid); health_check_exit(EXIT_FAILURE); } } if (opt_flags & OPT_GET_CHILDREN) if (pid_list_get_children(&pids) < 0) goto out; if (opt_duration_secs < 0.0) { fprintf(stderr, "Duration must positive.\n"); health_check_exit(EXIT_FAILURE); } net_connection_init(); if (net_connection_pids(&pids) < 0) goto out; #ifdef JSON_OUTPUT if (opt_json_file) { if ((json_obj = json_object_new_object()) == NULL) { health_check_out_of_memory("cannot allocate JSON object"); goto out; } if ((json_tests = json_object_new_object()) == NULL) { health_check_out_of_memory("cannot allocate JSON array"); goto out; } json_object_object_add(json_obj, "health-check", json_tests); } #endif #if FNOTIFY_SUPPORTED fnotify_init(); if ((fan_fd = fnotify_event_init()) < 0) goto out; #endif ret = posix_memalign(&buffer, 4096, 4096); if (ret != 0 || buffer == NULL) { health_check_out_of_memory("cannot allocate 4K aligned buffer"); goto out; } new_action.sa_handler = handle_sig; sigemptyset(&new_action.sa_mask); new_action.sa_flags = 0; sigaction(SIGINT, &new_action, &old_action); sigaction(SIGUSR1, &new_action, &old_action); #if SYSCALL_SUPPORTED syscall_init(); syscall_trace_proc(&pids); #endif mem_init(); #if EVENT_SUPPORTED event_init(); #endif cpustat_init(); ctxt_switch_init(); duration.tv_sec = (time_t)opt_duration_secs; duration.tv_usec = (suseconds_t)(opt_duration_secs * 1000000.0) - (duration.tv_sec * 1000000); gettimeofday(&tv_start, NULL); tv_end = timeval_add(&tv_start, &duration); #if EVENT_SUPPORTED if (event_get_all_pids(&pids, PROC_START) < 0) goto out; #endif if (cpustat_get_all_pids(&pids, PROC_START) < 0) goto out; if (mem_get_all_pids(&pids, PROC_START) < 0) goto out; if (ctxt_switch_get_all_pids(&pids, PROC_START) < 0) goto out; gettimeofday(&tv_now, NULL); duration = timeval_sub(&tv_end, &tv_now); while ((procs_traced > 0) && keep_running && (opt_duration_secs == DURATION_RUN_FOREVER || timeval_to_double(&duration) > 0.0)) { struct timeval *duration_ptr = opt_duration_secs == DURATION_RUN_FOREVER ? NULL : &duration; #if FNOTIFY_SUPPORTED fd_set rfds; FD_ZERO(&rfds); FD_SET(fan_fd, &rfds); ret = select(fan_fd + 1, &rfds, NULL, NULL, duration_ptr); if (ret < 0) { if (errno != EINTR) { fprintf(stderr, "Select failed: %s\n", strerror(errno)); gettimeofday(&tv_now, NULL); goto out; } } else if (ret > 0) { if (FD_ISSET(fan_fd, &rfds)) { ssize_t len; if ((len = read(fan_fd, (void *)buffer, 4096)) > 0) { const struct fanotify_event_metadata *metadata; metadata = (struct fanotify_event_metadata *)buffer; while (FAN_EVENT_OK(metadata, len)) { if (fnotify_event_add(&pids, metadata) < 0) goto out; metadata = FAN_EVENT_NEXT(metadata, len); } } } } #else ret = select(0, NULL, NULL, NULL, duration_ptr); if (ret < 0) { if (errno != EINTR) { fprintf(stderr, "Select failed: %s\n", strerror(errno)); gettimeofday(&tv_now, NULL); goto out; } } #endif gettimeofday(&tv_now, NULL); duration = timeval_sub(&tv_end, &tv_now); } keep_running = false; duration = timeval_sub(&tv_now, &tv_start); actual_duration = timeval_to_double(&duration); #if EVENT_SUPPORTED if (event_get_all_pids(&pids, PROC_FINISH) < 0) goto out; #endif if (cpustat_get_all_pids(&pids, PROC_FINISH) < 0) goto out; if (mem_get_all_pids(&pids, PROC_FINISH) < 0) goto out; if (ctxt_switch_get_all_pids(&pids, PROC_FINISH) < 0) goto out; #if EVENT_SUPPORTED event_stop(); #endif #if SYSCALL_SUPPORTED if (syscall_stop() < 0) goto out; #endif sigaction(SIGINT, &old_action, NULL); if (caught_sigint) putchar('\n'); cpustat_dump_diff(json_tests, actual_duration); pagefault_dump_diff(json_tests, actual_duration); #if EVENT_SUPPORTED event_dump_diff(json_tests, actual_duration); #endif ctxt_switch_dump_diff(json_tests, actual_duration); #if FNOTIFY_SUPPORTED fnotify_dump_events(json_tests, actual_duration, &pids); #endif #if SYSCALL_SUPPORTED syscall_dump_hashtable(json_tests, actual_duration); syscall_dump_pollers(json_tests, actual_duration); syscall_dump_sync(json_tests, actual_duration); syscall_dump_inotify(json_tests, actual_duration); syscall_dump_execve(json_tests, actual_duration); #endif if (mem_dump_diff(json_tests, actual_duration) < 0) goto out; mem_dump_brk(json_tests, actual_duration); mem_dump_mmap(json_tests, actual_duration); net_connection_dump(json_tests, actual_duration); #if FNOTIFY_SUPPORTED if (opt_flags & OPT_WAKELOCKS_LIGHT) fnotify_dump_wakelocks(json_tests, actual_duration); #endif #if SYSCALL_SUPPORTED if (opt_flags & OPT_WAKELOCKS_HEAVY) syscall_dump_wakelocks(json_tests, actual_duration, &pids); #endif if (actual_duration < 5.0) printf("Analysis ran for just %.4f seconds, so rate calculations may be misleading\n", actual_duration); #ifdef JSON_OUTPUT if (json_obj) json_write(json_obj, opt_json_file); #endif out: keep_running = false; /* Force stop if we aborted */ mem_cleanup(); net_connection_cleanup(); #if SYSCALL_SUPPORTED syscall_cleanup(); #endif #if EVENT_SUPPORTED event_cleanup(); #endif cpustat_cleanup(); ctxt_switch_cleanup(); #if FNOTIFY_SUPPORTED fnotify_cleanup(); #endif free(buffer); proc_cache_cleanup(); list_free(&pids, NULL); health_check_exit(rc); } health-check-0.01.58/event.c0000664000175000017500000002154212317222554014145 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #define _GNU_SOURCE #include #include #include #include #include #include #include "list.h" #include "json.h" #include "event.h" #include "health-check.h" static list_t event_info_start, event_info_finish; /* * event_timer_stat_set() * enable/disable timer stat */ static void event_timer_stat_set(const char *str, const bool carp) { FILE *fp; if ((fp = fopen(TIMER_STATS, "w")) == NULL) { if (carp) { fprintf(stderr, "Cannot write to %s.\n",TIMER_STATS); exit(EXIT_FAILURE); } else { return; } } fprintf(fp, "%s\n", str); fclose(fp); } /* * event_free() * free event info */ static void event_free(void *data) { event_info_t *ev = (event_info_t *)data; free(ev->func); free(ev->callback); free(ev->ident); free(ev); } /* * event_cmp() * compare event info for sorting */ static int event_cmp(const void *data1, const void *data2) { event_info_t *ev1 = (event_info_t *)data1; event_info_t *ev2 = (event_info_t *)data2; return ev2->count - ev1->count; } /* * event_add() * add event stats */ static int event_add( list_t *events, /* event list */ const uint64_t count, /* event count */ const pid_t pid, /* PID of task */ const char *func, /* Kernel function */ const char *callback) /* Kernel timer callback */ { char ident[4096]; event_info_t *ev; link_t *l; proc_info_t *p; /* Does it exist? */ if ((p = proc_cache_find_by_pid(pid)) == NULL) return 0; snprintf(ident, sizeof(ident), "%d:%s:%s:%s", pid, p->comm, func, callback); for (l = events->head; l; l = l->next) { ev = (event_info_t *)l->data; if (strcmp(ev->ident, ident) == 0) { ev->count += count; return 0; } } /* Not found, it is new! */ if ((ev = calloc(1, sizeof(event_info_t))) == NULL) { health_check_out_of_memory("allocting event information"); return -1; } ev->proc = p; ev->func = strdup(func); ev->callback = strdup(callback); ev->ident = strdup(ident); ev->count = count; if (ev->func == NULL || ev->callback == NULL || ev->ident == NULL) { health_check_out_of_memory("allocting event information"); goto err; } if (list_add_ordered(events, ev, event_cmp) == NULL) goto err; return 0; err: free(ev->func); free(ev->callback); free(ev->ident); free(ev); return -1; } /* * event_get_all_pids() * scan /proc/timer_stats and populate a timer stat hash table with * unique events */ int event_get_all_pids(const list_t *pids, proc_state state) { FILE *fp; char buf[4096]; list_t *events = (state == PROC_START) ? &event_info_start : &event_info_finish; if ((fp = fopen(TIMER_STATS, "r")) == NULL) { fprintf(stderr, "Cannot open %s.\n", TIMER_STATS); return 0; } while (!feof(fp)) { char *ptr = buf; uint64_t count = 0; pid_t event_pid = -1; char func[128]; char timer[128]; link_t *l; if (fgets(buf, sizeof(buf), fp) == NULL) break; if (strstr(buf, "total events") != NULL) break; if (strstr(buf, ",") == NULL) continue; /* format: count[D], pid, comm, func (timer) */ while (*ptr && *ptr != ',') ptr++; if (*ptr != ',') continue; if (ptr > buf && *(ptr-1) == 'D') continue; /* Deferred event, skip */ ptr++; sscanf(buf, "%" SCNu64, &count); sscanf(ptr, "%8d", &event_pid); sscanf(ptr + 24, "%127s (%127[^)])", func, timer); for (l = pids->head; l; l = l->next) { proc_info_t *p = (proc_info_t *)l->data; if (event_pid == p->pid) { if (event_add(events, count, event_pid, func, timer) < 0) { fclose(fp); return -1; } break; } } } fclose(fp); return 0; } /* * event_loading() * */ static const char *event_loading(const double wakeup_rate) { if (wakeup_rate == 0.0) return "idle"; if (wakeup_rate > 200.0) return "very excessive"; if (wakeup_rate > 60.0) return "excessive"; if (wakeup_rate > 20.0) return "very high"; if (wakeup_rate > 10.0) return "high"; if (wakeup_rate > 5.0) return "quite high"; if (wakeup_rate > 1.0) return "moderate"; if (wakeup_rate > 0.25) return "low"; return "very low"; } /* * event_delta() * find delta in events between old, new. * if no old then delta is the new. */ static uint64_t event_delta(const event_info_t *event_new, const list_t *events_old) { link_t *l; for (l = events_old->head; l; l = l->next) { event_info_t *event_old = (event_info_t*)l->data; if (strcmp(event_new->ident, event_old->ident) == 0) return event_new->count - event_old->count; } return event_new->count; } /* * event_dump_diff() * dump differences between old and new events */ void event_dump_diff( json_object *j_tests, const double duration) { link_t *l; #ifndef JSON_OUTPUT (void)j_tests; #endif printf("Wakeups:\n"); if (event_info_finish.head) { if (opt_flags & OPT_BRIEF) { double event_rate = 0.0; for (l = event_info_finish.head; l; l = l->next) { event_info_t *event_new = (event_info_t *)l->data; uint64_t delta = event_delta(event_new, &event_info_start); event_rate += (double)delta; } event_rate /= duration; printf(" %.2f wakeups/sec (%s)\n\n", event_rate, event_loading(event_rate)); } else { int count = 0; double total = 0.0; printf(" PID Process Wake/Sec Kernel Functions\n"); for (l = event_info_finish.head; l; l = l->next) { event_info_t *event_new = (event_info_t *)l->data; uint64_t delta = event_delta(event_new, &event_info_start); double event_rate = (double)delta / duration; printf(" %5d %-20.20s %9.2f (%s, %s) (%s)\n", event_new->proc->pid, event_new->proc->cmdline, event_rate, event_new->func, event_new->callback, event_loading(event_rate)); total += event_rate; count++; } if (count > 1) printf(" %-27.27s%9.2f\n", "Total", total); printf("\n"); } } else { printf(" No wakeups detected.\n\n"); } #ifdef JSON_OUTPUT if (j_tests) { json_object *j_event_test, *j_events, *j_event; uint64_t total_delta = 0; double total_event_rate; if ((j_event_test = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "wakeup-events", j_event_test); if ((j_events = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_event_test, "wakeup-events-per-process", j_events); for (l = event_info_finish.head; l; l = l->next) { event_info_t *event = (event_info_t *)l->data; uint64_t delta = event_delta(event, &event_info_start); double event_rate = (double)delta / duration; total_delta += delta; /* We may as well dump everything */ if ((j_event = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_event, "pid", event->proc->pid); j_obj_new_int32_add(j_event, "ppid", event->proc->ppid); j_obj_new_int32_add(j_event, "is-thread", event->proc->is_thread); j_obj_new_string_add(j_event, "name", event->proc->cmdline); j_obj_new_string_add(j_event, "kernel-timer-func", event->func); j_obj_new_string_add(j_event, "kernel-timer-callback", event->callback); j_obj_new_int64_add(j_event, "wakeups", delta); j_obj_new_double_add(j_event, "wakeup-rate", event_rate); j_obj_new_string_add(j_event, "load-hint", event_loading(event_rate)); j_obj_array_add(j_events, j_event); } total_event_rate = (double)total_delta / duration; if ((j_event = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_event_test, "wakeup-events-total", j_event); j_obj_new_int64_add(j_event, "wakeup-total", total_delta); j_obj_new_double_add(j_event, "wakeup-total-rate", total_event_rate); j_obj_new_string_add(j_event, "load-hint-total", event_loading(total_event_rate)); } out: #endif return; } /* * event_init() * initialise events and start timer stat */ void event_init(void) { list_init(&event_info_start); list_init(&event_info_finish); /* Should really catch signals and set back to zero before we die */ event_timer_stat_set("1", true); } /* * event_stop() * stop event timer stat */ void event_stop(void) { event_timer_stat_set("0", false); } /* * event_cleanup() * free memory */ void event_cleanup(void) { list_free(&event_info_start, event_free); list_free(&event_info_finish, event_free); } health-check-0.01.58/json.c0000664000175000017500000000444212317222554013775 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #include #include #include #include #include #include "json.h" #include "health-check.h" /* * Older versions json-c don't int64 */ extern json_object *json_object_new_int64(const int64_t) __attribute__((weak)); static inline void j_obj_is_null(json_object *obj, const char *msg) { if (!obj) health_check_out_of_memory(msg); } json_object *j_obj_new_array(void) { json_object *obj = json_object_new_array(); j_obj_is_null(obj, "cannot allocate JSON array"); return obj; } json_object *j_obj_new_obj(void) { json_object *obj = json_object_new_object(); j_obj_is_null(obj, "cannot allocate JSON object"); return obj; } json_object *j_obj_new_int32(const int32_t i) { json_object *obj = json_object_new_int(i); j_obj_is_null(obj, "cannot allocate JSON integer"); return obj; } json_object *j_obj_new_int64(const int64_t i) { json_object *obj = NULL; if (json_object_new_int64) { obj = json_object_new_int64(i); } else { /* Older json-c doesn't have int64, so convert to double */ obj = json_object_new_double((double)i); } j_obj_is_null(obj, "cannot allocate JSON integer"); return obj; } json_object *j_obj_new_double(const double d) { json_object *obj = json_object_new_double(d); j_obj_is_null(obj, "cannot allocate JSON double"); return obj; } json_object *j_obj_new_string(const char *str) { json_object *obj = json_object_new_string(str); j_obj_is_null(obj, "cannot allocate JSON string"); return obj; } health-check-0.01.58/list.c0000664000175000017500000000437212317222554014001 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #define _GNU_SOURCE #include #include #include "list.h" /* * list_init() * initialize list */ void list_init(list_t *list) { list->head = NULL; list->tail = NULL; list->length = 0; } /* * list_append() * add a new item to end of the list */ link_t *list_append(list_t *list, void *data) { link_t *link; if ((link = calloc(1, sizeof(link_t))) == NULL) { fprintf(stderr, "Cannot allocate list link.\n"); return NULL; } link->data = data; if (list->head == NULL) { list->head = link; } else { list->tail->next = link; } list->tail = link; list->length++; return link; } /* * list_add_ordered() * add new data into list, based on order from callback func compare(). */ link_t *list_add_ordered( list_t *list, void *new_data, const list_comp_t compare) { link_t *link, **l; if ((link = calloc(1, sizeof(link_t))) == NULL) return NULL; link->data = new_data; for (l = &list->head; *l; l = &(*l)->next) { void *data = (void *)(*l)->data; if (compare(data, new_data) >= 0) { link->next = (*l); break; } } if (!link->next) list->tail = link; *l = link; list->length++; return link; } /* * list_free() * free the list */ void list_free( list_t *list, const list_link_free_t freefunc) { link_t *link, *next; if (list == NULL) return; for (link = list->head; link; link = next) { next = link->next; if (link->data && freefunc) freefunc(link->data); free(link); } } health-check-0.01.58/cpustat.c0000664000175000017500000003024712317222554014511 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "list.h" #include "json.h" #include "cpustat.h" #include "timeval.h" #include "health-check.h" static list_t cpustat_info_start, cpustat_info_finish; /* * cpustat_loading() * map CPU loading to some human understandable form */ static const char *cpustat_loading(const double cpu_percent) { if (cpu_percent == 0.0) return "idle"; if (cpu_percent > 99.0) return "CPU fully loaded"; if (cpu_percent > 95.0) return "nearly 1 CPU fully loaded"; if (cpu_percent > 85.0) return "excessive load"; if (cpu_percent > 70.0) return "very high load"; if (cpu_percent > 40.0) return "high load"; if (cpu_percent > 20.0) return "medium load"; if (cpu_percent > 10.0) return "slight load"; if (cpu_percent > 2.5) return "light load"; return "very light load"; } /* * cpustat_cmp() * cpu total time list sort comparitor */ static int cpustat_cmp(const void *data1, const void *data2) { cpustat_info_t *cpustat1 = (cpustat_info_t *)data1; cpustat_info_t *cpustat2 = (cpustat_info_t *)data2; return cpustat2->ttime - cpustat1->ttime; } /* * cpustat_dump_diff() * dump difference in CPU loading between two snapshots in time */ int cpustat_dump_diff(json_object *j_tests, const double duration) { double nr_ticks = (double)sysconf(_SC_CLK_TCK) * duration; double utime_total = 0.0, stime_total = 0.0, ttime_total = 0.0; int rc = 0; int count = 0; link_t *lo, *ln; list_t sorted; cpustat_info_t *cio, *cin; #ifndef JSON_OUTPUT (void)j_tests; #endif list_init(&sorted); for (ln = cpustat_info_finish.head; ln; ln = ln->next) { cin = (cpustat_info_t*)ln->data; for (lo = cpustat_info_start.head; lo; lo = lo->next) { cio = (cpustat_info_t*)lo->data; if (cin->proc->pid == cio->proc->pid) { cpustat_info_t *cpustat; if ((cpustat = calloc(1, sizeof(*cpustat))) == NULL) { health_check_out_of_memory("cannot allocate cpustat information"); goto out; } cpustat->proc = cio->proc; cpustat->utime = cin->utime - cio->utime; cpustat->stime = cin->stime - cio->stime; cpustat->ttime = cin->ttime - cio->ttime; cpustat->duration = timeval_to_double(&cin->whence) - timeval_to_double(&cio->whence); if (list_add_ordered(&sorted, cpustat, cpustat_cmp) == NULL) { free(cpustat); goto out; } /* We calculate this in terms of ticks and duration of each process */ utime_total += (double)cpustat->utime / nr_ticks; stime_total += (double)cpustat->stime / nr_ticks; ttime_total += (double)cpustat->ttime / nr_ticks; count++; } } } printf("CPU usage (in terms of 1 CPU):\n"); if (sorted.head == NULL) { printf(" Nothing measured.\n"); } else { if (opt_flags & OPT_BRIEF) { printf(" User: %6.2f%%, System: %6.2f%%, Total: %6.2f%% (%s)\n", 100.0 * utime_total, 100.0 * stime_total, 100.0 * ttime_total, cpustat_loading(100.0 * (double)ttime_total)); } else { printf(" PID Process USR%% SYS%% TOTAL%% Duration\n"); for (ln = sorted.head; ln; ln = ln->next) { cin = (cpustat_info_t*)ln->data; printf(" %5d %-20.20s %6.2f %6.2f %6.2f %8.2f (%s)\n", cin->proc->pid, cin->proc->cmdline, 100.0 * (double)cin->utime / nr_ticks, 100.0 * (double)cin->stime / nr_ticks, 100.0 * (double)cin->ttime / nr_ticks, cin->duration, cpustat_loading(100.0 * (double)cin->ttime / nr_ticks)); } if (count > 1) printf(" %-26.26s %6.2f %6.2f %6.2f (%s)\n", "Total", 100.0 * utime_total, 100.0 * stime_total, 100.0 * ttime_total, cpustat_loading(100.0 * ttime_total)); } } #ifdef JSON_OUTPUT if (j_tests) { json_object *j_cpustat, *j_cpuload, *j_cpu; if ((j_cpustat = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "cpu-load", j_cpustat); if ((j_cpuload = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_cpustat, "cpu-load-per-process", j_cpuload); for (ln = sorted.head; ln; ln = ln->next) { cin = (cpustat_info_t*)ln->data; if ((j_cpu = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_cpu, "pid", cin->proc->pid); j_obj_new_int32_add(j_cpu, "ppid", cin->proc->ppid); j_obj_new_int32_add(j_cpu, "is-thread", cin->proc->is_thread); j_obj_new_string_add(j_cpu, "name", cin->proc->cmdline); j_obj_new_int64_add(j_cpu, "user-cpu-ticks", cin->utime); j_obj_new_int64_add(j_cpu, "system-cpu-ticks", cin->stime); j_obj_new_int64_add(j_cpu, "total-cpu-ticks", cin->ttime); j_obj_new_double_add(j_cpu, "user-cpu-percent", 100.0 * (double)cin->utime / nr_ticks); j_obj_new_double_add(j_cpu, "system-cpu-percent", 100.0 * (double)cin->stime / nr_ticks); j_obj_new_double_add(j_cpu, "total-cpu-percent", 100.0 * (double)cin->ttime / nr_ticks); j_obj_new_string_add(j_cpu, "load-hint", cpustat_loading(100.0 * (double)cin->ttime / nr_ticks)); j_obj_array_add(j_cpuload, j_cpu); } if ((j_cpu = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_cpustat, "cpu-load-total", j_cpu); j_obj_new_double_add(j_cpu, "user-cpu-percent", 100.0 * utime_total); j_obj_new_double_add(j_cpu, "system-cpu-percent", 100.0 * stime_total); j_obj_new_double_add(j_cpu, "total-cpu-percent", 100.0 * ttime_total); } #endif printf("\n"); out: list_free(&sorted, free); return rc; } /* * pagefault_cmp() * pagefault list sort comparitor */ static int pagefault_cmp(const void *data1, const void *data2) { cpustat_info_t *cpustat1 = (cpustat_info_t *)data1; cpustat_info_t *cpustat2 = (cpustat_info_t *)data2; return (cpustat2->major_fault + cpustat2->minor_fault) - (cpustat1->major_fault + cpustat1->minor_fault); } /* * pagefault_dump_diff() * dump difference in pagefaults between two snapshots in time */ int pagefault_dump_diff(json_object *j_tests, const double duration) { int rc = 0; link_t *lo, *ln; list_t sorted; cpustat_info_t *cio, *cin; #ifndef JSON_OUTPUT (void)j_tests; #endif list_init(&sorted); for (ln = cpustat_info_finish.head; ln; ln = ln->next) { cin = (cpustat_info_t*)ln->data; for (lo = cpustat_info_start.head; lo; lo = lo->next) { cio = (cpustat_info_t*)lo->data; if (cin->proc->pid == cio->proc->pid) { cpustat_info_t *cpustat; if ((cpustat = calloc(1, sizeof(*cpustat))) == NULL) { health_check_out_of_memory("cannot allocate cpustat information"); goto out; } cpustat->proc = cio->proc; cpustat->major_fault = cin->major_fault - cio->major_fault; cpustat->minor_fault = cin->minor_fault - cio->minor_fault; cpustat->duration = timeval_to_double(&cin->whence) - timeval_to_double(&cio->whence); if (list_add_ordered(&sorted, cpustat, pagefault_cmp) == NULL) { free(cpustat); goto out; } } } } printf("Page Faults:\n"); if (sorted.head == NULL) { printf(" Nothing measured.\n"); } else { printf(" PID Process Minor/sec Major/sec Total/sec\n"); for (ln = sorted.head; ln; ln = ln->next) { cin = (cpustat_info_t*)ln->data; printf(" %5d %-20.20s %12.2f %12.2f %12.2f\n", cin->proc->pid, cin->proc->cmdline, (double)cin->minor_fault / duration, (double)cin->major_fault / duration, (double)(cin->minor_fault + cin->major_fault) / duration); } } #ifdef JSON_OUTPUT if (j_tests) { json_object *j_fault_info, *j_faults, *j_fault; uint64_t minor_fault_total = 0, major_fault_total = 0; if ((j_fault_info = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "page-faults", j_fault_info); if ((j_faults = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_fault_info, "page-faults-per-process", j_faults); for (ln = sorted.head; ln; ln = ln->next) { cin = (cpustat_info_t*)ln->data; if ((j_fault = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_fault, "pid", cin->proc->pid); j_obj_new_int32_add(j_fault, "ppid", cin->proc->ppid); j_obj_new_int32_add(j_fault, "is-thread", cin->proc->is_thread); j_obj_new_string_add(j_fault, "name", cin->proc->cmdline); j_obj_new_int64_add(j_fault, "minor-page-faults", cin->minor_fault); j_obj_new_int64_add(j_fault, "major-page-faults", cin->major_fault); j_obj_new_int64_add(j_fault, "total-page-faults", cin->minor_fault + cin->major_fault); j_obj_new_double_add(j_fault, "minor-page-faults-rate", (double)cin->minor_fault / duration); j_obj_new_double_add(j_fault, "major-page-faults-rate", (double)cin->major_fault / duration); j_obj_new_double_add(j_fault, "total-page-faults-rate", (double)(cin->minor_fault + cin->major_fault) / duration); j_obj_array_add(j_faults, j_fault); minor_fault_total += cin->minor_fault; major_fault_total += cin->major_fault; } if ((j_fault = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_fault_info, "page-faults-total", j_fault); j_obj_new_int64_add(j_fault, "minor-page-faults-total", minor_fault_total); j_obj_new_int64_add(j_fault, "major-page-faults-total", major_fault_total); j_obj_new_int64_add(j_fault, "total-page-faults-total", minor_fault_total + major_fault_total); j_obj_new_double_add(j_fault, "minor-page-faults-total-rate", (double)minor_fault_total / duration); j_obj_new_double_add(j_fault, "major-page-faults-total-rate", (double)major_fault_total / duration); j_obj_new_double_add(j_fault, "total-page-faults-total-rate", (double)(minor_fault_total + major_fault_total) / duration); } #endif printf("\n"); out: list_free(&sorted, free); return rc; } /* * cpustat_get_by_proc() * get CPU stats for a process */ int cpustat_get_by_proc(proc_info_t *proc, proc_state state) { char filename[PATH_MAX]; FILE *fp; list_t *cpustat = (state == PROC_START) ? &cpustat_info_start : &cpustat_info_finish; snprintf(filename, sizeof(filename), "/proc/%d/stat", proc->pid); if ((fp = fopen(filename, "r")) != NULL) { char comm[20]; uint64_t utime, stime; uint64_t minor_fault, major_fault; pid_t pid; /* 3173 (a.out) R 3093 3173 3093 34818 3173 4202496 165 0 0 0 3194 0 */ if (fscanf(fp, "%8d (%19[^)]) %*c %*d %*d %*d %*d %*d %*u %20" SCNu64 " %*u %20" SCNu64 " %*u %20" SCNu64 " %20" SCNu64, &pid, comm, &minor_fault, &major_fault, &utime, &stime) == 6) { cpustat_info_t *info; info = calloc(1, sizeof(*info)); if (info == NULL) { health_check_out_of_memory("allocating cpustat information"); fclose(fp); return -1; } info->proc = proc; info->utime = utime; info->stime = stime; info->ttime = utime + stime; info->major_fault = major_fault; info->minor_fault = minor_fault; gettimeofday(&info->whence, NULL); info->duration = 0.0; if (list_append(cpustat, info) == NULL) { free(info); fclose(fp); return -1; } } fclose(fp); } return 0; } /* * cpustat_get_all_pids() * get CPU stats for all processes */ int cpustat_get_all_pids(const list_t *pids, proc_state state) { link_t *l; for (l = pids->head; l; l = l->next) { proc_info_t *p = (proc_info_t *)l->data; if (p->is_thread) continue; if (cpustat_get_by_proc(p, state) < 0) return -1; } return 0; } /* * cpustat_init() * initialize cpustat lists */ void cpustat_init(void) { list_init(&cpustat_info_start); list_init(&cpustat_info_finish); } /* * cpustat_cleanup() * free cpustat lists */ void cpustat_cleanup(void) { list_free(&cpustat_info_start, free); list_free(&cpustat_info_finish, free); } health-check-0.01.58/mem.c0000664000175000017500000005443212317222554013606 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "list.h" #include "mem.h" #include "health-check.h" static list_t mem_info_old, mem_info_new; static list_t mem_brk_info; static list_t mem_mmap_info; static const char *mem_types[] = { "Stack", "Heap", "Mapped", }; #ifdef JSON_OUTPUT /* * mem_tolower_str() * string to lower case */ static void mem_tolower_str(char *str) { for (;*str; str++) *str = tolower(*str); } #endif /* * mem_loading() * convert heath growth rate into human readable form */ static const char *mem_loading(const double mem_rate) { char *verb, *adverb; static char buffer[64]; double rate = mem_rate; if (rate == 0.0) return "no change"; if (rate < 0) { verb = "shrinking"; rate = -mem_rate; } else verb = "growing"; if (rate < 1024.0) adverb = " slowly"; else if (rate >= 2.0 * 1024.0 * 1024.0) adverb = " very fast"; else if (rate >= 256.0 * 1024.0) adverb = " fast"; else if (rate >= 8.0 * 1024.0) adverb = " moderately fast"; else adverb = ""; sprintf(buffer, "%s%s", verb, adverb); return buffer; } /* * mem_mmap_account() * do mmap/munmap accounting on pid of map size length. */ int mem_mmap_account(const pid_t pid, const size_t length, const bool mmap) { link_t *l; bool found = false; mem_mmap_info_t *info = NULL; for (l = mem_mmap_info.head; l; l = l->next) { info = (mem_mmap_info_t *)l->data; if (info->pid == pid) { found = true; break; } } if (!found) { if ((info = calloc(1, sizeof(*info))) == NULL) { health_check_out_of_memory("allocating memory tracking brk() information"); return -1; } info->pid = pid; if (list_append(&mem_mmap_info, info) == NULL) { free(info); return -1; } } if (mmap) { info->mmap_count++; info->mmap_length += length; } else { info->munmap_count++; info->munmap_length += length; } return 0; } /* * mem_mmap_cmp() * list sorting based on total mmap size */ static int mem_mmap_cmp(const void *data1, const void *data2) { mem_mmap_info_t *m1 = (mem_mmap_info_t *)data1; mem_mmap_info_t *m2 = (mem_mmap_info_t *)data2; int64_t d1 = m1->mmap_length - m1->munmap_length; int64_t d2 = m2->mmap_length - m2->munmap_length; return d2 - d1; } /* * mem_dump_mmap() * dump mmap changes */ void mem_dump_mmap(json_object *j_tests, const double duration) { list_t sorted; link_t *l; mem_mmap_info_t *info; #if !defined(JSON_OUTPUT) (void)j_tests; #endif printf("Memory Change via mmap() and munmap():\n"); list_init(&sorted); for (l = mem_mmap_info.head; l; l = l->next) { info = (mem_mmap_info_t *)l->data; if (list_add_ordered(&sorted, info, mem_mmap_cmp) == NULL) goto out; } if (mem_mmap_info.head == NULL) { printf(" None.\n\n"); } else { printf(" PID mmaps munmaps Change (K) Rate (K/Sec)\n"); for (l = sorted.head; l; l = l->next) { info = (mem_mmap_info_t *)l->data; proc_info_t *p = proc_cache_find_by_pid(info->pid); int64_t delta = info->mmap_length - info->munmap_length; double rate = ((double)delta) / duration; printf(" %5d %-20.20s %8" PRIu64 " %8" PRIu64 " %8" PRIi64 " %8.2f (%s)\n", info->pid, p ? p->cmdline : "", info->mmap_count, info->munmap_count, delta / 1024, rate / 1024.0, mem_loading(rate)); } printf("\n"); } #ifdef JSON_OUTPUT if (j_tests) { json_object *j_mem_test, *j_mem_infos, *j_mem_info; uint64_t total_mmap_count = 0, total_munmap_count = 0, total_delta = 0; if ((j_mem_test = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "memory-usage-via-mmap", j_mem_test); if ((j_mem_infos = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_mem_test, "memory-usage-via-mmap-per-process", j_mem_infos); for (l = sorted.head; l; l = l->next) { info = (mem_mmap_info_t *)l->data; proc_info_t *p = proc_cache_find_by_pid(info->pid); int64_t delta = info->mmap_length - info->munmap_length; if ((j_mem_info = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_mem_info, "pid", info->pid); if (p) { j_obj_new_int32_add(j_mem_info, "ppid", p->ppid); j_obj_new_int32_add(j_mem_info, "is-thread", p->is_thread); j_obj_new_string_add(j_mem_info, "name", p->cmdline); } j_obj_new_int64_add(j_mem_info, "mmap-count", info->mmap_count); j_obj_new_int64_add(j_mem_info, "munmap-count", info->munmap_count); j_obj_new_int64_add(j_mem_info, "mmap-total-Kbytes", (uint64_t)delta / 1024); j_obj_new_double_add(j_mem_info, "mmap-total-Kbytes-rate", ((double)delta / 1024.0) / duration ); j_obj_array_add(j_mem_infos, j_mem_info); total_mmap_count += info->mmap_count; total_munmap_count += info->munmap_count; total_delta += delta; } if ((j_mem_info = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_mem_test, "memory-usage-via-mmap-total", j_mem_info); j_obj_new_int64_add(j_mem_info, "mmap-count-total", total_mmap_count); j_obj_new_int64_add(j_mem_info, "munmap-count-total", total_munmap_count); j_obj_new_int64_add(j_mem_info, "mmap-total-Kbytes", total_delta / 1024); j_obj_new_double_add(j_mem_info, "mmap-total-Kbytes-rate", ((double)total_delta / 1024.0) / duration); } #endif out: list_free(&sorted, NULL); } /* * mem_brk_account() * sys_brk memory accouting, used in syscall.c */ int mem_brk_account(const pid_t pid, const void *addr) { link_t *l; mem_brk_info_t *info = NULL; if (!addr) return 0; for (l = mem_brk_info.head; l; l = l->next) { info = (mem_brk_info_t *)l->data; if (info->pid == pid) { info->brk_current = addr; info->brk_count++; return 0; } } if ((info = calloc(1, sizeof(*info))) == NULL) { health_check_out_of_memory("allocating memory tracking brk() information"); return -1; } info->pid = pid; info->brk_start = addr; info->brk_current = addr; info->brk_count = 1; if (list_append(&mem_brk_info, info) == NULL) { free(info); return -1; } return 0; } /* * mem_brk_cmp() * list sorting based on total brk size */ static int mem_brk_cmp(const void *data1, const void *data2) { mem_brk_info_t *m1 = (mem_brk_info_t *)data1; mem_brk_info_t *m2 = (mem_brk_info_t *)data2; ptrdiff_t p1 = (char *)m1->brk_current - (char *)m1->brk_start; ptrdiff_t p2 = (char *)m2->brk_current - (char *)m2->brk_start; return p2 - p1; } /* * mem_dump_brk() * dump brk heap changes */ void mem_dump_brk(json_object *j_tests, const double duration) { list_t sorted; link_t *l; mem_brk_info_t *info; #if !defined(JSON_OUTPUT) (void)j_tests; #endif printf("Heap Change via brk():\n"); list_init(&sorted); for (l = mem_brk_info.head; l; l = l->next) { info = (mem_brk_info_t *)l->data; if (list_add_ordered(&sorted, info, mem_brk_cmp) == NULL) goto out; } if (mem_brk_info.head == NULL) { printf(" None.\n\n"); } else { printf(" PID brk Count Change (K) Rate (K/Sec)\n"); for (l = sorted.head; l; l = l->next) { info = (mem_brk_info_t *)l->data; proc_info_t *p = proc_cache_find_by_pid(info->pid); ptrdiff_t delta = ((char *)info->brk_current - (char *)info->brk_start); double rate = ((double)delta) / duration; printf(" %5d %-20.20s %8" PRIu64 " %12td %8.2f (%s)\n", info->pid, p ? p->cmdline : "", info->brk_count, delta / 1024, rate / 1024.0, mem_loading(rate)); } printf("\n"); } #ifdef JSON_OUTPUT if (j_tests) { json_object *j_mem_test, *j_mem_infos, *j_mem_info; uint64_t total_brk_count = 0, total_delta = 0; if ((j_mem_test = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "heap-usage-via-brk", j_mem_test); if ((j_mem_infos = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_mem_test, "heap-usage-via-brk-per-process", j_mem_infos); for (l = sorted.head; l; l = l->next) { info = (mem_brk_info_t *)l->data; proc_info_t *p = proc_cache_find_by_pid(info->pid); ptrdiff_t delta = ((char *)info->brk_current - (char *)info->brk_start); if ((j_mem_info = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_mem_info, "pid", info->pid); if (p) { j_obj_new_int32_add(j_mem_info, "ppid", p->ppid); j_obj_new_int32_add(j_mem_info, "is-thread", p->is_thread); j_obj_new_string_add(j_mem_info, "name", p->cmdline); } j_obj_new_int64_add(j_mem_info, "brk-count", info->brk_count); j_obj_new_int64_add(j_mem_info, "brk-size-Kbytes", (uint64_t)delta / 1024); j_obj_new_double_add(j_mem_info, "brk-size-Kbytes-rate", ((double)delta / 1024.0) / duration); j_obj_array_add(j_mem_infos, j_mem_info); total_brk_count += info->brk_count; total_delta += (uint64_t)delta; } if ((j_mem_info = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_mem_test, "heap-usage-via-brk-total", j_mem_info); j_obj_new_int64_add(j_mem_info, "brk-count-total", total_brk_count); j_obj_new_int64_add(j_mem_info, "brk-size-total-Kbytes", total_delta / 1024); j_obj_new_double_add(j_mem_info, "brk-size-total-Kbytes-rate", ((double)total_delta / 1024.0) / duration); } #endif out: list_free(&sorted, NULL); } /* * mem_cmp() * list sorting based on total memory used */ static int mem_cmp(const void *data1, const void *data2) { mem_info_t *m1 = (mem_info_t *)data1; mem_info_t *m2 = (mem_info_t *)data2; return m2->grand_total - m1->grand_total; } /* * mem_get_size() * parse proc sizes in K bytes */ static int mem_get_size(FILE *fp, const char *field, uint64_t *size) { char tmp[4096]; uint64_t size_k; *size = 0; while (!feof(fp)) { if (fscanf(fp, "%4095[^:]: %" SCNi64 "%*[^\n]%*c", tmp, &size_k) == 2) { if (strcmp(tmp, field) == 0) { *size = size_k * 1024; return 0; } } } return -1; } /* * mem_get_entry() * parse a single memory mapping entry */ static int mem_get_entry(FILE *fp, mem_info_t *mem) { uint64_t addr_start, addr_end, addr_offset; int major, minor; mem_type_t type; char path[PATH_MAX]; uint64_t size, rss, pss; for (;;) { char buffer[4096]; if (fgets(buffer, sizeof(buffer), fp) == NULL) return -1; if (sscanf(buffer, "%" SCNx64 "-%" SCNx64 " %*s %" SCNx64 " %x:%x %*u %s", &addr_start, &addr_end, &addr_offset, &major, &minor, path) == 6) break; if (sscanf(buffer, "%" SCNx64 "-%" SCNx64 " %*s %" SCNx64 " %x:%x %*u", &addr_start, &addr_end, &addr_offset, &major, &minor) == 5) { *path = '\0'; break; } } if (strncmp(path, "[stack", 6) == 0) type = MEM_STACK; else if (strncmp(path, "[heap", 5) == 0) type = MEM_HEAP; else if (!*path && addr_offset == 0 && major == 0 && minor == 0) type = MEM_HEAP; else type = MEM_MAPPED; if (mem_get_size(fp, "Size", &size) < 0) return -1; if (mem_get_size(fp, "Rss", &rss) < 0) return -1; if (mem_get_size(fp, "Pss", &pss) < 0) return -1; mem->size[type] += size; mem->rss[type] += rss; mem->pss[type] += pss; mem->total[type] += size + rss + pss; return 0; } /* * mem_get_by_proc() * get mem info for a specific proc */ int mem_get_by_proc(proc_info_t *p, const proc_state state) { FILE *fp; char path[PATH_MAX]; mem_info_t *m; list_t *mem = (state == PROC_START) ? &mem_info_old : &mem_info_new; if (p->is_thread) return 0; snprintf(path, sizeof(path), "/proc/%i/smaps", p->pid); if ((fp = fopen(path, "r")) == NULL) return 0; if ((m = calloc(1, sizeof(*m))) == NULL) { health_check_out_of_memory("allocating memory tracking information"); fclose(fp); return -1; } m->proc = p; while (mem_get_entry(fp, m) != -1) ; if (list_append(mem, m) == NULL) { free(m); fclose(fp); return -1; } fclose(fp); return 0; } /* * mem_get_all_pids() * scan mem and get mmap info */ int mem_get_all_pids(const list_t *pids, const proc_state state) { link_t *l; for (l = pids->head; l; l = l->next) { proc_info_t *p = (proc_info_t *)l->data; if (mem_get_by_proc(p, state) < 0) { return -1; } } return 0; } /* * mem_delta() * compute memory size change */ static mem_info_t *mem_delta(mem_info_t *mem_new, const list_t *mem_old_list) { link_t *l; int i; mem_info_t *delta; if ((delta = calloc(1, sizeof(*delta))) == NULL) { health_check_out_of_memory("allocating memory delta tracking information"); return NULL; } memset(delta, 0, sizeof(*delta)); for (l = mem_old_list->head; l; l = l->next) { mem_info_t *mem_old = (mem_info_t *)l->data; if (mem_new->proc == mem_old->proc) { for (i = 0; i < MEM_MAX; i++) { delta->proc = mem_new->proc; delta->size[i] = mem_new->size[i] - mem_old->size[i]; delta->rss[i] = mem_new->rss[i] - mem_old->rss[i]; delta->pss[i] = mem_new->pss[i] - mem_old->pss[i]; delta->total[i] = mem_new->total[i] - mem_old->total[i]; } return delta; } } /* Old not found, return new */ memcpy(delta, mem_new, sizeof(*delta)); return delta; } /* * mem_dump_diff() * dump differences between old and new events */ int mem_dump_diff( json_object *j_tests, const double duration) { list_t sorted, sorted_delta; link_t *l; bool deltas = false; #ifndef JSON_OUTPUT (void)j_tests; #endif printf("Memory:\n"); list_init(&sorted); list_init(&sorted_delta); for (l = mem_info_new.head; l; l = l->next) { mem_type_t type; mem_info_t *mem_new = (mem_info_t *)l->data; for (type = MEM_STACK; type < MEM_MAX; type++) mem_new->grand_total += mem_new->total[type]; if (list_add_ordered(&sorted, mem_new, mem_cmp) == NULL) goto out; } for (l = mem_info_new.head; l; l = l->next) { mem_info_t *delta, *mem_new = (mem_info_t *)l->data; if ((delta = mem_delta(mem_new, &mem_info_old)) == NULL) return -1; if (list_add_ordered(&sorted_delta, delta, mem_cmp) == NULL) { free(delta); goto out; } } if (!(opt_flags & OPT_BRIEF)) { printf("Per Process Memory (K):\n"); if (mem_info_new.head == NULL) { printf(" No memory detected.\n\n"); } else { printf(" PID Process Type Size RSS PSS\n"); for (l = sorted.head; l; l = l->next) { mem_info_t *delta = (mem_info_t *)l->data; mem_type_t type; for (type = MEM_STACK; type < MEM_MAX; type++) { printf(" %5d %-20.20s %-6.6s %9" PRIi64 " %9" PRIi64 " %9" PRIi64 "\n", delta->proc->pid, delta->proc->cmdline, mem_types[type], delta->size[type] / 1024, delta->rss[type] / 1024, delta->pss[type] / 1024); } } printf("\n"); } } printf("Change in memory (K/second):\n"); if (mem_info_new.head == NULL) { printf(" No memory detected.\n\n"); } else { for (l = sorted_delta.head; l; l = l->next) { mem_info_t *delta = (mem_info_t *)l->data; mem_type_t type; for (type = MEM_STACK; type < MEM_MAX; type++) { if (delta->total[type]) { if (!deltas) { printf(" PID Process Type Size RSS PSS\n"); deltas = true; } printf(" %5d %-20.20s %-6.6s %9.2f %9.2f %9.2f (%s)\n", delta->proc->pid, delta->proc->cmdline, mem_types[type], (double)(delta->size[type] / 1024.0) / duration, (double)(delta->rss[type] / 1024.0) / duration, (double)(delta->pss[type] / 1024.0) / duration, mem_loading((double)(delta->total[type] / duration))); } } } } if (!deltas) printf(" No changes found.\n"); printf("\n"); #ifdef JSON_OUTPUT if (j_tests) { json_object *j_mem_test, *j_mem_infos, *j_mem_info; char label[128]; mem_type_t type; double rate; int64_t total_size[MEM_MAX], total_rss[MEM_MAX], total_pss[MEM_MAX]; memset(total_size, 0, sizeof(total_size)); memset(total_rss, 0, sizeof(total_rss)); memset(total_pss, 0, sizeof(total_pss)); if ((j_mem_test = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "memory-usage", j_mem_test); if ((j_mem_infos = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_mem_test, "memory-usage-per-process", j_mem_infos); for (l = sorted.head; l; l = l->next) { mem_info_t *delta = (mem_info_t *)l->data; for (type = MEM_STACK; type < MEM_MAX; type++) { if ((j_mem_info = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_mem_info, "pid", delta->proc->pid); j_obj_new_int32_add(j_mem_info, "ppid", delta->proc->ppid); j_obj_new_int32_add(j_mem_info, "is-thread", delta->proc->is_thread); j_obj_new_string_add(j_mem_info, "name", delta->proc->cmdline); /* Size */ snprintf(label, sizeof(label), "%s-size-Kbytes", mem_types[type]); mem_tolower_str(label); j_obj_new_int64_add(j_mem_info, label, delta->size[type] / 1024); /* RSS */ snprintf(label, sizeof(label), "%s-rss-Kbytes", mem_types[type]); mem_tolower_str(label); j_obj_new_int64_add(j_mem_info, label, delta->rss[type] / 1024); /* PSS */ snprintf(label, sizeof(label), "%s-pss-Kbytes", mem_types[type]); mem_tolower_str(label); j_obj_new_int64_add(j_mem_info, label, delta->pss[type] / 1024); j_obj_array_add(j_mem_infos, j_mem_info); total_size[type] += delta->size[type]; total_rss[type] += delta->rss[type]; total_pss[type] += delta->pss[type]; } } if ((j_mem_info = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_mem_test, "memory-usage-total", j_mem_info); for (type = MEM_STACK; type < MEM_MAX; type++) { /* Size */ snprintf(label, sizeof(label), "%s-size-total-Kbytes", mem_types[type]); mem_tolower_str(label); j_obj_new_int64_add(j_mem_info, label, total_size[type] / 1024); /* RSS */ snprintf(label, sizeof(label), "%s-rss-total-Kbytes", mem_types[type]); mem_tolower_str(label); j_obj_new_int64_add(j_mem_info, label, total_rss[type] / 1024); /* PSS */ snprintf(label, sizeof(label), "%s-pss-total-Kbytes", mem_types[type]); mem_tolower_str(label); j_obj_new_int64_add(j_mem_info, label, total_pss[type] / 1024); } memset(total_size, 0, sizeof(total_size)); memset(total_rss, 0, sizeof(total_rss)); memset(total_pss, 0, sizeof(total_pss)); if ((j_mem_test = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "memory-change", j_mem_test); if ((j_mem_infos = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_mem_test, "memory-change-per-process", j_mem_infos); for (l = sorted_delta.head; l; l = l->next) { mem_info_t *delta = (mem_info_t *)l->data; for (type = MEM_STACK; type < MEM_MAX; type++) { if ((j_mem_info = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_mem_info, "pid", delta->proc->pid); j_obj_new_int32_add(j_mem_info, "ppid", delta->proc->ppid); j_obj_new_int32_add(j_mem_info, "is-thread", delta->proc->is_thread); j_obj_new_string_add(j_mem_info, "name", delta->proc->cmdline); /* Size */ rate = (double)(delta->size[type] / 1024.0) / duration; snprintf(label, sizeof(label), "%s-change-size-Kbytes", mem_types[type]); mem_tolower_str(label); j_obj_new_int64_add(j_mem_info, label, delta->size[type] / 1024); snprintf(label, sizeof(label), "%s-change-size-Kbytes-rate", mem_types[type]); mem_tolower_str(label); j_obj_new_double_add(j_mem_info, label, rate); snprintf(label, sizeof(label), "%s-change-size-Kbytes-hint", mem_types[type]); mem_tolower_str(label); j_obj_new_string_add(j_mem_info, label, mem_loading(rate)); /* RSS */ rate = (double)(delta->rss[type] / 1024.0) / duration; snprintf(label, sizeof(label), "%s-change-rss-Kbytes", mem_types[type]); mem_tolower_str(label); j_obj_new_int64_add(j_mem_info, label, delta->rss[type] / 1024); snprintf(label, sizeof(label), "%s-change-rss-Kbytes-rate", mem_types[type]); mem_tolower_str(label); j_obj_new_double_add(j_mem_info, label, rate); snprintf(label, sizeof(label), "%s-change-rss-Kbytes-hint", mem_types[type]); mem_tolower_str(label); j_obj_new_string_add(j_mem_info, label, mem_loading(rate)); /* PSS */ rate = (double)(delta->pss[type] / 1024.0) / duration; snprintf(label, sizeof(label), "%s-change-pss-Kbytes", mem_types[type]); mem_tolower_str(label); j_obj_new_int64_add(j_mem_info, label, delta->pss[type] / 1024); snprintf(label, sizeof(label), "%s-change-pss-Kbytes-rate", mem_types[type]); mem_tolower_str(label); j_obj_new_double_add(j_mem_info, label, rate); snprintf(label, sizeof(label), "%s-change-pss-Kbytes-hint", mem_types[type]); mem_tolower_str(label); j_obj_new_string_add(j_mem_info, label, mem_loading(rate)); j_obj_array_add(j_mem_infos, j_mem_info); total_size[type] += delta->size[type]; total_rss[type] += delta->rss[type]; total_pss[type] += delta->pss[type]; } } if ((j_mem_info = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_mem_test, "memory-change-total", j_mem_info); for (type = MEM_STACK; type < MEM_MAX; type++) { /* Size */ snprintf(label, sizeof(label), "%s-change-size-total-Kbytes", mem_types[type]); mem_tolower_str(label); j_obj_new_int64_add(j_mem_info, label, total_size[type] / 1024); /* RSS */ snprintf(label, sizeof(label), "%s-change-rss-total-Kbytes", mem_types[type]); mem_tolower_str(label); j_obj_new_int64_add(j_mem_info, label, total_rss[type] / 1024); /* PSS */ snprintf(label, sizeof(label), "%s-change-pss-total-Kbytes", mem_types[type]); mem_tolower_str(label); j_obj_new_int64_add(j_mem_info, label, total_pss[type] / 1024); } } #endif out: list_free(&sorted, NULL); list_free(&sorted_delta, free); return 0; } /* * mem_init() * initialise mem lists */ void mem_init(void) { list_init(&mem_info_old); list_init(&mem_info_new); list_init(&mem_brk_info); list_init(&mem_mmap_info); } /* * mem_cleanup() * free mem lists */ void mem_cleanup(void) { list_free(&mem_info_old, free); list_free(&mem_info_new, free); list_free(&mem_brk_info, free); list_free(&mem_mmap_info, free); } health-check-0.01.58/json.h0000664000175000017500000000413012317222554013774 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __JSON_SHIM_H__ #define __JSON_SHIM_H__ #ifdef JSON_OUTPUT #include extern json_object *j_obj_new_array(void); extern json_object *j_obj_new_obj(void); extern json_object *j_obj_new_int32(const int32_t i); extern json_object *j_obj_new_int64(const int64_t i); extern json_object *j_obj_new_double(const double d); extern json_object *j_obj_new_string(const char *str); static inline void j_obj_obj_add(json_object *parent, const char *label, json_object *obj) { json_object_object_add(parent, label, obj); } static inline void j_obj_array_add(json_object *array, json_object *obj) { json_object_array_add(array, obj); } static inline void j_obj_new_int32_add(json_object *parent, const char *label, const int32_t i) { j_obj_obj_add(parent, label, j_obj_new_int32(i)); } static inline void j_obj_new_int64_add(json_object *parent, const char *label, const int64_t i) { j_obj_obj_add(parent, label, j_obj_new_int64(i)); } static inline void j_obj_new_double_add(json_object *parent, const char *label, const double d) { j_obj_obj_add(parent, label, j_obj_new_double(d)); } static inline void j_obj_new_string_add(json_object *parent, const char *label, const char *str) { j_obj_obj_add(parent, label, j_obj_new_string(str)); } #else #define json_object void #endif #endif health-check-0.01.58/ctxt-switch.c0000664000175000017500000002140112317222554015277 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "list.h" #include "json.h" #include "ctxt-switch.h" #include "health-check.h" static list_t ctxt_switch_info_start, ctxt_switch_info_finish; /* * ctxt_switch_cmp() * compare context switch info for sorting */ static int ctx_switch_cmp(const void *data1, const void *data2) { ctxt_switch_info_t *c1 = (ctxt_switch_info_t *)data1; ctxt_switch_info_t *c2 = (ctxt_switch_info_t *)data2; return c2->total - c1->total; } /* * ctxt_switch_get_by_proc() * get context switch info for a specific process */ int ctxt_switch_get_by_proc(proc_info_t *proc, proc_state state) { char path[PATH_MAX]; char buf[4096]; FILE *fp; ctxt_switch_info_t *info; list_t *ctxt_switches = (state == PROC_START) ? &ctxt_switch_info_start : &ctxt_switch_info_finish; snprintf(path, sizeof(path), "/proc/%i/status", proc->pid); if ((fp = fopen(path, "r")) == NULL) return 0; if ((info = calloc(1, sizeof(*info))) == NULL) { health_check_out_of_memory("allocating context switch information"); fclose(fp); return -1; } info->voluntary = 0; info->involuntary = 0; info->valid = false; info->proc = proc; while (!feof(fp)) { if (fgets(buf, sizeof(buf), fp) == NULL) break; if (!strncmp(buf, "voluntary_ctxt_switches:", 24)) { (void)sscanf(buf + 24, "%" SCNu64, &info->voluntary); continue; } if (!strncmp(buf, "nonvoluntary_ctxt_switches:", 27)) { (void)sscanf(buf + 27, "%" SCNu64, &info->involuntary); continue; } } fclose(fp); info->total = info->voluntary + info->involuntary; info->valid = true; if (list_append(ctxt_switches, info) == NULL) { free(info); return -1; } return 0; } /* * ctxt_switch_get_all_pids() * scan /proc/pid/status for context switch data */ int ctxt_switch_get_all_pids(const list_t *pids, proc_state state) { link_t *l; for (l = pids->head; l; l = l->next) { proc_info_t *p = (proc_info_t *)l->data; if (ctxt_switch_get_by_proc(p, state) < 0) return -1; } return 0; } /* * ctxt_switch_loading() * context switch rate to some human understandable text */ static const char *ctxt_switch_loading(const double rate) { if (rate == 0.0) return "idle"; if (rate > 10000.0) return "very high"; if (rate > 1000.0) return "high"; if (rate > 100.0) return "quite high"; if (rate > 10.0) return "moderate"; if (rate > 1.0) return "low"; return "very low"; } /* * ctxt_switch_delta() * find delta in context switches between old, new. * if no old then delta is the new. */ static void ctxt_switch_delta( const ctxt_switch_info_t *ctxt_switch_new, const list_t *ctxt_switches_old, uint64_t *total, uint64_t *voluntary, uint64_t *involuntary) { link_t *l; for (l = ctxt_switches_old->head; l; l = l->next) { ctxt_switch_info_t *ctxt_switch_old = (ctxt_switch_info_t*)l->data; if (ctxt_switch_new->proc == ctxt_switch_old->proc) { if (!ctxt_switch_old->valid) break; *total = ctxt_switch_new->total - ctxt_switch_old->total; *voluntary = ctxt_switch_new->voluntary - ctxt_switch_old->voluntary; *involuntary = ctxt_switch_new->involuntary - ctxt_switch_old->involuntary; return; } } *total = ctxt_switch_new->total; *voluntary = ctxt_switch_new->voluntary; *involuntary = ctxt_switch_new->involuntary; } /* * ctxt_switch_dump_diff() * dump differences between old and new events */ void ctxt_switch_dump_diff(json_object *j_tests, const double duration) { link_t *l; list_t sorted; #ifndef JSON_OUTPUT (void)j_tests; #endif printf("Context Switches:\n"); list_init(&sorted); for (l = ctxt_switch_info_finish.head; l; l = l->next) { ctxt_switch_info_t *new_info, *info = (ctxt_switch_info_t *)l->data; if (!info->valid) continue; if ((new_info = calloc(1, sizeof(*info))) == NULL) { health_check_out_of_memory("allocating context switch information"); goto out; } new_info->proc = info->proc; ctxt_switch_delta(info, &ctxt_switch_info_start, &new_info->total, &new_info->voluntary, &new_info->involuntary); if (list_add_ordered(&sorted, new_info, ctx_switch_cmp) == NULL) { free(new_info); goto out; } } if (sorted.head) { if (opt_flags & OPT_BRIEF) { double rate = 0.0; for (l = sorted.head; l; l = l->next) { ctxt_switch_info_t *info = (ctxt_switch_info_t *)l->data; rate += (double)info->total; } rate /= duration; printf(" %.2f context switches/sec (%s)\n\n", rate, ctxt_switch_loading(rate)); } else { int count = 0; double total_total = 0.0, total_voluntary = 0.0, total_involuntary = 0.0; printf(" PID Process Voluntary Involuntary Total\n"); printf(" Ctxt Sw/Sec Ctxt Sw/Sec Ctxt Sw/Sec\n"); for (l = sorted.head; l; l = l->next) { ctxt_switch_info_t *info = (ctxt_switch_info_t *)l->data; printf(" %5d %-20.20s %12.2f %12.2f %12.2f (%s)\n", info->proc->pid, info->proc->cmdline, (double)info->voluntary / duration, (double)info->involuntary / duration, (double)info->total / duration, ctxt_switch_loading((double)info->total / duration)); total_voluntary += (double)info->voluntary; total_involuntary += (double)info->involuntary; total_total += (double)info->total; count++; } if (count > 1) printf(" %-27.27s%12.2f %12.2f %12.2f\n", "Total", total_voluntary / duration, total_involuntary / duration, total_total / duration); printf("\n"); } } else { printf(" No context switches detected.\n\n"); } #ifdef JSON_OUTPUT if (j_tests) { json_object *j_ctxt_switch_test, *j_ctxt_switches, *j_ctxt_switch; uint64_t total = 0; double total_rate; if ((j_ctxt_switch_test = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "context-switches", j_ctxt_switch_test); if ((j_ctxt_switches = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_ctxt_switch_test, "context-switches-per-process", j_ctxt_switches); for (l = sorted.head; l; l = l->next) { ctxt_switch_info_t *info = (ctxt_switch_info_t *)l->data; total += (double)info->total; if ((j_ctxt_switch = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_ctxt_switch, "pid", info->proc->pid); j_obj_new_int32_add(j_ctxt_switch, "ppid", info->proc->ppid); j_obj_new_int32_add(j_ctxt_switch, "is-thread", info->proc->is_thread); j_obj_new_string_add(j_ctxt_switch, "name", info->proc->cmdline); j_obj_new_int64_add(j_ctxt_switch, "voluntary-context-switches", info->voluntary); j_obj_new_double_add(j_ctxt_switch, "voluntary-context-switch-rate", (double)info->voluntary / duration); j_obj_new_int64_add(j_ctxt_switch, "involuntary-context-switches", (double)info->involuntary / duration); j_obj_new_double_add(j_ctxt_switch, "involuntary-context-switch-rate", (double)info->involuntary / duration); j_obj_new_int64_add(j_ctxt_switch, "total-context-switches", info->total); j_obj_new_double_add(j_ctxt_switch, "total-context-switch-rate", (double)info->total / duration); j_obj_new_string_add(j_ctxt_switch, "load-hint", ctxt_switch_loading((double)info->total / duration)); j_obj_array_add(j_ctxt_switches, j_ctxt_switch); } total_rate = (double)total / duration; if ((j_ctxt_switch = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_ctxt_switch_test, "context-switches-total", j_ctxt_switch); j_obj_new_int64_add(j_ctxt_switch, "context-switch-total", total); j_obj_new_double_add(j_ctxt_switch, "context-switch-total-rate", total_rate); j_obj_new_string_add(j_ctxt_switch, "load-hint-total", ctxt_switch_loading(total_rate)); } #endif out: list_free(&sorted, free); } /* * ctxt_switch_init() * initialize lists */ void ctxt_switch_init(void) { list_init(&ctxt_switch_info_start); list_init(&ctxt_switch_info_finish); } /* * ctxt_switch_cleanup() * cleanup lists */ void ctxt_switch_cleanup(void) { list_free(&ctxt_switch_info_start, free); list_free(&ctxt_switch_info_finish, free); } health-check-0.01.58/alloc.h0000664000175000017500000000272012317222554014120 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __ALLOC_H__ #define __ALLOC_H__ #include #include #if DEBUG_MALLOC void *__malloc(const size_t size, const char *where, const int line); void __free(void *ptr, const char *where, const int line); void *__calloc(const size_t nmemb, const size_t size, const char *where, const int line); void *__realloc(void *ptr, const size_t size, const char *where, const int line); #define malloc(size) __malloc(size, __func__, __LINE__) #define free(ptr) __free(ptr, __func__, __LINE__) #define calloc(nmemb, size) __calloc(nmemb, size, __func__, __LINE__) #define realloc(ptr, size) __realloc(ptr, size, __func__, __LINE__) #endif #endif health-check-0.01.58/mem.h0000664000175000017500000000473212317222554013611 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __MEM_H__ #define __MEM_H__ #define _GNU_SOURCE #include "proc.h" #include "list.h" #include "json.h" #include typedef enum { MEM_STACK = 0, MEM_HEAP, MEM_MAPPED, MEM_MAX, } mem_type_t; /* wakeup event information per process */ typedef struct mem_info_t { proc_info_t *proc; /* Proc specific info */ int64_t size[MEM_MAX]; /* region size */ int64_t rss[MEM_MAX]; /* RSS size */ int64_t pss[MEM_MAX]; /* PSS size */ int64_t total[MEM_MAX]; /* total size */ int64_t grand_total; /* grand total of same mem types */ } mem_info_t; typedef struct { pid_t pid; /* process id */ const void *brk_start; /* start of brk location */ const void *brk_current; /* current brk location */ uint64_t brk_count; /* brk calls made */ } mem_brk_info_t; typedef struct { pid_t pid; /* process id */ uint64_t mmap_length; /* processes' total mmap region size */ uint64_t mmap_count; /* number of mmaps made */ uint64_t munmap_length; /* processes' total unmap region size */ uint64_t munmap_count; /* number of unmaps made */ } mem_mmap_info_t; extern void mem_init(void); extern void mem_cleanup(void); extern int mem_get_all_pids(const list_t *pids, const proc_state state); extern int mem_get_by_proc(proc_info_t *p, const proc_state state); extern int mem_brk_account(const pid_t pid, const void *addr); extern void mem_dump_brk(json_object *j_tests, const double duration); extern int mem_mmap_account(const pid_t pid, size_t length, bool mmap); extern void mem_dump_mmap(json_object *j_tests, const double duration); extern void mem_get(const list_t *pids, list_t *mem); extern int mem_dump_diff(json_object *j_tests, const double duration); #endif health-check-0.01.58/Makefile0000664000175000017500000000373112317222554014320 0ustar kingkingVERSION=0.01.58 # # Codename "Intrepid Inotify Interrogator" # JSON_OUTPUT=y CFLAGS += -Wall -Wextra -DVERSION='"$(VERSION)"' LDFLAGS += -lpthread ifeq ($(JSON_OUTPUT),y) LDFLAGS += -ljson CFLAGS += -DJSON_OUTPUT endif ifeq ($(FNOTIFY),y) CFLAGS += -DFNOTIFY endif #CFLAGS += -DDEBUG_MALLOC BINDIR=/usr/bin MANDIR=/usr/share/man/man8 OBJS = alloc.o list.o pid.o proc.o net.o syscall.o timeval.o fnotify.o event.o cpustat.o mem.o ctxt-switch.o health-check.o ifeq ($(JSON_OUTPUT),y) OBJS += json.o endif health-check: $(OBJS) Makefile $(CC) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS) health-check.8.gz: health-check.8 gzip -c $< > $@ alloc.o: alloc.c alloc.h cpustat.o: cpustat.c list.h json.h cpustat.h timeval.h health-check.h ctxt-switch.o: ctxt-switch.c list.h json.h ctxt-switch.h health-check.h event.o: event.c list.h json.h event.h health-check.h fnotify.o: fnotify.c fnotify.h list.h json.h proc.h health-check.h health-check.o: health-check.c list.h json.h pid.h proc.h syscall.h timeval.h \ fnotify.h event.h cpustat.h mem.h net.h ctxt-switch.h json.o: json.c json.h health-check.h list.o: list.c list.h mem.o: mem.c mem.h list.h health-check.h net.o: net.c net.h list.h proc.h json.h health-check.h pid.o: pid.c pid.h list.h proc.h alloc.h proc.o: proc.c list.h pid.h proc.h net.h health-check.h syscall.o: syscall.c syscall.h proc.h json.h net.h mem.h \ cpustat.h fnotify.h ctxt-switch.h health-check.h timeval.o: timeval.c timeval.h dist: rm -rf health-check-$(VERSION) mkdir health-check-$(VERSION) cp -rp Makefile *.c *.h scripts health-check.8 COPYING health-check-$(VERSION) tar -zcf health-check-$(VERSION).tar.gz health-check-$(VERSION) rm -rf health-check-$(VERSION) clean: rm -f health-check health-check.o health-check.8.gz rm -f health-check-$(VERSION).tar.gz rm -f $(OBJS) install: health-check health-check.8.gz mkdir -p ${DESTDIR}${BINDIR} cp health-check ${DESTDIR}${BINDIR} mkdir -p ${DESTDIR}${MANDIR} cp health-check.8.gz ${DESTDIR}${MANDIR} health-check-0.01.58/syscall.c0000664000175000017500000026234012317222554014501 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ int procs_traced = 0; #define _GNU_SOURCE #include #include #include #include #include #include #include #include #if defined(__arm__) #include #endif #if defined(__aarch64__) #include #include #include #endif #include #if defined(__x86_64__) || defined(__i386__) #include #endif #include #include #include #include #include "list.h" #include "syscall.h" #include "proc.h" #include "json.h" #include "net.h" #include "mem.h" #include "cpustat.h" #include "fnotify.h" #include "ctxt-switch.h" #include "health-check.h" #include "timeval.h" #ifdef SYSCALL_SUPPORTED #define HASH_TABLE_SIZE (1997) /* Must be prime */ #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #if defined(__aarch64__) struct arm_pt_regs { int regs[18]; }; struct aarch64_regs { struct user_pt_regs usr; struct arm_pt_regs arm; }; #endif typedef enum { SYSCALL_ENTRY = 0, SYSCALL_RETURN, SYSCALL_UNKNOWN } syscall_call_state; static pthread_t syscall_tracer; static int syscall_count = 0; static int info_emit = false; static pid_t main_pid = -1; static syscall_context_t *syscall_get_context(pid_t pid); static list_t syscall_wakelocks; static list_t syscall_contexts; /* This links all the items in syscall_contexts_cache */ static list_t syscall_syncs; static list_t *__pids; /* We need to fix this into a global pids cache/list */ /* hash table for syscalls, hashed on pid and syscall number */ static syscall_info_t *syscall_info[HASH_TABLE_SIZE]; /* hash table for cached fds, hashed on pid and fd */ static fd_cache_t *fd_cache[HASH_TABLE_SIZE]; /* hash table for cached filenames, hashed on pid and filename */ static filename_info_t *filename_cache[HASH_TABLE_SIZE]; /* hash table for cached context info, hased on pid */ static syscall_context_t *syscall_contexts_cache[HASH_TABLE_SIZE]; /* minimum allowed thresholds for poll'd system calls that have timeouts */ static double syscall_timeout[] = { #ifdef SYS_clock_nanosleep TIMEOUT(clock_nanosleep, 1.0), #endif #ifdef SYS_epoll_pwait TIMEOUT(epoll_pwait, 1.0), #endif #ifdef SYS_epoll_wait TIMEOUT(epoll_wait, 1.0), #endif #ifdef SYS_mq_timedreceive TIMEOUT(mq_timedreceive, 1.0), #endif #ifdef SYS_mq_timedsend TIMEOUT(mq_timedsend, 1.0), #endif #ifdef SYS_nanosleep TIMEOUT(nanosleep, 1.0), #endif #ifdef SYS_poll TIMEOUT(poll, 1.0), #endif #ifdef SYS_ppoll TIMEOUT(ppoll, 1.0), #endif #ifdef SYS_pselect6 TIMEOUT(pselect6, 1.0), #endif #ifdef SYS_recvmmsg TIMEOUT(recvmmsg, 1.0), #endif #ifdef SYS_rt_sigtimedwait TIMEOUT(rt_sigtimedwait, 1.0), #endif #ifdef SYS_select TIMEOUT(select, 1.0), #endif #ifdef SYS_semtimedop TIMEOUT(semtimedop, 1.0), #endif }; /* * syscall_valid() * is syscall in the syscall table bounds? */ static bool syscall_valid(const int syscall) { return (syscall >= 0) && (syscall <= (int)syscalls_len); } #ifdef SYS_connect /* * syscall_connect_args() * stub for connect syscalls. * A smart approach is to inspect the connect and * see what address is being connected to for the net_* * connections. */ static void syscall_connect_args( const syscall_t *sc, const syscall_info_t *s, const pid_t pid) { (void)sc; (void)s; (void)pid; /* Inspect connect address, update network stats */ } /* * syscall_connect_ret() * trigger a network connection update once a * connect syscall has occurred. */ static void syscall_connect_ret(const syscall_t *sc, const syscall_info_t *s, const int ret) { (void)sc; (void)ret; (void)net_connection_pid(s->proc->pid); } #endif /* * syscall_nanosleep_generic_ret() * handle nanosecond syscall returns */ #if defined(SYS_clock_nanosleep) || defined(SYS_nanosleep) static void syscall_nanosleep_generic_ret(json_object *j_obj, const syscall_t *sc, const syscall_info_t *s) { link_t *l; #ifndef JSON_OUTPUT (void)j_obj; #endif uint64_t ret_error = 0; for (l = s->return_history.head; l; l = l->next) { syscall_return_info_t *ret = (syscall_return_info_t *)l->data; if (ret->ret != 0) ret_error++; } if (ret_error) { printf(" %s (%i), %s:\n", s->proc->cmdline, s->proc->pid, sc->name); printf(" %8" PRIu64 " %s system call errors\n", ret_error, sc->name); info_emit = true; #ifdef JSON_OUTPUT if (j_obj) { json_object *j_nanosleep_error, *j_error; if ((j_nanosleep_error= j_obj_new_obj()) == NULL) return; j_obj_array_add(j_obj, j_nanosleep_error); if ((j_error = j_obj_new_obj()) == NULL) return; j_obj_obj_add(j_nanosleep_error, "nanosleep-error", j_error); j_obj_new_int32_add(j_error, "pid", s->proc->pid); j_obj_new_int32_add(j_error, "ppid", s->proc->ppid); j_obj_new_int32_add(j_error, "is-thread", s->proc->is_thread); j_obj_new_string_add(j_error, "name", s->proc->cmdline); j_obj_new_string_add(j_error, "system-call", sc->name); j_obj_new_int64_add(j_error, "error-count", ret_error); } #endif } } #endif #if defined(SYS_epoll_pwait) || defined(SYS_epoll_wait) || \ defined(SYS_poll) || defined(SYS_ppol) || \ defined(SYS_pselect6) || defined(SYS_rt_sigtimedwait) || \ defined(SYS_select) /* * syscall_poll_generic_ret() * handle generic poll returns */ static void syscall_poll_generic_ret(json_object *j_obj, const syscall_t *sc, const syscall_info_t *s) { link_t *l; int prev_ret = -1; double prev_timeout = -1.0; uint64_t zero_timeout_repeats = 0; uint64_t zero_timeouts = 0; uint64_t timeout_repeats = 0; uint64_t ret_error = 0; #ifndef JSON_OUTPUT (void)j_obj; #endif for (l = s->return_history.head; l; l = l->next) { syscall_return_info_t *ret = (syscall_return_info_t *)l->data; /* Timed out? */ if (ret->ret == 0) { /* And the timeout time was zero, we're abusing the poll */ if (ret->timeout == 0.0) { zero_timeouts++; /* And if the previous poll was also abusive.. */ if (prev_ret == 0) { if (prev_timeout == 0.0) { /* somebody is polling hard and wasting cycles */ zero_timeout_repeats++; } else { /* polling, but not so hard */ timeout_repeats++; } } } } else if (ret->ret < 0) ret_error++; prev_ret = ret->ret; prev_timeout = ret->timeout; } if (zero_timeouts | timeout_repeats | zero_timeout_repeats | ret_error) { printf(" %s (%i), %s:\n", s->proc->cmdline, s->proc->pid, sc->name); if (zero_timeouts) printf(" %8" PRIu64 " immediate timed out calls with zero timeout (non-blocking peeks)\n", zero_timeouts); if (timeout_repeats) printf(" %8" PRIu64 " repeated timed out polled calls with non-zero timeouts (light polling)\n", timeout_repeats); if (zero_timeout_repeats) printf(" %8" PRIu64 " repeated immediate timed out polled calls with zero timeouts (heavy polling peeks)\n", zero_timeout_repeats); if (ret_error) printf(" %8" PRIu64 " system call errors\n", ret_error); info_emit = true; } #ifdef JSON_OUTPUT if (j_obj) { json_object *j_timeout, *j_poll; if ((j_timeout = j_obj_new_obj()) == NULL) return; j_obj_array_add(j_obj, j_timeout); if ((j_poll = j_obj_new_obj()) == NULL) return; j_obj_obj_add(j_timeout, "polling-timeout", j_poll); j_obj_new_int32_add(j_poll, "pid", s->proc->pid); j_obj_new_int32_add(j_poll, "ppid", s->proc->ppid); j_obj_new_int32_add(j_poll, "is-thread", s->proc->is_thread); j_obj_new_string_add(j_poll, "name", s->proc->cmdline); j_obj_new_string_add(j_poll, "system-call", sc->name); j_obj_new_int64_add(j_poll, "zero-timeouts", zero_timeouts); j_obj_new_int64_add(j_poll, "repeat-timeouts", timeout_repeats); j_obj_new_int64_add(j_poll, "repeat-zero-timeouts", zero_timeout_repeats); j_obj_new_int64_add(j_poll, "error-count", ret_error); } #endif } #endif #if defined(SYS_semtimedop) /* * syscall_semtimedop_ret() * handler for return for semtimedop syscall */ static void syscall_semtimedop_ret(json_object *j_obj, const syscall_t *sc, const syscall_info_t *s) { (void)j_obj, (void)sc; (void)s; /* No-op for now, need to examine errno */ } #endif #if defined(SYS_mq_timedreceive) /* * syscall_mq_timedreceive_ret() * handler for return for mq_timedreceive syscall */ static void syscall_mq_timedreceive_ret(json_object *j_obj, const syscall_t *sc, const syscall_info_t *s) { (void)j_obj, (void)sc; (void)s; /* No-op for now, need to examine errno */ } #endif #if defined(SYS_mq_timedsend) /* * syscall_mq_timedsend_ret() * handler for return for mq_timedsend syscall */ static void syscall_mq_timedsend_ret(json_object *j_obj, const syscall_t *sc, const syscall_info_t *s) { (void)j_obj, (void)sc; (void)s; /* No-op for now, need to examine errno */ } #endif /* * syscall_get_call_state() * are we entering a system call or exiting it or don't know? */ static syscall_call_state syscall_get_call_state(const pid_t pid) { #if defined(__x86_64__) errno = 0; if (ptrace(PTRACE_PEEKUSER, pid, sizeof(long) * RAX, NULL) != -ENOSYS) return SYSCALL_RETURN; if (errno) return SYSCALL_UNKNOWN; return SYSCALL_ENTRY; #elif defined(__i386__) errno = 0; if (ptrace(PTRACE_PEEKUSER, pid, sizeof(long) * EAX, NULL) != -ENOSYS) return SYSCALL_RETURN; if (errno) return SYSCALL_UNKNOWN; return SYSCALL_ENTRY; #elif defined(__arm__) || defined(__aarch64__) || defined (__powerpc__) (void)pid; return SYSCALL_UNKNOWN; /* Don't think it is possible to do this */ #else (void)pid; return SYSCALL_UNKNOWN; #endif } /* * syscall_get_call() * get syscall number */ static int syscall_get_call(const pid_t pid, int *syscall) { #if defined(__x86_64__) errno = 0; *syscall = ptrace(PTRACE_PEEKUSER, pid, sizeof(long) * ORIG_RAX, NULL); if (errno) { *syscall = -1; return -1; } return 0; #elif defined(__i386__) errno = 0; *syscall = ptrace(PTRACE_PEEKUSER, pid, sizeof(long) * ORIG_EAX, NULL); if (errno) { *syscall = -1; return -1; } return 0; #elif defined(__arm__) struct pt_regs regs; unsigned long sc; errno = 0; ptrace(PTRACE_GETREGS, pid, NULL, (void *)®s); if (errno) { *syscall = -1; return -1; } /* Thumb mode */ if (regs.ARM_cpsr & 0x20) { *syscall = regs.ARM_r7; return 0; } errno = 0; sc = ptrace(PTRACE_PEEKTEXT, pid, (void *)(regs.ARM_pc - 4), NULL); if (errno) { *syscall = -1; return -1; } if (sc == 0xef000000) sc = regs.ARM_r7; else { if ((sc & 0x0ff00000) != 0x0f900000) { fprintf(stderr, "bad syscall trap 0x%lx.\n", sc); *syscall = -1; return -1; } sc &= 0xfffff; } if (sc & 0x0f0000) sc &= 0xffff; *syscall = sc; return 0; #elif defined(__powerpc__) errno = 0; *syscall = ptrace(PTRACE_PEEKUSER, pid, sizeof(long) * PT_R0, NULL); if (errno) { *syscall = -1; return -1; } return 0; #elif defined (__aarch64__) struct aarch64_regs regs; struct iovec io = { .iov_base = ®s }; errno = 0; io.iov_len = sizeof(struct aarch64_regs); ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &io); if (errno) return -1; switch (io.iov_len) { case sizeof(regs.arm): *syscall = regs.arm.regs[7]; break; case sizeof(regs.usr): *syscall = regs.usr.regs[8]; break; default: *syscall = 0; return -1; } return 0; #else #warning syscall_get_call not implemented for this arch (void)pid; *syscall = -1; return -1; #endif } /* * syscall_get_return() * get syscall return code */ static int syscall_get_return(const pid_t pid, int *rc) { #if defined (__x86_64__) errno = 0; *rc = ptrace(PTRACE_PEEKUSER, pid, sizeof(long) * RAX, NULL); if (errno) return -1; if (*rc == -ENOSYS) { printf("got unexpected SYSCALL entry\n"); return -1; /* Not in syscall entry */ } return 0; #elif defined (__i386__) errno = 0; *rc = ptrace(PTRACE_PEEKUSER, pid, sizeof(long) * EAX, NULL); if (errno) return -1; if (*rc == -ENOSYS) { printf("got unexpected SYSCALL entry\n"); return -1; /* Not in syscall entry */ } return 0; #elif defined (__arm__) struct pt_regs regs; errno = 0; ptrace(PTRACE_GETREGS, pid, NULL, (void *)®s); if (errno) return -1; *rc = regs.ARM_r0; return 0; #elif defined (__aarch64__) struct aarch64_regs regs; struct iovec io = { .iov_base = ®s }; errno = 0; io.iov_len = sizeof(struct aarch64_regs); ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &io); if (errno) return -1; *rc = regs.usr.regs[0]; return 0; #elif defined(__powerpc__) long flag; errno = 0; flag = ptrace(PTRACE_PEEKUSER, pid, sizeof(long) * PT_CCR, NULL); if (errno) return -1; *rc = ptrace(PTRACE_PEEKUSER, pid, sizeof(long) * PT_R3, NULL); if (errno) return -1; if (flag & 0x10000000) *rc = -(*rc); return 0; #else return -1; #endif } /* * syscall_get_args() * fetch n args from system call */ static int syscall_get_args( const pid_t pid, const int arg, unsigned long args[]) { #if defined (__i386__) int i; for (i = 0; i <= arg; i++) args[i] = ptrace(PTRACE_PEEKUSER, pid, i * 4, &args); return 0; #elif defined (__x86_64__) int i; long cs; int *regs; static int regs32[] = { RBX, RCX, RDX, RSI, RDI, RBP }; static int regs64[] = { RDI, RSI, RDX, R10, R8, R9 }; cs = ptrace(PTRACE_PEEKUSER, pid, 8*CS, NULL); switch (cs) { case 0x23: /* 32 bit mode */ regs = regs32; break; case 0x33: /* 64 bit mode */ regs = regs64; break; default: fprintf(stderr, "Unknown personality, CS=0x%x.\n", (int)cs); return -1; } for (i = 0; i <= arg; i++) args[i] = ptrace(PTRACE_PEEKUSER, pid, regs[i] * 8, NULL); return 0; #elif defined (__arm__) int i; struct pt_regs regs; if (ptrace(PTRACE_GETREGS, pid, NULL, (void *)®s) < 0) return -1; for (i = 0; i <= arg; i++) args[i] = regs.uregs[i]; return 0; #elif defined (__aarch64__) struct aarch64_regs regs; struct iovec io = { .iov_base = ®s }; int i; errno = 0; io.iov_len = sizeof(struct aarch64_regs); ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &io); if (errno) return -1; for (i = 0; i <= arg; i++) args[i] = regs.usr.regs[i]; return 0; #elif defined (__powerpc__) int i; for (i = 0; i <= arg; i++) { int reg = (i == 0) ? PT_ORIG_R3 : (PT_R3 + i); errno = 0; args[i] = ptrace(PTRACE_PEEKUSER, pid, reg * sizeof(unsigned long), NULL); if (errno) return -1; } return 0; #else int i; for (i = 0; i <= arg; i++) args[i] = 0; fprintf(stderr, "Unknown arch.\n"); return -1; #endif } /* * syscall_name * get system call name */ static void syscall_name(const int syscall, char *name, const size_t len) { if (syscall_valid(syscall) && (syscalls[syscall].name)) { strncpy(name, syscalls[syscall].name, len); } else { /* Don't know it */ snprintf(name, len, "SYS_NR_%d", syscall); } } /* * hash_syscall() * hash syscall and pid */ static inline unsigned long hash_syscall(const pid_t pid, const int syscall) { unsigned long h; h = (pid ^ (pid << 3) ^ syscall) % HASH_TABLE_SIZE; return h; } /* * hash_fd() * hash fd and pid */ static inline unsigned long hash_fd(const pid_t pid, const int fd) { unsigned long h; h = (pid ^ (pid << 3) ^ fd) % HASH_TABLE_SIZE; return h; } /* * hash_filename() * hash pid and filename */ static inline unsigned long hash_filename(const pid_t pid, const char *filename) { unsigned long h; h = pid; while (*filename) { unsigned long g; h = (h << 4) + (*filename); if (0 != (g = h & 0xf0000000)) { h = h ^ (g >> 24); h = h ^ g; } filename++; } return h % HASH_TABLE_SIZE; } /* * hash_syscall_context * hash by pid */ static inline unsigned long hash_syscall_context(const pid_t pid) { unsigned long h = (unsigned long)pid; return h % HASH_TABLE_SIZE; } /* * syscall_count_cmp() * syscall usage count sort comparitor */ static int syscall_count_cmp(const void *data1, const void *data2) { syscall_info_t *s1 = (syscall_info_t *)data1; syscall_info_t *s2 = (syscall_info_t *)data2; if (s1->proc->pid == s2->proc->pid) return s2->count - s1->count; else return s1->proc->pid - s2->proc->pid; } /* * syscall_hashtable_free() * free syscall hash table */ static void syscall_hashtable_free(void) { int i; for (i = 0; i < HASH_TABLE_SIZE; i++) { syscall_info_t *next, *s = syscall_info[i]; while (s) { next = s->next; list_free(&s->return_history, free); free(s); s = next; } } } /* * syscall_dump_hashtable * dump syscall hashtable stats */ void syscall_dump_hashtable(json_object *j_tests, const double duration) { list_t sorted; link_t *l; int i; int count = 0; uint64_t total, usecs_total = 0; #ifndef JSON_OUTPUT (void)j_tests; #endif if (opt_flags & OPT_BRIEF) return; list_init(&sorted); for (i = 0; i < HASH_TABLE_SIZE; i++) { syscall_info_t *s; for (s = syscall_info[i]; s; s = s->next) { if (list_add_ordered(&sorted, s, syscall_count_cmp) == NULL) goto out; usecs_total += s->usecs_total; } } printf("System calls traced:\n"); printf(" PID Process Syscall Count Rate/Sec Total μSecs %% Call Time\n"); for (total = 0, l = sorted.head; l; l = l->next) { char name[64]; syscall_info_t *s = (syscall_info_t *)l->data; syscall_name(s->syscall, name, sizeof(name)); printf(" %5i %-20.20s %-20.20s %6" PRIu64 " %12.4f %13" PRIu64 " %8.4f\n", s->proc->pid, s->proc->cmdline, name, s->count, (double)s->count / duration, s->usecs_total, (double)s->usecs_total * 100.0 / (double)usecs_total); count++; total += s->count; } if (count > 1) { printf(" %-46.46s%8" PRIu64 " %12.4f %13" PRIu64 "\n", "Total", total, (double)total / duration, usecs_total); } printf("\n"); #ifdef JSON_OUTPUT if (j_tests) { json_object *j_syscall, *j_syscall_infos, *j_syscall_info; if ((j_syscall = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "system-calls", j_syscall); if ((j_syscall_infos = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_syscall, "system-calls-per-process", j_syscall_infos); for (total = 0, l = sorted.head; l; l = l->next) { char name[64]; syscall_info_t *s = (syscall_info_t *)l->data; syscall_name(s->syscall, name, sizeof(name)); if ((j_syscall_info = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_syscall_info, "pid", s->proc->pid); j_obj_new_int32_add(j_syscall_info, "ppid", s->proc->ppid); j_obj_new_int32_add(j_syscall_info, "is-thread", s->proc->is_thread); j_obj_new_string_add(j_syscall_info, "name", s->proc->cmdline); j_obj_new_string_add(j_syscall_info, "system-call", name); j_obj_new_int64_add(j_syscall_info, "system-call-count", s->count); j_obj_new_double_add(j_syscall_info, "system-call-rate", (double)s->count / duration); j_obj_new_int64_add(j_syscall_info, "system-call-total-microseconds", s->usecs_total); j_obj_array_add(j_syscall_infos, j_syscall_info); total += s->count; } if ((j_syscall_info = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_syscall, "system-calls-total", j_syscall_info); j_obj_new_int64_add(j_syscall_info, "system-call-count-total", total); j_obj_new_double_add(j_syscall_info, "system-call-count-total-rate", (double)total / duration); } #endif out: list_free(&sorted, NULL); } /* * syscall_count_timeout * gather stats on timeout */ static void syscall_count_timeout( syscall_info_t *s, const double timeout, const double threshold) { double t = BUCKET_START; int bucket = 0; while ((t <= timeout) && (bucket < MAX_BUCKET - 1)) { bucket++; t *= 10; } s->poll_count++; /* Indefinite waits we ignore in the stats */ if (timeout < 0.0) { s->poll_infinite++; return; } if (s->poll_min < 0.0) { s->poll_min = timeout; s->poll_max = timeout; } else { if (s->poll_min > timeout) s->poll_min = timeout; if (s->poll_max < timeout) s->poll_max = timeout; } if (timeout == 0.0) { s->poll_zero++; s->poll_too_low++; return; } s->poll_total += timeout; s->bucket[bucket]++; if (timeout <= threshold) s->poll_too_low++; } /* * syscall_get_arg_data() * gather dereferenced arg data */ static void syscall_get_arg_data( const unsigned long addr, const pid_t pid, void *data, const size_t len) { size_t i, n = (len + sizeof(unsigned long) - 1) / sizeof(unsigned long); unsigned long tmpdata[n]; for (i = 0; i < n; i++) tmpdata[i] = ptrace(PTRACE_PEEKDATA, pid, addr + (sizeof(unsigned long) * i), NULL); memcpy(data, tmpdata, len); } /* * syscall_timespec_timeout() * keep tally of timespec timeouts */ static void syscall_timespec_timeout( const syscall_t *sc, syscall_info_t *s, const pid_t pid, const double threshold, double *ret_timeout) { unsigned long args[sc->arg + 1]; struct timespec timeout; syscall_get_args(pid, sc->arg, args); if (args[sc->arg] == 0) { *ret_timeout = -1.0; /* block indefinitely, flagged with -ve timeout */ } else { syscall_get_arg_data(args[sc->arg], pid, &timeout, sizeof(timeout)); *ret_timeout = timeout.tv_sec + (timeout.tv_nsec / 1000000000.0); } syscall_count_timeout(s, *ret_timeout, threshold); } #if defined(SYS__newselect) || defined(SYS_select) /* * syscall_timeval_timeout() * keep tally of timeval timeouts */ static void syscall_timeval_timeout( const syscall_t *sc, syscall_info_t *s, const pid_t pid, const double threshold, double *ret_timeout) { unsigned long args[sc->arg + 1]; struct timeval timeout; syscall_get_args(pid, sc->arg, args); if (args[sc->arg] == 0) { *ret_timeout = -1.0; /* block indefinitely, flagged with -ve timeout */ } else { syscall_get_arg_data(args[sc->arg], pid, &timeout, sizeof(timeout)); *ret_timeout = timeout.tv_sec + (timeout.tv_usec / 1000000.0); } syscall_count_timeout(s, *ret_timeout, threshold); } #endif /* * syscall_timeout_millisec() * keep tally of integer millisecond timeouts */ static void syscall_timeout_millisec( const syscall_t *sc, syscall_info_t *s, const pid_t pid, const double threshold, double *ret_timeout) { unsigned long args[sc->arg + 1]; syscall_get_args(pid, sc->arg, args); *ret_timeout = (double)(int)args[sc->arg] / 1000.0; syscall_count_timeout(s, *ret_timeout, threshold); } /* * syscall_peek_data() * peek data */ #if defined(SYS_write) && defined(SYS_close) static void *syscall_peek_data(const pid_t pid, const unsigned long addr, const size_t len) { unsigned *data; size_t i, n = (len + sizeof(unsigned long) - 1) / sizeof(unsigned long); if ((data = calloc(sizeof(unsigned long), n + 1)) == NULL) { health_check_out_of_memory("allocating syscall peek buffer"); return NULL; } for (i = 0; i < n; i++) data[i] = ptrace(PTRACE_PEEKDATA, pid, addr + (sizeof(unsigned long) * i), NULL); *((char *)data + len) = 0; return (void *)data; } /* * syscall_wakelock_free() * free a wakelock info struct from list */ static void syscall_wakelock_free(void *ptr) { syscall_wakelock_info_t *info = (syscall_wakelock_info_t *)ptr; free(info->lockname); free(info); } /* * syscall_wakelock_fd_cache_free() * */ static void syscall_wakelock_fd_cache_free(void) { int i; for (i = 0; i < HASH_TABLE_SIZE; i++) { fd_cache_t *next, *fc = fd_cache[i]; while (fc) { next = fc->next; free(fc->filename); free(fc); fc = next; } } } /* * syscall_fd_cache_lookup() * lookup a fd cache item based on the pid and file descriptor * if it does not exist, create a new entry. if it was previously * closed re-load the cache item for this fd. */ static fd_cache_t *syscall_fd_cache_lookup(const pid_t pid, const int fd) { fd_cache_t *fc; unsigned long h = hash_fd(pid, fd); for (fc = fd_cache[h]; fc; fc = fc->next) { if (fc->pid == pid && fc->fd == fd) break; } if (fc == NULL) { if ((fc = calloc(1, sizeof(*fc))) == NULL) { health_check_out_of_memory("allocating file descriptor cache item"); return NULL; } fc->pid = pid; fc->fd = fd; fc->filename = fnotify_get_filename(pid, fd); if (fc->filename == NULL) { health_check_out_of_memory("allocating filename"); free(fc); return NULL; } pthread_mutex_init(&fc->mutex, NULL); fc->next = fd_cache[h]; fd_cache[h] = fc; } else { /* * We've found a cached file, but it may be null if closed * so check for this and re-refetch name if it was closed */ pthread_mutex_lock(&fc->mutex); if (fc->filename == NULL) { fc->filename = fnotify_get_filename(pid, fd); if (fc->filename == NULL) { pthread_mutex_unlock(&fc->mutex); health_check_out_of_memory("allocating filename"); free(fc); return NULL; } } pthread_mutex_unlock(&fc->mutex); } return fc; } /* * syscall_close_args() * keep track of wakelock closes */ static void syscall_close_args( const syscall_t *sc, const syscall_info_t *s, const pid_t pid) { unsigned long args[sc->arg + 1]; fd_cache_t *fc; unsigned long h; int fd; (void)s; if (!(opt_flags & OPT_WAKELOCKS_HEAVY)) return; syscall_get_args(pid, sc->arg, args); fd = (int)args[0]; h = hash_fd(pid, fd); for (fc = fd_cache[h]; fc; fc = fc->next) { if (fc->pid == pid && fc->fd == fd) { pthread_mutex_lock(&fc->mutex); free(fc->filename); fc->filename = NULL; pthread_mutex_unlock(&fc->mutex); return; } } } /* * syscall_write_args() * keep track of wakelock writes */ static void syscall_write_args( const syscall_t *sc, const syscall_info_t *s, const pid_t pid) { unsigned long args[sc->arg + 1]; fd_cache_t *fc; int fd; if (!(opt_flags & OPT_WAKELOCKS_HEAVY)) return; (void)s; syscall_get_args(pid, sc->arg, args); fd = (int)args[0]; if ((fc = syscall_fd_cache_lookup(pid, fd)) == NULL) return; pthread_mutex_lock(&fc->mutex); if (!strcmp(fc->filename, "/sys/power/wake_lock") || !strcmp(fc->filename, "/sys/power/wake_unlock")) { unsigned long addr = args[1]; size_t count = (size_t)args[2]; char *lockname; syscall_wakelock_info_t *info; if ((lockname = syscall_peek_data(pid, addr, count)) == NULL) { pthread_mutex_unlock(&fc->mutex); return; } if ((info = calloc(1, sizeof(*info))) == NULL) { health_check_out_of_memory("allocating wakelock information"); pthread_mutex_unlock(&fc->mutex); free(lockname); return; } info->pid = pid; info->lockname = lockname; info->locked = strcmp(fc->filename, "/sys/power/wake_unlock"); gettimeofday(&info->tv, NULL); if (list_append(&syscall_wakelocks, info) == NULL) { free(info); } } pthread_mutex_unlock(&fc->mutex); } #endif #ifdef SYS_exit /* * syscall_exit_args() * keep track of exit calls */ static void syscall_exit_args( const syscall_t *sc, const syscall_info_t *s, const pid_t pid) { (void)s; (void)sc; /* * Before we exit we need to gather the * final accounting stats for the process */ proc_info_t *proc = proc_cache_find_by_pid(pid); if (proc) { (void)cpustat_get_by_proc(proc, PROC_FINISH); (void)ctxt_switch_get_by_proc(proc, PROC_FINISH); (void)mem_get_by_proc(proc, PROC_FINISH); } } #endif #if defined(SYS_fsync) || defined(SYS_fdatasync) || defined(SYS_syncfs) || defined(SYS_sync) /* * syscall_sync_info_find_by_pid() * local sync accounting related data from pid */ static syscall_sync_info_t *syscall_sync_info_find_by_pid(const pid_t pid) { link_t *l; syscall_sync_info_t *info; for (l = syscall_syncs.head; l; l = l->next) { info = (syscall_sync_info_t *)l->data; if (info->pid == pid) return info; } if ((info = calloc(1, sizeof(*info))) == NULL) { health_check_out_of_memory("allocating file sync accounting info"); return NULL; } info->pid = pid; if (list_append(&syscall_syncs, info) == NULL) { free(info); return NULL; } return info; } #endif #if defined(SYS_fsync) || defined(SYS_fdatasync) || defined(SYS_syncfs) || defined(SYS_sync_file_range) /* * syscall_account_sync_file() * accounting for fsync, fdatasync and syncfs system calls */ static void syscall_account_sync_file(syscall_sync_info_t *info, const int syscall, const int pid, const int fd) { syscall_sync_file_t *f; fd_cache_t *fc; link_t *l; if ((fc = syscall_fd_cache_lookup(pid, fd)) == NULL) return; for (l = info->sync_file.head; l; l = l->next) { f = (syscall_sync_file_t *)l->data; if ((f->syscall == syscall) && !strcmp(f->filename, fc->filename)) { f->count++; return; } } if ((f = calloc(1, sizeof(*f))) == NULL) { health_check_out_of_memory("allocating file sync filename info"); return; } f->filename = strdup(fc->filename); f->syscall = syscall; f->count = 1; if (list_append(&info->sync_file, f) == NULL) { free(f->filename); free(f); } } /* * syscall_fsync_generic_args() * keep track of fsync, fdatasync and syncfs calls */ static void syscall_fsync_generic_args( const syscall_t *sc, const syscall_info_t *s, const pid_t pid) { unsigned long args[sc->arg + 1]; syscall_sync_info_t *info; (void)s; syscall_get_args(pid, sc->arg, args); if ((info = syscall_sync_info_find_by_pid(pid)) == NULL) return; info->fsync_count++; info->total_count++; syscall_account_sync_file(info, sc->syscall, pid, (int)args[0]); } #endif #ifdef SYS_sync /* * syscall_sync_args() * keep track of sync calls */ static void syscall_sync_args( const syscall_t *sc, const syscall_info_t *s, const pid_t pid) { syscall_sync_info_t *info; (void)sc; (void)s; if ((info = syscall_sync_info_find_by_pid(pid)) == NULL) return; info->sync_count++; info->total_count++; } #endif #ifdef SYS_brk /* * syscall_account_sync_file() * accounting for brk system call */ static void syscall_brk_args( const syscall_t *sc, const syscall_info_t *s, const pid_t pid) { unsigned long args[sc->arg + 1]; void *addr; (void)s; syscall_get_args(pid, sc->arg, args); addr = (void *)args[0]; (void)mem_brk_account(pid, addr); } #endif #if defined(SYS_mmap) || defined(SYS_mmap2) /* * syscall_mmap_args() * accounting for mmap and mmap2 system calls */ static void syscall_mmap_args( const syscall_t *sc, const syscall_info_t *s, const pid_t pid) { unsigned long args[sc->arg + 2]; (void)s; syscall_get_args(pid, sc->arg, args); (void)mem_mmap_account(pid, (size_t)args[1], true); } #endif #ifdef SYS_munmap /* * syscall_munmap_args() * accounting for munmap system call */ static void syscall_munmap_args( const syscall_t *sc, const syscall_info_t *s, const pid_t pid) { unsigned long args[sc->arg + 2]; (void)s; syscall_get_args(pid, sc->arg, args); mem_mmap_account(pid, (size_t)args[1], false); } #endif /* * syscall_sync_cmp() * syscall total usage list sort compare */ static int syscall_sync_cmp(const void *d1, const void *d2) { syscall_sync_info_t *s1 = (syscall_sync_info_t *)d1; syscall_sync_info_t *s2 = (syscall_sync_info_t *)d2; return s2->total_count - s1->total_count; } /* * syscall_sync_free_fileinfo() * free sync file accounting info */ static void syscall_sync_free_fileinfo(void *data) { syscall_sync_file_t *f = (syscall_sync_file_t *)data; free(f->filename); free(f); } /* * syscall_syncs_free_item() * free sync accounting info */ static void syscall_sync_free_item(void *data) { syscall_sync_info_t *info = (syscall_sync_info_t *)data; list_free(&info->sync_file, syscall_sync_free_fileinfo); free(info); } /* * syscall_dump_sync() * dump sync family of syscall usage stats */ void syscall_dump_sync(json_object *j_tests, double duration) { list_t sorted; link_t *l; syscall_sync_info_t *info; bool sync_filenames = false; #if !defined(JSON_OUTPUT) (void)j_tests; #endif printf("Filesystem Syncs:\n"); list_init(&sorted); for (l = syscall_syncs.head; l; l = l->next) { if (list_add_ordered(&sorted, l->data, syscall_sync_cmp) == NULL) goto out; } if (syscall_syncs.head == NULL) { printf(" None.\n\n"); } else { printf(" PID fdatasync fsync sync syncfs total total (Rate)\n"); for (l = sorted.head; l; l = l->next) { info = (syscall_sync_info_t *)l->data; printf(" %5i %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8" PRIu64 " %8.2f\n", info->pid, info->fdatasync_count, info->fsync_count, info->sync_count, info->syncfs_count, info->total_count, (double)info->total_count / duration); if (info->sync_file.length) sync_filenames = true; } printf("\n"); } #ifdef JSON_OUTPUT if (j_tests) { json_object *j_syscall, *j_syscall_infos, *j_syscall_info; uint64_t total_fdatasync = 0, total_fsync = 0, total_sync = 0, total_syncfs = 0; if ((j_syscall = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "file-system-syncs", j_syscall); if ((j_syscall_infos = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_syscall, "sync-system-calls-per-process", j_syscall_infos); for (l = sorted.head; l; l = l->next) { info = (syscall_sync_info_t *)l->data; j_syscall_info = j_obj_new_obj(); j_obj_new_int32_add(j_syscall_info, "pid", info->pid); j_obj_new_int64_add(j_syscall_info, "fdatasync-call-count", info->fdatasync_count); j_obj_new_double_add(j_syscall_info, "fdatasync-call-rate", (double)info->fdatasync_count / duration); j_obj_new_int64_add(j_syscall_info, "fsync-call-count", info->fsync_count); j_obj_new_double_add(j_syscall_info, "fsync-call-rate", (double)info->fsync_count / duration); j_obj_new_int64_add(j_syscall_info, "sync-call-count", info->sync_count); j_obj_new_double_add(j_syscall_info, "sync-call-rate", (double)info->sync_count / duration); j_obj_new_int64_add(j_syscall_info, "syncfs-call-count", info->syncfs_count); j_obj_new_double_add(j_syscall_info, "syncfs-call-rate", (double)info->syncfs_count / duration); j_obj_array_add(j_syscall_infos, j_syscall_info); total_fdatasync += info->fdatasync_count; total_fsync += info->fsync_count; total_sync += info->sync_count; total_syncfs += info->syncfs_count; } if ((j_syscall_info = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_syscall, "sync-system-calls-total", j_syscall_info); j_obj_new_int64_add(j_syscall_info, "fdatasync-call-count-total", total_fdatasync); j_obj_new_double_add(j_syscall_info, "fdatasync-call-count-total-rate", (double)total_fdatasync / duration); j_obj_new_int64_add(j_syscall_info, "fsync-call-count-total", total_fsync); j_obj_new_double_add(j_syscall_info, "fsync-call-count-total-rate", (double)total_fsync / duration); j_obj_new_int64_add(j_syscall_info, "sync-call-count-total", total_sync); j_obj_new_double_add(j_syscall_info, "sync-call-count-total-rate", (double)total_sync / duration); j_obj_new_int64_add(j_syscall_info, "syncfs-call-count-total", total_syncfs); j_obj_new_double_add(j_syscall_info, "syncfs-call-count-total-rate", (double)total_syncfs / duration); } #endif if (sync_filenames) { printf("Files Sync'd:\n"); printf(" PID syscall # sync's filename\n"); for (l = sorted.head; l; l = l->next) { link_t *ll; info = (syscall_sync_info_t *)l->data; for (ll = info->sync_file.head; ll; ll = ll->next) { char tmp[64]; syscall_sync_file_t *f = (syscall_sync_file_t *)ll->data; syscall_name(f->syscall, tmp, sizeof(tmp)); printf(" %5i %-10.10s %8" PRIu64 " %s\n", info->pid, tmp, f->count, f->filename); } } printf("\n"); } #ifdef JSON_OUTPUT if (j_tests) { json_object *j_syscall, *j_syscall_infos, *j_syscall_info; uint64_t total_files_sync = 0; if ((j_syscall = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "files-synced", j_syscall); if ((j_syscall_infos = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_syscall, "file-sync-per-process", j_syscall_infos); for (l = sorted.head; l; l = l->next) { link_t *ll; info = (syscall_sync_info_t *)l->data; for (ll = info->sync_file.head; ll; ll = ll->next) { info = (syscall_sync_info_t *)l->data; char tmp[64]; syscall_sync_file_t *f = (syscall_sync_file_t *)ll->data; syscall_name(f->syscall, tmp, sizeof(tmp)); j_syscall_info = j_obj_new_obj(); j_obj_new_int32_add(j_syscall_info, "pid", info->pid); j_obj_new_string_add(j_syscall_info, "syscall", tmp); j_obj_new_int64_add(j_syscall_info, "call-count", f->count); j_obj_new_double_add(j_syscall_info, "call-rate", (double)f->count / duration); j_obj_new_string_add(j_syscall_info, "filename", f->filename); j_obj_array_add(j_syscall_infos, j_syscall_info); total_files_sync += f->count; } } if ((j_syscall_info = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_syscall, "files-synced-total", j_syscall_info); j_obj_new_int64_add(j_syscall_info, "files-synced-total", total_files_sync); j_obj_new_double_add(j_syscall_info, "files-synced-total-rate", (double)total_files_sync / duration); } #endif out: list_free(&sorted, NULL); } #ifdef SYS_sendto /* * syscall_sendto_ret() * keep track of sendto returns */ static void syscall_sendto_ret( const syscall_t *sc, const syscall_info_t *s, const int ret) { unsigned long args[sc->arg + 1]; int sockfd; pid_t pid = s->proc->pid; syscall_get_args(pid, sc->arg, args); sockfd = (int)args[0]; if (ret >= 0) net_account_send(pid, sockfd, (size_t)ret); } #endif #ifdef SYS_recvfrom /* * syscall_recvfrom_ret() * keep track of recvfrom returns */ static void syscall_recvfrom_ret( const syscall_t *sc, const syscall_info_t *s, const int ret) { unsigned long args[sc->arg + 1]; int sockfd; pid_t pid = s->proc->pid; syscall_get_args(pid, sc->arg, args); sockfd = (int)args[0]; if (ret >= 0) net_account_recv(pid, sockfd, (size_t)ret); } #endif /* * syscall_wakelock_cmp() * sorted wakelock list name compare */ static int syscall_wakelock_cmp(const void *data1, const void *data2) { return strcmp((char *)data1, (char *)data2); } /* * syscall_timeval_to_double() * convert timeval time to double */ static inline double syscall_timeval_to_double(struct timeval *tv) { return (double)tv->tv_sec + ((double)tv->tv_usec) / 1000000.0; } /* * syscall_wakelock_names_by_pid() * update wakelock_names list for a new wakelock for a given pid */ void syscall_wakelock_names_by_pid(pid_t pid, list_t *wakelock_names) { link_t *l, *ln; for (l = syscall_wakelocks.head; l; l = l->next) { syscall_wakelock_info_t *info = (syscall_wakelock_info_t *)l->data; if (info->pid == pid) { bool found = false; for (ln = wakelock_names->head; ln; ln = ln->next) { char *lockname = (char *)ln->data; if (!strcmp(lockname, info->lockname)) { found = true; break; } } if (!found) (void)list_add_ordered(wakelock_names, info->lockname, syscall_wakelock_cmp); } } } /* * syscall_dump_wakelocks() * dump wakelock activity */ void syscall_dump_wakelocks(json_object *j_tests, const double duration, list_t *pids) { link_t *lp; uint64_t total_locked = 0, total_unlocked = 0; uint32_t total_count = 0; #ifdef JSON_OUTPUT json_object *j_wakelock_test = NULL, *j_wakelock_infos = NULL, *j_wakelock_info; #endif (void)j_tests; if (!(opt_flags & OPT_WAKELOCKS_HEAVY)) return; #ifdef JSON_OUTPUT if (j_tests) { if ((j_wakelock_test = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "wakelock-operations-heavy", j_wakelock_test); if ((j_wakelock_infos = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_wakelock_test, "wakelock-operations-heavy-per-process", j_wakelock_infos); } #endif printf("Wakelock operations by wakelock:\n"); if (!syscall_wakelocks.head) { printf(" None.\n\n"); } else { double total_locked_duration = 0.0; printf(" PID Process Wakelock Locks Unlocks Locks Unlocks Lock Duration\n"); printf("%65s%s", "", "Per Sec Per Sec (Average Sec)\n"); for (lp = pids->head; lp; lp = lp->next) { link_t *ln; list_t wakelock_names; proc_info_t *p = (proc_info_t *)lp->data; list_init(&wakelock_names); syscall_wakelock_names_by_pid(p->pid, &wakelock_names); for (ln = wakelock_names.head; ln; ln = ln->next) { char *lockname = (char *)ln->data; uint64_t locked = 0, unlocked = 0; double locked_time = -1.0, unlocked_time; double locked_duration = 0.0; uint32_t count = 0; link_t *ls; for (ls = syscall_wakelocks.head; ls; ls = ls->next) { syscall_wakelock_info_t *info = (syscall_wakelock_info_t *)ls->data; if (info->pid == p->pid && !strcmp(lockname, info->lockname)) { if (info->locked) { locked++; locked_time = syscall_timeval_to_double(&info->tv); } else { unlocked++; unlocked_time = syscall_timeval_to_double(&info->tv); if (locked_time >= 0.0) { count++; locked_duration += unlocked_time - locked_time; } } } } total_locked += locked; total_unlocked += unlocked; total_count += count; total_locked_duration += locked_duration; printf(" %5i %-20.20s %-16.16s %8" PRIu64 " %8" PRIu64 " %8.2f %8.2f %12.5f\n", p->pid, p->cmdline, lockname, locked, unlocked, (double)locked / duration, (double)unlocked / duration, count ? locked_duration / count : 0.0); #ifdef JSON_OUTPUT if (j_tests) { if ((j_wakelock_info = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_wakelock_info, "pid", p->pid); j_obj_new_int32_add(j_wakelock_info, "ppid", p->ppid); j_obj_new_int32_add(j_wakelock_info, "is-thread", p->is_thread); j_obj_new_string_add(j_wakelock_info, "name", p->cmdline); j_obj_new_string_add(j_wakelock_info, "lockname", lockname); j_obj_new_int64_add(j_wakelock_info, "wakelock-locked", locked); j_obj_new_double_add(j_wakelock_info, "wakelock-locked-rate", (double)locked / duration); j_obj_new_int64_add(j_wakelock_info, "wakelock-unlocked", unlocked); j_obj_new_double_add(j_wakelock_info, "wakelock-unlocked-rate", (double)unlocked / duration); j_obj_new_double_add(j_wakelock_info, "wakelock-locked-duration", count ? locked_duration / count : 0.0); j_obj_array_add(j_wakelock_infos, j_wakelock_info); } #endif } list_free(&wakelock_names, NULL); } printf(" Total%40s%8" PRIu64 " %8" PRIu64 " %8.2f %8.2f %12.5f\n", "", total_locked, total_unlocked, (double)total_locked / duration, (double)total_unlocked / duration, total_count ? total_locked_duration / total_count : 0.0); printf("\n"); } #ifdef JSON_OUTPUT if (j_tests) { if ((j_wakelock_info = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_wakelock_test, "wakelock-operations-heavy-total", j_wakelock_info); j_obj_new_int64_add(j_wakelock_info, "wakelock-locked-total", total_locked); j_obj_new_double_add(j_wakelock_info, "wakelock-locked-total-rate", (double)total_locked / duration); j_obj_new_int64_add(j_wakelock_info, "wakelock-unlocked-total", total_unlocked); j_obj_new_double_add(j_wakelock_info, "wakelock-unlocked-total-rate", (double)total_unlocked / duration); } out: #endif if (syscall_wakelocks.head && opt_flags & OPT_VERBOSE) { link_t *ls; printf("Verbose Dump of Wakelock Actions:\n"); printf(" PID Wakelock Date Time Action Duration (Secs)\n"); for (ls = syscall_wakelocks.head; ls; ls = ls->next) { char buf[64]; syscall_wakelock_info_t *info = (syscall_wakelock_info_t *)ls->data; time_t whence_time = (time_t)info->tv.tv_sec; struct tm *whence_tm = localtime(&whence_time); strftime(buf, sizeof(buf), "%x %X", whence_tm); if (info->locked) { link_t *l; for (l = ls; l; l = l->next) { syscall_wakelock_info_t *info2 = (syscall_wakelock_info_t *)l->data; if (info->pid == info2->pid && !info2->locked && !strcmp(info->lockname, info2->lockname)) { info2->paired = info; break; } } } if (info->paired) { double locked_time = syscall_timeval_to_double(&info->paired->tv); double unlocked_time = syscall_timeval_to_double(&info->tv); printf(" %5i %-16.16s %s.%06d %-8.8s %f\n", info->pid, info->lockname, buf, (int)info->tv.tv_usec, info->locked ? "Locked" : "Unlocked", unlocked_time - locked_time); } else { printf(" %5i %-16.16s %s.%06d %-8.8s\n", info->pid, info->lockname, buf, (int)info->tv.tv_usec, info->locked ? "Locked" : "Unlocked"); } } } } #if defined(SYS_inotify_add_watch) || defined(SYS_execve) /* * syscall_peek_filename() * get a filename as pointed to by addr */ static char *syscall_peek_filename(const pid_t pid, const unsigned long addr) { char *data; size_t i, n = 0; unsigned long v; /* Find how long it is */ do { v = ptrace(PTRACE_PEEKDATA, pid, addr + n, NULL) & 0xff; n++; } while (v); if ((data = calloc(sizeof(unsigned char), n)) == NULL) { health_check_out_of_memory("allocating syscall peek buffer"); return NULL; } for (i = 0; i < n; i++) data[i] = ptrace(PTRACE_PEEKDATA, pid, addr + i, NULL); return data; } /* * syscall_add_filename() * Add filename into filename cache */ void syscall_add_filename(const int syscall, const pid_t pid, const char *filename) { unsigned long h; filename_info_t *info; if (filename == NULL) return; h = hash_filename(pid, filename); info = filename_cache[h]; while (info) { if (info->pid == pid && !strcmp(info->filename, filename)) break; info = info->next; } if (!info) { info = calloc(1, sizeof(*info)); if (!info) return; info->filename = strdup(filename); if (!info->filename) { free(info); return; } info->syscall = syscall; info->pid = pid; info->proc = proc_cache_find_by_pid(pid); info->count = 1; info->next = filename_cache[h]; filename_cache[h] = info; } else { info->count++; } } /* * syscall_filename_cmp() * filename and pid compare */ static int syscall_filename_cmp(const void *d1, const void *d2) { filename_info_t *f1 = (filename_info_t *)d1; filename_info_t *f2 = (filename_info_t *)d2; return f2->count - f1->count; } /* * syscall_filename_cache_free() * free up filename cache */ static void syscall_filename_cache_free(void) { int i; for (i = 0; i < HASH_TABLE_SIZE; i++) { filename_info_t *next, *info = filename_cache[i]; while (info) { next = info->next; free(info->filename); free(info); info = next; } } } /* * syscall_dump_filename() * dump filename usage by syscall */ void syscall_dump_filename(const char *label, const int syscall, json_object *j_obj, double duration) { int i; list_t sorted; link_t *l; list_init(&sorted); for (i = 0; i < HASH_TABLE_SIZE; i++) { filename_info_t *info; for (info = filename_cache[i]; info; info = info->next) { if (info->syscall == syscall) { if (list_add_ordered(&sorted, info, syscall_filename_cmp) == NULL) goto out; } } } if (sorted.length == 0) { printf(" None.\n\n"); } else { printf(" PID Process Rate/Sec File\n"); for (l = sorted.head; l; l = l->next) { filename_info_t *info = (filename_info_t *)l->data; printf(" %5i %-20.20s %8.3f %s\n", info->pid, info->proc->cmdline, (double)info->count / duration, info->filename); } printf("\n"); } #ifdef JSON_OUTPUT if (j_obj) { json_object *j_syscall, *j_syscall_infos, *j_syscall_info; if ((j_syscall = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_obj, label, j_syscall); if ((j_syscall_infos = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_syscall, "files", j_syscall_infos); for (l = sorted.head; l; l = l->next) { filename_info_t *info = (filename_info_t *)l->data; j_syscall_info = j_obj_new_obj(); j_obj_new_int32_add(j_syscall_info, "pid", info->pid); j_obj_new_int32_add(j_syscall_info, "ppid", info->proc->ppid); j_obj_new_int32_add(j_syscall_info, "is_thread", info->proc->is_thread); j_obj_new_string_add(j_syscall_info, "name", info->proc->cmdline); j_obj_new_int64_add(j_syscall_info, "count", info->count); j_obj_new_double_add(j_syscall_info, "access-rate", (double)info->count / duration); j_obj_new_string_add(j_syscall_info, "filename", info->filename); j_obj_array_add(j_syscall_infos, j_syscall_info); } } #endif list_free(&sorted, NULL); out: return; } #endif #ifdef SYS_inotify_add_watch /* * syscall_inotify_add_watch_args() * trace filenames used by inotify_add_watch() */ void syscall_inotify_add_watch_args( const syscall_t *sc, const syscall_info_t *s, const pid_t pid) { unsigned long args[sc->arg + 1]; char *filename; (void)s; syscall_get_args(pid, sc->arg, args); filename = syscall_peek_filename(pid, args[1]); if (filename) { syscall_add_filename(sc->syscall, pid, filename); free(filename); } } void syscall_dump_inotify(json_object *j_obj, double duration) { printf("Inotify watches added:\n"); syscall_dump_filename("inotify-watches-added", SYS_inotify_add_watch, j_obj, duration); } #else void syscall_dump_inotify(json_object *j_obj, double duration) { (void)duration; (void)j_obj; } #endif #ifdef SYS_execve /* * syscall_execve_args() * trace filenames used by execve() */ void syscall_execve_args( const syscall_t *sc, const syscall_info_t *s, const pid_t pid) { unsigned long args[sc->arg + 1]; char *filename; (void)s; syscall_get_args(pid, sc->arg, args); filename = syscall_peek_filename(pid, args[0]); if (filename) { syscall_add_filename(sc->syscall, pid, filename); free(filename); } } void syscall_dump_execve(json_object *j_obj, double duration) { (void)duration; (void)j_obj; /* * Not 100% reliable yet, so disable this for the moment * printf("Programs exec'd:\n"); syscall_dump_filename("execed-programs", SYS_execve, j_obj, duration); */ } #else void syscall_dump_execve(double duration) { (void)duration; (void)j_obj; } #endif /* * syscall_timeout_to_human_time() * convert timeout time into something human readable */ static char *syscall_timeout_to_human_time( const double timeout, const bool end, char *buffer, const size_t len) { char *units[] = { "sec", "msec", "usec", "nsec", "psec" }; int i; double t = timeout; for (i = 0; t != 0.0 && t < 0.99999; i++) t *= 1000.0; if (end) { if (t - 0.1 < 0.99999) { t *= 1000.0; i++; } t -= 0.1; } snprintf(buffer, len, "%5.1f", t); return units[i]; } /* * syscall_dump_pollers() * dump polling syscall abusers */ void syscall_dump_pollers(json_object *j_tests, const double duration) { int i; list_t sorted; link_t *l; json_object *j_pollers = NULL; #if !defined(JSON_OUTPUT) (void)j_tests; #endif list_init(&sorted); for (i = 0; i < HASH_TABLE_SIZE; i++) { syscall_info_t *s; for (s = syscall_info[i]; s; s = s->next) { int syscall = s->syscall; if (syscalls[syscall].call_enter_timeout) { if (list_add_ordered(&sorted, s, syscall_count_cmp) == NULL) goto out; break; } } } #ifdef JSON_OUTPUT uint64_t poll_infinite = 0, poll_zero = 0, count = 0; json_object *j_poll_test; char tmp[64]; if (j_tests) { json_object *j_syscall, *j_syscall_infos, *j_syscall_info; if ((j_syscall = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "polling-system-calls", j_syscall); if ((j_syscall_infos = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_syscall, "polling-system-calls-per-process", j_syscall_infos); for (count = 0, l = sorted.head; l; l = l->next) { syscall_info_t *s = (syscall_info_t *)l->data; syscall_name(s->syscall, tmp, sizeof(tmp)); double rate = (double)s->count / duration; count += s->count; if ((j_syscall_info = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_syscall_info, "pid", s->proc->pid); j_obj_new_int32_add(j_syscall_info, "ppid", s->proc->ppid); j_obj_new_int32_add(j_syscall_info, "is_thread", s->proc->is_thread); j_obj_new_string_add(j_syscall_info, "name", s->proc->cmdline); j_obj_new_string_add(j_syscall_info, "system-call", tmp); j_obj_new_int64_add(j_syscall_info, "system-call-count", s->count); j_obj_new_double_add(j_syscall_info, "system-call-rate", rate); j_obj_new_int64_add(j_syscall_info, "poll-count-infinite-timeout", s->poll_infinite); j_obj_new_int64_add(j_syscall_info, "poll-count-zero-timeout", s->poll_zero); j_obj_new_double_add(j_syscall_info, "poll-minimum-timeout-millisecs", s->poll_min < 0.0 ? 0.0 : s->poll_min); j_obj_new_double_add(j_syscall_info, "poll-maximum-timeout-millisecs", s->poll_max < 0.0 ? 0.0 : s->poll_max); j_obj_new_double_add(j_syscall_info, "poll-average-timeout-millisecs", s->poll_total / (double)s->count); j_obj_array_add(j_syscall_infos, j_syscall_info); } if ((j_syscall_info = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_syscall, "polling-system-calls-total", j_syscall_info); j_obj_new_int64_add(j_syscall_info, "system-call-count-total", count); j_obj_new_double_add(j_syscall_info, "system-call-total-rate", (double)count / duration); j_obj_new_int64_add(j_syscall_info, "poll-count-infinite-total", (int64_t)poll_infinite); j_obj_new_double_add(j_syscall_info, "poll-count-infinite-total-rate", (double)poll_infinite / duration); j_obj_new_int64_add(j_syscall_info, "poll-count-zero-total", poll_zero); j_obj_new_double_add(j_syscall_info, "poll-count-zero-total-rate", (double)poll_zero / duration); } #endif if (sorted.head) { if (!(opt_flags & OPT_BRIEF)) { double prev, bucket; char tmp[64], *units; double total_rate = 0.0; uint64_t poll_infinite = 0, poll_zero = 0, count = 0; printf("Top polling system calls:\n"); printf(" PID Process Syscall Rate/Sec Infinite Zero Minimum Maximum Average\n"); printf(" Timeouts Timeouts Timeout Timeout Timeout\n"); for (l = sorted.head; l; l = l->next) { syscall_info_t *s = (syscall_info_t *)l->data; syscall_name(s->syscall, tmp, sizeof(tmp)); double rate = (double)s->count / duration; printf(" %5i %-20.20s %-17.17s %12.4f %8" PRIu64 " %8" PRIu64, s->proc->pid, s->proc->cmdline, tmp, rate, s->poll_infinite, s->poll_zero); if (s->poll_count) { char min_timeout[64], max_timeout[64], avg_timeout[64]; units = syscall_timeout_to_human_time(s->poll_min < 0.0 ? 0.0 : s->poll_min, false, tmp, sizeof(tmp)); snprintf(min_timeout, sizeof(min_timeout), "%s %-4s", tmp, units); units = syscall_timeout_to_human_time(s->poll_max < 0.0 ? 0.0 : s->poll_max, false, tmp, sizeof(tmp)); snprintf(max_timeout, sizeof(max_timeout), "%s %-4s", tmp, units); units = syscall_timeout_to_human_time(s->poll_total / (double)s->count, false, tmp, sizeof(tmp)); snprintf(avg_timeout, sizeof(avg_timeout), "%s %-4s", tmp, units); printf(" %10s %10s %10s", min_timeout, max_timeout, avg_timeout); } else { printf(" %-10s %-10s %-10s", " n/a", " n/a", " n/a"); } printf("\n"); total_rate += rate; poll_infinite += s->poll_infinite; poll_zero += s->poll_zero; count++; } if (count > 1) printf(" %-45.45s%12.4f %8" PRIu64 " %8" PRIu64 "\n", "Total", total_rate, poll_infinite, poll_zero); printf("\nDistribution of poll timeout times:\n"); printf("%50.50s", ""); for (prev = 0.0, bucket = BUCKET_START, i = 0; i < MAX_BUCKET; i++, bucket *= 10.0) { (void)syscall_timeout_to_human_time(prev, false, tmp, sizeof(tmp)); printf(" %6s", i == 0 ? "" : tmp); prev = bucket; } printf("\n"); printf("%50.50s", ""); for (bucket = BUCKET_START, i = 0; i < MAX_BUCKET; i++) { if (i == 0) printf(" up to"); else if (i == MAX_BUCKET - 1) printf(" or more"); else printf(" to "); } printf("\n"); printf("%46.46sZero", ""); for (bucket = BUCKET_START, i = 0; i < MAX_BUCKET; i++, bucket *= 10.0) { (void)syscall_timeout_to_human_time(bucket, true, tmp, sizeof(tmp)); printf(" %6s", i == (MAX_BUCKET-1) ? "" : tmp); } printf(" Infinite\n"); printf(" PID Process Syscall sec"); for (bucket = BUCKET_START, i = 0; i < MAX_BUCKET; i++, bucket *= 10.0) { units = syscall_timeout_to_human_time(bucket, true, tmp, sizeof(tmp)); printf(" %6s", units); } printf(" Wait\n"); for (l = sorted.head; l; l = l->next) { syscall_info_t *s = (syscall_info_t *)l->data; syscall_name(s->syscall, tmp, sizeof(tmp)); printf(" %5u %-20.20s %-15.15s %6" PRIu64, s->proc->pid, s->proc->cmdline, tmp, s->poll_zero); for (i = 0; i < MAX_BUCKET; i++) { if (s->bucket[i]) printf(" %6" PRIu64, s->bucket[i]); else printf(" - "); } printf(" %6" PRIu64, s->poll_infinite); printf("\n"); } printf("\n"); } #ifdef JSON_OUTPUT if (j_tests) { if ((j_poll_test = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "polling-system-call-returns", j_poll_test); if ((j_pollers = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_poll_test, "polling-system-call-returns-per-process", j_pollers); } #endif printf("Polling system call analysis:\n"); for (l = sorted.head; l; l = l->next) { syscall_info_t *s = (syscall_info_t *)l->data; if (syscall_valid(s->syscall)) { syscall_t *sc = &syscalls[s->syscall]; if (sc->call_return_timeout) sc->call_return_timeout(j_pollers, sc, s); } } if (!info_emit) printf(" No bad polling discovered.\n"); printf("\n"); } out: list_free(&sorted, NULL); } /* * syscall_account_return() * if the system call has return check handler then * fetch return value from syscall and add it to * list of returns */ static void syscall_account_return( syscall_info_t *s, const int pid, const int syscall, const double timeout) { if (syscall_valid(syscall)) { syscall_t *sc = &syscalls[syscall]; int ret; if (syscall_get_return(pid, &ret) < 0) return; if (sc->call_return) { /* Do call return handling immediately */ sc->call_return(sc, s, ret); } else if (sc->call_return_timeout) { /* Collect data and process it at the end of the run */ syscall_return_info_t *info; if ((info = (syscall_return_info_t *)calloc(1, sizeof(*info))) == NULL) { health_check_out_of_memory("allocating syscall accounting information"); return; } info->timeout = timeout; info->ret = ret; if (list_append(&s->return_history, info) == NULL) free(info); } } } /* * syscall_count() * tally syscall usage */ static syscall_info_t *syscall_count_usage( const pid_t pid, const int syscall, double *timeout) { unsigned long h = hash_syscall(pid, syscall); syscall_info_t *s = NULL; syscall_t *sc; bool found = false; sc = syscall_valid(syscall) ? &syscalls[syscall] : NULL; if (!sc) return NULL; *timeout = -1.0; for (s = syscall_info[h]; s; s = s->next) { if ((s->syscall == syscall) && (s->proc->pid == pid)) { s->count++; found = true; break; } } if (!found) { /* * Doesn't exist, create new one */ if ((s = calloc(1, sizeof(*s))) == NULL) { health_check_out_of_memory("allocating syscall hash item"); return NULL; } s->syscall = syscall; s->proc = proc_cache_find_by_pid(pid); s->count = 1; s->poll_zero = 0; s->poll_infinite = 0; s->poll_count = 0; s->poll_min = -1.0; s->poll_max = -1.0; s->poll_total = 0; s->poll_too_low = 0; list_init(&s->return_history); s->next = syscall_info[h]; syscall_info[h] = s; } if (++syscall_count >= opt_max_syscalls) { #if SYSCALL_DEBUG printf("HIT SYSCALL LIMIT\n"); #endif keep_running = false; } if (sc->call_enter) sc->call_enter(sc, s, pid); else if (sc->call_enter_timeout) sc->call_enter_timeout(sc, s, pid, *(sc->threshold), timeout); return s; } /* * syscall_handle_syscall() * system call entry or exit handling */ static void syscall_handle_syscall(syscall_context_t *ctxt) { int syscall; syscall_call_state state = syscall_get_call_state(ctxt->pid); switch (state) { case SYSCALL_ENTRY: if (syscall_get_call(ctxt->pid, &ctxt->syscall) == -1) { ctxt->syscall_info = NULL; ctxt->timeout = 0.0; } else { ctxt->syscall_info = syscall_count_usage(ctxt->pid, ctxt->syscall, &ctxt->timeout); if (ctxt->syscall_info) gettimeofday(&ctxt->syscall_info->usec_enter, NULL); } return; default: /* We don't know what it was, so try and figure it out */ if (syscall_get_call(ctxt->pid, &syscall) == -1) { printf("syscall give up\n"); /* Not good, abort stats */ ctxt->syscall_info = NULL; ctxt->timeout = 0.0; return; } if (syscall != ctxt->syscall) { /* syscall is different, so can't be a return, must be a new syscall */ ctxt->syscall = syscall; ctxt->syscall_info = syscall_count_usage(ctxt->pid, ctxt->syscall, &ctxt->timeout); if (ctxt->syscall_info) gettimeofday(&ctxt->syscall_info->usec_enter, NULL); return; } /* assume it is a return, but it may not be, fall through to SYSCALL_RETURN.. */ case SYSCALL_RETURN: if (ctxt->syscall_info != NULL) { struct timeval t; uint64_t usec; gettimeofday(&ctxt->syscall_info->usec_return, NULL); t = timeval_sub(&ctxt->syscall_info->usec_return, &ctxt->syscall_info->usec_enter); usec = (t.tv_sec * 1000000) + t.tv_usec; ctxt->syscall_info->usecs_total += usec; syscall_account_return(ctxt->syscall_info, ctxt->pid, ctxt->syscall, ctxt->timeout); } /* We've got a return, so clear info for next syscall */ ctxt->syscall = -1; ctxt->syscall_info = NULL; ctxt->timeout = 0.0; } } /* * syscall_handle_event() * handle a ptrace event (clone, fork, vfork) */ static void syscall_handle_event(syscall_context_t *ctxt, int event) { unsigned long msg; pid_t child; proc_info_t *p; #if SYSCALL_DEBUG printf("EVENT: %d\n", event); #endif switch (event) { case PTRACE_EVENT_CLONE: case PTRACE_EVENT_FORK: case PTRACE_EVENT_VFORK: ptrace(PTRACE_GETEVENTMSG, ctxt->pid, 0, &msg); child = (pid_t)msg; #if SYSCALL_DEBUG if (event == PTRACE_EVENT_CLONE) printf("PID %d is a clone\n", child); if (event == PTRACE_EVENT_FORK) printf("PID %d forked\n", child); if (event == PTRACE_EVENT_VFORK) printf("PID %d vforked\n", child); #endif if ((p = proc_cache_add(child, 0, event == PTRACE_EVENT_CLONE)) != NULL) { (void)proc_pids_add_proc(__pids, p); (void)mem_get_by_proc(p, PROC_START); (void)cpustat_get_by_proc(p, PROC_START); (void)ctxt_switch_get_by_proc(p, PROC_START); (void)syscall_get_context(child); } (void)net_connection_pid(child); /* Update net connections on new process */ break; case PTRACE_EVENT_EXIT: #if SYSCALL_DEBUG printf("PID %d exited\n", ctxt->pid); #endif if (ctxt->state & SYSCALL_CTX_ATTACHED) ptrace(PTRACE_CONT, ctxt->pid, 0, 0); break; default: break; } } /* * syscall_handle_trap() * handle ptrace trap */ static void syscall_handle_trap(syscall_context_t *ctxt) { siginfo_t siginfo; if (ptrace(PTRACE_GETSIGINFO, ctxt->pid, 0, &siginfo) == -1) { fprintf(stderr, "Cannot get signal info on pid %d.\n", ctxt->pid); return; } if (siginfo.si_code == SIGTRAP) { syscall_handle_syscall(ctxt); } else { /* printf("breakpoint on PID %d\n", ctxt->pid); */ } } /* * syscall_handle_stop * handle a ptrace stop */ static int syscall_handle_stop(syscall_context_t *ctxt, const int status) { int event = status >> 16; int sig = WSTOPSIG(status); if (sig == SIGTRAP) { #if SYSCALL_DEBUG printf("GOT SIGTRAP, event: %d\n", event); #endif if (event) { syscall_handle_event(ctxt, event); } else { syscall_handle_trap(ctxt); } } else if (sig == SIGCHLD) { #if SYSCALL_DEBUG printf("GOT SIGCHLD, %d\n", ctxt->pid); #endif //procs_traced--; } else if (sig == (SIGTRAP | 0x80)) { syscall_handle_syscall(ctxt); } else if (sig != SIGSTOP) { return sig; } return 0; } /* * syscall_context_find_by_pid() * find syscall context by pid */ static syscall_context_t *syscall_context_find_by_pid(const pid_t pid) { syscall_context_t *ctxt; unsigned long h = hash_syscall_context(pid); for (ctxt = syscall_contexts_cache[h]; ctxt; ctxt = ctxt->next) if (ctxt->pid == pid) return ctxt; return NULL; } /* * syscall_get_context() * each ptraced thread or process has a ptrace state context, * this function looks this up via the pid. If it does not * already exist, create a new context. */ static syscall_context_t *syscall_get_context(pid_t pid) { syscall_context_t *ctxt; ctxt = syscall_context_find_by_pid(pid); if (ctxt == NULL) { unsigned long h = hash_syscall_context(pid); if ((ctxt = calloc(1, sizeof(*ctxt))) == NULL) { fprintf(stderr, "Out of memory allocating tracing context.\n"); return NULL; } ctxt->pid = pid; ctxt->proc = proc_cache_find_by_pid(pid); ctxt->timeout = 0.0; ctxt->syscall = -1; ctxt->syscall_info = NULL; ctxt->state |= SYSCALL_CTX_ALIVE; /* Add to fast look up cache and list */ if (list_append(&syscall_contexts, ctxt) == NULL) { free(ctxt); return NULL; } ctxt->next = syscall_contexts_cache[h]; syscall_contexts_cache[h] = ctxt; procs_traced++; #if SYSCALL_DEBUG printf("NEW PROCESS %d, TRACED: %d\n", pid, procs_traced); #endif } return ctxt; } /* * syscall_trace_cleanup() * clean up tracing */ static void syscall_trace_cleanup(void *arg) { link_t *l; (void)arg; for (l = syscall_contexts.head; l; l = l->next) { syscall_context_t *ctxt = (syscall_context_t *)l->data; if (ctxt->state & SYSCALL_CTX_ATTACHED) { int status; kill(ctxt->pid, SIGSTOP); waitpid(ctxt->pid, &status, __WALL); ptrace(PTRACE_DETACH, ctxt->pid, 0, 0); kill(ctxt->pid, SIGCONT); } } #if SYSCALL_DEBUG printf("SYSCALL TRACE CLEANUP\n"); #endif keep_running = false; } /* * syscall_trace() * syscall tracer, run in a pthread */ void *syscall_trace(void *arg) { syscall_context_t *ctxt; int status; link_t *l; unsigned long ptrace_flags = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXIT; static int ret = 0; (void)arg; pthread_cleanup_push(syscall_trace_cleanup, arg); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); if (opt_flags & OPT_FOLLOW_NEW_PROCS) { ptrace_flags |= (PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK); #if SYSCALL_DEBUG printf("FOLLOW PROCS\n"); #endif } for (l = syscall_contexts.head; l; l = l->next) { ctxt = (syscall_context_t *)l->data; if (ptrace(PTRACE_ATTACH, ctxt->pid, 0, 0) < 0) { if (errno == EPERM) { fprintf(stderr, "Insufficient privilege to trace process %d\n", ctxt->pid); } else { fprintf(stderr, "Cannot attach trace to process %d\n", ctxt->pid); } ret = -1; goto done; } ctxt->state |= SYSCALL_CTX_ATTACHED; (void)ptrace(PTRACE_SETOPTIONS, ctxt->pid, 0, ptrace_flags); } #if SYSCALL_DEBUG printf("TRACE LOOP\n"); #endif while (keep_running && procs_traced > 0) { int sig = 0; pid_t pid; errno = 0; #if SYSCALL_DEBUG printf("WAITPID..\n"); #endif if ((pid = waitpid(-1, &status, __WALL)) == -1) { if (errno == EINTR || errno == ECHILD) { #if SYSCALL_DEBUG printf("WAITPID returned errno: %d\n", errno); #endif break; } } if ((ctxt = syscall_get_context(pid)) == NULL) { fprintf(stderr, "Out of memory allocating tracing context.\n"); break; } if (WIFSTOPPED(status)) { #if SYSCALL_DEBUG printf("PROC %d\n", ctxt->pid); #endif sig = syscall_handle_stop(ctxt, status); } else if (WIFEXITED(status)) { #if SYSCALL_DEBUG printf("PROC WIFEXITED %d\n", ctxt->pid); #endif if (ctxt->proc) { /* * We need to probably catch exit in the system call * so we can do accounting, it seems that the proc files * disappear too early. */ /* (void)cpustat_get_by_proc(ctxt->proc, PROC_FINISH); (void)ctxt_switch_get_by_proc(ctxt->proc, PROC_FINISH); (void)mem_get_by_proc(ctxt->proc, PROC_FINISH); */ } ctxt->state &= ~(SYSCALL_CTX_ALIVE | SYSCALL_CTX_ATTACHED); procs_traced--; #if SYSCALL_DEBUG printf("PROC TRACED: %d\n", procs_traced); #endif } else if (WIFSIGNALED(status)) { /* * In an ideal world we could find the final * stats *before* it died and update CPU stat etc * TODO: See if we can find out final state before * signalled. */ #if SYSCALL_DEBUG printf("PROC WIGSIGNALED\n"); #endif if (WTERMSIG(status) == SIGKILL) { /* It died */ printf("Process %d received SIGKILL during monitoring.\n", pid); ctxt->state &= ~(SYSCALL_CTX_ALIVE | SYSCALL_CTX_ATTACHED); procs_traced--; } } #if SYSCALL_DEBUG else if (WIFCONTINUED(status)) { printf("Continued %d\n", ctxt->pid); } else { printf("Unexpected status %d for PID %d\n", status, ctxt->pid); } #endif ptrace(PTRACE_SYSCALL, ctxt->pid, 0, sig); } #if SYSCALL_DEBUG printf("SYSCALL TRACE COMPLETE\n"); #endif done: syscall_trace_cleanup(NULL); pthread_cleanup_pop(NULL); kill(main_pid, SIGUSR1); pthread_exit(&ret); } /* * syscall_init() * initialize */ void syscall_init(void) { list_init(&syscall_wakelocks); list_init(&syscall_contexts); list_init(&syscall_syncs); } /* * syscall_stop() * stop the ptrace thread */ int syscall_stop(void) { int *status = 0; pthread_cancel(syscall_tracer); pthread_join(syscall_tracer, (void **)&status); if (status == PTHREAD_CANCELED) return 0; return *status; } /* * syscall_cleanup() * free up memory */ void syscall_cleanup(void) { list_free(&syscall_wakelocks, syscall_wakelock_free); list_free(&syscall_contexts, free); list_free(&syscall_syncs, syscall_sync_free_item); syscall_wakelock_fd_cache_free(); syscall_hashtable_free(); syscall_filename_cache_free(); } /* * syscall_trace_proc() * kick off ptrace thread */ int syscall_trace_proc(list_t *pids) { link_t *l; __pids = pids; main_pid = getpid(); for (l = pids->head; l; l = l->next) { proc_info_t *p = (proc_info_t *)l->data; (void)syscall_get_context(p->pid); } if (pthread_create(&syscall_tracer, NULL, syscall_trace, NULL) < 0) { fprintf(stderr, "Failed to create tracing thread.\n"); return -1; } return 0; } /* * The system call table */ syscall_t syscalls[] = { #ifdef SYS_setup SYSCALL(setup), #endif #ifdef SYS_accept SYSCALL(accept), #endif #ifdef SYS_accept4 SYSCALL(accept4), #endif #ifdef SYS_access SYSCALL(access), #endif #ifdef SYS_acct SYSCALL(acct), #endif #ifdef SYS_add_key SYSCALL(add_key), #endif #ifdef SYS_adjtimex SYSCALL(adjtimex), #endif #ifdef SYS_afs_syscall SYSCALL(afs_syscall), #endif #ifdef SYS_alarm SYSCALL(alarm), #endif #ifdef SYS_arch_prctl SYSCALL(arch_prctl), #endif #ifdef SYS_bdflush SYSCALL(bdflush), #endif #ifdef SYS_bind SYSCALL(bind), #endif #ifdef SYS_break SYSCALL(break), #endif #ifdef SYS_brk SYSCALL_CHK(brk, 0, syscall_brk_args, NULL), #endif #ifdef SYS_capget SYSCALL(capget), #endif #ifdef SYS_capset SYSCALL(capset), #endif #ifdef SYS_chdir SYSCALL(chdir), #endif #ifdef SYS_chmod SYSCALL(chmod), #endif #ifdef SYS_chown SYSCALL(chown), #endif #ifdef SYS_chown32 SYSCALL(chown32), #endif #ifdef SYS_chroot SYSCALL(chroot), #endif #ifdef SYS_clock_adjtime SYSCALL(clock_adjtime), #endif #ifdef SYS_clock_getres SYSCALL(clock_getres), #endif #ifdef SYS_clock_gettime SYSCALL(clock_gettime), #endif #ifdef SYS_clock_nanosleep SYSCALL_CHK_TIMEOUT(clock_nanosleep, 2, syscall_timespec_timeout, syscall_nanosleep_generic_ret), #endif #ifdef SYS_clock_settime SYSCALL(clock_settime), #endif #ifdef SYS_clone SYSCALL(clone), #endif #ifdef SYS_close SYSCALL_CHK(close, 0, syscall_close_args, NULL), #endif #ifdef SYS_connect SYSCALL_CHK(connect, 0, syscall_connect_args, syscall_connect_ret), #endif #ifdef SYS_creat SYSCALL(creat), #endif #ifdef SYS_create_module SYSCALL(create_module), #endif #ifdef SYS_delete_module SYSCALL(delete_module), #endif #ifdef SYS_dup SYSCALL(dup), #endif #ifdef SYS_dup2 SYSCALL(dup2), #endif #ifdef SYS_dup3 SYSCALL(dup3), #endif #ifdef SYS_epoll_create SYSCALL(epoll_create), #endif #ifdef SYS_epoll_create1 SYSCALL(epoll_create1), #endif #ifdef SYS_epoll_ctl SYSCALL(epoll_ctl), #endif #ifdef SYS_epoll_ctl_old SYSCALL(epoll_ctl_old), #endif #ifdef SYS_epoll_pwait SYSCALL_CHK_TIMEOUT(epoll_pwait, 3, syscall_timeout_millisec, syscall_poll_generic_ret), #endif #ifdef SYS_epoll_wait SYSCALL_CHK_TIMEOUT(epoll_wait, 3, syscall_timeout_millisec, syscall_poll_generic_ret), #endif #ifdef SYS_epoll_wait_old SYSCALL(epoll_wait_old), #endif #ifdef SYS_eventfd SYSCALL(eventfd), #endif #ifdef SYS_eventfd2 SYSCALL(eventfd2), #endif #ifdef SYS_execve SYSCALL_CHK(execve, 0, syscall_execve_args, NULL), #endif #ifdef SYS_exit SYSCALL_CHK(exit, 0, syscall_exit_args, NULL), #endif #ifdef SYS_exit_group SYSCALL_CHK(exit_group, 0, syscall_exit_args, NULL), #endif #ifdef SYS_faccessat SYSCALL(faccessat), #endif #ifdef SYS_fadvise64 SYSCALL(fadvise64), #endif #ifdef SYS_fadvise64_64 SYSCALL(fadvise64_64), #endif #ifdef SYS_fallocate SYSCALL(fallocate), #endif #ifdef SYS_fanotify_init SYSCALL(fanotify_init), #endif #ifdef SYS_fanotify_mark SYSCALL(fanotify_mark), #endif #ifdef SYS_fchdir SYSCALL(fchdir), #endif #ifdef SYS_fchmod SYSCALL(fchmod), #endif #ifdef SYS_fchmodat SYSCALL(fchmodat), #endif #ifdef SYS_fchown SYSCALL(fchown), #endif #ifdef SYS_fchown32 SYSCALL(fchown32), #endif #ifdef SYS_fchownat SYSCALL(fchownat), #endif #ifdef SYS_fcntl SYSCALL(fcntl), #endif #ifdef SYS_fcntl64 SYSCALL(fcntl64), #endif #ifdef SYS_fdatasync SYSCALL_CHK(fdatasync, 0, syscall_fsync_generic_args, NULL), #endif #ifdef SYS_fgetxattr SYSCALL(fgetxattr), #endif #ifdef SYS_finit_module SYSCALL(finit_module), #endif #ifdef SYS_flistxattr SYSCALL(flistxattr), #endif #ifdef SYS_flock SYSCALL(flock), #endif #ifdef SYS_fork SYSCALL(fork), #endif #ifdef SYS_fremovexattr SYSCALL(fremovexattr), #endif #ifdef SYS_fsetxattr SYSCALL(fsetxattr), #endif #ifdef SYS_fstat SYSCALL(fstat), #endif #ifdef SYS_fstat64 SYSCALL(fstat64), #endif #ifdef SYS_fstatat64 SYSCALL(fstatat64), #endif #ifdef SYS_fstatfs SYSCALL(fstatfs), #endif #ifdef SYS_fstatfs64 SYSCALL(fstatfs64), #endif #ifdef SYS_fsync SYSCALL_CHK(fsync, 0, syscall_fsync_generic_args, NULL), #endif #ifdef SYS_ftime SYSCALL(ftime), #endif #ifdef SYS_ftruncate SYSCALL(ftruncate), #endif #ifdef SYS_ftruncate64 SYSCALL(ftruncate64), #endif #ifdef SYS_futex SYSCALL(futex), #endif #ifdef SYS_futimesat SYSCALL(futimesat), #endif #ifdef SYS_getcpu SYSCALL(getcpu), #endif #ifdef SYS_getcwd SYSCALL(getcwd), #endif #ifdef SYS_getdents SYSCALL(getdents), #endif #ifdef SYS_getdents64 SYSCALL(getdents64), #endif #ifdef SYS_getegid SYSCALL(getegid), #endif #ifdef SYS_getegid32 SYSCALL(getegid32), #endif #ifdef SYS_geteuid SYSCALL(geteuid), #endif #ifdef SYS_geteuid32 SYSCALL(geteuid32), #endif #ifdef SYS_getgid SYSCALL(getgid), #endif #ifdef SYS_getgid32 SYSCALL(getgid32), #endif #ifdef SYS_getgroups SYSCALL(getgroups), #endif #ifdef SYS_getgroups32 SYSCALL(getgroups32), #endif #ifdef SYS_getitimer SYSCALL(getitimer), #endif #ifdef SYS_get_kernel_syms SYSCALL(get_kernel_syms), #endif #ifdef SYS_get_mempolicy SYSCALL(get_mempolicy), #endif #ifdef SYS_getpeername SYSCALL(getpeername), #endif #ifdef SYS_getpgid SYSCALL(getpgid), #endif #ifdef SYS_getpgrp SYSCALL(getpgrp), #endif #ifdef SYS_getpid SYSCALL(getpid), #endif #ifdef SYS_getpmsg SYSCALL(getpmsg), #endif #ifdef SYS_getppid SYSCALL(getppid), #endif #ifdef SYS_getpriority SYSCALL(getpriority), #endif #ifdef SYS_getresgid SYSCALL(getresgid), #endif #ifdef SYS_getresgid32 SYSCALL(getresgid32), #endif #ifdef SYS_getresuid SYSCALL(getresuid), #endif #ifdef SYS_getresuid32 SYSCALL(getresuid32), #endif #ifdef SYS_getrlimit SYSCALL(getrlimit), #endif #ifdef SYS_get_robust_list SYSCALL(get_robust_list), #endif #ifdef SYS_getrusage SYSCALL(getrusage), #endif #ifdef SYS_getsid SYSCALL(getsid), #endif #ifdef SYS_getsockname SYSCALL(getsockname), #endif #ifdef SYS_getsockopt SYSCALL(getsockopt), #endif #ifdef SYS_get_thread_area SYSCALL(get_thread_area), #endif #ifdef SYS_gettid SYSCALL(gettid), #endif #ifdef SYS_gettimeofday SYSCALL(gettimeofday), #endif #ifdef SYS_getuid SYSCALL(getuid), #endif #ifdef SYS_getuid32 SYSCALL(getuid32), #endif #ifdef SYS_getxattr SYSCALL(getxattr), #endif #ifdef SYS_gtty SYSCALL(gtty), #endif #ifdef SYS_idle SYSCALL(idle), #endif #ifdef SYS_init_module SYSCALL(init_module), #endif #ifdef SYS_inotify_add_watch SYSCALL_CHK(inotify_add_watch, 1, syscall_inotify_add_watch_args, NULL), #endif #ifdef SYS_inotify_init SYSCALL(inotify_init), #endif #ifdef SYS_inotify_init1 SYSCALL(inotify_init1), #endif #ifdef SYS_inotify_rm_watch SYSCALL(inotify_rm_watch), #endif #ifdef SYS_io_cancel SYSCALL(io_cancel), #endif #ifdef SYS_ioctl SYSCALL(ioctl), #endif #ifdef SYS_io_destroy SYSCALL(io_destroy), #endif #ifdef SYS_io_getevents SYSCALL(io_getevents), #endif #ifdef SYS_ioperm SYSCALL(ioperm), #endif #ifdef SYS_iopl SYSCALL(iopl), #endif #ifdef SYS_ioprio_get SYSCALL(ioprio_get), #endif #ifdef SYS_ioprio_set SYSCALL(ioprio_set), #endif #ifdef SYS_io_setup SYSCALL(io_setup), #endif #ifdef SYS_io_submit SYSCALL(io_submit), #endif #ifdef SYS_ipc SYSCALL(ipc), #endif #ifdef SYS_kcmp SYSCALL(kcmp), #endif #ifdef SYS_kexec_load SYSCALL(kexec_load), #endif #ifdef SYS_keyctl SYSCALL(keyctl), #endif #ifdef SYS_kill SYSCALL(kill), #endif #ifdef SYS_lchown SYSCALL(lchown), #endif #ifdef SYS_lchown32 SYSCALL(lchown32), #endif #ifdef SYS_lgetxattr SYSCALL(lgetxattr), #endif #ifdef SYS_link SYSCALL(link), #endif #ifdef SYS_linkat SYSCALL(linkat), #endif #ifdef SYS_listen SYSCALL(listen), #endif #ifdef SYS_listxattr SYSCALL(listxattr), #endif #ifdef SYS_llistxattr SYSCALL(llistxattr), #endif #ifdef SYS__llseek SYSCALL(_llseek), #endif #ifdef SYS_lock SYSCALL(lock), #endif #ifdef SYS_lookup_dcookie SYSCALL(lookup_dcookie), #endif #ifdef SYS_lremovexattr SYSCALL(lremovexattr), #endif #ifdef SYS_lseek SYSCALL(lseek), #endif #ifdef SYS_lsetxattr SYSCALL(lsetxattr), #endif #ifdef SYS_lstat SYSCALL(lstat), #endif #ifdef SYS_lstat64 SYSCALL(lstat64), #endif #ifdef SYS_madvise SYSCALL(madvise), #endif #ifdef SYS_mbind SYSCALL(mbind), #endif #ifdef SYS_migrate_pages SYSCALL(migrate_pages), #endif #ifdef SYS_mincore SYSCALL(mincore), #endif #ifdef SYS_mkdir SYSCALL(mkdir), #endif #ifdef SYS_mkdirat SYSCALL(mkdirat), #endif #ifdef SYS_mknod SYSCALL(mknod), #endif #ifdef SYS_mknodat SYSCALL(mknodat), #endif #ifdef SYS_mlock SYSCALL(mlock), #endif #ifdef SYS_mlockall SYSCALL(mlockall), #endif #ifdef SYS_mmap SYSCALL_CHK(mmap, 1, syscall_mmap_args, NULL), #endif #ifdef SYS_mmap2 SYSCALL_CHK(mmap2, 1, syscall_mmap_args, NULL), #endif #ifdef SYS_modify_ldt SYSCALL(modify_ldt), #endif #ifdef SYS_mount SYSCALL(mount), #endif #ifdef SYS_move_pages SYSCALL(move_pages), #endif #ifdef SYS_mprotect SYSCALL(mprotect), #endif #ifdef SYS_mpx SYSCALL(mpx), #endif #ifdef SYS_mq_getsetattr SYSCALL(mq_getsetattr), #endif #ifdef SYS_mq_notify SYSCALL(mq_notify), #endif #ifdef SYS_mq_open SYSCALL(mq_open), #endif #ifdef SYS_mq_timedreceive SYSCALL_CHK_TIMEOUT(mq_timedreceive, 4, syscall_timespec_timeout, syscall_mq_timedreceive_ret), #endif #ifdef SYS_mq_timedsend SYSCALL_CHK_TIMEOUT(mq_timedsend, 4, syscall_timespec_timeout, syscall_mq_timedsend_ret), #endif #ifdef SYS_mq_unlink SYSCALL(mq_unlink), #endif #ifdef SYS_mremap SYSCALL(mremap), #endif #ifdef SYS_msgctl SYSCALL(msgctl), #endif #ifdef SYS_msgget SYSCALL(msgget), #endif #ifdef SYS_msgrcv SYSCALL(msgrcv), #endif #ifdef SYS_msgsnd SYSCALL(msgsnd), #endif #ifdef SYS_msync SYSCALL(msync), #endif #ifdef SYS_munlock SYSCALL(munlock), #endif #ifdef SYS_munlockall SYSCALL(munlockall), #endif #ifdef SYS_munmap SYSCALL_CHK(munmap, 1, syscall_munmap_args, NULL), #endif #ifdef SYS_name_to_handle_at SYSCALL(name_to_handle_at), #endif #ifdef SYS_nanosleep SYSCALL_CHK_TIMEOUT(nanosleep, 0, syscall_timespec_timeout, syscall_nanosleep_generic_ret), #endif #ifdef SYS_newfstatat SYSCALL(newfstatat), #endif #ifdef SYS__newselect SYSCALL_CHK_TIMEOUT(_newselect, 4, syscall_timeval_timeout, syscall_poll_generic_ret), #endif #ifdef SYS_nfsservctl SYSCALL(nfsservctl), #endif #ifdef SYS_nice SYSCALL(nice), #endif #ifdef SYS_oldfstat SYSCALL(oldfstat), #endif #ifdef SYS_oldlstat SYSCALL(oldlstat), #endif #ifdef SYS_oldolduname SYSCALL(oldolduname), #endif #ifdef SYS_oldstat SYSCALL(oldstat), #endif #ifdef SYS_olduname SYSCALL(olduname), #endif #ifdef SYS_open SYSCALL(open), #endif #ifdef SYS_openat SYSCALL(openat), #endif #ifdef SYS_open_by_handle_at SYSCALL(open_by_handle_at), #endif #ifdef SYS_pause SYSCALL(pause), #endif #ifdef SYS_perf_event_open SYSCALL(perf_event_open), #endif #ifdef SYS_personality SYSCALL(personality), #endif #ifdef SYS_pipe SYSCALL(pipe), #endif #ifdef SYS_pipe2 SYSCALL(pipe2), #endif #ifdef SYS_pivot_root SYSCALL(pivot_root), #endif #ifdef SYS_poll SYSCALL_CHK_TIMEOUT(poll, 2, syscall_timeout_millisec, syscall_poll_generic_ret), #endif #ifdef SYS_ppoll SYSCALL_CHK_TIMEOUT(ppoll, 2, syscall_timespec_timeout, syscall_poll_generic_ret), #endif #ifdef SYS_prctl SYSCALL(prctl), #endif #ifdef SYS_pread64 SYSCALL(pread64), #endif #ifdef SYS_preadv SYSCALL(preadv), #endif #ifdef SYS_prlimit64 SYSCALL(prlimit64), #endif #ifdef SYS_process_vm_readv SYSCALL(process_vm_readv), #endif #ifdef SYS_process_vm_writev SYSCALL(process_vm_writev), #endif #ifdef SYS_prof SYSCALL(prof), #endif #ifdef SYS_profil SYSCALL(profil), #endif #ifdef SYS_pselect6 SYSCALL_CHK_TIMEOUT(pselect6, 4, syscall_timespec_timeout, syscall_poll_generic_ret), #endif #ifdef SYS_ptrace SYSCALL(ptrace), #endif #ifdef SYS_putpmsg SYSCALL(putpmsg), #endif #ifdef SYS_pwrite64 SYSCALL(pwrite64), #endif #ifdef SYS_pwritev SYSCALL(pwritev), #endif #ifdef SYS_query_module SYSCALL(query_module), #endif #ifdef SYS_quotactl SYSCALL(quotactl), #endif #ifdef SYS_read SYSCALL(read), #endif #ifdef SYS_readahead SYSCALL(readahead), #endif #ifdef SYS_readdir SYSCALL(readdir), #endif #ifdef SYS_readlink SYSCALL(readlink), #endif #ifdef SYS_readlinkat SYSCALL(readlinkat), #endif #ifdef SYS_readv SYSCALL(readv), #endif #ifdef SYS_reboot SYSCALL(reboot), #endif #ifdef SYS_recvfrom SYSCALL_CHK(recvfrom, 0, NULL, syscall_recvfrom_ret), #endif #ifdef SYS_recvmmsg SYSCALL_CHK_TIMEOUT(recvmmsg, 4, syscall_timespec_timeout, NULL), #endif #ifdef SYS_recvmsg SYSCALL_CHK(recvmsg, 0, NULL, syscall_recvfrom_ret), #endif #ifdef SYS_remap_file_pages SYSCALL(remap_file_pages), #endif #ifdef SYS_removexattr SYSCALL(removexattr), #endif #ifdef SYS_rename SYSCALL(rename), #endif #ifdef SYS_renameat SYSCALL(renameat), #endif #ifdef SYS_request_key SYSCALL(request_key), #endif #ifdef SYS_restart_syscall SYSCALL(restart_syscall), #endif #ifdef SYS_rmdir SYSCALL(rmdir), #endif #ifdef SYS_rt_sigaction SYSCALL(rt_sigaction), #endif #ifdef SYS_rt_sigpending SYSCALL(rt_sigpending), #endif #ifdef SYS_rt_sigprocmask SYSCALL(rt_sigprocmask), #endif #ifdef SYS_rt_sigqueueinfo SYSCALL(rt_sigqueueinfo), #endif #ifdef SYS_rt_sigreturn SYSCALL(rt_sigreturn), #endif #ifdef SYS_rt_sigsuspend SYSCALL(rt_sigsuspend), #endif #ifdef SYS_rt_sigtimedwait SYSCALL_CHK_TIMEOUT(rt_sigtimedwait, 2, syscall_timespec_timeout, syscall_poll_generic_ret), #endif #ifdef SYS_rt_tgsigqueueinfo SYSCALL(rt_tgsigqueueinfo), #endif #ifdef SYS_sched_getaffinity SYSCALL(sched_getaffinity), #endif #ifdef SYS_sched_getparam SYSCALL(sched_getparam), #endif #ifdef SYS_sched_get_priority_max SYSCALL(sched_get_priority_max), #endif #ifdef SYS_sched_get_priority_min SYSCALL(sched_get_priority_min), #endif #ifdef SYS_sched_getscheduler SYSCALL(sched_getscheduler), #endif #ifdef SYS_sched_rr_get_interval SYSCALL(sched_rr_get_interval), #endif #ifdef SYS_sched_setaffinity SYSCALL(sched_setaffinity), #endif #ifdef SYS_sched_setparam SYSCALL(sched_setparam), #endif #ifdef SYS_sched_setscheduler SYSCALL(sched_setscheduler), #endif #ifdef SYS_sched_yield SYSCALL(sched_yield), #endif #ifdef SYS_security SYSCALL(security), #endif #ifdef SYS_select SYSCALL_CHK_TIMEOUT(select, 4, syscall_timeval_timeout, syscall_poll_generic_ret), #endif #ifdef SYS_semctl SYSCALL(semctl), #endif #ifdef SYS_semget SYSCALL(semget), #endif #ifdef SYS_semop SYSCALL(semop), #endif #ifdef SYS_semtimedop SYSCALL_CHK_TIMEOUT(semtimedop, 3, syscall_timespec_timeout, syscall_semtimedop_ret), #endif #ifdef SYS_sendfile SYSCALL(sendfile), #endif #ifdef SYS_sendfile64 SYSCALL(sendfile64), #endif #ifdef SYS_sendmmsg SYSCALL(sendmmsg), #endif #ifdef SYS_sendmsg SYSCALL_CHK(sendmsg, 0, NULL, syscall_sendto_ret), #endif #ifdef SYS_sendto SYSCALL_CHK(sendto, 0, NULL, syscall_sendto_ret), #endif #ifdef SYS_setdomainname SYSCALL(setdomainname), #endif #ifdef SYS_setfsgid SYSCALL(setfsgid), #endif #ifdef SYS_setfsgid32 SYSCALL(setfsgid32), #endif #ifdef SYS_setfsuid SYSCALL(setfsuid), #endif #ifdef SYS_setfsuid32 SYSCALL(setfsuid32), #endif #ifdef SYS_setgid SYSCALL(setgid), #endif #ifdef SYS_setgid32 SYSCALL(setgid32), #endif #ifdef SYS_setgroups SYSCALL(setgroups), #endif #ifdef SYS_setgroups32 SYSCALL(setgroups32), #endif #ifdef SYS_sethostname SYSCALL(sethostname), #endif #ifdef SYS_setitimer SYSCALL(setitimer), #endif #ifdef SYS_set_mempolicy SYSCALL(set_mempolicy), #endif #ifdef SYS_setns SYSCALL(setns), #endif #ifdef SYS_setpgid SYSCALL(setpgid), #endif #ifdef SYS_setpriority SYSCALL(setpriority), #endif #ifdef SYS_setregid SYSCALL(setregid), #endif #ifdef SYS_setregid32 SYSCALL(setregid32), #endif #ifdef SYS_setresgid SYSCALL(setresgid), #endif #ifdef SYS_setresgid32 SYSCALL(setresgid32), #endif #ifdef SYS_setresuid SYSCALL(setresuid), #endif #ifdef SYS_setresuid32 SYSCALL(setresuid32), #endif #ifdef SYS_setreuid SYSCALL(setreuid), #endif #ifdef SYS_setreuid32 SYSCALL(setreuid32), #endif #ifdef SYS_setrlimit SYSCALL(setrlimit), #endif #ifdef SYS_set_robust_list SYSCALL(set_robust_list), #endif #ifdef SYS_setsid SYSCALL(setsid), #endif #ifdef SYS_setsockopt SYSCALL(setsockopt), #endif #ifdef SYS_set_thread_area SYSCALL(set_thread_area), #endif #ifdef SYS_set_tid_address SYSCALL(set_tid_address), #endif #ifdef SYS_settimeofday SYSCALL(settimeofday), #endif #ifdef SYS_setuid SYSCALL(setuid), #endif #ifdef SYS_setuid32 SYSCALL(setuid32), #endif #ifdef SYS_setxattr SYSCALL(setxattr), #endif #ifdef SYS_sgetmask SYSCALL(sgetmask), #endif #ifdef SYS_shmat SYSCALL(shmat), #endif #ifdef SYS_shmctl SYSCALL(shmctl), #endif #ifdef SYS_shmdt SYSCALL(shmdt), #endif #ifdef SYS_shmget SYSCALL(shmget), #endif #ifdef SYS_shutdown SYSCALL(shutdown), #endif #ifdef SYS_sigaction SYSCALL(sigaction), #endif #ifdef SYS_sigaltstack SYSCALL(sigaltstack), #endif #ifdef SYS_signal SYSCALL(signal), #endif #ifdef SYS_signalfd SYSCALL(signalfd), #endif #ifdef SYS_signalfd4 SYSCALL(signalfd4), #endif #ifdef SYS_sigpending SYSCALL(sigpending), #endif #ifdef SYS_sigprocmask SYSCALL(sigprocmask), #endif #ifdef SYS_sigreturn SYSCALL(sigreturn), #endif #ifdef SYS_sigsuspend SYSCALL(sigsuspend), #endif #ifdef SYS_socket SYSCALL(socket), #endif #ifdef SYS_socketcall SYSCALL(socketcall), #endif #ifdef SYS_socketpair SYSCALL(socketpair), #endif #ifdef SYS_splice SYSCALL(splice), #endif #ifdef SYS_ssetmask SYSCALL(ssetmask), #endif #ifdef SYS_stat SYSCALL(stat), #endif #ifdef SYS_stat64 SYSCALL(stat64), #endif #ifdef SYS_statfs SYSCALL(statfs), #endif #ifdef SYS_statfs64 SYSCALL(statfs64), #endif #ifdef SYS_stime SYSCALL(stime), #endif #ifdef SYS_stty SYSCALL(stty), #endif #ifdef SYS_swapoff SYSCALL(swapoff), #endif #ifdef SYS_swapon SYSCALL(swapon), #endif #ifdef SYS_symlink SYSCALL(symlink), #endif #ifdef SYS_symlinkat SYSCALL(symlinkat), #endif #ifdef SYS_sync SYSCALL_CHK(sync, 0, syscall_sync_args, NULL), #endif #ifdef SYS_sync_file_range SYSCALL_CHK(sync_file_range, 0, syscall_fsync_generic_args, NULL), #endif #ifdef SYS_syncfs SYSCALL_CHK(syncfs, 0, syscall_fsync_generic_args, NULL), #endif #ifdef SYS__sysctl SYSCALL(_sysctl), #endif #ifdef SYS_sysfs SYSCALL(sysfs), #endif #ifdef SYS_sysinfo SYSCALL(sysinfo), #endif #ifdef SYS_syslog SYSCALL(syslog), #endif #ifdef SYS_tee SYSCALL(tee), #endif #ifdef SYS_tgkill SYSCALL(tgkill), #endif #ifdef SYS_time SYSCALL(time), #endif #ifdef SYS_timer_create SYSCALL(timer_create), #endif #ifdef SYS_timer_delete SYSCALL(timer_delete), #endif #ifdef SYS_timerfd_create SYSCALL(timerfd_create), #endif #ifdef SYS_timerfd_gettime SYSCALL(timerfd_gettime), #endif #ifdef SYS_timerfd_settime SYSCALL(timerfd_settime), #endif #ifdef SYS_timer_getoverrun SYSCALL(timer_getoverrun), #endif #ifdef SYS_timer_gettime SYSCALL(timer_gettime), #endif #ifdef SYS_timer_settime SYSCALL(timer_settime), #endif #ifdef SYS_times SYSCALL(times), #endif #ifdef SYS_tkill SYSCALL(tkill), #endif #ifdef SYS_truncate SYSCALL(truncate), #endif #ifdef SYS_truncate64 SYSCALL(truncate64), #endif #ifdef SYS_tuxcall SYSCALL(tuxcall), #endif #ifdef SYS_ugetrlimit SYSCALL(ugetrlimit), #endif #ifdef SYS_ulimit SYSCALL(ulimit), #endif #ifdef SYS_umask SYSCALL(umask), #endif #ifdef SYS_umount SYSCALL(umount), #endif #ifdef SYS_umount2 SYSCALL(umount2), #endif #ifdef SYS_uname SYSCALL(uname), #endif #ifdef SYS_unlink SYSCALL(unlink), #endif #ifdef SYS_unlinkat SYSCALL(unlinkat), #endif #ifdef SYS_unshare SYSCALL(unshare), #endif #ifdef SYS_uselib SYSCALL(uselib), #endif #ifdef SYS_ustat SYSCALL(ustat), #endif #ifdef SYS_utime SYSCALL(utime), #endif #ifdef SYS_utimensat SYSCALL(utimensat), #endif #ifdef SYS_utimes SYSCALL(utimes), #endif #ifdef SYS_vfork SYSCALL(vfork), #endif #ifdef SYS_vhangup SYSCALL(vhangup), #endif #ifdef SYS_vm86 SYSCALL(vm86), #endif #ifdef SYS_vm86old SYSCALL(vm86old), #endif #ifdef SYS_vmsplice SYSCALL(vmsplice), #endif #ifdef SYS_vserver SYSCALL(vserver), #endif #ifdef SYS_wait4 SYSCALL(wait4), #endif #ifdef SYS_waitid SYSCALL(waitid), #endif #ifdef SYS_waitpid SYSCALL(waitpid), #endif #ifdef SYS_write SYSCALL_CHK(write, 2, syscall_write_args, NULL), #endif #ifdef SYS_writev SYSCALL(writev), #endif }; size_t syscalls_len = ARRAY_SIZE(syscalls); #endif health-check-0.01.58/timeval.h0000664000175000017500000000234412317222554014471 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __TIMEVAL_H__ #define __TIMEVAL_H__ #define _GNU_SOURCE #include #include #include extern double timeval_double(const struct timeval *tv); extern double timeval_to_double(const struct timeval *tv); extern struct timeval timeval_add(const struct timeval *a, const struct timeval *b); extern struct timeval timeval_sub(const struct timeval *a, const struct timeval *b); #endif health-check-0.01.58/scripts/0000775000175000017500000000000012317222554014343 5ustar kingkinghealth-check-0.01.58/scripts/health-check-test-pid.py0000775000175000017500000001147312317222554020775 0ustar kingking#! /usr/bin/python # # # Copyright (C) 2013-2014 Canonical # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # # Syntax: # health-check-test-pid.py pid # # The process name is resolved and the tool will use a `procname`.threshold file # to compare against. If this file does not exist, default.threshold is used. # import sys, os, json, psutil # # Processes we don't want to run health-check on # ignore_procs = [ 'health-check', 'sh', 'init', 'cat', 'vi', 'emacs', 'getty', 'csh', 'bash' ] # # Default test run durations in seconds # default_duration = 60 # # Parse thresholds file: # lines starting with '#' are comments # format is: key value, e.g. # health-check.cpu-load.cpu-load-total.total-cpu-percent 0.5 # health-check.cpu-load.cpu-load-total.user-cpu-percent 0.5 # health-check.cpu-load.cpu-load-total.system-cpu-percent 0.5 # def read_threshold(procname): filename = procname + ".threshold" thresholds = { } n = 0 try: with open(filename) as file: for line in file: n = n + 1 if len(line) > 1 and not line.startswith("#"): tmp = line.split() if len(tmp) == 2: thresholds[tmp[0]] = tmp[1] #sys.stderr.write(tmp[0] + " : " + tmp[1] + "\n") else: sys.stderr.write("Threshold file " + filename + " line " + str(n) + " format error.\n") except: pass #sys.stderr.write("Cannot process threshold file " + filename + "\n"); return thresholds # # Locate a threshold in the JSON data, compare it to the given threshold # def check_threshold(data, key, fullkey, threshold): try: d = data[key[0]] except: sys.stderr.write("health-check JSON data does not have key " + fullkey + "\n") return (True, "Attribute not found and ignored") key = key[1:] if len(key) > 0: return check_threshold(d, key, fullkey, threshold) else: val = float(d) if threshold >= val: cmp = str(threshold) + " >= " + str(val) return (True, cmp) else: cmp = str(threshold) + " < " + str(val) return (False, cmp) def check_thresholds(procname, data, thresholds): print "process: " + procname failed = False for key in thresholds.keys(): if key.startswith("health-check"): (ret, str) = check_threshold(data, key.split('.'), key, float(thresholds[key])) if ret: msg = "PASSED" else: msg = "FAILED" failed = True sys.stderr.write(msg + ": " + str + ": " + key + "\n") return failed # # run health-check on a given process # def health_check(pid, procname): thresholds = read_threshold(procname) # # Can't test without thresholds # if len(thresholds) == 0: thresholds = read_threshold("default") if len(thresholds) == 0: sys.stderr.write("No thresholds for process " + procname + "\n") else: sys.stderr.write("Using default thresholds for process " + procname + "\n") duration = default_duration if 'duration' in thresholds: duration = int(thresholds['duration']) filename = "/tmp/health-check-" + str(pid) + ".log" cmd = "health-check -c -f -d " + str(duration) + " -w -W -r -p " + str(pid) + " -o " + filename + " > /dev/null" try: os.system(cmd) except: sys.stderr.write("Failed to run " + cmd + "\n"); os._exit(1) try: f = open(filename, 'r') data = json.load(f) f.close() except: sys.syderr.write("Failed to open JSON file " + filename + "\n"); os._exit(1) check_thresholds(procname, data, thresholds) # # Start here! # if len(sys.argv) < 2: sys.stderr.write("Usage: " + sys.argv[0] + " PID\n") os._exit(1) pid = int(sys.argv[1]) try: p = psutil.Process(pid) except: sys.stderr.write("Cannot find process with PID " + str(pid) + "\n") os._exit(1) try: pgid = os.getpgid(pid) except: sys.stderr.write("Cannot find pgid on process with PID " + str(pid) + "\n") os._exit(1) if pgid == 0: sys.stderr.write("Cannot run health-check on kernel task with PID " + str(pid) + "\n") os._exit(1) try: procname = os.path.basename(p.name) if p.name in ignore_procs: sys.stderr.write("Cannot run health-check on process " + procname + "\n") os._exit(1) else: # # Did it fail? # if (health_check(pid, procname)): os._exit(1) else: os._exit(0) except: sys.stderr.write("An execption occurred, failed to test on PID " + str(pid) + "\n") sys.exit(1) health-check-0.01.58/proc.h0000664000175000017500000000355712317222554014002 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __PROC_H__ #define __PROC_H__ #define _GNU_SOURCE #include #include #include #include "list.h" typedef enum { PROC_START = 0x00000001, PROC_FINISH = 0x00000002 } proc_state; /* process specific information */ typedef struct proc_info { pid_t pid; /* PID */ pid_t ppid; /* Parent PID */ char *comm; /* Kernel process comm name */ char *cmdline; /* Process name from cmdline */ bool is_thread; /* true if process is a thread */ struct proc_info *next; /* next in hash */ } proc_info_t; extern list_t proc_cache_list; extern proc_info_t *proc_cache_add(const pid_t pid, const pid_t ppid, const bool is_thread); extern proc_info_t *proc_cache_find_by_pid(pid_t pid); extern int proc_cache_get(void); extern int proc_cache_get_pthreads(void); extern void proc_cache_dump(void); extern int proc_cache_find_by_procname(list_t *pids, const char *procname); extern int proc_pids_add_proc(list_t *pids, proc_info_t *p); extern void proc_cache_init(void); extern void proc_cache_cleanup(void); #endif health-check-0.01.58/health-check.h0000664000175000017500000000302612317222554015346 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __HEALTH_CHECK_H__ #define __HEALTH_CHECK_H__ #define _GNU_SOURCE #include #include "json.h" #include "alloc.h" #define OPT_GET_CHILDREN 0x00000001 #define OPT_BRIEF 0x00000002 #define OPT_ADDR_RESOLVE 0x00000004 #define OPT_WAKELOCKS_LIGHT 0x00000008 #define OPT_WAKELOCKS_HEAVY 0x00000010 #define OPT_VERBOSE 0x00000020 #define OPT_FOLLOW_NEW_PROCS 0x00000040 #define OPT_DURATION 0x00000080 extern void health_check_exit(const int status) __attribute__ ((noreturn)); extern void health_check_out_of_memory(const char *msg); extern volatile bool keep_running; extern int opt_max_syscalls; extern int opt_flags; #endif health-check-0.01.58/ctxt-switch.h0000664000175000017500000000313312317222554015306 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __CTXT_SWITCH_H__ #define __CTXT_SWITCH_H__ #define _GNU_SOURCE #include #include "json.h" #include "proc.h" #include "list.h" /* context switch event information per process */ typedef struct { proc_info_t *proc; /* Proc specific info */ uint64_t voluntary; /* Voluntary context switches */ uint64_t involuntary; /* Unvoluntary context switches */ uint64_t total; /* Total context switches */ bool valid; /* true if valid data */ } ctxt_switch_info_t; extern int ctxt_switch_get_all_pids(const list_t *pids, proc_state state); extern int ctxt_switch_get_by_proc(proc_info_t *proc, proc_state state); extern void ctxt_switch_dump_diff(json_object *j_tests, const double duration); extern void ctxt_switch_init(void); extern void ctxt_switch_cleanup(void); #endif health-check-0.01.58/net.c0000664000175000017500000004311712317222554013614 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #include #include #include #include #include #include #include #include #include #include #include #include #include "list.h" #include "proc.h" #include "json.h" #include "health-check.h" #include "net.h" #ifndef LINE_MAX #define LINE_MAX (4096) #endif #define NET_HASH_SIZE (1993) typedef struct { uint64_t call_count; uint64_t data_total; } net_stats_t; typedef struct { proc_info_t *proc; uint64_t inode; uint32_t fd; net_stats_t send; net_stats_t recv; } net_hash_t; typedef enum { NET_TCP, NET_UDP, NET_UNIX, } net_type_t; typedef struct { net_type_t type; union { struct sockaddr_in addr4; struct sockaddr_in6 addr6; char path[PATH_MAX + 1]; } u; int family; uint64_t inode; } net_addr_info_t; typedef struct { net_addr_info_t *addr_info; net_hash_t *nh; uint64_t send_recv_total; } net_dump_info_t; static const char *net_types[] = { "TCP", "UDP", "UNIX", }; /* * Cache of addresses used by the applications. * This shouldn't be too large, so O(n) lookup is * just bearable for now. */ static list_t net_cached_addrs; /* * Hash table of inode to process mappings. */ static list_t net_hash_table[NET_HASH_SIZE]; /* * net_hash() * hash an inode, just modulo the table size for now */ static inline unsigned long net_hash(const uint64_t inode) { return inode % NET_HASH_SIZE; } /* * net_hash_add() * add inode, pid and fd to inode hash table */ static net_hash_t *net_hash_add(const uint64_t inode, const pid_t pid, const uint32_t fd) { net_hash_t *n; link_t *l; unsigned long h = net_hash(inode); /* Don't add it we have it already */ for (l = net_hash_table[h].head; l; l = l->next) { n = (net_hash_t *)l->data; if (n->proc->pid == pid && n->inode == inode) return n; } if ((n = calloc(1, sizeof(*n))) == NULL) { health_check_out_of_memory("allocating net hash data"); return NULL; } n->inode = inode; n->proc = proc_cache_find_by_pid(pid); n->fd = fd; if (list_append(&net_hash_table[h], n) == NULL) { free(n); return NULL; } return n; } /* * net_hash_find_inode() * given an inode, find the associated hash'd data */ static inline net_hash_t *net_hash_find_inode(const uint64_t inode) { link_t *l; unsigned long h = net_hash(inode); /* Don't add it we have it already */ for (l = net_hash_table[h].head; l; l = l->next) { net_hash_t *n = (net_hash_t *)l->data; if (n->inode == inode) return n; } return NULL; } /* * net_get_inode() * find inode in given readlink data, return -1 fail, 0 OK */ static int net_get_inode(const char *str, uint64_t *inode) { size_t len = strlen(str); /* Likely */ if (!strncmp(str, "socket:[", 8) && str[len - 1] == ']') return sscanf(str + 8, "%" SCNu64, inode) == 1 ? 0 : -1; /* Less likely */ if (!strncmp(str, "[0000]:", 7)) return sscanf(str + 7, "%" SCNu64, inode) == 1 ? 0 : -1; return -1; } /* * net_get_inode_by_path() * given a /proc/$pid/fd/fdnum path, look up a network inode */ static int net_get_inode_by_path(const char *path, uint64_t *inode) { char link[PATH_MAX]; ssize_t len; if ((len = readlink(path, link, sizeof(link) - 1)) < 0) return -1; link[len] = '\0'; return net_get_inode(link, inode); } /* * net_cache_inode_by_pid_and_fd() * get a net hash given a file's owner pid and the fd */ static net_hash_t *net_cache_inode_by_pid_and_fd(const pid_t pid, const int fd) { char path[PATH_MAX]; uint64_t inode; net_hash_t *nh = NULL; snprintf(path, sizeof(path), "/proc/%i/fd/%i", pid, fd); if (net_get_inode_by_path(path, &inode) != -1) nh = net_hash_add(inode, pid, fd); return nh; } /* * net_account_send() * account for net send transfers */ void net_account_send(const pid_t pid, const int fd, size_t size) { net_hash_t *nh = net_cache_inode_by_pid_and_fd(pid, fd); if (nh != NULL) { nh->send.call_count++; nh->send.data_total += size; } } /* * net_account_recv() * account for net receive transfers */ void net_account_recv(const pid_t pid, const int fd, size_t size) { net_hash_t *nh = net_cache_inode_by_pid_and_fd(pid, fd); if (nh != NULL) { nh->recv.call_count++; nh->recv.data_total += size; } } /* * net_cache_inodes_pid() * given a pid, find all the network inodes associated * with it's current file descriptors */ static int net_cache_inodes_pid(const pid_t pid) { char path[PATH_MAX]; DIR *fds; struct dirent *d; snprintf(path, sizeof(path), "/proc/%i/fd", pid); if ((fds = opendir(path)) == NULL) return -1; while ((d = readdir(fds)) != NULL) { uint64_t inode; char tmp[LINE_MAX]; uint32_t fd; if (d->d_name[0] == '.') continue; if (strlen(path) + strlen(d->d_name) + 2 > sizeof(tmp)) continue; snprintf(tmp, sizeof(tmp), "%s/%s", path, d->d_name); if (net_get_inode_by_path(tmp, &inode) != -1) { sscanf(d->d_name, "%" SCNu32, &fd); if (net_hash_add(inode, pid, fd) == NULL) { closedir(fds); return -1; } } } closedir(fds); return 0; } /* * net_cache_inodes() * given a list of pidis, find all the network inodes associated * with the processes' current file descriptors */ static int net_cache_inodes(list_t *pids) { link_t *l; for (l = pids->head; l; l = l->next) { proc_info_t *p = (proc_info_t *)l->data; if (net_cache_inodes_pid(p->pid) < 0) return -1; } return 0; } /* * net_inet4_resolve() * turn ipv4 addr to human readable address */ static void net_inet4_resolve(char *name, const size_t len, struct sockaddr_in *sin) { if ((opt_flags & OPT_ADDR_RESOLVE) && (sin->sin_addr.s_addr != INADDR_ANY)) { struct hostent *e; e = gethostbyaddr((char *)&sin->sin_addr.s_addr, sizeof(struct in_addr), AF_INET); if (e) { strncpy(name, e->h_name, len - 1); name[len - 1] = '\0'; return; } } inet_ntop(AF_INET, &sin->sin_addr, name, len); return; } /* * net_inet6_resolve() * turn ipv6 addr to human readable address */ static void net_inet6_resolve(char *name, const size_t len, struct sockaddr_in6 *sin6) { if ((opt_flags & OPT_ADDR_RESOLVE) && (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))) { struct hostent *e; e = gethostbyaddr((char *)&sin6->sin6_addr.s6_addr, sizeof(struct in6_addr), AF_INET); if (e) { strncpy(name, e->h_name, len - 1); name[len - 1] = '\0'; return; } } inet_ntop(AF_INET6, &sin6->sin6_addr, name, len - 1); return; } /* * net_addr_add() * Add a new address to the cached list of addresses. * This is an O(n) search and add, so we may need to * re-work this if the number of addresses gets too large. */ static void net_addr_add(net_addr_info_t *addr) { link_t *l; net_addr_info_t *new_addr; for (l = net_cached_addrs.head; l; l = l->next) { net_addr_info_t *old_addr = (net_addr_info_t *)l->data; if (memcmp(addr, old_addr, sizeof(*addr)) == 0) return; /* Duplicate, ignore */ } if ((new_addr = calloc(1, sizeof(*new_addr))) == NULL) { health_check_out_of_memory("allocating net address information"); return; } memcpy(new_addr, addr, sizeof(*addr)); if (list_append(&net_cached_addrs, new_addr) == NULL) free(new_addr); } /* * net_get_addr() * turn the addr info into human readable form */ static char *net_get_addr(net_addr_info_t *addr_info) { static char tmp[256]; char buf[4096]; in_port_t port; switch (addr_info->type) { case NET_TCP: case NET_UDP: switch (addr_info->family) { case AF_INET6: net_inet6_resolve(buf, sizeof(buf), &addr_info->u.addr6); port = addr_info->u.addr6.sin6_port; break; case AF_INET: net_inet4_resolve(buf, sizeof(buf), &addr_info->u.addr4); port = addr_info->u.addr4.sin_port; break; default: /* No idea what it is */ return NULL; } snprintf(tmp, sizeof(tmp), "%s:%d", buf, port); return tmp; case NET_UNIX: return addr_info->u.path; default: break; } return NULL; } /* * net_add_dump_info() * either accumulate existing send/recv net info, or add it if not * already unique */ static void net_add_dump_info(list_t *list, net_dump_info_t *new_dump_info) { link_t *l; for (l = list->head; l; l = l->next) { net_dump_info_t *dump_info = (net_dump_info_t *)l->data; if (dump_info->nh->proc == new_dump_info->nh->proc && dump_info->addr_info->type == new_dump_info->addr_info->type && memcmp(&dump_info->addr_info->u, &new_dump_info->addr_info->u, sizeof(dump_info->addr_info->u)) == 0 && dump_info->addr_info->family == new_dump_info->addr_info->family) { dump_info->nh->send.call_count += new_dump_info->nh->send.call_count; dump_info->nh->send.data_total += new_dump_info->nh->send.data_total; dump_info->nh->recv.call_count += new_dump_info->nh->recv.call_count; dump_info->nh->recv.data_total += new_dump_info->nh->recv.data_total; dump_info->send_recv_total += new_dump_info->send_recv_total; free(new_dump_info); return; } } list_append(list, new_dump_info); } /* * net_dump_info_cmp() * Sort for dumping net send/recv stats, sorted on total * data send/recv and then if no difference on pid order */ static int net_dump_info_cmp(const void *p1, const void *p2) { const net_dump_info_t *d1 = (const net_dump_info_t *)p1; const net_dump_info_t *d2 = (const net_dump_info_t *)p2; if (d2->send_recv_total - d1->send_recv_total == 0) return d1->nh->proc->pid - d2->nh->proc->pid; else return d2->send_recv_total - d1->send_recv_total; } /* * net_size_to_str() * turn transfer size in bytes to a more human readable form */ static void net_size_to_str(char *buf, size_t buf_len, uint64_t size) { double s; char unit; if (size < 1024) { s = (double)size; unit = 'B'; } else if (size < 1024 * 1024) { s = (double)size / 1024.0; unit = 'K'; } else { s = (double)size / (1024 * 1024); unit = 'M'; } snprintf(buf, buf_len, "%7.2f %c", s, unit); } /* * net_connection_dump() * dump out network connections */ void net_connection_dump(json_object *j_tests, double duration) { link_t *l; list_t dump_info_list; list_t sorted; #ifdef JSON_OUTPUT json_object *j_net_test = NULL, *j_net_infos = NULL, *j_net_info; uint64_t send_total = 0, recv_total = 0; #else (void)j_tests; (void)duration; #endif printf("Open Network Connections:\n"); list_init(&dump_info_list); list_init(&sorted); #ifdef JSON_OUTPUT if (j_tests) { if ((j_net_test = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_tests, "network-connections", j_net_test); if ((j_net_infos = j_obj_new_array()) == NULL) goto out; j_obj_obj_add(j_net_test, "network-connections-per-process", j_net_infos); } #endif /* * Collate data */ for (l = net_cached_addrs.head; l; l = l->next) { net_addr_info_t *addr_info = (net_addr_info_t *)l->data; link_t *ln; unsigned long h; h = net_hash(addr_info->inode); for (ln = net_hash_table[h].head; ln; ln = ln->next) { net_hash_t *nh = (net_hash_t *)ln->data; if (nh->inode == addr_info->inode) { net_dump_info_t *dump_info; /* Skip threads that do nothing */ if ((nh->send.data_total + nh->recv.data_total == 0) && nh->proc->is_thread) continue; if ((dump_info = calloc(1, sizeof(net_dump_info_t))) == NULL) goto out; dump_info->addr_info = addr_info; dump_info->nh = nh; dump_info->send_recv_total = nh->send.data_total + nh->recv.data_total; net_add_dump_info(&dump_info_list, dump_info); } } } /* * We've now got a reduced list of useful data, so now sort it */ for (l = dump_info_list.head; l; l = l->next) { net_dump_info_t *dump_info = l->data; list_add_ordered(&sorted, dump_info, net_dump_info_cmp); } if (!dump_info_list.head) { printf(" None.\n\n"); } else { printf(" PID Process Proto Send Receive Address\n"); for (l = sorted.head; l; l = l->next) { net_dump_info_t *dump_info = (net_dump_info_t *)l->data; char *addr = net_get_addr(dump_info->addr_info); char sendbuf[64], recvbuf[64]; net_size_to_str(sendbuf, sizeof(sendbuf), dump_info->nh->send.data_total); net_size_to_str(recvbuf, sizeof(recvbuf), dump_info->nh->recv.data_total); printf(" %5i %-20.20s %-4.4s %s %s %s\n", dump_info->nh->proc->pid, dump_info->nh->proc->cmdline, net_types[dump_info->addr_info->type], sendbuf, recvbuf, addr); #ifdef JSON_OUTPUT if (j_tests) { if ((j_net_info = j_obj_new_obj()) == NULL) goto out; j_obj_new_int32_add(j_net_info, "pid", dump_info->nh->proc->pid); j_obj_new_int32_add(j_net_info, "ppid", dump_info->nh->proc->ppid); j_obj_new_int32_add(j_net_info, "is-thread", dump_info->nh->proc->is_thread); j_obj_new_string_add(j_net_info, "name", dump_info->nh->proc->cmdline); j_obj_new_string_add(j_net_info, "protocol", net_types[dump_info->addr_info->type]); j_obj_new_string_add(j_net_info, "address", addr); j_obj_new_int64_add(j_net_info, "send", dump_info->nh->send.data_total); j_obj_new_int64_add(j_net_info, "receive", dump_info->nh->recv.data_total); j_obj_array_add(j_net_infos, j_net_info); send_total += dump_info->nh->send.data_total; recv_total += dump_info->nh->recv.data_total; } #endif } printf("\n"); } #ifdef JSON_OUTPUT if (j_tests) { if ((j_net_info = j_obj_new_obj()) == NULL) goto out; j_obj_obj_add(j_net_test, "network-connections-total", j_net_info); j_obj_new_int64_add(j_net_info, "send-total", send_total); j_obj_new_int64_add(j_net_info, "receive-total", recv_total); j_obj_new_double_add(j_net_info, "send-total-rate", (double)send_total / duration); j_obj_new_double_add(j_net_info, "receive-total-rate", (double)recv_total / duration); } #endif out: list_free(&sorted, NULL); list_free(&dump_info_list, free); } /* * net_unix_parse() * parse /proc/net/unix and cache data */ static int net_unix_parse(void) { FILE *fp; char buf[4096]; int i; if ((fp = fopen("/proc/net/unix", "r")) == NULL) { fprintf(stderr, "Cannot open /proc/net/unix\n"); return -1; } for (i = 0; fgets(buf, sizeof(buf), fp) != NULL; i++) { uint64_t inode; char path[4096]; net_addr_info_t new_addr; if (i == 0) /* Skip header */ continue; sscanf(buf, "%*x: %*x %*x %*x %*x %*x %" SCNu64 " %s\n", &inode, path); memset(&new_addr, 0, sizeof(new_addr)); new_addr.inode = inode; new_addr.type = NET_UNIX; strncpy(new_addr.u.path, path, PATH_MAX); net_addr_add(&new_addr); } fclose(fp); return 0; } /* * net_tcp_udp_parse() * parse /proc/net/{tcp,udp} and cache data for * faster lookup */ static int net_tcp_udp_parse(const net_type_t type) { FILE *fp; char *procfile; char buf[4096]; char addr_str[128]; in_port_t port; int i; uint64_t inode; switch (type) { case NET_TCP: procfile = "/proc/net/tcp"; break; case NET_UDP: procfile = "/proc/net/udp"; break; default: fprintf(stderr, "net_parse given bad net type.\n"); return -1; } if ((fp = fopen(procfile, "r")) == NULL) { fprintf(stderr, "Cannot open %s.\n", procfile); return -1; } for (i = 0; fgets(buf, sizeof(buf), fp) != NULL; i++) { net_addr_info_t new_addr; if (i == 0) /* Skip header */ continue; sscanf(buf, "%*d: %*64[0-9A-Fa-f]:%*X %64[0-9A-Fa-f]:%" SCNx16 " %*X %*X:%*X %*X:%*X %*X %*d %*d %" SCNu64, addr_str, &port, &inode); memset(&new_addr, 0, sizeof(new_addr)); new_addr.inode = inode; new_addr.type = type; if (strlen(addr_str) > 8) { new_addr.family = new_addr.u.addr6.sin6_family = AF_INET6; new_addr.u.addr6.sin6_port = port; sscanf(addr_str, "%08X%08X%08X%08X", &new_addr.u.addr6.sin6_addr.s6_addr32[0], &new_addr.u.addr6.sin6_addr.s6_addr32[1], &new_addr.u.addr6.sin6_addr.s6_addr32[2], &new_addr.u.addr6.sin6_addr.s6_addr32[3]); } else { new_addr.family = new_addr.u.addr4.sin_family = AF_INET; new_addr.u.addr4.sin_port = port; sscanf(addr_str, "%8X", &new_addr.u.addr4.sin_addr.s_addr); } net_addr_add(&new_addr); } fclose(fp); return 0; } /* * net_parse() * parse various /proc net files */ static int net_parse(void) { if (net_tcp_udp_parse(NET_TCP) < 0) return -1; if (net_tcp_udp_parse(NET_UDP) < 0) return -1; if (net_unix_parse() < 0) return -1; return 0; } /* * net_connection_pids() * find network inodes assocated with given * pids and find network addresses */ int net_connection_pids(list_t *pids) { if (net_cache_inodes(pids) < 0) return -1; return net_parse(); } /* * net_connection_pid() * find network inodes assocated with given * pid and find network addresses */ int net_connection_pid(const pid_t pid) { if (net_cache_inodes_pid(pid) < 0) return -1; return net_parse(); } /* * net_connection_init() * initialise */ void net_connection_init(void) { int i; list_init(&net_cached_addrs); for (i = 0; i < NET_HASH_SIZE; i++) list_init(&net_hash_table[i]); } /* * net_connection_cleanup() * tidy up behind ourselves */ void net_connection_cleanup(void) { int i; list_free(&net_cached_addrs, free); for (i = 0; i < NET_HASH_SIZE; i++) list_free(&net_hash_table[i], free); } health-check-0.01.58/list.h0000664000175000017500000000310412317222554013776 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __LIST_H__ #define __LIST_H__ #include "alloc.h" #define _GNU_SOURCE /* single link and pointer to data item for a generic linked list */ typedef struct link { void *data; /* Data in list */ struct link *next; /* Next item in list */ } link_t; /* linked list */ typedef struct { link_t *head; /* Head of list */ link_t *tail; /* Tail of list */ size_t length; /* Length of list */ } list_t; typedef void (*list_link_free_t)(void *); typedef int (*list_comp_t)(const void *, const void *); extern void list_init(list_t *list); extern link_t *list_append(list_t *list, void *data); extern link_t *list_add_ordered(list_t *list, void *new_data, const list_comp_t compare); extern void list_free(list_t *list, const list_link_free_t freefunc); #endif health-check-0.01.58/proc.c0000664000175000017500000001603712317222554013772 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "list.h" #include "pid.h" #include "proc.h" #include "net.h" #include "health-check.h" #define HASH_TABLE_SIZE (1997) list_t proc_cache_list; static proc_info_t *proc_cache_hash[HASH_TABLE_SIZE]; static pthread_mutex_t pids_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t proc_cache_mutex = PTHREAD_MUTEX_INITIALIZER; /* * proc_cache_hash_pid() * hash a process id */ static inline unsigned long proc_cache_hash_pid(const pid_t pid) { unsigned long h = (unsigned long)pid; return h % HASH_TABLE_SIZE; } /* * proc_cache_add_at_hash_index() * heler function to add proc info to the proc cache and list */ static proc_info_t *proc_cache_add_at_hash_index( const unsigned long h, const pid_t pid, const pid_t ppid, const bool is_thread) { proc_info_t *p; if ((p = calloc(1, sizeof(*p))) == NULL) { health_check_out_of_memory("allocating proc cache"); return NULL; } p->pid = pid; p->ppid = ppid; p->cmdline = get_pid_cmdline(pid); p->comm = get_pid_comm(pid); p->is_thread = is_thread; pthread_mutex_lock(&proc_cache_mutex); if (list_append(&proc_cache_list, p) == NULL) { pthread_mutex_unlock(&proc_cache_mutex); free(p->cmdline); free(p); return NULL; } p->next = proc_cache_hash[h]; proc_cache_hash[h] = p; pthread_mutex_unlock(&proc_cache_mutex); return p; } /* * proc_cache_add() * explicity add process info to global cache ONLY if it is a traceable process */ proc_info_t *proc_cache_add(const pid_t pid, const pid_t ppid, const bool is_thread) { proc_info_t *p; unsigned long h; if (!pid_exists(pid) || (pid == getpid())) return NULL; pthread_mutex_lock(&proc_cache_mutex); h = proc_cache_hash_pid(pid); for (p = proc_cache_hash[h]; p; p = p->next) { if (p->pid == pid) { pthread_mutex_unlock(&proc_cache_mutex); return p; } } pthread_mutex_unlock(&proc_cache_mutex); return proc_cache_add_at_hash_index(h, pid, ppid, is_thread); } /* * proc_cache_find_by_pid() * find process info by the process id, if it is not found * and it is a traceable process then cache it */ proc_info_t *proc_cache_find_by_pid(const pid_t pid) { unsigned long h; proc_info_t *p; pthread_mutex_lock(&proc_cache_mutex); h = proc_cache_hash_pid(pid); for (p = proc_cache_hash[h]; p; p = p->next) { if (p->pid == pid) { pthread_mutex_unlock(&proc_cache_mutex); return p; } } pthread_mutex_unlock(&proc_cache_mutex); /* * Not found, so add it and return it if it is a legitimate * process to trace */ if (!pid_exists(pid) || (pid == getpid())) return NULL; /* Be lazy and ignore the parent info lookup */ return proc_cache_add_at_hash_index(h, pid, 0, false); } /* * proc_cache_get() * load proc cache with current system process info */ int proc_cache_get(void) { DIR *procdir; struct dirent *procentry; if ((procdir = opendir("/proc")) == NULL) { fprintf(stderr, "Cannot open directory /proc.\n"); return -1; } /* * Gather pid -> ppid mapping */ while ((procentry = readdir(procdir)) != NULL) { FILE *fp; char path[PATH_MAX]; if (!isdigit(procentry->d_name[0])) continue; snprintf(path, sizeof(path), "/proc/%s/stat", procentry->d_name); if ((fp = fopen(path, "r")) != NULL) { pid_t pid, ppid; char comm[64]; /* 3173 (a.out) R 3093 3173 3093 34818 3173 4202496 165 0 0 0 3194 0 */ if (fscanf(fp, "%8d (%63[^)]) %*c %8i", &pid, comm, &ppid) == 3) (void)proc_cache_add(pid, ppid, false); fclose(fp); } } closedir(procdir); return 0; } /* * proc_cache_get_pthreads() * load proc cache with pthreads from current system process info */ int proc_cache_get_pthreads(void) { DIR *procdir; struct dirent *procentry; if ((procdir = opendir("/proc")) == NULL) { fprintf(stderr, "Cannot open directory /proc.\n"); return -1; } /* * Gather pid -> ppid mapping */ while ((procentry = readdir(procdir)) != NULL) { DIR *taskdir; struct dirent *taskentry; char path[PATH_MAX]; pid_t ppid; if (!isdigit(procentry->d_name[0])) continue; ppid = atoi(procentry->d_name); snprintf(path, sizeof(path), "/proc/%i/task", ppid); if ((taskdir = opendir(path)) == NULL) continue; (void)proc_cache_add(ppid, 0, false); while ((taskentry = readdir(taskdir)) != NULL) { pid_t pid; if (!isdigit(taskentry->d_name[0])) continue; pid = atoi(taskentry->d_name); if (pid == ppid) continue; if (proc_cache_add(pid, ppid, true) == NULL) { closedir(taskdir); closedir(procdir); return -1; } } closedir(taskdir); } closedir(procdir); return 0; } /* * proc_cache_info_free() * free a proc cache item */ static void proc_cache_info_free(void *data) { proc_info_t *p = (proc_info_t*)data; free(p->cmdline); free(p->comm); free(p); } /* * proc_pids_add_proc() * add a process to pid list if it is sensible */ int proc_pids_add_proc(list_t *pids, proc_info_t *p) { int rc = 0; if (p->pid == 1) { fprintf(stderr, "Cannot run health-check on init. Aborting.\n"); health_check_exit(EXIT_FAILURE); } if (p->pid == getpid()) { fprintf(stderr, "Cannot run health-check on itself. Aborting.\n"); health_check_exit(EXIT_FAILURE); } pthread_mutex_lock(&pids_mutex); if (list_append(pids, p) == NULL) rc = -1; pthread_mutex_unlock(&pids_mutex); return rc; } /* * proc_cache_find_by_procname() * find process by process name (in cmdline) * we don't do this often, so a linear search is fine */ int proc_cache_find_by_procname( list_t *pids, const char *procname) { bool found = false; link_t *l; pthread_mutex_lock(&proc_cache_mutex); for (l = proc_cache_list.head; l; l = l->next) { proc_info_t *p = (proc_info_t *)l->data; if (p->cmdline && strcmp(p->cmdline, procname) == 0) { proc_pids_add_proc(pids, p); found = true; } } pthread_mutex_unlock(&proc_cache_mutex); if (!found) { fprintf(stderr, "Cannot find process %s.\n", procname); return -1; } return 0; } /* * proc_cache_init() * initialize proc cache */ void proc_cache_init(void) { list_init(&proc_cache_list); } /* * proc_cache_cleanup * cleanup */ void proc_cache_cleanup(void) { list_free(&proc_cache_list, proc_cache_info_free); } health-check-0.01.58/syscall.h0000664000175000017500000001540112317222554014500 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __SYSCALL_H__ #define __SYSCALL_H__ #define _GNU_SOURCE #include #if (( defined(__x86_64__) && defined(__LP64__) ) || \ defined(__i386__) || \ defined(__arm__) || \ defined(__powerpc__) || \ defined(__aarch64__)) #define SYSCALL_SUPPORTED 1 #else #define SYSCALL_SUPPORTED 0 #endif #include "proc.h" #include "list.h" #include "json.h" #define MAX_BUCKET (9) #define BUCKET_START (0.00001) #define SYSCALL(n) \ [SYS_ ## n] = { #n, SYS_ ## n, 0, NULL, NULL, NULL, NULL, NULL } #define SYSCALL_CHK_TIMEOUT(n, arg, func_check, func_ret) \ [SYS_ ## n] = { #n, SYS_ ## n, arg, &syscall_timeout[SYS_ ## n], func_check, func_ret, NULL, NULL } #define SYSCALL_CHK(n, arg, func_check, func_ret) \ [SYS_ ## n] = { #n, SYS_ ## n, arg, &syscall_timeout[SYS_ ## n], NULL, NULL, func_check, func_ret } #define TIMEOUT(n, timeout) \ [SYS_ ## n] = timeout /* Stash timeout related syscall return value */ typedef struct { double timeout; /* syscall timeout in seconds */ int ret; /* syscall return */ } syscall_return_info_t; /* Syscall polling stats for a particular process */ typedef struct syscall_info { proc_info_t *proc; /* process info */ int syscall; /* system call number */ uint64_t count; /* number times call has been made */ uint64_t usecs_total; /* total number of uSecs in system call */ struct timeval usec_enter; /* Time when a syscall was entered */ struct timeval usec_return; /* Time when a syscall was exited */ double poll_min; /* minimum poll time */ double poll_max; /* maximum poll time */ double poll_total; /* sum of non zero or negative poll times */ uint64_t poll_count; /* number of polls */ uint64_t poll_too_low; /* number of poll times below a threshold */ uint64_t poll_infinite; /* number of -ve (infinite) poll times */ uint64_t poll_zero; /* number of zero poll times */ uint64_t bucket[MAX_BUCKET]; /* bucket count of poll times */ list_t return_history; /* history system call returns */ struct syscall_info *next; } syscall_info_t; typedef struct syscall syscall_t; typedef void (*call_enter_timeout_t)(const syscall_t *sc, syscall_info_t *s, const pid_t pid, const double threshold, double *timeout); typedef void (*call_return_timeout_t)(json_object *j_tests, const syscall_t *sc, const syscall_info_t *s); typedef void (*call_enter_t)(const syscall_t *sc, const syscall_info_t *s, const pid_t pid); typedef void (*call_return_t)(const syscall_t *sc, const syscall_info_t *s, const int ret); /* syscall specific information */ typedef struct syscall { char *name; /* name of the syscall */ int syscall; /* system call number */ int arg; /* nth arg to check for timeout value (1st arg is zero) */ double *threshold; /* threshold - points to timeout array items indexed by syscall */ call_enter_timeout_t call_enter_timeout; /* timeout checking function, NULL means don't check */ call_return_timeout_t call_return_timeout; /* return checking function, NULL means don't check */ call_enter_t call_enter; /* non-timout call checking function, NULL means don't check */ call_return_t call_return; /* non-timeout return checking function, NULL means don't check */ } syscall_t; /* fd cache */ typedef struct fd_cache { pid_t pid; /* process */ int fd; /* file descriptor */ char *filename; /* filename, NULL if closed */ pthread_mutex_t mutex; /* mutex on filename */ struct fd_cache *next; /* next one in cache */ } fd_cache_t; typedef struct syscall_wakelock_info { pid_t pid; /* process */ bool locked; /* true = lock, false = unlock */ struct timeval tv; /* when locked/unlocked */ char *lockname; /* wake lock name */ struct syscall_wakelock_info *paired; /* ptr to lock/unlock pair */ } syscall_wakelock_info_t; typedef struct { pid_t pid; /* process */ uint64_t sync_count; /* sync syscall count */ uint64_t fsync_count; /* fsync syscall count */ uint64_t fdatasync_count;/* fdatasync syscall count */ uint64_t syncfs_count; /* syncfs syscall count */ uint64_t total_count; /* total count */ list_t sync_file; /* list of files that were sync'd */ } syscall_sync_info_t; typedef struct { char *filename; /* name of file being sync'd */ uint64_t count; /* count of sync calls */ int syscall; /* specific sync syscall being used */ } syscall_sync_file_t; /* sycall states */ #define SYSCALL_CTX_ALIVE 0x00000001 #define SYSCALL_CTX_ATTACHED 0x00000002 typedef struct syscall_context { pid_t pid; /* process */ proc_info_t *proc; /* proc info */ int syscall; /* syscall detected */ double timeout; /* timeout on poll syscalls */ syscall_info_t *syscall_info; /* syscall accounting */ int state; /* is traced thread alive and/or attached? */ struct syscall_context *next; } syscall_context_t; typedef struct socket_info { pid_t pid; /* process id */ int fd; /* socket fd */ bool send; /* true = send, false = recv */ uint64_t count; /* number of calls */ uint64_t total; /* number of bytes tx/rx */ struct socket_info *next; } socket_info_t; typedef struct filename_info { int syscall; /* syscall number */ pid_t pid; /* process id */ proc_info_t *proc; /* proc info */ char *filename; /* file being inotify_added */ uint64_t count; /* number of times accessed */ struct filename_info *next; } filename_info_t; extern int procs_traced; extern syscall_t syscalls[]; extern size_t syscalls_len; extern void *syscall_trace(void *arg); extern void syscall_dump_hashtable(json_object *j_tests, const double duration); extern void syscall_dump_pollers(json_object *j_tests, const double duration); extern void syscall_init(void); extern void syscall_cleanup(void); extern void syscall_dump_wakelocks(json_object *j_tests, const double duration, list_t *pids); extern void syscall_dump_sync(json_object *j_tests, double duration); extern void syscall_dump_inotify(json_object *j_obj, double duration); extern void syscall_dump_execve(json_object *j_obj, double duration); extern int syscall_trace_proc(list_t *pids); extern int syscall_stop(void); #endif health-check-0.01.58/net.h0000664000175000017500000000242412317222554013615 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __NET_H__ #define __NET_H__ #include "list.h" #include "json.h" extern int net_connection_pids(list_t *pids); extern int net_connection_pid(const pid_t); extern void net_connection_dump(json_object *j_tests, double duration); extern void net_account_send(const pid_t pid, const int fd, size_t size); extern void net_account_recv(const pid_t pid, const int fd, size_t size); extern void net_connection_init(void); extern void net_connection_cleanup(void); #endif health-check-0.01.58/pid.h0000664000175000017500000000224212317222554013601 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __PID_H__ #define __PID_H__ #define _GNU_SOURCE #include #include "unistd.h" #include "list.h" extern char *get_pid_comm(const pid_t pid); extern char *get_pid_cmdline(const pid_t pid); extern bool pid_exists(const pid_t pid); extern bool pid_list_find(pid_t pid, list_t *list); extern int pid_list_get_children(list_t *pids); #endif health-check-0.01.58/COPYING0000664000175000017500000004325412317222554013717 0ustar kingking GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. health-check-0.01.58/health-check.80000664000175000017500000000567412317222554015301 0ustar kingking.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH HEALTH-CHECK 8 "January 28, 2014" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME health-check \- a tool to measure system events. .br .SH SYNOPSIS .B health-check .RI [options] .RI [ command \ [ options ]] .br .SH DESCRIPTION Health-check monitors a process and optionally their child processes and threads for a given amount of time. At the end of the monitoring it will display the CPU time used, wakeup events generated and I/O operations of the given processes. It can be used to diagnose unhealthy badly behaving processes. .SH OPTIONS health-check options are as follow: .TP .B \-h Show help .TP .B \-b Brief (terse) output for quick overview. .TP .B \-c Find and monitor all child and threads of a given set of processes. This option is only useful when attaching to already running processes using the \-p option. .TP .B \-d Specify analysis duration in seconds. Default is 60 seconds. A duration of 0 will make health-check run forever, or until the monitored process exits. .TP .B \-f Follow fork, vfork and clone system calls. .TP .B \-p pid[,pid] Specify which processes to analyse. Can be process ID or process name. .TP .B \-r Resolve IP addresses, this can take some time, hence it is an opt-in feature. .TP .B \-m max Specify maximum number of timeout blocking system calls are logged before completing. This is useful with very busy processes that can generate tens of thousands of ptrace events that have to be logged by health-check. The default is 1 million. .TP .B \-o logfile Specify output log file to export JSON formatted results. The resulting data can be then easily imported and analysed using JSON parsing tools. .TP .B \-u username Run command as the specified user. This cannot be used with the \-p option. .TP .B \-v verbose Enable verbose mode (currently just for \-W wakelock option). Not compatible with the \-b brief option. .TP .B \-w monitor wakelock count This uses fnotify to count the number of wakelock lock/unlocks. Lightweight and simple wakelock monitoring. .TP .B \-W monitor wakelock usage This does deeper system call inspection to monitor wakelock usage and uses up more run time processing to perform the inspection. .SH AUTHOR health-check was written by Colin King .PP This manual page was written by Colin King , for the Ubuntu project (but may be used by others). health-check-0.01.58/cpustat.h0000664000175000017500000000337512317222554014520 0ustar kingking/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: Colin Ian King */ #ifndef __CPUSTAT_H__ #define __CPUSTAT_H__ #define _GNU_SOURCE #include #include "list.h" #include "proc.h" #include "json.h" /* cpu usage information per process */ typedef struct { proc_info_t *proc; /* Proc specific info */ uint64_t utime; /* User time quantum */ uint64_t stime; /* System time quantum */ uint64_t ttime; /* Total time */ uint64_t minor_fault; /* Minor page faults */ uint64_t major_fault; /* Minor page faults */ struct timeval whence; /* When sample was taken */ double duration; /* Duration between old and new samples */ } cpustat_info_t; extern int cpustat_dump_diff(json_object *json_obj, const double duration); extern int cpustat_get_all_pids(const list_t *pids, proc_state state); extern int cpustat_get_by_proc(proc_info_t *proc, proc_state state); extern int pagefault_dump_diff(json_object *j_tests, const double duration); extern void cpustat_init(void); extern void cpustat_cleanup(void); #endif