├── debian ├── source │ └── format ├── rules ├── watch ├── copyright └── control ├── .travis.yml ├── pid.h ├── timeval.h ├── net.h ├── list.h ├── bash-completion └── health-check ├── ctxt-switch.h ├── cpustat.h ├── proc.h ├── json.h ├── health-check.h ├── timeval.c ├── fnotify.h ├── json.c ├── mem.h ├── list.c ├── health-check.8 ├── Makefile ├── pid.c ├── scripts └── health-check-test-pid.py ├── syscall.h ├── proc.c ├── ctxt-switch.c ├── README.md ├── cpustat.c ├── COPYING ├── health-check.c ├── fnotify.c ├── net.c └── mem.c /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | export DEB_BUILD_MAINT_OPTIONS = hardening=+all 4 | DPKG_EXPORT_BUILDFLAGS = 1 5 | include /usr/share/dpkg/buildflags.mk 6 | 7 | %: 8 | dh $@ 9 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | version=4 2 | opts="filenamemangle=s%(?:.*?)?V?(\d[\d.]*)\.tar\.gz%@PACKAGE@-$1.tar.gz%" \ 3 | https://github.com/ColinIanKing/health-check/tags \ 4 | (?:.*?/)?V?(\d[\d.]*)\.tar\.gz debian uupdate 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: bionic 2 | sudo: required 3 | 4 | matrix: 5 | include: 6 | - env: PEDANTIC=1 7 | 8 | before_install: 9 | - sudo apt-get update -q 10 | - sudo apt-get install build-essential 11 | - sudo apt-get install libjson-c-dev || true 12 | - sudo apt-get install libjson0-dev || true 13 | - sudo apt-get install libbsd-dev || true 14 | 15 | language: c 16 | 17 | script: 18 | - make -j2 PEDANTIC=$PEDANTIC 19 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: health-check 3 | Upstream-Contact: Colin Ian King 4 | Source: https://github.com/ColinIanKing/health-check 5 | 6 | Files: * 7 | Copyright: 2013-2021, Canonical Ltd 8 | 2021-2022, Colin Ian King 9 | License: GPL-2+ 10 | On Debian systems, the complete text of the GNU General Public 11 | License can be found in `/usr/share/common-licenses/GPL-2'. 12 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: health-check 2 | Rules-Requires-Root: no 3 | Section: admin 4 | Priority: optional 5 | Maintainer: Colin Ian King 6 | Standards-Version: 4.7.0 7 | Build-Depends: debhelper (>= 13), 8 | debhelper-compat (=13), 9 | libjson-c-dev (>= 0.9), 10 | libbsd-dev 11 | Homepage: https://github.com/ColinIanKing/health-check 12 | 13 | Package: health-check 14 | Architecture: i386 amd64 armel armhf arm64 powerpc ppc64 ppc64el 15 | Depends: ${shlibs:Depends}, ${misc:Depends} 16 | Description: process monitoring tool 17 | Health-check monitors processes and optionally their child 18 | processes and threads for a given amount of time. At the end 19 | of the monitoring it will display the CPU time used, wakeup 20 | events generated and I/O operations of the given processes. 21 | It can be used to diagnose unhealthy bad processes. 22 | -------------------------------------------------------------------------------- /pid.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * Author: Colin Ian King 19 | */ 20 | #ifndef __PID_H__ 21 | #define __PID_H__ 22 | 23 | #define _GNU_SOURCE 24 | 25 | #include 26 | 27 | #include "unistd.h" 28 | #include "list.h" 29 | 30 | extern char *get_pid_comm(const pid_t pid); 31 | extern char *get_pid_cmdline(const pid_t pid); 32 | extern bool pid_exists(const pid_t pid); 33 | extern bool pid_list_find(pid_t pid, list_t *list); 34 | extern int pid_list_get_children(list_t *pids); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /timeval.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * Author: Colin Ian King 19 | */ 20 | #ifndef __TIMEVAL_H__ 21 | #define __TIMEVAL_H__ 22 | 23 | #define _GNU_SOURCE 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | extern double timeval_double(const struct timeval *tv); 30 | extern double timeval_to_double(const struct timeval *tv); 31 | extern struct timeval timeval_add(const struct timeval *a, const struct timeval *b); 32 | extern struct timeval timeval_sub(const struct timeval *a, const struct timeval *b); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /net.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * Author: Colin Ian King 19 | */ 20 | #ifndef __NET_H__ 21 | #define __NET_H__ 22 | 23 | #include "list.h" 24 | #include "json.h" 25 | 26 | extern int net_connection_pids(list_t *pids); 27 | extern int net_connection_pid(const pid_t); 28 | extern void net_connection_dump(json_object *j_tests, double duration); 29 | extern void net_account_send(const pid_t pid, const int fd, size_t size); 30 | extern void net_account_recv(const pid_t pid, const int fd, size_t size); 31 | extern void net_connection_init(void); 32 | extern void net_connection_cleanup(void); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /list.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * Author: Colin Ian King 19 | */ 20 | #ifndef __LIST_H__ 21 | #define __LIST_H__ 22 | 23 | #define _GNU_SOURCE 24 | 25 | /* single link and pointer to data item for a generic linked list */ 26 | typedef struct link { 27 | void *data; /* Data in list */ 28 | struct link *next; /* Next item in list */ 29 | } link_t; 30 | 31 | /* linked list */ 32 | typedef struct { 33 | link_t *head; /* Head of list */ 34 | link_t *tail; /* Tail of list */ 35 | size_t length; /* Length of list */ 36 | } list_t; 37 | 38 | typedef void (*list_link_free_t)(void *); 39 | typedef int (*list_comp_t)(const void *, const void *); 40 | 41 | extern void list_init(list_t *list); 42 | extern link_t *list_append(list_t *list, void *data); 43 | extern link_t *list_add_ordered(list_t *list, void *new_data, const list_comp_t compare); 44 | extern void list_free(list_t *list, const list_link_free_t freefunc); 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /bash-completion/health-check: -------------------------------------------------------------------------------- 1 | # health-check tab completion for bash. 2 | # 3 | # Copyright (C) 2020-2021 Canonical 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | 19 | _health-check() 20 | { 21 | local cur prev 22 | _init_completion || return 23 | 24 | case "$prev" in 25 | '-d') COMPREPLY=( $(compgen -W "duration" -- $cur) ) 26 | return 0 27 | ;; 28 | '-p') COMPREPLY=( $(compgen -W '$(command ps axo pid | sed 1d) ' $cur ) ) 29 | return 0 30 | ;; 31 | '-m') COMPREPLY=( $(compgen -W "maxcalls" -- $cur) ) 32 | return 0 33 | ;; 34 | '-o') _filedir 35 | return 0 36 | ;; 37 | '-u') COMPREPLY=( $(compgen -W '$(command ps axo user | sed 1d | sort | uniq) ' $cur ) ) 38 | return 0 39 | ;; 40 | esac 41 | 42 | case "$cur" in 43 | -*) 44 | OPTS="-b -c -d -f -h -p -m -o -r -u -v -w -W" 45 | COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) ) 46 | return 0 47 | ;; 48 | esac 49 | return 0 50 | } 51 | 52 | # load the completion 53 | complete -F _health-check health-check 54 | -------------------------------------------------------------------------------- /ctxt-switch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * Author: Colin Ian King 19 | */ 20 | #ifndef __CTXT_SWITCH_H__ 21 | #define __CTXT_SWITCH_H__ 22 | 23 | #define _GNU_SOURCE 24 | 25 | #include 26 | 27 | #include "json.h" 28 | #include "proc.h" 29 | #include "list.h" 30 | 31 | /* context switch event information per process */ 32 | typedef struct { 33 | proc_info_t *proc; /* Proc specific info */ 34 | uint64_t voluntary; /* Voluntary context switches */ 35 | uint64_t involuntary; /* Unvoluntary context switches */ 36 | uint64_t total; /* Total context switches */ 37 | bool valid; /* true if valid data */ 38 | } ctxt_switch_info_t; 39 | 40 | extern int ctxt_switch_get_all_pids(const list_t *pids, proc_state state); 41 | extern int ctxt_switch_get_by_proc(proc_info_t *proc, proc_state state); 42 | extern void ctxt_switch_dump_diff(json_object *j_tests, const double duration); 43 | extern void ctxt_switch_init(void); 44 | extern void ctxt_switch_cleanup(void); 45 | 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /cpustat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * Author: Colin Ian King 19 | */ 20 | #ifndef __CPUSTAT_H__ 21 | #define __CPUSTAT_H__ 22 | 23 | #define _GNU_SOURCE 24 | 25 | #include 26 | 27 | #include "list.h" 28 | #include "proc.h" 29 | #include "json.h" 30 | 31 | /* cpu usage information per process */ 32 | typedef struct { 33 | proc_info_t *proc; /* Proc specific info */ 34 | uint64_t utime; /* User time quantum */ 35 | uint64_t stime; /* System time quantum */ 36 | uint64_t ttime; /* Total time */ 37 | uint64_t minor_fault; /* Minor page faults */ 38 | uint64_t major_fault; /* Minor page faults */ 39 | struct timeval whence; /* When sample was taken */ 40 | double duration; /* Duration between old and new samples */ 41 | } cpustat_info_t; 42 | 43 | extern int cpustat_dump_diff(json_object *json_obj, const double duration); 44 | extern int cpustat_get_all_pids(const list_t *pids, proc_state state); 45 | extern int cpustat_get_by_proc(proc_info_t *proc, proc_state state); 46 | extern int pagefault_dump_diff(json_object *j_tests, const double duration); 47 | extern void cpustat_init(void); 48 | extern void cpustat_cleanup(void); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /proc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * Author: Colin Ian King 19 | */ 20 | #ifndef __PROC_H__ 21 | #define __PROC_H__ 22 | 23 | #define _GNU_SOURCE 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include "list.h" 30 | 31 | typedef enum { 32 | PROC_START = 0x00000001, 33 | PROC_FINISH = 0x00000002 34 | } proc_state; 35 | 36 | /* process specific information */ 37 | typedef struct proc_info { 38 | pid_t pid; /* PID */ 39 | pid_t ppid; /* Parent PID */ 40 | char *comm; /* Kernel process comm name */ 41 | char *cmdline; /* Process name from cmdline */ 42 | bool is_thread; /* true if process is a thread */ 43 | struct proc_info *next; /* next in hash */ 44 | } proc_info_t; 45 | 46 | extern list_t proc_cache_list; 47 | 48 | extern proc_info_t *proc_cache_add(const pid_t pid, const pid_t ppid, const bool is_thread); 49 | extern proc_info_t *proc_cache_find_by_pid(pid_t pid); 50 | extern int proc_cache_get(void); 51 | extern int proc_cache_get_pthreads(void); 52 | extern void proc_cache_dump(void); 53 | extern int proc_cache_find_by_procname(list_t *pids, const char *procname); 54 | extern int proc_pids_add_proc(list_t *pids, proc_info_t *p); 55 | extern void proc_cache_init(void); 56 | extern void proc_cache_cleanup(void); 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /json.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * Author: Colin Ian King 19 | */ 20 | #ifndef __JSON_SHIM_H__ 21 | #define __JSON_SHIM_H__ 22 | 23 | #ifdef JSON_OUTPUT 24 | 25 | #include 26 | 27 | extern json_object *j_obj_new_array(void); 28 | extern json_object *j_obj_new_obj(void); 29 | extern json_object *j_obj_new_int32(const int32_t i); 30 | extern json_object *j_obj_new_int64(const int64_t i); 31 | extern json_object *j_obj_new_double(const double d); 32 | extern json_object *j_obj_new_string(const char *str); 33 | 34 | static inline void j_obj_obj_add(json_object *parent, const char *label, json_object *obj) 35 | { 36 | json_object_object_add(parent, label, obj); 37 | } 38 | 39 | static inline void j_obj_array_add(json_object *array, json_object *obj) 40 | { 41 | json_object_array_add(array, obj); 42 | } 43 | 44 | static inline void j_obj_new_int32_add(json_object *parent, const char *label, const int32_t i) 45 | { 46 | j_obj_obj_add(parent, label, j_obj_new_int32(i)); 47 | } 48 | 49 | static inline void j_obj_new_int64_add(json_object *parent, const char *label, const int64_t i) 50 | { 51 | j_obj_obj_add(parent, label, j_obj_new_int64(i)); 52 | } 53 | 54 | static inline void j_obj_new_double_add(json_object *parent, const char *label, const double d) 55 | { 56 | j_obj_obj_add(parent, label, j_obj_new_double(d)); 57 | } 58 | 59 | static inline void j_obj_new_string_add(json_object *parent, const char *label, const char *str) 60 | { 61 | j_obj_obj_add(parent, label, j_obj_new_string(str)); 62 | } 63 | 64 | #else 65 | #define json_object void 66 | #endif 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /health-check.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * Author: Colin Ian King 19 | */ 20 | #ifndef __HEALTH_CHECK_H__ 21 | #define __HEALTH_CHECK_H__ 22 | 23 | #define _GNU_SOURCE 24 | 25 | #include 26 | #include 27 | #include "json.h" 28 | 29 | #define OPT_GET_CHILDREN 0x00000001 30 | #define OPT_BRIEF 0x00000002 31 | #define OPT_ADDR_RESOLVE 0x00000004 32 | #define OPT_WAKELOCKS_LIGHT 0x00000008 33 | #define OPT_WAKELOCKS_HEAVY 0x00000010 34 | #define OPT_VERBOSE 0x00000020 35 | #define OPT_FOLLOW_NEW_PROCS 0x00000040 36 | #define OPT_DURATION 0x00000080 37 | 38 | #define FLOAT_TINY (0.0000001) 39 | #define FLOAT_CMP(a, b) (fabs((a) - (b)) < FLOAT_TINY) 40 | 41 | #define _VER_(major, minor, patchlevel) \ 42 | ((major * 10000) + (minor * 100) + patchlevel) 43 | 44 | #if defined(__GNUC__) && defined(__GNUC_MINOR__) 45 | #if defined(__GNUC_PATCHLEVEL__) 46 | #define NEED_GNUC(major, minor, patchlevel) \ 47 | _VER_(major, minor, patchlevel) <= _VER_(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) 48 | #else 49 | #define NEED_GNUC(major, minor, patchlevel) \ 50 | _VER_(major, minor, patchlevel) <= _VER_(__GNUC__, __GNUC_MINOR__, 0) 51 | #endif 52 | #else 53 | #define NEED_GNUC(major, minor, patchlevel) (0) 54 | #endif 55 | 56 | extern void health_check_exit(const int status) __attribute__ ((noreturn)); 57 | extern void health_check_out_of_memory(const char *msg); 58 | extern int pid_max_digits(void); 59 | extern volatile bool keep_running; 60 | extern long int opt_max_syscalls; 61 | extern int opt_flags; 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /timeval.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * Copyright (C) 2021-2025 Colin Ian King 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | * 19 | * Author: Colin Ian King 20 | */ 21 | #define _GNU_SOURCE 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "timeval.h" 28 | 29 | /* 30 | * timeval_to_double() 31 | * convert timeval to seconds as a double 32 | */ 33 | double timeval_to_double(const struct timeval *tv) 34 | { 35 | return (double)tv->tv_sec + ((double)tv->tv_usec / 1000000.0); 36 | } 37 | 38 | /* 39 | * timeval_add() 40 | * timeval a + b 41 | */ 42 | struct timeval timeval_add(const struct timeval *a, const struct timeval *b) 43 | { 44 | struct timeval ret; 45 | 46 | ret.tv_sec = a->tv_sec + b->tv_sec; 47 | ret.tv_usec = a->tv_usec + b->tv_usec; 48 | if (ret.tv_usec > 1000000) { 49 | int nsec = (ret.tv_usec / 1000000); 50 | ret.tv_sec += nsec; 51 | ret.tv_usec -= (1000000ULL * nsec); 52 | } 53 | 54 | return ret; 55 | } 56 | 57 | /* 58 | * timeval_sub() 59 | * timeval a - b 60 | */ 61 | struct timeval timeval_sub( 62 | const struct timeval *a, 63 | const struct timeval *b) 64 | { 65 | struct timeval ret, _b; 66 | 67 | _b.tv_sec = b->tv_sec; 68 | _b.tv_usec = b->tv_usec; 69 | 70 | if (a->tv_usec < _b.tv_usec) { 71 | int nsec = ((_b.tv_usec - a->tv_usec) / 1000000) + 1; 72 | _b.tv_sec += nsec; 73 | _b.tv_usec -= (1000000ULL * nsec); 74 | } 75 | if (a->tv_usec - _b.tv_usec > 1000000) { 76 | int nsec = (a->tv_usec - _b.tv_usec) / 1000000; 77 | _b.tv_sec -= nsec; 78 | _b.tv_usec += (1000000ULL * nsec); 79 | } 80 | 81 | ret.tv_sec = a->tv_sec - _b.tv_sec; 82 | ret.tv_usec = a->tv_usec - _b.tv_usec; 83 | 84 | return ret; 85 | } 86 | -------------------------------------------------------------------------------- /fnotify.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * Author: Colin Ian King 19 | */ 20 | #ifndef __FNOTIFY_H__ 21 | #define __FNOTIFY_H__ 22 | 23 | #define _GNU_SOURCE 24 | 25 | #include 26 | 27 | #include "list.h" 28 | #include "json.h" 29 | 30 | #if defined(__aarch64__) 31 | #define FNOTIFY_SUPPORTED 0 32 | #else 33 | #define FNOTIFY_SUPPORTED 1 34 | #endif 35 | 36 | /* fnotify file information per process */ 37 | typedef struct { 38 | proc_info_t *proc; /* Proc specific info */ 39 | char *filename; /* Name of device or filename being accessed */ 40 | unsigned int mask; /* fnotify access mask */ 41 | uint64_t count; /* Count of accesses */ 42 | } fnotify_fileinfo_t; 43 | 44 | /* fnotify wakelock accounting */ 45 | typedef struct { 46 | proc_info_t *proc; /* Proc specific info */ 47 | uint64_t locked; /* Count of wake locks */ 48 | uint64_t unlocked; /* Count of wake unlocks */ 49 | uint64_t total; /* Total of wake locks and unlocks */ 50 | } fnotify_wakelock_info_t; 51 | 52 | /* fnotify I/O operations counts per process */ 53 | typedef struct { 54 | uint64_t open_total; /* open() count */ 55 | uint64_t close_total; /* close() count */ 56 | uint64_t read_total; /* read() count */ 57 | uint64_t write_total; /* write() count */ 58 | uint64_t total; /* total count */ 59 | proc_info_t *proc; /* process information */ 60 | } io_ops_t; 61 | 62 | extern int fnotify_event_init(void); 63 | extern int fnotify_event_add(const list_t *pids, const struct fanotify_event_metadata *metadata); 64 | extern void fnotify_dump_events(json_object *j_tests, const double duration, const list_t *pids); 65 | extern void fnotify_dump_wakelocks(json_object *j_tests, const double duration); 66 | extern char *fnotify_get_filename(const pid_t pid, const int fd); 67 | 68 | extern void fnotify_init(void); 69 | extern void fnotify_cleanup(void); 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /json.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * Copyright (C) 2021-2025 Colin Ian King 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | * 19 | * Author: Colin Ian King 20 | */ 21 | #define _GNU_SOURCE 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "json.h" 30 | #include "health-check.h" 31 | 32 | /* 33 | * Older versions json-c don't have int64 34 | */ 35 | extern json_object *json_object_new_int64(const int64_t) __attribute__((weak)); 36 | 37 | static inline void j_obj_is_null(const json_object *obj, const char *msg) 38 | { 39 | if (!obj) 40 | health_check_out_of_memory(msg); 41 | } 42 | 43 | json_object *j_obj_new_array(void) 44 | { 45 | json_object *obj = json_object_new_array(); 46 | 47 | j_obj_is_null(obj, "cannot allocate JSON array"); 48 | return obj; 49 | } 50 | 51 | json_object *j_obj_new_obj(void) 52 | { 53 | json_object *obj = json_object_new_object(); 54 | 55 | j_obj_is_null(obj, "cannot allocate JSON object"); 56 | return obj; 57 | } 58 | 59 | json_object *j_obj_new_int32(const int32_t i) 60 | { 61 | json_object *obj = json_object_new_int(i); 62 | 63 | j_obj_is_null(obj, "cannot allocate JSON integer"); 64 | return obj; 65 | } 66 | 67 | json_object *j_obj_new_int64(const int64_t i) 68 | { 69 | json_object *obj = NULL; 70 | 71 | if (json_object_new_int64) { 72 | obj = json_object_new_int64(i); 73 | } else { 74 | /* Older json-c doesn't have int64, so convert to double */ 75 | obj = json_object_new_double((double)i); 76 | } 77 | j_obj_is_null(obj, "cannot allocate JSON integer"); 78 | return obj; 79 | } 80 | 81 | json_object *j_obj_new_double(const double d) 82 | { 83 | json_object *obj = json_object_new_double(d); 84 | 85 | j_obj_is_null(obj, "cannot allocate JSON double"); 86 | return obj; 87 | } 88 | 89 | json_object *j_obj_new_string(const char *str) 90 | { 91 | json_object *obj = json_object_new_string(str); 92 | 93 | j_obj_is_null(obj, "cannot allocate JSON string"); 94 | return obj; 95 | } 96 | -------------------------------------------------------------------------------- /mem.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * Author: Colin Ian King 19 | */ 20 | #ifndef __MEM_H__ 21 | #define __MEM_H__ 22 | 23 | #define _GNU_SOURCE 24 | 25 | #include "proc.h" 26 | #include "list.h" 27 | #include "json.h" 28 | 29 | #include 30 | 31 | typedef enum { 32 | MEM_STACK = 0, 33 | MEM_HEAP, 34 | MEM_MAPPED, 35 | MEM_MAX, 36 | } mem_type_t; 37 | 38 | /* wakeup event information per process */ 39 | typedef struct mem_info_t { 40 | proc_info_t *proc; /* Proc specific info */ 41 | int64_t size[MEM_MAX]; /* region size */ 42 | int64_t rss[MEM_MAX]; /* RSS size */ 43 | int64_t pss[MEM_MAX]; /* PSS size */ 44 | int64_t total[MEM_MAX]; /* total size */ 45 | int64_t grand_total; /* grand total of same mem types */ 46 | } mem_info_t; 47 | 48 | typedef struct { 49 | pid_t pid; /* process id */ 50 | const void *brk_start; /* start of brk location */ 51 | const void *brk_current; /* current brk location */ 52 | uint64_t brk_count; /* brk calls made */ 53 | } mem_brk_info_t; 54 | 55 | typedef struct { 56 | pid_t pid; /* process id */ 57 | uint64_t mmap_length; /* processes' total mmap region size */ 58 | uint64_t mmap_count; /* number of mmaps made */ 59 | uint64_t munmap_length; /* processes' total unmap region size */ 60 | uint64_t munmap_count; /* number of unmaps made */ 61 | } mem_mmap_info_t; 62 | 63 | extern void mem_init(void); 64 | extern void mem_cleanup(void); 65 | extern int mem_get_all_pids(const list_t *pids, const proc_state state); 66 | extern int mem_get_by_proc(proc_info_t *p, const proc_state state); 67 | extern int mem_brk_account(const pid_t pid, const void *addr); 68 | extern void mem_dump_brk(json_object *j_tests, const double duration); 69 | extern int mem_mmap_account(const pid_t pid, size_t length, bool mmap); 70 | extern void mem_dump_mmap(json_object *j_tests, const double duration); 71 | 72 | extern void mem_get(const list_t *pids, list_t *mem); 73 | extern int mem_dump_diff(json_object *j_tests, const double duration); 74 | 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /list.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * Copyright (C) 2021-2025 Colin Ian King 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | * 19 | * Author: Colin Ian King 20 | */ 21 | #define _GNU_SOURCE 22 | 23 | #include 24 | #include 25 | #include "list.h" 26 | 27 | /* 28 | * list_init() 29 | * initialize list 30 | */ 31 | void list_init(list_t *list) 32 | { 33 | list->head = NULL; 34 | list->tail = NULL; 35 | list->length = 0; 36 | } 37 | 38 | /* 39 | * list_append() 40 | * add a new item to end of the list 41 | */ 42 | link_t *list_append(list_t *list, void *data) 43 | { 44 | link_t *link; 45 | 46 | if ((link = calloc(1, sizeof(link_t))) == NULL) { 47 | fprintf(stderr, "Cannot allocate list link.\n"); 48 | return NULL; 49 | } 50 | link->data = data; 51 | if (list->head == NULL) { 52 | list->head = link; 53 | } else { 54 | list->tail->next = link; 55 | } 56 | list->tail = link; 57 | list->length++; 58 | 59 | return link; 60 | } 61 | 62 | 63 | /* 64 | * list_add_ordered() 65 | * add new data into list, based on order from callback func compare(). 66 | */ 67 | link_t *list_add_ordered( 68 | list_t *list, 69 | void *new_data, 70 | const list_comp_t compare) 71 | { 72 | link_t *link, **l; 73 | 74 | if ((link = calloc(1, sizeof(link_t))) == NULL) 75 | return NULL; 76 | 77 | link->data = new_data; 78 | 79 | for (l = &list->head; *l; l = &(*l)->next) { 80 | void *data = (void *)(*l)->data; 81 | if (compare(data, new_data) >= 0) { 82 | link->next = (*l); 83 | break; 84 | } 85 | } 86 | if (!link->next) 87 | list->tail = link; 88 | 89 | *l = link; 90 | list->length++; 91 | 92 | return link; 93 | } 94 | 95 | /* 96 | * list_free() 97 | * free the list 98 | */ 99 | void list_free( 100 | list_t *list, 101 | const list_link_free_t freefunc) 102 | { 103 | link_t *link, *next; 104 | 105 | if (list == NULL) 106 | return; 107 | 108 | for (link = list->head; link; link = next) { 109 | next = link->next; 110 | if (link->data && freefunc) 111 | freefunc(link->data); 112 | free(link); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /health-check.8: -------------------------------------------------------------------------------- 1 | .\" Hey, EMACS: -*- nroff -*- 2 | .\" First parameter, NAME, should be all caps 3 | .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection 4 | .\" other parameters are allowed: see man(7), man(1) 5 | .TH HEALTH-CHECK 8 "12 January 2024" 6 | .\" Please adjust this date whenever revising the manpage. 7 | .\" 8 | .\" Some roff macros, for reference: 9 | .\" .nh disable hyphenation 10 | .\" .hy enable hyphenation 11 | .\" .ad l left justify 12 | .\" .ad b justify to both left and right margins 13 | .\" .nf disable filling 14 | .\" .fi enable filling 15 | .\" .br insert line break 16 | .\" .sp insert n+1 empty lines 17 | .\" for manpage-specific macros, see man(7) 18 | .SH NAME 19 | health-check \- a tool to measure system events. 20 | .br 21 | 22 | .SH SYNOPSIS 23 | .B health-check 24 | .RI [options] 25 | .br 26 | 27 | .SH DESCRIPTION 28 | Health-check monitors a process and optionally their child 29 | processes and threads for a given amount of time. At the end 30 | of the monitoring it will display the CPU time used, wakeup 31 | events generated and I/O operations of the given processes. 32 | It can be used to diagnose unhealthy badly behaving processes. 33 | .SH OPTIONS 34 | health-check options are as follow: 35 | .TP 36 | .B \-h 37 | Show help 38 | .TP 39 | .B \-b 40 | Brief (terse) output for quick overview. 41 | .TP 42 | .B \-c 43 | Find and monitor all child and threads of a given set of processes. This 44 | option is only useful when attaching to already running processes using 45 | the \-p option. 46 | .TP 47 | .B \-d 48 | Specify analysis duration in seconds. Default is 60 seconds. A duration 49 | of 0 will make health-check run forever, or until the monitored process 50 | exits. 51 | .TP 52 | .B \-f 53 | Follow fork, vfork and clone system calls. 54 | .TP 55 | .B \-p pid[,pid] 56 | Specify which processes to analyse. Can be process ID or process name. 57 | .TP 58 | .B \-r 59 | Resolve IP addresses, this can take some time, hence it is an opt-in 60 | feature. 61 | .TP 62 | .B \-m max 63 | Specify maximum number of timeout blocking system calls are logged 64 | before completing. This is useful with very busy processes that can 65 | generate tens of thousands of ptrace events that have to be logged by 66 | health-check. The default is 1 million. 67 | .TP 68 | .B \-o logfile 69 | Specify output log file to export JSON formatted results. The resulting 70 | data can be then easily imported and analysed using JSON parsing tools. 71 | .TP 72 | .B \-u username 73 | Run command as the specified user. This cannot be used with the \-p option. 74 | .TP 75 | .B \-v verbose 76 | Enable verbose mode (currently just for \-W wakelock option). Not compatible 77 | with the \-b brief option. 78 | .TP 79 | .B \-w 80 | This uses fnotify to count the number of wakelock lock/unlocks. Lightweight 81 | and simple wakelock monitoring. 82 | .TP 83 | .B \-W 84 | This does deeper system call inspection to monitor wakelock usage and uses 85 | up more run time processing to perform the inspection. 86 | .SH AUTHOR 87 | health-check was written by Colin Ian King 88 | .PP 89 | This manual page was written by Colin Ian King, 90 | for the Ubuntu project (but may be used by others). 91 | .SH COPYRIGHT 92 | Copyright \(co 2014-2021 Canonical Ltd, Copyright \(co 2021-2025 Colin Ian King 93 | .br 94 | This is free software; see the source for copying conditions. There is NO 95 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 96 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2013-2021 Canonical, Ltd. 3 | # Copyright (C) 2021-2025 Colin Ian King. 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | VERSION=0.04.01 20 | # 21 | # Codename "Where have all my cycles gone?" 22 | # 23 | 24 | JSON_OUTPUT=y 25 | 26 | CFLAGS += -Wall -Wextra -DVERSION='"$(VERSION)"' -O2 -g 27 | 28 | # 29 | # Pedantic flags 30 | # 31 | ifeq ($(PEDANTIC),1) 32 | CFLAGS += -Wabi -Wcast-qual -Wfloat-equal -Wmissing-declarations \ 33 | -Wmissing-format-attribute -Wno-long-long -Wpacked \ 34 | -Wredundant-decls -Wshadow -Wno-missing-field-initializers \ 35 | -Wno-missing-braces -Wno-sign-compare -Wno-multichar 36 | endif 37 | 38 | LDFLAGS += -lpthread -lbsd 39 | ifeq ($(JSON_OUTPUT),y) 40 | LDFLAGS += -ljson-c 41 | CFLAGS += -DJSON_OUTPUT 42 | endif 43 | ifeq ($(FNOTIFY),y) 44 | CFLAGS += -DFNOTIFY 45 | endif 46 | 47 | BINDIR=/usr/bin 48 | MANDIR=/usr/share/man/man8 49 | BASHDIR=/usr/share/bash-completion/completions 50 | 51 | OBJS = list.o pid.o proc.o net.o syscall.o timeval.o \ 52 | fnotify.o cpustat.o mem.o ctxt-switch.o health-check.o 53 | ifeq ($(JSON_OUTPUT),y) 54 | OBJS += json.o 55 | endif 56 | 57 | health-check: $(OBJS) Makefile 58 | $(CC) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS) 59 | 60 | health-check.8.gz: health-check.8 61 | gzip -c $< > $@ 62 | 63 | cpustat.o: cpustat.c list.h json.h cpustat.h timeval.h health-check.h 64 | 65 | ctxt-switch.o: ctxt-switch.c list.h json.h ctxt-switch.h health-check.h 66 | 67 | fnotify.o: fnotify.c fnotify.h list.h json.h proc.h health-check.h 68 | 69 | health-check.o: health-check.c list.h json.h pid.h proc.h syscall.h timeval.h \ 70 | fnotify.h cpustat.h mem.h net.h ctxt-switch.h 71 | 72 | json.o: json.c json.h health-check.h 73 | 74 | list.o: list.c list.h 75 | 76 | mem.o: mem.c mem.h list.h health-check.h 77 | 78 | net.o: net.c net.h list.h proc.h json.h health-check.h 79 | 80 | pid.o: pid.c pid.h list.h proc.h 81 | 82 | proc.o: proc.c list.h pid.h proc.h net.h health-check.h 83 | 84 | syscall.o: syscall.c syscall.h proc.h json.h net.h mem.h \ 85 | cpustat.h fnotify.h ctxt-switch.h health-check.h 86 | 87 | timeval.o: timeval.c timeval.h 88 | 89 | dist: 90 | rm -rf health-check-$(VERSION) 91 | mkdir health-check-$(VERSION) 92 | cp -rp Makefile *.c *.h .travis.yml scripts health-check.8 \ 93 | bash-completion COPYING README.md health-check-$(VERSION) 94 | tar -Jcf health-check-$(VERSION).tar.xz health-check-$(VERSION) 95 | rm -rf health-check-$(VERSION) 96 | 97 | clean: 98 | rm -f health-check health-check.o health-check.8.gz 99 | rm -f health-check-$(VERSION).tar.xz 100 | rm -f $(OBJS) 101 | 102 | install: health-check health-check.8.gz 103 | mkdir -p ${DESTDIR}${BINDIR} 104 | cp health-check ${DESTDIR}${BINDIR} 105 | mkdir -p ${DESTDIR}${MANDIR} 106 | cp health-check.8.gz ${DESTDIR}${MANDIR} 107 | mkdir -p ${DESTDIR}${BASHDIR} 108 | cp bash-completion/health-check ${DESTDIR}${BASHDIR} 109 | -------------------------------------------------------------------------------- /pid.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * Copyright (C) 2021-2025 Colin Ian King 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | * 19 | * Author: Colin Ian King 20 | */ 21 | #define _GNU_SOURCE 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "pid.h" 35 | #include "list.h" 36 | #include "proc.h" 37 | 38 | /* 39 | * get_pid_comm 40 | * 41 | */ 42 | char *get_pid_comm(const pid_t pid) 43 | { 44 | char buffer[4096]; 45 | int fd; 46 | ssize_t ret; 47 | 48 | snprintf(buffer, sizeof(buffer), "/proc/%i/comm", pid); 49 | 50 | if ((fd = open(buffer, O_RDONLY)) < 0) 51 | return NULL; 52 | 53 | if ((ret = read(fd, buffer, sizeof(buffer))) <= 0) { 54 | (void)close(fd); 55 | return NULL; 56 | } 57 | (void)close(fd); 58 | buffer[ret-1] = '\0'; 59 | 60 | return strdup(buffer); 61 | } 62 | 63 | /* 64 | * get_pid_cmdline 65 | * get process's /proc/pid/cmdline 66 | */ 67 | char *get_pid_cmdline(const pid_t pid) 68 | { 69 | char buffer[4096]; 70 | char *ptr; 71 | int fd; 72 | ssize_t ret; 73 | 74 | snprintf(buffer, sizeof(buffer), "/proc/%i/cmdline", pid); 75 | 76 | if ((fd = open(buffer, O_RDONLY)) < 0) 77 | return NULL; 78 | 79 | if ((ret = read(fd, buffer, sizeof(buffer))) <= 0) { 80 | (void)close(fd); 81 | return NULL; 82 | } 83 | (void)close(fd); 84 | 85 | if (ret >= (ssize_t)sizeof(buffer)) 86 | ret = sizeof(buffer) - 1; 87 | buffer[ret] = '\0'; 88 | 89 | for (ptr = buffer; *ptr && (ptr < buffer + ret); ptr++) { 90 | if (*ptr == ' ') 91 | *ptr = '\0'; 92 | } 93 | 94 | ptr = basename(buffer); 95 | if (ptr) 96 | return strdup(ptr); 97 | 98 | return strdup(buffer); 99 | } 100 | 101 | /* 102 | * pid_exists() 103 | * true if given process with given pid exists 104 | */ 105 | bool pid_exists(const pid_t pid) 106 | { 107 | char path[PATH_MAX]; 108 | struct stat statbuf; 109 | 110 | snprintf(path, sizeof(path), "/proc/%i", pid); 111 | return stat(path, &statbuf) == 0; 112 | } 113 | 114 | /* 115 | * pid_list_find() 116 | * find a pid in the pid list 117 | */ 118 | bool pid_list_find( 119 | const pid_t pid, 120 | list_t *list) 121 | { 122 | link_t *l; 123 | 124 | for (l = list->head; l; l = l->next) { 125 | proc_info_t *p = (proc_info_t*)l->data; 126 | if (p->pid == pid) 127 | return true; 128 | } 129 | return false; 130 | } 131 | 132 | /* 133 | * pid_get_children() 134 | * get all the children from the given pid, add 135 | * to children list 136 | */ 137 | static int pid_get_children( 138 | const pid_t pid, 139 | list_t *children) 140 | { 141 | link_t *l; 142 | 143 | for (l = proc_cache_list.head; l; l = l->next) { 144 | proc_info_t *p = (proc_info_t*)l->data; 145 | if (p->ppid == pid) { 146 | if (list_append(children, p) == NULL) { 147 | return -1; 148 | } 149 | pid_get_children(p->pid, children); 150 | } 151 | } 152 | return 0; 153 | } 154 | 155 | /* 156 | * pid_list_get_children() 157 | * get all the chindren in the given pid list 158 | * and add this to the list 159 | */ 160 | int pid_list_get_children(list_t *pids) 161 | { 162 | link_t *l; 163 | list_t children; 164 | proc_info_t *p; 165 | 166 | list_init(&children); 167 | 168 | for (l = pids->head; l; l = l->next) { 169 | p = (proc_info_t *)l->data; 170 | if (pid_get_children(p->pid, &children) < 0) 171 | return -1; 172 | } 173 | 174 | /* Append the children onto the pid list */ 175 | for (l = children.head; l; l = l->next) { 176 | p = (proc_info_t *)l->data; 177 | if (!pid_list_find(p->pid, pids)) 178 | if (list_append(pids, p) == NULL) 179 | return -1; 180 | } 181 | 182 | /* Free the children list, not the data */ 183 | list_free(&children, NULL); 184 | 185 | return 0; 186 | } 187 | -------------------------------------------------------------------------------- /scripts/health-check-test-pid.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # 3 | # 4 | # Copyright (C) 2013-2014 Canonical 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; either version 2 9 | # of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | # 20 | # 21 | # Syntax: 22 | # health-check-test-pid.py pid 23 | # 24 | # The process name is resolved and the tool will use a `procname`.threshold file 25 | # to compare against. If this file does not exist, default.threshold is used. 26 | # 27 | import sys, os, json, psutil 28 | 29 | # 30 | # Processes we don't want to run health-check on 31 | # 32 | ignore_procs = [ 'health-check', 'sh', 'init', 'cat', 'vi', 'emacs', 'getty', 'csh', 'bash' ] 33 | 34 | # 35 | # Default test run durations in seconds 36 | # 37 | default_duration = 60 38 | 39 | # 40 | # Parse thresholds file: 41 | # lines starting with '#' are comments 42 | # format is: key value, e.g. 43 | # health-check.cpu-load.cpu-load-total.total-cpu-percent 0.5 44 | # health-check.cpu-load.cpu-load-total.user-cpu-percent 0.5 45 | # health-check.cpu-load.cpu-load-total.system-cpu-percent 0.5 46 | # 47 | def read_threshold(procname): 48 | filename = procname + ".threshold" 49 | thresholds = { } 50 | n = 0 51 | 52 | try: 53 | with open(filename) as file: 54 | for line in file: 55 | n = n + 1 56 | if len(line) > 1 and not line.startswith("#"): 57 | tmp = line.split() 58 | if len(tmp) == 2: 59 | thresholds[tmp[0]] = tmp[1] 60 | #sys.stderr.write(tmp[0] + " : " + tmp[1] + "\n") 61 | else: 62 | sys.stderr.write("Threshold file " + filename + " line " + str(n) + " format error.\n") 63 | except: 64 | pass 65 | #sys.stderr.write("Cannot process threshold file " + filename + "\n"); 66 | 67 | return thresholds 68 | 69 | # 70 | # Locate a threshold in the JSON data, compare it to the given threshold 71 | # 72 | def check_threshold(data, key, fullkey, threshold): 73 | try: 74 | d = data[key[0]] 75 | except: 76 | sys.stderr.write("health-check JSON data does not have key " + fullkey + "\n") 77 | return (True, "Attribute not found and ignored") 78 | 79 | key = key[1:] 80 | if len(key) > 0: 81 | return check_threshold(d, key, fullkey, threshold) 82 | else: 83 | val = float(d) 84 | if threshold >= val: 85 | cmp = str(threshold) + " >= " + str(val) 86 | return (True, cmp) 87 | else: 88 | cmp = str(threshold) + " < " + str(val) 89 | return (False, cmp) 90 | 91 | def check_thresholds(procname, data, thresholds): 92 | print "process: " + procname 93 | failed = False 94 | for key in thresholds.keys(): 95 | if key.startswith("health-check"): 96 | (ret, str) = check_threshold(data, key.split('.'), key, float(thresholds[key])) 97 | if ret: 98 | msg = "PASSED" 99 | else: 100 | msg = "FAILED" 101 | failed = True 102 | 103 | sys.stderr.write(msg + ": " + str + ": " + key + "\n") 104 | 105 | return failed 106 | 107 | # 108 | # run health-check on a given process 109 | # 110 | def health_check(pid, procname): 111 | thresholds = read_threshold(procname) 112 | # 113 | # Can't test without thresholds 114 | # 115 | if len(thresholds) == 0: 116 | thresholds = read_threshold("default") 117 | if len(thresholds) == 0: 118 | sys.stderr.write("No thresholds for process " + procname + "\n") 119 | else: 120 | sys.stderr.write("Using default thresholds for process " + procname + "\n") 121 | 122 | duration = default_duration 123 | 124 | if 'duration' in thresholds: 125 | duration = int(thresholds['duration']) 126 | 127 | filename = "/tmp/health-check-" + str(pid) + ".log" 128 | cmd = "health-check -c -f -d " + str(duration) + " -w -W -r -p " + str(pid) + " -o " + filename + " > /dev/null" 129 | 130 | try: 131 | os.system(cmd) 132 | except: 133 | sys.stderr.write("Failed to run " + cmd + "\n"); 134 | os._exit(1) 135 | 136 | try: 137 | f = open(filename, 'r') 138 | data = json.load(f) 139 | f.close() 140 | except: 141 | sys.syderr.write("Failed to open JSON file " + filename + "\n"); 142 | os._exit(1) 143 | 144 | check_thresholds(procname, data, thresholds) 145 | 146 | 147 | 148 | # 149 | # Start here! 150 | # 151 | if len(sys.argv) < 2: 152 | sys.stderr.write("Usage: " + sys.argv[0] + " PID\n") 153 | os._exit(1) 154 | 155 | pid = int(sys.argv[1]) 156 | 157 | try: 158 | p = psutil.Process(pid) 159 | except: 160 | sys.stderr.write("Cannot find process with PID " + str(pid) + "\n") 161 | os._exit(1) 162 | 163 | try: 164 | pgid = os.getpgid(pid) 165 | except: 166 | sys.stderr.write("Cannot find pgid on process with PID " + str(pid) + "\n") 167 | os._exit(1) 168 | 169 | if pgid == 0: 170 | sys.stderr.write("Cannot run health-check on kernel task with PID " + str(pid) + "\n") 171 | os._exit(1) 172 | 173 | try: 174 | procname = os.path.basename(p.name) 175 | if p.name in ignore_procs: 176 | sys.stderr.write("Cannot run health-check on process " + procname + "\n") 177 | os._exit(1) 178 | else: 179 | # 180 | # Did it fail? 181 | # 182 | if (health_check(pid, procname)): 183 | os._exit(1) 184 | else: 185 | os._exit(0) 186 | except: 187 | sys.stderr.write("An execption occurred, failed to test on PID " + str(pid) + "\n") 188 | sys.exit(1) 189 | -------------------------------------------------------------------------------- /syscall.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * Author: Colin Ian King 19 | */ 20 | #ifndef __SYSCALL_H__ 21 | #define __SYSCALL_H__ 22 | 23 | #define _GNU_SOURCE 24 | 25 | #include 26 | 27 | #if (( defined(__x86_64__) && defined(__LP64__) ) || \ 28 | defined(__i386__) || \ 29 | defined(__arm__) || \ 30 | defined(__powerpc__) || \ 31 | defined(__aarch64__)) 32 | #define SYSCALL_SUPPORTED 1 33 | #else 34 | #define SYSCALL_SUPPORTED 0 35 | #endif 36 | 37 | #include "proc.h" 38 | #include "list.h" 39 | #include "json.h" 40 | 41 | #define MAX_BUCKET (9) 42 | #define BUCKET_START (0.00001) 43 | 44 | #define SYSCALL(n) \ 45 | [SYS_ ## n] = { #n, SYS_ ## n, 0, NULL, NULL, NULL, NULL, NULL } 46 | 47 | #define SYSCALL_CHK_TIMEOUT(n, arg, func_check, func_ret) \ 48 | [SYS_ ## n] = { #n, SYS_ ## n, arg, &syscall_timeout[SYS_ ## n], func_check, func_ret, NULL, NULL } 49 | 50 | #define SYSCALL_CHK(n, arg, func_check, func_ret) \ 51 | [SYS_ ## n] = { #n, SYS_ ## n, arg, &syscall_timeout[SYS_ ## n], NULL, NULL, func_check, func_ret } 52 | 53 | #define TIMEOUT(n, timeout) \ 54 | [SYS_ ## n] = timeout 55 | 56 | /* Stash timeout related syscall return value */ 57 | typedef struct { 58 | double timeout; /* syscall timeout in seconds */ 59 | int ret; /* syscall return */ 60 | } syscall_return_info_t; 61 | 62 | /* Syscall polling stats for a particular process */ 63 | typedef struct syscall_info { 64 | proc_info_t *proc; /* process info */ 65 | int syscall; /* system call number */ 66 | uint64_t count; /* number times call has been made */ 67 | uint64_t usecs_total; /* total number of uSecs in system call */ 68 | struct timeval usec_enter; /* Time when a syscall was entered */ 69 | struct timeval usec_return; /* Time when a syscall was exited */ 70 | double poll_min; /* minimum poll time */ 71 | double poll_max; /* maximum poll time */ 72 | double poll_total; /* sum of non zero or negative poll times */ 73 | uint64_t poll_count; /* number of polls */ 74 | uint64_t poll_too_low; /* number of poll times below a threshold */ 75 | uint64_t poll_infinite; /* number of -ve (infinite) poll times */ 76 | uint64_t poll_zero; /* number of zero poll times */ 77 | uint64_t bucket[MAX_BUCKET]; /* bucket count of poll times */ 78 | list_t return_history; /* history system call returns */ 79 | struct syscall_info *next; 80 | } syscall_info_t; 81 | 82 | typedef struct syscall syscall_t; 83 | 84 | typedef void (*call_enter_timeout_t)(const syscall_t *sc, syscall_info_t *s, const pid_t pid, const double threshold, double *timeout); 85 | typedef void (*call_return_timeout_t)(json_object *j_tests, const syscall_t *sc, const syscall_info_t *s); 86 | 87 | typedef void (*call_enter_t)(const syscall_t *sc, const syscall_info_t *s, const pid_t pid); 88 | typedef void (*call_return_t)(const syscall_t *sc, const syscall_info_t *s, const int ret); 89 | 90 | /* syscall specific information */ 91 | struct syscall { 92 | char *name; /* name of the syscall */ 93 | int syscall; /* system call number */ 94 | int arg; /* nth arg to check for timeout value (1st arg is zero) */ 95 | double *threshold; /* threshold - points to timeout array items indexed by syscall */ 96 | call_enter_timeout_t call_enter_timeout; /* timeout checking function, NULL means don't check */ 97 | call_return_timeout_t call_return_timeout; /* return checking function, NULL means don't check */ 98 | call_enter_t call_enter; /* non-timout call checking function, NULL means don't check */ 99 | call_return_t call_return; /* non-timeout return checking function, NULL means don't check */ 100 | }; 101 | 102 | /* fd cache */ 103 | typedef struct fd_cache { 104 | pid_t pid; /* process */ 105 | int fd; /* file descriptor */ 106 | char *filename; /* filename, NULL if closed */ 107 | pthread_mutex_t mutex; /* mutex on filename */ 108 | struct fd_cache *next; /* next one in cache */ 109 | } fd_cache_t; 110 | 111 | typedef struct syscall_wakelock_info { 112 | pid_t pid; /* process */ 113 | bool locked; /* true = lock, false = unlock */ 114 | struct timeval tv; /* when locked/unlocked */ 115 | char *lockname; /* wake lock name */ 116 | struct syscall_wakelock_info *paired; /* ptr to lock/unlock pair */ 117 | } syscall_wakelock_info_t; 118 | 119 | typedef struct { 120 | pid_t pid; /* process */ 121 | uint64_t sync_count; /* sync syscall count */ 122 | uint64_t fsync_count; /* fsync syscall count */ 123 | uint64_t fdatasync_count;/* fdatasync syscall count */ 124 | uint64_t syncfs_count; /* syncfs syscall count */ 125 | uint64_t total_count; /* total count */ 126 | list_t sync_file; /* list of files that were sync'd */ 127 | } syscall_sync_info_t; 128 | 129 | typedef struct { 130 | char *filename; /* name of file being sync'd */ 131 | uint64_t count; /* count of sync calls */ 132 | int syscall; /* specific sync syscall being used */ 133 | } syscall_sync_file_t; 134 | 135 | /* sycall states */ 136 | #define SYSCALL_CTX_ALIVE 0x00000001 137 | #define SYSCALL_CTX_ATTACHED 0x00000002 138 | 139 | typedef struct syscall_context { 140 | pid_t pid; /* process */ 141 | proc_info_t *proc; /* proc info */ 142 | int syscall; /* syscall detected */ 143 | double timeout; /* timeout on poll syscalls */ 144 | syscall_info_t *syscall_info; /* syscall accounting */ 145 | int state; /* is traced thread alive and/or attached? */ 146 | struct syscall_context *next; 147 | } syscall_context_t; 148 | 149 | typedef struct socket_info { 150 | pid_t pid; /* process id */ 151 | int fd; /* socket fd */ 152 | bool send; /* true = send, false = recv */ 153 | uint64_t count; /* number of calls */ 154 | uint64_t total; /* number of bytes tx/rx */ 155 | struct socket_info *next; 156 | } socket_info_t; 157 | 158 | typedef struct filename_info { 159 | int syscall; /* syscall number */ 160 | pid_t pid; /* process id */ 161 | proc_info_t *proc; /* proc info */ 162 | char *filename; /* file being inotify_added */ 163 | uint64_t count; /* number of times accessed */ 164 | struct filename_info *next; 165 | } filename_info_t; 166 | 167 | extern int procs_traced; 168 | extern syscall_t syscalls[]; 169 | extern size_t syscalls_len; 170 | 171 | extern void *syscall_trace(void *arg); 172 | extern void syscall_dump_hashtable(json_object *j_tests, const double duration); 173 | extern void syscall_dump_pollers(json_object *j_tests, const double duration); 174 | extern void syscall_init(void); 175 | extern void syscall_cleanup(void); 176 | extern void syscall_dump_wakelocks(json_object *j_tests, const double duration, list_t *pids); 177 | extern void syscall_dump_sync(json_object *j_tests, double duration); 178 | extern void syscall_dump_inotify(json_object *j_obj, double duration); 179 | extern void syscall_dump_execve(json_object *j_obj, double duration); 180 | extern int syscall_trace_proc(list_t *pids); 181 | extern int syscall_stop(void); 182 | 183 | #endif 184 | -------------------------------------------------------------------------------- /proc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * Copyright (C) 2021-2025 Colin Ian King 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | * 19 | * Author: Colin Ian King 20 | */ 21 | #define _GNU_SOURCE 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "list.h" 33 | #include "pid.h" 34 | #include "proc.h" 35 | #include "net.h" 36 | #include "health-check.h" 37 | 38 | #define HASH_TABLE_SIZE (1997) 39 | 40 | list_t proc_cache_list; 41 | static proc_info_t *proc_cache_hash[HASH_TABLE_SIZE]; 42 | static pthread_mutex_t pids_mutex = PTHREAD_MUTEX_INITIALIZER; 43 | static pthread_mutex_t proc_cache_mutex = PTHREAD_MUTEX_INITIALIZER; 44 | 45 | /* 46 | * proc_cache_hash_pid() 47 | * hash a process id 48 | */ 49 | static inline unsigned long proc_cache_hash_pid(const pid_t pid) 50 | { 51 | unsigned long h = (unsigned long)pid; 52 | 53 | return h % HASH_TABLE_SIZE; 54 | } 55 | 56 | /* 57 | * proc_cache_add_at_hash_index() 58 | * heler function to add proc info to the proc cache and list 59 | */ 60 | static proc_info_t *proc_cache_add_at_hash_index( 61 | const unsigned long h, 62 | const pid_t pid, 63 | const pid_t ppid, 64 | const bool is_thread) 65 | { 66 | proc_info_t *p; 67 | 68 | if ((p = calloc(1, sizeof(*p))) == NULL) { 69 | health_check_out_of_memory("allocating proc cache"); 70 | return NULL; 71 | } 72 | 73 | p->pid = pid; 74 | p->ppid = ppid; 75 | p->cmdline = get_pid_cmdline(pid); 76 | p->comm = get_pid_comm(pid); 77 | p->is_thread = is_thread; 78 | 79 | pthread_mutex_lock(&proc_cache_mutex); 80 | if (list_append(&proc_cache_list, p) == NULL) { 81 | pthread_mutex_unlock(&proc_cache_mutex); 82 | free(p->cmdline); 83 | free(p); 84 | return NULL; 85 | } 86 | p->next = proc_cache_hash[h]; 87 | proc_cache_hash[h] = p; 88 | pthread_mutex_unlock(&proc_cache_mutex); 89 | 90 | return p; 91 | } 92 | 93 | /* 94 | * proc_cache_add() 95 | * explicity add process info to global cache ONLY if it is a traceable process 96 | */ 97 | proc_info_t *proc_cache_add(const pid_t pid, const pid_t ppid, const bool is_thread) 98 | { 99 | proc_info_t *p; 100 | unsigned long h; 101 | 102 | if (!pid_exists(pid) || (pid == getpid())) 103 | return NULL; 104 | 105 | pthread_mutex_lock(&proc_cache_mutex); 106 | h = proc_cache_hash_pid(pid); 107 | for (p = proc_cache_hash[h]; p; p = p->next) { 108 | if (p->pid == pid) { 109 | pthread_mutex_unlock(&proc_cache_mutex); 110 | return p; 111 | } 112 | } 113 | pthread_mutex_unlock(&proc_cache_mutex); 114 | 115 | return proc_cache_add_at_hash_index(h, pid, ppid, is_thread); 116 | } 117 | 118 | /* 119 | * proc_cache_find_by_pid() 120 | * find process info by the process id, if it is not found 121 | * and it is a traceable process then cache it 122 | */ 123 | proc_info_t *proc_cache_find_by_pid(const pid_t pid) 124 | { 125 | unsigned long h; 126 | proc_info_t *p; 127 | 128 | pthread_mutex_lock(&proc_cache_mutex); 129 | h = proc_cache_hash_pid(pid); 130 | for (p = proc_cache_hash[h]; p; p = p->next) { 131 | if (p->pid == pid) { 132 | pthread_mutex_unlock(&proc_cache_mutex); 133 | return p; 134 | } 135 | } 136 | pthread_mutex_unlock(&proc_cache_mutex); 137 | 138 | /* 139 | * Not found, so add it and return it if it is a legitimate 140 | * process to trace 141 | */ 142 | if (!pid_exists(pid) || (pid == getpid())) 143 | return NULL; 144 | 145 | /* Be lazy and ignore the parent info lookup */ 146 | return proc_cache_add_at_hash_index(h, pid, 0, false); 147 | } 148 | 149 | /* 150 | * proc_cache_get() 151 | * load proc cache with current system process info 152 | */ 153 | int proc_cache_get(void) 154 | { 155 | DIR *procdir; 156 | struct dirent *procentry; 157 | 158 | if ((procdir = opendir("/proc")) == NULL) { 159 | fprintf(stderr, "Cannot open directory /proc.\n"); 160 | return -1; 161 | } 162 | 163 | /* 164 | * Gather pid -> ppid mapping 165 | */ 166 | while ((procentry = readdir(procdir)) != NULL) { 167 | FILE *fp; 168 | char path[PATH_MAX]; 169 | 170 | if (!isdigit(procentry->d_name[0])) 171 | continue; 172 | 173 | snprintf(path, sizeof(path), "/proc/%s/stat", procentry->d_name); 174 | if ((fp = fopen(path, "r")) != NULL) { 175 | pid_t pid, ppid; 176 | char comm[64]; 177 | /* 3173 (a.out) R 3093 3173 3093 34818 3173 4202496 165 0 0 0 3194 0 */ 178 | if (fscanf(fp, "%8d (%63[^)]) %*c %8i", &pid, comm, &ppid) == 3) 179 | (void)proc_cache_add(pid, ppid, false); 180 | (void)fclose(fp); 181 | } 182 | } 183 | (void)closedir(procdir); 184 | 185 | return 0; 186 | } 187 | 188 | /* 189 | * proc_cache_get_pthreads() 190 | * load proc cache with pthreads from current system process info 191 | */ 192 | int proc_cache_get_pthreads(void) 193 | { 194 | DIR *procdir; 195 | struct dirent *procentry; 196 | 197 | if ((procdir = opendir("/proc")) == NULL) { 198 | fprintf(stderr, "Cannot open directory /proc.\n"); 199 | return -1; 200 | } 201 | 202 | /* 203 | * Gather pid -> ppid mapping 204 | */ 205 | while ((procentry = readdir(procdir)) != NULL) { 206 | DIR *taskdir; 207 | struct dirent *taskentry; 208 | char path[PATH_MAX]; 209 | pid_t ppid; 210 | 211 | if (!isdigit(procentry->d_name[0])) 212 | continue; 213 | 214 | errno = 0; 215 | ppid = (pid_t)strtol(procentry->d_name, NULL, 10); 216 | if (errno) 217 | continue; 218 | 219 | snprintf(path, sizeof(path), "/proc/%i/task", ppid); 220 | 221 | if ((taskdir = opendir(path)) == NULL) 222 | continue; 223 | 224 | (void)proc_cache_add(ppid, 0, false); 225 | 226 | while ((taskentry = readdir(taskdir)) != NULL) { 227 | pid_t pid; 228 | if (!isdigit(taskentry->d_name[0])) 229 | continue; 230 | pid = atoi(taskentry->d_name); 231 | if (pid == ppid) 232 | continue; 233 | if (proc_cache_add(pid, ppid, true) == NULL) { 234 | (void)closedir(taskdir); 235 | (void)closedir(procdir); 236 | return -1; 237 | } 238 | } 239 | (void)closedir(taskdir); 240 | } 241 | (void)closedir(procdir); 242 | 243 | return 0; 244 | } 245 | 246 | /* 247 | * proc_cache_info_free() 248 | * free a proc cache item 249 | */ 250 | static void proc_cache_info_free(void *data) 251 | { 252 | proc_info_t *p = (proc_info_t*)data; 253 | 254 | free(p->cmdline); 255 | free(p->comm); 256 | free(p); 257 | } 258 | 259 | /* 260 | * proc_pids_add_proc() 261 | * add a process to pid list if it is sensible 262 | */ 263 | int proc_pids_add_proc(list_t *pids, proc_info_t *p) 264 | { 265 | int rc = 0; 266 | 267 | if (p->pid == 1) { 268 | fprintf(stderr, "Cannot run health-check on init. Aborting.\n"); 269 | health_check_exit(EXIT_FAILURE); 270 | } 271 | if (p->pid == getpid()) { 272 | fprintf(stderr, "Cannot run health-check on itself. Aborting.\n"); 273 | health_check_exit(EXIT_FAILURE); 274 | } 275 | pthread_mutex_lock(&pids_mutex); 276 | if (list_append(pids, p) == NULL) 277 | rc = -1; 278 | pthread_mutex_unlock(&pids_mutex); 279 | 280 | return rc; 281 | } 282 | 283 | /* 284 | * proc_cache_find_by_procname() 285 | * find process by process name (in cmdline) 286 | * we don't do this often, so a linear search is fine 287 | */ 288 | int proc_cache_find_by_procname( 289 | list_t *pids, 290 | const char *procname) 291 | { 292 | bool found = false; 293 | link_t *l; 294 | 295 | pthread_mutex_lock(&proc_cache_mutex); 296 | for (l = proc_cache_list.head; l; l = l->next) { 297 | proc_info_t *p = (proc_info_t *)l->data; 298 | 299 | if (p->cmdline && strcmp(p->cmdline, procname) == 0) { 300 | (void)proc_pids_add_proc(pids, p); 301 | found = true; 302 | } 303 | } 304 | pthread_mutex_unlock(&proc_cache_mutex); 305 | 306 | if (!found) { 307 | fprintf(stderr, "Cannot find process %s.\n", procname); 308 | return -1; 309 | } 310 | 311 | return 0; 312 | } 313 | 314 | /* 315 | * proc_cache_init() 316 | * initialize proc cache 317 | */ 318 | void proc_cache_init(void) 319 | { 320 | list_init(&proc_cache_list); 321 | } 322 | 323 | /* 324 | * proc_cache_cleanup 325 | * cleanup 326 | */ 327 | void proc_cache_cleanup(void) 328 | { 329 | list_free(&proc_cache_list, proc_cache_info_free); 330 | } 331 | -------------------------------------------------------------------------------- /ctxt-switch.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * Copyright (C) 2021-2025 Colin Ian King 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | * 19 | * Author: Colin Ian King 20 | */ 21 | #define _GNU_SOURCE 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "list.h" 32 | #include "json.h" 33 | #include "ctxt-switch.h" 34 | #include "health-check.h" 35 | 36 | static list_t ctxt_switch_info_start, ctxt_switch_info_finish; 37 | 38 | /* 39 | * ctxt_switch_cmp() 40 | * compare context switch info for sorting 41 | */ 42 | static int ctx_switch_cmp(const void *data1, const void *data2) 43 | { 44 | const ctxt_switch_info_t *c1 = (const ctxt_switch_info_t *)data1; 45 | const ctxt_switch_info_t *c2 = (const ctxt_switch_info_t *)data2; 46 | 47 | return c2->total - c1->total; 48 | } 49 | 50 | /* 51 | * ctxt_switch_get_by_proc() 52 | * get context switch info for a specific process 53 | */ 54 | int ctxt_switch_get_by_proc(proc_info_t *proc, proc_state state) 55 | { 56 | char path[PATH_MAX]; 57 | char buf[4096]; 58 | FILE *fp; 59 | ctxt_switch_info_t *info; 60 | list_t *ctxt_switches = 61 | (state == PROC_START) ? &ctxt_switch_info_start : &ctxt_switch_info_finish; 62 | 63 | snprintf(path, sizeof(path), "/proc/%i/status", proc->pid); 64 | if ((fp = fopen(path, "r")) == NULL) 65 | return 0; 66 | 67 | if ((info = calloc(1, sizeof(*info))) == NULL) { 68 | health_check_out_of_memory("allocating context switch information"); 69 | (void)fclose(fp); 70 | return -1; 71 | } 72 | info->voluntary = 0; 73 | info->involuntary = 0; 74 | info->valid = false; 75 | info->proc = proc; 76 | 77 | while (!feof(fp)) { 78 | if (fgets(buf, sizeof(buf), fp) == NULL) 79 | break; 80 | if (!strncmp(buf, "voluntary_ctxt_switches:", 24)) { 81 | (void)sscanf(buf + 24, "%" SCNu64, &info->voluntary); 82 | continue; 83 | } 84 | if (!strncmp(buf, "nonvoluntary_ctxt_switches:", 27)) { 85 | (void)sscanf(buf + 27, "%" SCNu64, &info->involuntary); 86 | continue; 87 | } 88 | } 89 | (void)fclose(fp); 90 | info->total = info->voluntary + info->involuntary; 91 | info->valid = true; 92 | 93 | if (list_append(ctxt_switches, info) == NULL) { 94 | free(info); 95 | return -1; 96 | } 97 | 98 | return 0; 99 | } 100 | 101 | /* 102 | * ctxt_switch_get_all_pids() 103 | * scan /proc/pid/status for context switch data 104 | */ 105 | int ctxt_switch_get_all_pids(const list_t *pids, proc_state state) 106 | { 107 | link_t *l; 108 | 109 | for (l = pids->head; l; l = l->next) { 110 | proc_info_t *p = (proc_info_t *)l->data; 111 | if (ctxt_switch_get_by_proc(p, state) < 0) 112 | return -1; 113 | } 114 | return 0; 115 | } 116 | 117 | /* 118 | * ctxt_switch_loading() 119 | * context switch rate to some human understandable text 120 | */ 121 | static const char *ctxt_switch_loading(const double rate) 122 | { 123 | if (FLOAT_CMP(rate, 0.0)) 124 | return "idle"; 125 | if (rate > 10000.0) 126 | return "very high"; 127 | if (rate > 1000.0) 128 | return "high"; 129 | if (rate > 100.0) 130 | return "quite high"; 131 | if (rate > 10.0) 132 | return "moderate"; 133 | if (rate > 1.0) 134 | return "low"; 135 | return "very low"; 136 | } 137 | 138 | /* 139 | * ctxt_switch_delta() 140 | * find delta in context switches between old, new. 141 | * if no old then delta is the new. 142 | */ 143 | static void ctxt_switch_delta( 144 | const ctxt_switch_info_t *ctxt_switch_new, 145 | const list_t *ctxt_switches_old, 146 | uint64_t *total, 147 | uint64_t *voluntary, 148 | uint64_t *involuntary) 149 | { 150 | link_t *l; 151 | 152 | for (l = ctxt_switches_old->head; l; l = l->next) { 153 | ctxt_switch_info_t *ctxt_switch_old = (ctxt_switch_info_t*)l->data; 154 | if (ctxt_switch_new->proc == ctxt_switch_old->proc) { 155 | if (!ctxt_switch_old->valid) 156 | break; 157 | *total = ctxt_switch_new->total - ctxt_switch_old->total; 158 | *voluntary = ctxt_switch_new->voluntary - ctxt_switch_old->voluntary; 159 | *involuntary = ctxt_switch_new->involuntary - ctxt_switch_old->involuntary; 160 | return; 161 | } 162 | } 163 | 164 | *total = ctxt_switch_new->total; 165 | *voluntary = ctxt_switch_new->voluntary; 166 | *involuntary = ctxt_switch_new->involuntary; 167 | } 168 | 169 | 170 | /* 171 | * ctxt_switch_dump_diff() 172 | * dump differences between old and new events 173 | */ 174 | void ctxt_switch_dump_diff(json_object *j_tests, const double duration) 175 | { 176 | link_t *l; 177 | list_t sorted; 178 | 179 | #ifndef JSON_OUTPUT 180 | (void)j_tests; 181 | #endif 182 | printf("Context Switches:\n"); 183 | list_init(&sorted); 184 | for (l = ctxt_switch_info_finish.head; l; l = l->next) { 185 | ctxt_switch_info_t *new_info, *info = (ctxt_switch_info_t *)l->data; 186 | 187 | if (!info->valid) 188 | continue; 189 | 190 | if ((new_info = calloc(1, sizeof(*info))) == NULL) { 191 | health_check_out_of_memory("allocating context switch information"); 192 | goto out; 193 | } 194 | new_info->proc = info->proc; 195 | ctxt_switch_delta(info, 196 | &ctxt_switch_info_start, 197 | &new_info->total, 198 | &new_info->voluntary, 199 | &new_info->involuntary); 200 | if (list_add_ordered(&sorted, new_info, ctx_switch_cmp) == NULL) { 201 | free(new_info); 202 | goto out; 203 | } 204 | } 205 | 206 | if (sorted.head) { 207 | if (opt_flags & OPT_BRIEF) { 208 | double rate = 0.0; 209 | 210 | for (l = sorted.head; l; l = l->next) { 211 | ctxt_switch_info_t *info = (ctxt_switch_info_t *)l->data; 212 | rate += (double)info->total; 213 | } 214 | rate /= duration; 215 | printf(" %.2f context switches/sec (%s)\n\n", 216 | rate, ctxt_switch_loading(rate)); 217 | } else { 218 | int count = 0; 219 | double total_total = 0.0, total_voluntary = 0.0, total_involuntary = 0.0; 220 | const int pid_size = pid_max_digits(); 221 | 222 | printf(" %*s Process Voluntary Involuntary Total\n", 223 | pid_size, "PID"); 224 | printf(" %*s Ctxt Sw/Sec Ctxt Sw/Sec Ctxt Sw/Sec\n", 225 | pid_size, " "); 226 | for (l = sorted.head; l; l = l->next) { 227 | ctxt_switch_info_t *info = (ctxt_switch_info_t *)l->data; 228 | 229 | printf(" %*d %-20.20s %12.2f %12.2f %12.2f (%s)\n", 230 | pid_size, info->proc->pid, 231 | info->proc->cmdline, 232 | (double)info->voluntary / duration, 233 | (double)info->involuntary / duration, 234 | (double)info->total / duration, 235 | ctxt_switch_loading((double)info->total / duration)); 236 | total_voluntary += (double)info->voluntary; 237 | total_involuntary += (double)info->involuntary; 238 | total_total += (double)info->total; 239 | count++; 240 | } 241 | if (count > 1) 242 | printf(" %-27.27s%12.2f %12.2f %12.2f\n", "Total", 243 | total_voluntary / duration, 244 | total_involuntary / duration, 245 | total_total / duration); 246 | printf("\n"); 247 | } 248 | } else { 249 | printf(" No context switches detected.\n\n"); 250 | } 251 | 252 | #ifdef JSON_OUTPUT 253 | if (j_tests) { 254 | json_object *j_ctxt_switch_test, *j_ctxt_switches, *j_ctxt_switch; 255 | uint64_t total = 0; 256 | double total_rate; 257 | 258 | if ((j_ctxt_switch_test = j_obj_new_obj()) == NULL) 259 | goto out; 260 | j_obj_obj_add(j_tests, "context-switches", j_ctxt_switch_test); 261 | if ((j_ctxt_switches = j_obj_new_array()) == NULL) 262 | goto out; 263 | j_obj_obj_add(j_ctxt_switch_test, "context-switches-per-process", j_ctxt_switches); 264 | 265 | for (l = sorted.head; l; l = l->next) { 266 | ctxt_switch_info_t *info = (ctxt_switch_info_t *)l->data; 267 | 268 | total += (double)info->total; 269 | if ((j_ctxt_switch = j_obj_new_obj()) == NULL) 270 | goto out; 271 | j_obj_new_int32_add(j_ctxt_switch, "pid", info->proc->pid); 272 | j_obj_new_int32_add(j_ctxt_switch, "ppid", info->proc->ppid); 273 | j_obj_new_int32_add(j_ctxt_switch, "is-thread", info->proc->is_thread); 274 | j_obj_new_string_add(j_ctxt_switch, "name", info->proc->cmdline); 275 | j_obj_new_int64_add(j_ctxt_switch, "voluntary-context-switches", info->voluntary); 276 | j_obj_new_double_add(j_ctxt_switch, "voluntary-context-switch-rate", (double)info->voluntary / duration); 277 | j_obj_new_int64_add(j_ctxt_switch, "involuntary-context-switches", (double)info->involuntary / duration); 278 | j_obj_new_double_add(j_ctxt_switch, "involuntary-context-switch-rate", (double)info->involuntary / duration); 279 | j_obj_new_int64_add(j_ctxt_switch, "total-context-switches", info->total); 280 | j_obj_new_double_add(j_ctxt_switch, "total-context-switch-rate", (double)info->total / duration); 281 | j_obj_new_string_add(j_ctxt_switch, "load-hint", ctxt_switch_loading((double)info->total / duration)); 282 | j_obj_array_add(j_ctxt_switches, j_ctxt_switch); 283 | } 284 | total_rate = (double)total / duration; 285 | if ((j_ctxt_switch = j_obj_new_obj()) == NULL) 286 | goto out; 287 | j_obj_obj_add(j_ctxt_switch_test, "context-switches-total", j_ctxt_switch); 288 | j_obj_new_int64_add(j_ctxt_switch, "context-switch-total", total); 289 | j_obj_new_double_add(j_ctxt_switch, "context-switch-total-rate", total_rate); 290 | j_obj_new_string_add(j_ctxt_switch, "load-hint-total", ctxt_switch_loading(total_rate)); 291 | } 292 | #endif 293 | 294 | out: 295 | list_free(&sorted, free); 296 | } 297 | 298 | /* 299 | * ctxt_switch_init() 300 | * initialize lists 301 | */ 302 | void ctxt_switch_init(void) 303 | { 304 | list_init(&ctxt_switch_info_start); 305 | list_init(&ctxt_switch_info_finish); 306 | } 307 | 308 | /* 309 | * ctxt_switch_cleanup() 310 | * cleanup lists 311 | */ 312 | void ctxt_switch_cleanup(void) 313 | { 314 | list_free(&ctxt_switch_info_start, free); 315 | list_free(&ctxt_switch_info_finish, free); 316 | } 317 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Health-check 2 | 3 | The health-check tool monitors processes in various ways to help identify 4 | areas where it is consuming too many resources. One can trace one or more 5 | processes (including all their threads and child processes too) for a full 6 | story of system activity. 7 | 8 | ## Health-check can monitor: 9 | * CPU usage 10 | * Kernel wake-up events 11 | * File I/O activity (open,read,write,close) 12 | * System call activity 13 | * Excessive polling of timeout wait blocked system calls (such as poll, select, etc) 14 | * Memory usage (such as heap and stack growth) 15 | * Network connections (to spot rogue internet activity) 16 | * Network usage (send/receive) accounting 17 | * Syncing data via fsync, fdatasync, syncfs and sync system calls 18 | * Page fault accounting 19 | 20 | ..and can also dump the stats into a JSON formatted file for later analysis. 21 | 22 | ## Health-check command line options: 23 | 24 | * -b brief (simple mode) output 25 | * -c trace all child processes 26 | * -d duration of the test in seconds 27 | * -f follow fork/vfork/clone system calls 28 | * -h show help information 29 | * -p comma separated list of process IDs or process names to trace 30 | * -m maximum number of system calls to trace before stopping (default 1 million) 31 | * -o JSON output file 32 | * -r resolve IP addresses (may take a while to do) 33 | * -u run a command to trace as a specified user 34 | * -v verbose output 35 | * -w monitor wakelock counts (lightweight fnotify monitoring) 36 | * -W monitor wakelock usage (expensive syscall inspection overhead) 37 | 38 | Health check can be used to either attach to one or more existing running 39 | processes using the -p option, or one can specify a command to be run and 40 | it will executed and traced. The latter option also allows one to specify 41 | the user id to run the command under. 42 | 43 | ## Example Output: 44 | ``` 45 | sudo health-check -p camera-app -c -d 60 46 | CPU usage: 47 | PID Process USR% SYS% TOTAL% 48 | 3585 camera-app 18.97 16.93 35.90 (medium load) 49 | 50 | Wakeups: 51 | PID Process Wake/Sec Kernel Functions 52 | 3608 camera-app 13.62 (add_timer, OSTimerCallbackWrapper) (high) 53 | 3589 camera-app 3.17 (hrtimer_start_range_ns, hrtimer_wakeup) (moderate) 54 | 3608 camera-app 0.37 (schedule_timeout_uninterruptible, process_timeout) (low) 55 | 3585 camera-app 0.28 (hrtimer_start_range_ns, hrtimer_wakeup) (low) 56 | Total 17.43 57 | 58 | Context Switches: 59 | PID Process Voluntary Involuntary Total 60 | Ctxt Sw/Sec Ctxt Sw/Sec Ctxt Sw/Sec 61 | 3608 camera-app 1712.07 178.02 1890.09 (high) 62 | 3585 camera-app 1498.67 10.72 1509.39 (high) 63 | 3606 camera-app 143.25 18.55 161.80 (quite high) 64 | 3667 camera-app 142.33 18.68 161.02 (quite high) 65 | 3605 camera-app 141.25 18.32 159.57 (quite high) 66 | 3628 camera-app 128.12 16.50 144.62 (quite high) 67 | 3587 camera-app 108.98 0.05 109.03 (quite high) 68 | 3661 camera-app 6.48 46.30 52.78 (moderate) 69 | 3607 camera-app 27.75 1.65 29.40 (moderate) 70 | 3589 camera-app 20.48 0.02 20.50 (moderate) 71 | 3592 camera-app 0.00 0.00 0.00 (idle) 72 | 3590 camera-app 0.00 0.00 0.00 (idle) 73 | 3588 camera-app 0.00 0.00 0.00 (idle) 74 | Total 4238.19 308.80 3929.39 75 | 76 | File I/O operations: 77 | PID Process Count Op Filename 78 | 3585 camera-app 43 O /home/phablet/.config/user-dirs.dirs 79 | 3585 camera-app 41 CW /home/phablet/Pictures/image20130905_0004.jpg 80 | 3585 camera-app 22 W /tmp/Camera App.nS3585 81 | 3585 camera-app 21 C /home/phablet/.config/user-dirs.dirs 82 | 3585 camera-app 21 R /home/phablet/.config/user-dirs.dirs 83 | 3585 camera-app 1 OCR /home/phablet/.config/user-dirs.dirs 84 | Total 149 85 | 86 | File I/O Operations per second: 87 | PID Process Open Close Read Write 88 | 3585 camera-app 0.73 1.05 0.37 1.05 89 | 90 | System calls traced: 91 | PID Process Syscall Count Rate/Sec 92 | 3608 camera-app ioctl 25035 417.2470 93 | 3585 camera-app futex 13713 228.5484 94 | 3608 camera-app futex 12382 206.3652 95 | 3585 camera-app clock_gettime 9065 151.0823 96 | 3585 camera-app poll 6098 101.6326 97 | 3585 camera-app read 5929 98.8160 98 | 3608 camera-app clock_gettime 2949 49.1497 99 | 3608 camera-app mmap2 2389 39.8164 100 | 3628 camera-app ioctl 1532 25.5332 101 | 3608 camera-app poll 1421 23.6832 102 | 3608 camera-app munmap 1022 17.0332 103 | 3587 camera-app epoll_wait 927 15.4499 104 | 3587 camera-app recvfrom 926 15.4332 105 | 3585 camera-app stat64 682 11.3666 106 | 3628 camera-app getpriority 409 6.8166 107 | 3585 camera-app recvmsg 396 6.6000 108 | 3585 camera-app gettimeofday 319 5.3166 109 | 3628 camera-app munmap 279 4.6500 110 | 3585 camera-app sendmsg 242 4.0333 111 | 3589 camera-app futex 231 3.8500 112 | 3589 camera-app nanosleep 190 3.1666 113 | 3628 camera-app clock_gettime 155 2.5833 114 | 3585 camera-app ioctl 96 1.6000 115 | 3585 camera-app fstat64 88 1.4667 116 | 3628 camera-app futex 84 1.4000 117 | 3608 camera-app read 82 1.3667 118 | 3608 camera-app open 44 0.7333 119 | 3661 camera-app futex 30 0.5000 120 | 3585 camera-app munmap 24 0.4000 121 | 3661 camera-app poll 23 0.3833 122 | 3661 camera-app read 23 0.3833 123 | 3661 camera-app clock_gettime 23 0.3833 124 | 3585 camera-app fcntl64 22 0.3667 125 | 3608 camera-app writev 22 0.3667 126 | 3585 camera-app access 22 0.3667 127 | 3585 camera-app open 22 0.3667 128 | 3661 camera-app mmap2 22 0.3667 129 | 3585 camera-app fork 21 0.3500 130 | 3587 camera-app mprotect 18 0.3000 131 | 3587 camera-app futex 12 0.2000 132 | 3628 camera-app dup 10 0.1667 133 | 3628 camera-app _llseek 5 0.0833 134 | 3628 camera-app fcntl64 5 0.0833 135 | 3628 camera-app stat64 5 0.0833 136 | 3628 camera-app mmap2 5 0.0833 137 | 3628 camera-app fstat64 5 0.0833 138 | 3628 camera-app lstat64 5 0.0833 139 | 3628 camera-app rename 5 0.0833 140 | 3628 camera-app open 5 0.0833 141 | 3628 camera-app unlink 5 0.0833 142 | 3661 camera-app munmap 1 0.0167 143 | 3661 camera-app restart_syscall 1 0.0167 144 | 3592 camera-app restart_syscall 1 0.0167 145 | 3585 camera-app restart_syscall 1 0.0167 146 | Total 87028 1450.4563 147 | 148 | Top polling system calls: 149 | PID Process Syscall Rate/Sec Infinite Zero Minimum Maximum Average 150 | Timeouts Timeouts Timeout Timeout Timeout 151 | 3585 camera-app poll 101.6326 2974 1853 0.0 sec 25.0 sec 788.5 msec 152 | 3608 camera-app poll 23.6832 0 1421 0.0 sec 0.0 sec 0.0 sec 153 | 3587 camera-app epoll_wait 15.4499 0 0 5.0 sec 5.0 sec 5.0 sec 154 | 3589 camera-app nanosleep 3.1666 0 0 900.0 usec 900.0 usec 900.0 usec 155 | 3661 camera-app poll 0.3833 22 1 0.0 sec 0.0 sec 0.0 sec 156 | Total 144.3156 2996 3275 157 | 158 | Distribution of poll timeout times: 159 | 10.0 100.0 1.0 10.0 100.0 1.0 10.0 100.0 160 | up to to to to to to to to or more 161 | Zero 9.9 99.9 999.9 9.9 99.9 999.9 9.9 99.9 Infinite 162 | PID Process Syscall sec usec usec usec msec msec msec sec sec sec Wait 163 | 3585 camera-app poll 1853 - - - 29 178 864 24 176 - 2974 164 | 3608 camera-app poll 1421 - - - - - - - - - 0 165 | 3587 camera-app epoll_wait 0 - - - - - - 927 - - 0 166 | 3589 camera-app nanosleep 0 - - 190 - - - - - - 0 167 | 3661 camera-app poll 1 - - - - - - - - - 22 168 | 169 | Polling system call analysis: 170 | camera-app (3585), poll: 171 | 2 immediate timed out calls with zero timeout (non-blocking peeks) 172 | camera-app (3608), poll: 173 | 1339 immediate timed out calls with zero timeout (non-blocking peeks) 174 | 1297 repeated immediate timed out polled calls with zero timeouts (heavy polling peeks) 175 | Polling system call analysis: 176 | camera-app (3585), poll: 177 | 2 immediate timed out calls with zero timeout (non-blocking peeks) 178 | camera-app (3608), poll: 179 | 1339 immediate timed out calls with zero timeout (non-blocking peeks) 180 | 1297 repeated immediate timed out polled calls with zero timeouts (heavy polling peeks) 181 | 182 | Per Process Memory (K): 183 | PID Process Type Size RSS PSS 184 | 3585 camera-app Stack 136 64 64 185 | 3585 camera-app Heap 130440 11476 11476 186 | 3585 camera-app Mapped 131308 44688 24811 187 | 188 | Change in memory (K/second): 189 | PID Process Type Size RSS PSS 190 | 3585 camera-app Heap 0.00 1.80 1.80 (growing) 191 | 3585 camera-app Mapped 0.00 0.40 0.37 (growing slowly) 192 | 193 | Open Network Connections: 194 | None. 195 | ``` 196 | 197 | ## Another example: 198 | 199 | ``` 200 | sudo health-check -u king dd if=/dev/zero of=test bs=1k count=100000 201 | 100000+0 records in 202 | 100000+0 records out 203 | 102400000 bytes (102 MB) copied, 7.85756 s, 13.0 MB/s 204 | CPU usage (in terms of 1 CPU): 205 | PID Process USR% SYS% TOTAL% Duration 206 | 22572 dd 3.17 56.53 59.69 7.89 (high load) 207 | 208 | Page Faults: 209 | PID Process Minor/sec Major/sec Total/sec 210 | 22572 dd 32.83 0.00 32.83 211 | 212 | Wakeups: 213 | No wakeups detected. 214 | ``` 215 | 216 | -------------------------------------------------------------------------------- /cpustat.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * Copyright (C) 2021-2025 Colin Ian King 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | * 19 | * Author: Colin Ian King 20 | */ 21 | #define _GNU_SOURCE 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "list.h" 32 | #include "json.h" 33 | #include "cpustat.h" 34 | #include "timeval.h" 35 | #include "health-check.h" 36 | 37 | static list_t cpustat_info_start, cpustat_info_finish; 38 | 39 | /* 40 | * cpustat_loading() 41 | * map CPU loading to some human understandable form 42 | */ 43 | static const char *cpustat_loading(const double cpu_percent) 44 | { 45 | if (FLOAT_CMP(cpu_percent, 0.0)) 46 | return "idle"; 47 | if (cpu_percent > 99.0) 48 | return "CPU fully loaded"; 49 | if (cpu_percent > 95.0) 50 | return "nearly 1 CPU fully loaded"; 51 | if (cpu_percent > 85.0) 52 | return "excessive load"; 53 | if (cpu_percent > 70.0) 54 | return "very high load"; 55 | if (cpu_percent > 40.0) 56 | return "high load"; 57 | if (cpu_percent > 20.0) 58 | return "medium load"; 59 | if (cpu_percent > 10.0) 60 | return "slight load"; 61 | if (cpu_percent > 2.5) 62 | return "light load"; 63 | return "very light load"; 64 | } 65 | 66 | /* 67 | * cpustat_cmp() 68 | * cpu total time list sort comparitor 69 | */ 70 | static int cpustat_cmp(const void *data1, const void *data2) 71 | { 72 | const cpustat_info_t *cpustat1 = (const cpustat_info_t *)data1; 73 | const cpustat_info_t *cpustat2 = (const cpustat_info_t *)data2; 74 | 75 | return cpustat2->ttime - cpustat1->ttime; 76 | } 77 | 78 | /* 79 | * cpustat_dump_diff() 80 | * dump difference in CPU loading between two snapshots in time 81 | */ 82 | int cpustat_dump_diff(json_object *j_tests, const double duration) 83 | { 84 | double nr_ticks = (double)sysconf(_SC_CLK_TCK) * duration; 85 | double utime_total = 0.0, stime_total = 0.0, ttime_total = 0.0; 86 | const int pid_size = pid_max_digits(); 87 | int rc = 0; 88 | int count = 0; 89 | link_t *lo, *ln; 90 | list_t sorted; 91 | cpustat_info_t *cio, *cin; 92 | #ifndef JSON_OUTPUT 93 | (void)j_tests; 94 | #endif 95 | list_init(&sorted); 96 | for (ln = cpustat_info_finish.head; ln; ln = ln->next) { 97 | cin = (cpustat_info_t*)ln->data; 98 | 99 | for (lo = cpustat_info_start.head; lo; lo = lo->next) { 100 | cio = (cpustat_info_t*)lo->data; 101 | 102 | if (cin->proc->pid == cio->proc->pid) { 103 | cpustat_info_t *cpustat; 104 | 105 | if ((cpustat = calloc(1, sizeof(*cpustat))) == NULL) { 106 | health_check_out_of_memory("cannot allocate cpustat information"); 107 | goto out; 108 | } 109 | cpustat->proc = cio->proc; 110 | cpustat->utime = cin->utime - cio->utime; 111 | cpustat->stime = cin->stime - cio->stime; 112 | cpustat->ttime = cin->ttime - cio->ttime; 113 | cpustat->duration = 114 | timeval_to_double(&cin->whence) - 115 | timeval_to_double(&cio->whence); 116 | if (list_add_ordered(&sorted, cpustat, cpustat_cmp) == NULL) { 117 | free(cpustat); 118 | goto out; 119 | } 120 | 121 | /* We calculate this in terms of ticks and duration of each process */ 122 | utime_total += (double)cpustat->utime / nr_ticks; 123 | stime_total += (double)cpustat->stime / nr_ticks; 124 | ttime_total += (double)cpustat->ttime / nr_ticks; 125 | count++; 126 | } 127 | } 128 | } 129 | 130 | printf("CPU usage (in terms of 1 CPU):\n"); 131 | if (sorted.head == NULL) { 132 | printf(" Nothing measured.\n"); 133 | } else { 134 | if (opt_flags & OPT_BRIEF) { 135 | printf(" User: %6.2f%%, System: %6.2f%%, Total: %6.2f%% (%s)\n", 136 | 100.0 * utime_total, 137 | 100.0 * stime_total, 138 | 100.0 * ttime_total, 139 | cpustat_loading(100.0 * (double)ttime_total)); 140 | } else { 141 | printf(" %*s Process USR%% SYS%% TOTAL%% Duration\n", 142 | pid_size, "PID"); 143 | for (ln = sorted.head; ln; ln = ln->next) { 144 | cin = (cpustat_info_t*)ln->data; 145 | printf(" %*d %-20.20s %6.2f %6.2f %6.2f %8.2f (%s)\n", 146 | pid_size, cin->proc->pid, 147 | cin->proc->cmdline, 148 | 100.0 * (double)cin->utime / nr_ticks, 149 | 100.0 * (double)cin->stime / nr_ticks, 150 | 100.0 * (double)cin->ttime / nr_ticks, 151 | cin->duration, 152 | cpustat_loading(100.0 * (double)cin->ttime / nr_ticks)); 153 | } 154 | if (count > 1) 155 | printf(" %-26.26s %6.2f %6.2f %6.2f (%s)\n", 156 | "Total", 157 | 100.0 * utime_total, 158 | 100.0 * stime_total, 159 | 100.0 * ttime_total, 160 | cpustat_loading(100.0 * ttime_total)); 161 | } 162 | } 163 | 164 | #ifdef JSON_OUTPUT 165 | if (j_tests) { 166 | json_object *j_cpustat, *j_cpuload, *j_cpu; 167 | 168 | if ((j_cpustat = j_obj_new_obj()) == NULL) 169 | goto out; 170 | j_obj_obj_add(j_tests, "cpu-load", j_cpustat); 171 | if ((j_cpuload = j_obj_new_array()) == NULL) 172 | goto out; 173 | j_obj_obj_add(j_cpustat, "cpu-load-per-process", j_cpuload); 174 | 175 | for (ln = sorted.head; ln; ln = ln->next) { 176 | cin = (cpustat_info_t*)ln->data; 177 | 178 | if ((j_cpu = j_obj_new_obj()) == NULL) 179 | goto out; 180 | j_obj_new_int32_add(j_cpu, "pid", cin->proc->pid); 181 | j_obj_new_int32_add(j_cpu, "ppid", cin->proc->ppid); 182 | j_obj_new_int32_add(j_cpu, "is-thread", cin->proc->is_thread); 183 | j_obj_new_string_add(j_cpu, "name", cin->proc->cmdline); 184 | j_obj_new_int64_add(j_cpu, "user-cpu-ticks", cin->utime); 185 | j_obj_new_int64_add(j_cpu, "system-cpu-ticks", cin->stime); 186 | j_obj_new_int64_add(j_cpu, "total-cpu-ticks", cin->ttime); 187 | j_obj_new_double_add(j_cpu, "user-cpu-percent", 188 | 100.0 * (double)cin->utime / nr_ticks); 189 | j_obj_new_double_add(j_cpu, "system-cpu-percent", 190 | 100.0 * (double)cin->stime / nr_ticks); 191 | j_obj_new_double_add(j_cpu, "total-cpu-percent", 192 | 100.0 * (double)cin->ttime / nr_ticks); 193 | j_obj_new_string_add(j_cpu, "load-hint", 194 | cpustat_loading(100.0 * (double)cin->ttime / nr_ticks)); 195 | j_obj_array_add(j_cpuload, j_cpu); 196 | } 197 | 198 | if ((j_cpu = j_obj_new_obj()) == NULL) 199 | goto out; 200 | j_obj_obj_add(j_cpustat, "cpu-load-total", j_cpu); 201 | j_obj_new_double_add(j_cpu, "user-cpu-percent", 100.0 * utime_total); 202 | j_obj_new_double_add(j_cpu, "system-cpu-percent", 100.0 * stime_total); 203 | j_obj_new_double_add(j_cpu, "total-cpu-percent", 100.0 * ttime_total); 204 | } 205 | #endif 206 | printf("\n"); 207 | out: 208 | list_free(&sorted, free); 209 | 210 | return rc; 211 | } 212 | 213 | /* 214 | * pagefault_cmp() 215 | * pagefault list sort comparitor 216 | */ 217 | static int pagefault_cmp(const void *data1, const void *data2) 218 | { 219 | const cpustat_info_t *cpustat1 = (const cpustat_info_t *)data1; 220 | const cpustat_info_t *cpustat2 = (const cpustat_info_t *)data2; 221 | 222 | return (cpustat2->major_fault + cpustat2->minor_fault) - 223 | (cpustat1->major_fault + cpustat1->minor_fault); 224 | } 225 | 226 | /* 227 | * pagefault_dump_diff() 228 | * dump difference in pagefaults between two snapshots in time 229 | */ 230 | int pagefault_dump_diff(json_object *j_tests, const double duration) 231 | { 232 | int rc = 0; 233 | link_t *lo, *ln; 234 | list_t sorted; 235 | cpustat_info_t *cio, *cin; 236 | #ifndef JSON_OUTPUT 237 | (void)j_tests; 238 | #endif 239 | list_init(&sorted); 240 | for (ln = cpustat_info_finish.head; ln; ln = ln->next) { 241 | cin = (cpustat_info_t*)ln->data; 242 | 243 | for (lo = cpustat_info_start.head; lo; lo = lo->next) { 244 | cio = (cpustat_info_t*)lo->data; 245 | 246 | if (cin->proc->pid == cio->proc->pid) { 247 | cpustat_info_t *cpustat; 248 | 249 | if ((cpustat = calloc(1, sizeof(*cpustat))) == NULL) { 250 | health_check_out_of_memory("cannot allocate cpustat information"); 251 | goto out; 252 | } 253 | cpustat->proc = cio->proc; 254 | cpustat->major_fault = cin->major_fault - cio->major_fault; 255 | cpustat->minor_fault = cin->minor_fault - cio->minor_fault; 256 | cpustat->duration = 257 | timeval_to_double(&cin->whence) - 258 | timeval_to_double(&cio->whence); 259 | if (list_add_ordered(&sorted, cpustat, pagefault_cmp) == NULL) { 260 | free(cpustat); 261 | goto out; 262 | } 263 | } 264 | } 265 | } 266 | 267 | printf("Page Faults:\n"); 268 | if (sorted.head == NULL) { 269 | printf(" Nothing measured.\n"); 270 | } else { 271 | const int pid_size = pid_max_digits(); 272 | 273 | printf(" %*s Process Minor/sec Major/sec Total/sec\n", 274 | pid_size, "PID"); 275 | for (ln = sorted.head; ln; ln = ln->next) { 276 | cin = (cpustat_info_t*)ln->data; 277 | printf(" %*d %-20.20s %12.2f %12.2f %12.2f\n", 278 | pid_size, cin->proc->pid, 279 | cin->proc->cmdline, 280 | (double)cin->minor_fault / duration, 281 | (double)cin->major_fault / duration, 282 | (double)(cin->minor_fault + cin->major_fault) / duration); 283 | } 284 | } 285 | 286 | #ifdef JSON_OUTPUT 287 | if (j_tests) { 288 | json_object *j_fault_info, *j_faults, *j_fault; 289 | uint64_t minor_fault_total = 0, major_fault_total = 0; 290 | 291 | if ((j_fault_info = j_obj_new_obj()) == NULL) 292 | goto out; 293 | j_obj_obj_add(j_tests, "page-faults", j_fault_info); 294 | if ((j_faults = j_obj_new_array()) == NULL) 295 | goto out; 296 | j_obj_obj_add(j_fault_info, "page-faults-per-process", j_faults); 297 | 298 | for (ln = sorted.head; ln; ln = ln->next) { 299 | cin = (cpustat_info_t*)ln->data; 300 | 301 | if ((j_fault = j_obj_new_obj()) == NULL) 302 | goto out; 303 | j_obj_new_int32_add(j_fault, "pid", cin->proc->pid); 304 | j_obj_new_int32_add(j_fault, "ppid", cin->proc->ppid); 305 | j_obj_new_int32_add(j_fault, "is-thread", cin->proc->is_thread); 306 | j_obj_new_string_add(j_fault, "name", cin->proc->cmdline); 307 | j_obj_new_int64_add(j_fault, "minor-page-faults", cin->minor_fault); 308 | j_obj_new_int64_add(j_fault, "major-page-faults", cin->major_fault); 309 | j_obj_new_int64_add(j_fault, "total-page-faults", cin->minor_fault + cin->major_fault); 310 | j_obj_new_double_add(j_fault, "minor-page-faults-rate", (double)cin->minor_fault / duration); 311 | j_obj_new_double_add(j_fault, "major-page-faults-rate", (double)cin->major_fault / duration); 312 | j_obj_new_double_add(j_fault, "total-page-faults-rate", (double)(cin->minor_fault + cin->major_fault) / duration); 313 | j_obj_array_add(j_faults, j_fault); 314 | 315 | minor_fault_total += cin->minor_fault; 316 | major_fault_total += cin->major_fault; 317 | } 318 | 319 | if ((j_fault = j_obj_new_obj()) == NULL) 320 | goto out; 321 | j_obj_obj_add(j_fault_info, "page-faults-total", j_fault); 322 | j_obj_new_int64_add(j_fault, "minor-page-faults-total", minor_fault_total); 323 | j_obj_new_int64_add(j_fault, "major-page-faults-total", major_fault_total); 324 | j_obj_new_int64_add(j_fault, "total-page-faults-total", minor_fault_total + major_fault_total); 325 | j_obj_new_double_add(j_fault, "minor-page-faults-total-rate", (double)minor_fault_total / duration); 326 | j_obj_new_double_add(j_fault, "major-page-faults-total-rate", (double)major_fault_total / duration); 327 | j_obj_new_double_add(j_fault, "total-page-faults-total-rate", (double)(minor_fault_total + major_fault_total) / duration); 328 | } 329 | #endif 330 | printf("\n"); 331 | out: 332 | list_free(&sorted, free); 333 | 334 | return rc; 335 | } 336 | 337 | /* 338 | * cpustat_get_by_proc() 339 | * get CPU stats for a process 340 | */ 341 | int cpustat_get_by_proc(proc_info_t *proc, proc_state state) 342 | { 343 | char filename[PATH_MAX]; 344 | FILE *fp; 345 | list_t *cpustat = (state == PROC_START) ? &cpustat_info_start : &cpustat_info_finish; 346 | 347 | snprintf(filename, sizeof(filename), "/proc/%d/stat", proc->pid); 348 | if ((fp = fopen(filename, "r")) != NULL) { 349 | char comm[20]; 350 | uint64_t utime, stime; 351 | uint64_t minor_fault, major_fault; 352 | pid_t pid; 353 | 354 | /* 3173 (a.out) R 3093 3173 3093 34818 3173 4202496 165 0 0 0 3194 0 */ 355 | if (fscanf(fp, "%8d (%19[^)]) %*c %*d %*d %*d %*d %*d %*u %20" SCNu64 356 | " %*u %20" SCNu64 " %*u %20" SCNu64 " %20" SCNu64, 357 | &pid, comm, &minor_fault, &major_fault, &utime, &stime) == 6) { 358 | cpustat_info_t *info; 359 | 360 | info = calloc(1, sizeof(*info)); 361 | if (info == NULL) { 362 | health_check_out_of_memory("allocating cpustat information"); 363 | (void)fclose(fp); 364 | return -1; 365 | } 366 | info->proc = proc; 367 | info->utime = utime; 368 | info->stime = stime; 369 | info->ttime = utime + stime; 370 | info->major_fault = major_fault; 371 | info->minor_fault = minor_fault; 372 | gettimeofday(&info->whence, NULL); 373 | info->duration = 0.0; 374 | if (list_append(cpustat, info) == NULL) { 375 | free(info); 376 | (void)fclose(fp); 377 | return -1; 378 | } 379 | } 380 | (void)fclose(fp); 381 | } 382 | return 0; 383 | } 384 | 385 | /* 386 | * cpustat_get_all_pids() 387 | * get CPU stats for all processes 388 | */ 389 | int cpustat_get_all_pids(const list_t *pids, proc_state state) 390 | { 391 | link_t *l; 392 | 393 | for (l = pids->head; l; l = l->next) { 394 | proc_info_t *p = (proc_info_t *)l->data; 395 | 396 | if (p->is_thread) 397 | continue; 398 | 399 | if (cpustat_get_by_proc(p, state) < 0) 400 | return -1; 401 | } 402 | return 0; 403 | } 404 | 405 | /* 406 | * cpustat_init() 407 | * initialize cpustat lists 408 | */ 409 | void cpustat_init(void) 410 | { 411 | list_init(&cpustat_info_start); 412 | list_init(&cpustat_info_finish); 413 | } 414 | 415 | /* 416 | * cpustat_cleanup() 417 | * free cpustat lists 418 | */ 419 | void cpustat_cleanup(void) 420 | { 421 | list_free(&cpustat_info_start, free); 422 | list_free(&cpustat_info_finish, free); 423 | } 424 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /health-check.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * Copyright (C) 2021-2025 Colin Ian King 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | * 19 | * Author: Colin Ian King 20 | */ 21 | #define _GNU_SOURCE 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include "list.h" 44 | #include "json.h" 45 | #include "pid.h" 46 | #include "proc.h" 47 | #include "syscall.h" 48 | #include "timeval.h" 49 | #include "fnotify.h" 50 | #include "cpustat.h" 51 | #include "mem.h" 52 | #include "net.h" 53 | #include "ctxt-switch.h" 54 | #include "health-check.h" 55 | 56 | #define APP_NAME "health-check" 57 | 58 | #define DURATION_RUN_FOREVER (0.0) 59 | 60 | static bool caught_sigint = false; 61 | volatile bool keep_running = true; 62 | int opt_flags; 63 | long int opt_max_syscalls = 1000000; 64 | 65 | /* 66 | * pid_max_digits() 67 | * determine (or guess) maximum digits of pids 68 | */ 69 | int pid_max_digits(void) 70 | { 71 | static int max_digits; 72 | ssize_t n; 73 | int fd; 74 | const int default_digits = 6; 75 | const int min_digits = 5; 76 | char buf[32]; 77 | 78 | if (max_digits) 79 | goto ret; 80 | 81 | max_digits = default_digits; 82 | fd = open("/proc/sys/kernel/pid_max", O_RDONLY); 83 | if (fd < 0) 84 | goto ret; 85 | n = read(fd, buf, sizeof(buf) - 1); 86 | (void)close(fd); 87 | if (n < 0) 88 | goto ret; 89 | 90 | buf[n] = '\0'; 91 | max_digits = 0; 92 | while ((max_digits < n) && (buf[max_digits] >= '0') && (buf[max_digits] <= '9')) 93 | max_digits++; 94 | if (max_digits < min_digits) 95 | max_digits = min_digits; 96 | ret: 97 | return max_digits; 98 | } 99 | 100 | /* 101 | * handle_sig() 102 | * catch signal, stop program 103 | */ 104 | static void handle_sig(int dummy) 105 | { 106 | (void)dummy; /* Stop unused parameter warning with -Wextra */ 107 | 108 | keep_running = false; 109 | caught_sigint = true; 110 | } 111 | 112 | /* 113 | * health_check_exit() 114 | * exit and set timer stat to 0 115 | */ 116 | void health_check_exit(const int status) 117 | { 118 | exit(status); 119 | } 120 | 121 | /* 122 | * health_check_out_of_memory(); 123 | * report out of memory condition 124 | */ 125 | void health_check_out_of_memory(const char *msg) 126 | { 127 | fprintf(stderr, "Out of memory: %s.\n", msg); 128 | } 129 | 130 | /* 131 | * show_usage() 132 | * show how to use 133 | */ 134 | static void show_usage(void) 135 | { 136 | printf("%s, version %s\n\n", APP_NAME, VERSION); 137 | printf("Usage: %s [options] [command [options]]\n", APP_NAME); 138 | printf("Options are:\n"); 139 | printf(" -b brief (terse) output\n"); 140 | printf(" -c find all child processes on start-up\n"); 141 | printf(" (only useful with -p option)\n"); 142 | printf(" -d specify the analysis duration in seconds\n"); 143 | printf(" (default is 60 seconds)\n"); 144 | printf(" -f follow fork/vfork/clone system calls\n"); 145 | printf(" -h show this help\n"); 146 | printf(" -p pid[,pid] specify process id(s) or process name(s) to be traced\n"); 147 | printf(" -m max specify maximum number of system calls to trace\n"); 148 | printf(" (default is 1000000)\n"); 149 | #ifdef JSON_OUTPUT 150 | printf(" -o file output results to a json data file\n"); 151 | #endif 152 | printf(" -r resolve IP addresses\n"); 153 | printf(" -u user run command as a specified user\n"); 154 | printf(" -v verbose output\n"); 155 | #if FNOTIFY_SUPPORTED 156 | printf(" -w monitor wakelock count\n"); 157 | #endif 158 | printf(" -W monitor wakelock usage (has overhead)\n"); 159 | 160 | health_check_exit(EXIT_SUCCESS); 161 | } 162 | 163 | /* 164 | * parse_pid_list() 165 | * parse list of process IDs or process names, 166 | * collect process info in pids list 167 | */ 168 | static int parse_pid_list(char *arg, list_t *pids) 169 | { 170 | char *str, *token; 171 | 172 | for (str = arg; (token = strtok(str, ",")) != NULL; str = NULL) { 173 | if (isdigit(token[0])) { 174 | proc_info_t *p; 175 | pid_t pid; 176 | 177 | errno = 0; 178 | pid = strtol(token, NULL, 10); 179 | if (errno) { 180 | fprintf(stderr, "Invalid pid specified.\n"); 181 | return -1; 182 | } 183 | if ((p = proc_cache_find_by_pid(pid)) == NULL) { 184 | fprintf(stderr, "Cannot find process with PID %i.\n", pid); 185 | return -1; 186 | } 187 | if (proc_pids_add_proc(pids, p) < 0) 188 | return -1; 189 | } else { 190 | if (proc_cache_find_by_procname(pids, token) < 0) { 191 | return -1; 192 | } 193 | } 194 | } 195 | 196 | return 0; 197 | } 198 | 199 | #ifdef JSON_OUTPUT 200 | /* 201 | * json_write() 202 | * dump out collected JSON data 203 | */ 204 | static int json_write(json_object *obj, const char *filename) 205 | { 206 | const char *str; 207 | FILE *fp; 208 | 209 | if (obj == NULL) { 210 | fprintf(stderr, "Cannot create JSON log, no JSON data.\n"); 211 | return -1; 212 | } 213 | 214 | #ifdef JSON_C_TO_STRING_PRETTY 215 | str = json_object_to_json_string_ext( 216 | obj, JSON_C_TO_STRING_PRETTY); 217 | #else 218 | str = json_object_to_json_string(obj); 219 | #endif 220 | if (str == NULL) { 221 | fprintf(stderr, "Cannot turn JSON object to text for JSON output.\n"); 222 | return -1; 223 | } 224 | if ((fp = fopen(filename, "w")) == NULL) { 225 | fprintf(stderr, "Cannot create JSON log file %s.\n", filename); 226 | return -1; 227 | } 228 | 229 | fprintf(fp, "%s", str); 230 | (void)fclose(fp); 231 | json_object_put(obj); 232 | 233 | return 0; 234 | } 235 | #endif 236 | 237 | /* 238 | * exec_executable() 239 | * exec a program 240 | */ 241 | static pid_t exec_executable(const char *opt_username, const char *path, char **argv) 242 | { 243 | uid_t uid; 244 | gid_t gid; 245 | pid_t pid; 246 | 247 | pid = fork(); 248 | if (pid < 0) { 249 | fprintf(stderr, "Cannot fork to run %s.\n", path); 250 | exit(EXIT_FAILURE); 251 | } 252 | if (pid != 0) 253 | return pid; /* We are the tracer, return tracee pid */ 254 | 255 | /* Traced process starts here */ 256 | if (opt_username) { 257 | struct passwd *pw; 258 | 259 | if ((pw = getpwnam(opt_username)) == NULL) { 260 | fprintf(stderr, "Username %s does not exist.\n", opt_username); 261 | exit(EXIT_FAILURE); 262 | } 263 | uid = pw->pw_uid; 264 | gid = pw->pw_gid; 265 | 266 | if (initgroups(opt_username, gid) < 0) { 267 | fprintf(stderr, "initgroups failed user on %s\n", opt_username); 268 | exit(EXIT_FAILURE); 269 | } 270 | if (setregid(gid, gid) < 0) { 271 | fprintf(stderr, "setregid failed\n"); 272 | exit(EXIT_FAILURE); 273 | } 274 | if (setreuid(uid, uid) < 0) { 275 | fprintf(stderr, "setreuid failed\n"); 276 | exit(EXIT_FAILURE); 277 | } 278 | } else { 279 | if (geteuid() != 0) { 280 | uid = getuid(); 281 | if (setreuid(uid, uid) < 0) { 282 | fprintf(stderr, "setreuid failed\n"); 283 | exit(EXIT_FAILURE); 284 | } 285 | } 286 | } 287 | 288 | /* Suspend ourself waiting for tracer */ 289 | kill(getpid(), SIGSTOP); 290 | execv(path, argv); 291 | 292 | printf("Failed to execv %s\n", path); 293 | exit(EXIT_FAILURE); 294 | } 295 | 296 | /* 297 | * is_executable() 298 | * check path to see if it is an executable image 299 | */ 300 | inline static int is_executable(const char *path) 301 | { 302 | struct stat buf; 303 | 304 | return ((stat(path, &buf) == 0) && 305 | (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) && 306 | S_ISREG(buf.st_mode)); 307 | } 308 | 309 | /* 310 | * find_executable() 311 | * find executable given a filename 312 | */ 313 | static const char *find_executable(const char *filename) 314 | { 315 | static char path[PATH_MAX]; 316 | size_t filenamelen = strlen(filename); 317 | 318 | if (strchr(filename, '/')) { 319 | /* Given a full path, try this */ 320 | if (strlen(filename) > sizeof(path) - 1) { 321 | fprintf(stderr, "executable name too long.\n"); 322 | health_check_exit(EXIT_FAILURE); 323 | } 324 | strlcpy(path, filename, sizeof(path) - 1); 325 | if (is_executable(path)) 326 | return path; 327 | else 328 | fprintf(stderr, "%s is not a valid executable program\n", filename); 329 | } else { 330 | /* Try and find it in $PATH */ 331 | size_t skiplen; 332 | char *p; 333 | 334 | for (p = getenv("PATH"); p && *p; p += skiplen) { 335 | size_t len, pathlen; 336 | char *ptr = strchr(p, ':'); 337 | 338 | if (ptr) { 339 | len = ptr - p; 340 | skiplen = len + 1; 341 | } else { 342 | skiplen = len = strlen(p); 343 | } 344 | 345 | if (len) { 346 | if (len > sizeof(path) - 1) 347 | continue; /* Too long */ 348 | else { 349 | pathlen = len; 350 | strlcpy(path, p, pathlen); 351 | } 352 | } else { 353 | if (getcwd(p, PATH_MAX) == NULL) 354 | continue; /* Silently ignore */ 355 | pathlen = strlen(p); 356 | } 357 | if (pathlen + filenamelen + 2 > sizeof(path)) 358 | continue; 359 | 360 | if ((pathlen > 0) && (path[pathlen - 1] != '/')) { 361 | if (pathlen >= sizeof(path) - 1) 362 | continue; /* Too big! */ 363 | path[pathlen++] = '/'; 364 | } 365 | 366 | /* is Filename + '/' + pathname + EOS too big? */ 367 | if (filenamelen + pathlen >= sizeof(path) - 2) 368 | continue; 369 | strcpy(path + pathlen, filename); 370 | 371 | if (is_executable(path)) 372 | return path; 373 | } 374 | fprintf(stderr, "Cannot find %s in $PATH\n", filename); 375 | } 376 | return NULL; /* No hope */ 377 | } 378 | 379 | int main(int argc, char **argv) 380 | { 381 | double actual_duration, opt_duration_secs = 60.0; 382 | struct timeval tv_start, tv_end, tv_now, duration; 383 | int ret, rc = EXIT_SUCCESS; 384 | #if FNOTIFY_SUPPORTED 385 | int fan_fd = 0; 386 | #endif 387 | list_t pids; 388 | link_t *l; 389 | void *buffer = NULL; 390 | char *opt_username = NULL; 391 | #ifdef JSON_OUTPUT 392 | char *opt_json_file = NULL; 393 | json_object *json_obj = NULL; 394 | #endif 395 | json_object *json_tests = NULL; 396 | struct sigaction new_action, old_action; 397 | 398 | list_init(&pids); 399 | proc_cache_init(); 400 | 401 | /* Get a cached view of current process state */ 402 | if (proc_cache_get() < 0) 403 | goto out; 404 | if (proc_cache_get_pthreads() < 0) 405 | goto out; 406 | 407 | sigaction(SIGCHLD, NULL, &old_action); 408 | if (old_action.sa_handler != SIG_DFL) { 409 | new_action.sa_handler = SIG_DFL; 410 | sigemptyset(&new_action.sa_mask); 411 | new_action.sa_flags = 0; 412 | sigaction(SIGCHLD, &new_action, NULL); 413 | } 414 | 415 | for (;;) { 416 | int c = getopt(argc, argv, "+bcd:fhp:m:o:ru:vwW"); 417 | if (c == -1) 418 | break; 419 | switch (c) { 420 | case 'b': 421 | opt_flags |= OPT_BRIEF; 422 | break; 423 | case 'c': 424 | opt_flags |= OPT_GET_CHILDREN; 425 | break; 426 | case 'f': 427 | opt_flags |= OPT_FOLLOW_NEW_PROCS; 428 | break; 429 | case 'h': 430 | show_usage(); 431 | break; 432 | case 'p': 433 | if (parse_pid_list(optarg, &pids) < 0) 434 | health_check_exit(EXIT_FAILURE); 435 | break; 436 | case 'd': 437 | opt_duration_secs = atof(optarg); 438 | opt_flags |= OPT_DURATION; 439 | break; 440 | case 'm': 441 | errno = 0; 442 | opt_max_syscalls = strtol(optarg, NULL, 10); 443 | if (errno) { 444 | fprintf(stderr, "Invalid maximum number of system calls specified.\n"); 445 | health_check_exit(EXIT_FAILURE); 446 | } 447 | break; 448 | #ifdef JSON_OUTPUT 449 | case 'o': 450 | opt_json_file = optarg; 451 | break; 452 | #endif 453 | case 'r': 454 | opt_flags |= OPT_ADDR_RESOLVE; 455 | break; 456 | case 'u': 457 | opt_username = optarg; 458 | break; 459 | case 'v': 460 | opt_flags |= OPT_VERBOSE; 461 | break; 462 | #if FNOTIFY_SUPPORTED 463 | case 'w': 464 | opt_flags |= OPT_WAKELOCKS_LIGHT; 465 | break; 466 | #endif 467 | case 'W': 468 | opt_flags |= OPT_WAKELOCKS_HEAVY; 469 | break; 470 | default: 471 | show_usage(); 472 | } 473 | } 474 | 475 | if ((opt_flags & (OPT_VERBOSE | OPT_BRIEF)) == (OPT_VERBOSE | OPT_BRIEF)) { 476 | fprintf(stderr, "Cannot have verbose -v and brief -b flags together.\n"); 477 | health_check_exit(EXIT_FAILURE); 478 | } 479 | 480 | if ((getuid() !=0 ) || (geteuid() != 0)) { 481 | fprintf(stderr, "%s requires root trace processes\n", 482 | APP_NAME); 483 | health_check_exit(EXIT_FAILURE); 484 | } 485 | 486 | if (optind < argc) { 487 | const char *path; 488 | 489 | if (pids.head != NULL) { 490 | fprintf(stderr, "Cannot heath-check a program and provide pids to trace at same time\n"); 491 | health_check_exit(EXIT_FAILURE); 492 | } 493 | 494 | argv += optind; 495 | path = find_executable(argv[0]); 496 | if (path) { 497 | pid_t pid; 498 | proc_info_t *p; 499 | 500 | /* No duration given, so run until completion */ 501 | if (!(opt_flags & OPT_DURATION)) 502 | opt_duration_secs = DURATION_RUN_FOREVER; 503 | 504 | pid = exec_executable(opt_username, path, argv); 505 | if ((p = proc_cache_add(pid, 0, false)) == NULL) { 506 | fprintf(stderr, "Cannot find process with PID %i\n", pid); 507 | goto out; 508 | } 509 | free(p->cmdline); 510 | if ((p->cmdline = strdup(basename(path))) == NULL) { 511 | health_check_out_of_memory("cannot allocate process cmdline"); 512 | goto out; 513 | } 514 | if (proc_pids_add_proc(&pids, p) < 0) 515 | goto out; 516 | } else 517 | health_check_exit(EXIT_FAILURE); 518 | } 519 | 520 | if (pids.head == NULL) { 521 | fprintf(stderr, "Must provide one or more valid process IDs or name\n"); 522 | health_check_exit(EXIT_FAILURE); 523 | } 524 | for (l = pids.head; l; l = l->next) { 525 | proc_info_t *p = (proc_info_t *)l->data; 526 | if (!pid_exists(p->pid)) { 527 | fprintf(stderr, "Cannot check process %i, no such process pid\n", p->pid); 528 | health_check_exit(EXIT_FAILURE); 529 | } 530 | } 531 | if (opt_flags & OPT_GET_CHILDREN) 532 | if (pid_list_get_children(&pids) < 0) 533 | goto out; 534 | 535 | if (opt_duration_secs < 0.0) { 536 | fprintf(stderr, "Duration must positive.\n"); 537 | health_check_exit(EXIT_FAILURE); 538 | } 539 | 540 | net_connection_init(); 541 | if (net_connection_pids(&pids) < 0) 542 | goto out; 543 | 544 | #ifdef JSON_OUTPUT 545 | if (opt_json_file) { 546 | if ((json_obj = json_object_new_object()) == NULL) { 547 | health_check_out_of_memory("cannot allocate JSON object"); 548 | goto out; 549 | } 550 | if ((json_tests = json_object_new_object()) == NULL) { 551 | health_check_out_of_memory("cannot allocate JSON array"); 552 | goto out; 553 | } 554 | json_object_object_add(json_obj, "health-check", json_tests); 555 | } 556 | #endif 557 | #if FNOTIFY_SUPPORTED 558 | fnotify_init(); 559 | if ((fan_fd = fnotify_event_init()) < 0) 560 | goto out; 561 | #endif 562 | 563 | ret = posix_memalign(&buffer, 4096, 4096); 564 | if (ret != 0 || buffer == NULL) { 565 | health_check_out_of_memory("cannot allocate 4K aligned buffer"); 566 | goto out; 567 | } 568 | 569 | new_action.sa_handler = handle_sig; 570 | sigemptyset(&new_action.sa_mask); 571 | new_action.sa_flags = 0; 572 | sigaction(SIGINT, &new_action, &old_action); 573 | sigaction(SIGUSR1, &new_action, &old_action); 574 | #if SYSCALL_SUPPORTED 575 | syscall_init(); 576 | syscall_trace_proc(&pids); 577 | #endif 578 | mem_init(); 579 | cpustat_init(); 580 | ctxt_switch_init(); 581 | 582 | duration.tv_sec = (time_t)opt_duration_secs; 583 | duration.tv_usec = (suseconds_t)(opt_duration_secs * 1000000.0) - (duration.tv_sec * 1000000); 584 | 585 | gettimeofday(&tv_start, NULL); 586 | tv_end = timeval_add(&tv_start, &duration); 587 | 588 | if (cpustat_get_all_pids(&pids, PROC_START) < 0) 589 | goto out; 590 | if (mem_get_all_pids(&pids, PROC_START) < 0) 591 | goto out; 592 | if (ctxt_switch_get_all_pids(&pids, PROC_START) < 0) 593 | goto out; 594 | 595 | gettimeofday(&tv_now, NULL); 596 | duration = timeval_sub(&tv_end, &tv_now); 597 | 598 | while ((procs_traced > 0) && 599 | keep_running && 600 | (FLOAT_CMP(opt_duration_secs, DURATION_RUN_FOREVER)|| 601 | timeval_to_double(&duration) > 0.0)) { 602 | 603 | struct timeval *duration_ptr = 604 | FLOAT_CMP(opt_duration_secs, DURATION_RUN_FOREVER) ? NULL : &duration; 605 | #if FNOTIFY_SUPPORTED 606 | fd_set rfds; 607 | FD_ZERO(&rfds); 608 | FD_SET(fan_fd, &rfds); 609 | 610 | ret = select(fan_fd + 1, &rfds, NULL, NULL, duration_ptr); 611 | if (ret < 0) { 612 | if (errno != EINTR) { 613 | fprintf(stderr, "Select failed: %s\n", strerror(errno)); 614 | gettimeofday(&tv_now, NULL); 615 | goto out; 616 | } 617 | } else if (ret > 0) { 618 | if (FD_ISSET(fan_fd, &rfds)) { 619 | ssize_t len; 620 | 621 | if ((len = read(fan_fd, (void *)buffer, 4096)) > 0) { 622 | struct fanotify_event_metadata *metadata; 623 | metadata = (struct fanotify_event_metadata *)buffer; 624 | 625 | while (FAN_EVENT_OK(metadata, len)) { 626 | if (fnotify_event_add(&pids, metadata) < 0) 627 | goto out; 628 | metadata = FAN_EVENT_NEXT(metadata, len); 629 | } 630 | } 631 | } 632 | } 633 | #else 634 | ret = select(0, NULL, NULL, NULL, duration_ptr); 635 | if (ret < 0) { 636 | if (errno != EINTR) { 637 | fprintf(stderr, "Select failed: %s\n", strerror(errno)); 638 | gettimeofday(&tv_now, NULL); 639 | goto out; 640 | } 641 | } 642 | #endif 643 | gettimeofday(&tv_now, NULL); 644 | duration = timeval_sub(&tv_end, &tv_now); 645 | } 646 | keep_running = false; 647 | 648 | duration = timeval_sub(&tv_now, &tv_start); 649 | actual_duration = timeval_to_double(&duration); 650 | 651 | if (cpustat_get_all_pids(&pids, PROC_FINISH) < 0) 652 | goto out; 653 | if (mem_get_all_pids(&pids, PROC_FINISH) < 0) 654 | goto out; 655 | if (ctxt_switch_get_all_pids(&pids, PROC_FINISH) < 0) 656 | goto out; 657 | #if SYSCALL_SUPPORTED 658 | if (syscall_stop() < 0) 659 | goto out; 660 | #endif 661 | 662 | sigaction(SIGINT, &old_action, NULL); 663 | 664 | if (caught_sigint) 665 | putchar('\n'); 666 | 667 | cpustat_dump_diff(json_tests, actual_duration); 668 | pagefault_dump_diff(json_tests, actual_duration); 669 | ctxt_switch_dump_diff(json_tests, actual_duration); 670 | #if FNOTIFY_SUPPORTED 671 | fnotify_dump_events(json_tests, actual_duration, &pids); 672 | #endif 673 | #if SYSCALL_SUPPORTED 674 | syscall_dump_hashtable(json_tests, actual_duration); 675 | syscall_dump_pollers(json_tests, actual_duration); 676 | syscall_dump_sync(json_tests, actual_duration); 677 | syscall_dump_inotify(json_tests, actual_duration); 678 | syscall_dump_execve(json_tests, actual_duration); 679 | #endif 680 | if (mem_dump_diff(json_tests, actual_duration) < 0) 681 | goto out; 682 | mem_dump_brk(json_tests, actual_duration); 683 | mem_dump_mmap(json_tests, actual_duration); 684 | net_connection_dump(json_tests, actual_duration); 685 | 686 | #if FNOTIFY_SUPPORTED 687 | if (opt_flags & OPT_WAKELOCKS_LIGHT) 688 | fnotify_dump_wakelocks(json_tests, actual_duration); 689 | #endif 690 | 691 | #if SYSCALL_SUPPORTED 692 | if (opt_flags & OPT_WAKELOCKS_HEAVY) 693 | syscall_dump_wakelocks(json_tests, actual_duration, &pids); 694 | #endif 695 | 696 | if (actual_duration < 5.0) 697 | printf("Analysis ran for just %.4f seconds, so rate calculations may be misleading\n", 698 | actual_duration); 699 | 700 | #ifdef JSON_OUTPUT 701 | if (json_obj) 702 | json_write(json_obj, opt_json_file); 703 | #endif 704 | 705 | out: 706 | keep_running = false; /* Force stop if we aborted */ 707 | mem_cleanup(); 708 | net_connection_cleanup(); 709 | #if SYSCALL_SUPPORTED 710 | syscall_cleanup(); 711 | #endif 712 | cpustat_cleanup(); 713 | ctxt_switch_cleanup(); 714 | #if FNOTIFY_SUPPORTED 715 | fnotify_cleanup(); 716 | #endif 717 | free(buffer); 718 | proc_cache_cleanup(); 719 | list_free(&pids, NULL); 720 | 721 | health_check_exit(rc); 722 | } 723 | -------------------------------------------------------------------------------- /fnotify.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * Copyright (C) 2021-2025 Colin Ian King 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | * 19 | * Author: Colin Ian King 20 | */ 21 | #define _GNU_SOURCE 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "list.h" 40 | #include "json.h" 41 | #include "proc.h" 42 | #include "fnotify.h" 43 | #include "health-check.h" 44 | 45 | static list_t fnotify_files, fnotify_wakelocks; 46 | 47 | /* 48 | * fnotify_event_init() 49 | * initialize fnotify 50 | */ 51 | int fnotify_event_init(void) 52 | { 53 | int fan_fd; 54 | int ret; 55 | FILE* mounts; 56 | struct mntent* mount; 57 | 58 | if ((fan_fd = fanotify_init (0, 0)) < 0) { 59 | fprintf(stderr, "Cannot initialize fanotify: %s.\n", 60 | strerror(errno)); 61 | return -1; 62 | } 63 | 64 | ret = fanotify_mark(fan_fd, FAN_MARK_ADD | FAN_MARK_MOUNT, 65 | FAN_ACCESS| FAN_MODIFY | FAN_OPEN | FAN_CLOSE | 66 | FAN_ONDIR | FAN_EVENT_ON_CHILD, AT_FDCWD, "/"); 67 | if (ret < 0) { 68 | fprintf(stderr, "Cannot add fanotify watch on /: %s.\n", 69 | strerror(errno)); 70 | } 71 | 72 | if ((mounts = setmntent("/proc/self/mounts", "r")) == NULL) { 73 | fprintf(stderr, "Cannot get mount points.\n"); 74 | return -1; 75 | } 76 | 77 | while ((mount = getmntent (mounts)) != NULL) { 78 | /* 79 | if (access (mount->mnt_fsname, F_OK) != 0) 80 | continue; 81 | */ 82 | 83 | ret = fanotify_mark(fan_fd, FAN_MARK_ADD | FAN_MARK_MOUNT, 84 | FAN_ACCESS| FAN_MODIFY | FAN_OPEN | FAN_CLOSE | 85 | FAN_ONDIR | FAN_EVENT_ON_CHILD, AT_FDCWD, 86 | mount->mnt_dir); 87 | if ((ret < 0) && (errno != ENOENT)) { 88 | continue; 89 | } 90 | } 91 | endmntent (mounts); 92 | 93 | /* Track /sys/power ops for wakealarm analysis */ 94 | (void)fanotify_mark(fan_fd, FAN_MARK_ADD, 95 | FAN_ACCESS | FAN_MODIFY, AT_FDCWD, 96 | "/sys/power/wake_lock"); 97 | (void)fanotify_mark(fan_fd, FAN_MARK_ADD, 98 | FAN_ACCESS | FAN_MODIFY, AT_FDCWD, 99 | "/sys/power/wake_unlock"); 100 | 101 | return fan_fd; 102 | } 103 | 104 | /* 105 | * fnotify_event_free() 106 | * free event info 107 | */ 108 | static void fnotify_event_free(void *data) 109 | { 110 | fnotify_fileinfo_t *fileinfo = (fnotify_fileinfo_t *)data; 111 | 112 | free(fileinfo->filename); 113 | free(fileinfo); 114 | } 115 | 116 | /* 117 | * fnotify_get_filename() 118 | * look up a in-use file descriptor from a given pid 119 | * and find the associated filename 120 | */ 121 | char *fnotify_get_filename(const pid_t pid, const int fd) 122 | { 123 | char buf[256]; 124 | char path[PATH_MAX]; 125 | ssize_t len; 126 | char *filename; 127 | 128 | /* 129 | * With fnotifies, fd of the file is added to the process 130 | * fd array, so we just pick them up from /proc/self. Use 131 | * a pid of -1 for self 132 | */ 133 | if (pid == -1) 134 | snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd); 135 | else 136 | snprintf(buf, sizeof(buf), "/proc/%d/fd/%d", pid, fd); 137 | 138 | len = readlink(buf, path, sizeof(path)); 139 | if (len < 0) { 140 | struct stat statbuf; 141 | if (fstat(fd, &statbuf) < 0) 142 | filename = strdup("(unknown)"); 143 | else { 144 | snprintf(buf, sizeof(buf), "dev: %i:%i inode %ld", 145 | major(statbuf.st_dev), minor(statbuf.st_dev), statbuf.st_ino); 146 | filename = strdup(buf); 147 | } 148 | } else { 149 | /* 150 | * In an ideal world we should allocate the path 151 | * based on a lstat'd size, but because this can be 152 | * racey on has to re-check, which involves 153 | * re-allocing the buffer. Since we need to be 154 | * fast let's just fetch up to PATH_MAX-1 of data. 155 | */ 156 | path[len >= PATH_MAX ? PATH_MAX - 1 : len] = '\0'; 157 | filename = strdup(path); 158 | } 159 | return filename; 160 | } 161 | 162 | /* 163 | * fnotify_event_add() 164 | * add a new fnotify event 165 | */ 166 | int fnotify_event_add( 167 | const list_t *pids, 168 | const struct fanotify_event_metadata *metadata) 169 | { 170 | link_t *l; 171 | 172 | if ((metadata->fd == FAN_NOFD) && (metadata->fd < 0)) 173 | return 0; 174 | 175 | for (l = pids->head; l; l = l->next) { 176 | proc_info_t *p = (proc_info_t*)l->data; 177 | 178 | if (metadata->pid == p->pid) { 179 | char *filename = fnotify_get_filename(-1, metadata->fd); 180 | 181 | if (filename == NULL) { 182 | health_check_out_of_memory("allocating fnotify filename"); 183 | (void)close(metadata->fd); 184 | return -1; 185 | } 186 | if ((opt_flags & OPT_WAKELOCKS_LIGHT) && 187 | (metadata->mask & (FAN_MODIFY | FAN_CLOSE_WRITE)) && 188 | (!strcmp(filename, "/sys/power/wake_lock") || 189 | !strcmp(filename, "/sys/power/wake_unlock"))) { 190 | fnotify_wakelock_info_t *wakelock_info; 191 | link_t *wl; 192 | bool found = false; 193 | 194 | for (wl = fnotify_wakelocks.head; wl; wl = wl->next) { 195 | wakelock_info = (fnotify_wakelock_info_t *)wl->data; 196 | if (wakelock_info->proc == p) { 197 | found = true; 198 | break; 199 | } 200 | } 201 | 202 | if (!found) { 203 | if ((wakelock_info = calloc(1, sizeof(*wakelock_info))) == NULL) { 204 | health_check_out_of_memory("allocating wakelock information"); 205 | free(filename); 206 | (void)close(metadata->fd); 207 | return -1; 208 | } 209 | wakelock_info->proc = p; 210 | wakelock_info->locked = 0; 211 | wakelock_info->unlocked = 0; 212 | wakelock_info->total = 0; 213 | if (list_append(&fnotify_wakelocks, wakelock_info) == NULL) { 214 | free(filename); 215 | (void)close(metadata->fd); 216 | return -1; 217 | } 218 | } 219 | 220 | if (strcmp(filename, "/sys/power/wake_unlock")) 221 | wakelock_info->locked++; 222 | else 223 | wakelock_info->unlocked++; 224 | 225 | free(filename); 226 | wakelock_info->total++; 227 | } else { 228 | fnotify_fileinfo_t *fileinfo; 229 | link_t *fl; 230 | bool found = false; 231 | 232 | for (fl = fnotify_files.head; fl; fl = fl->next) { 233 | fileinfo = (fnotify_fileinfo_t *)fl->data; 234 | if ((metadata->mask == fileinfo->mask) && 235 | (!strcmp(fileinfo->filename, filename))) { 236 | found = true; 237 | break; 238 | } 239 | } 240 | 241 | if (!found) { 242 | if ((fileinfo = calloc(1, sizeof(*fileinfo))) == NULL) { 243 | health_check_out_of_memory("allocating fnotify file information"); 244 | free(filename); 245 | (void)close(metadata->fd); 246 | return -1; 247 | } 248 | fileinfo->filename = filename; 249 | fileinfo->mask = metadata->mask; 250 | fileinfo->proc = p; 251 | fileinfo->count = 0; 252 | if (list_append(&fnotify_files, fileinfo) == NULL) { 253 | free(filename); 254 | (void)close(metadata->fd); 255 | return -1; 256 | } 257 | } else { 258 | free(filename); 259 | } 260 | fileinfo->count++; 261 | } 262 | } 263 | } 264 | (void)close(metadata->fd); 265 | 266 | return 0; 267 | } 268 | 269 | /* 270 | * fnotify_event_cmp_count() 271 | * for list sorting, compare counts 272 | */ 273 | static int fnotify_event_cmp_count(const void *data1, const void *data2) 274 | { 275 | const fnotify_fileinfo_t *info1 = (const fnotify_fileinfo_t *)data1; 276 | const fnotify_fileinfo_t *info2 = (const fnotify_fileinfo_t *)data2; 277 | 278 | return info2->count - info1->count; 279 | } 280 | 281 | /* 282 | * fnotify_event_cmp_io_ops() 283 | * for list sorting, compare io op totals 284 | */ 285 | static int fnotify_event_cmp_io_ops(const void *data1, const void *data2) 286 | { 287 | const io_ops_t *io_ops1 = (const io_ops_t *)data1; 288 | const io_ops_t *io_ops2 = (const io_ops_t *)data2; 289 | 290 | return io_ops2->total - io_ops1->total; 291 | } 292 | 293 | /* 294 | * fnotify_wakelock_cmp_count() 295 | * for list sorting, compare wakelock totals 296 | */ 297 | static int fnotify_wakelock_cmp_count(const void *data1, const void *data2) 298 | { 299 | const fnotify_wakelock_info_t *w1 = (const fnotify_wakelock_info_t *)data1; 300 | const fnotify_wakelock_info_t *w2 = (const fnotify_wakelock_info_t *)data2; 301 | 302 | return w2->total - w1->total; 303 | } 304 | 305 | /* 306 | * fnotify_mask_to_str() 307 | * convert fnotify mask to readable string 308 | */ 309 | static const char *fnotify_mask_to_str(const int mask) 310 | { 311 | static char modes[5]; 312 | int i = 0; 313 | 314 | if (mask & FAN_OPEN) 315 | modes[i++] = 'O'; 316 | if (mask & (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE)) 317 | modes[i++] = 'C'; 318 | if (mask & FAN_ACCESS) 319 | modes[i++] = 'R'; 320 | if (mask & (FAN_MODIFY | FAN_CLOSE_WRITE)) 321 | modes[i++] = 'W'; 322 | modes[i] = '\0'; 323 | 324 | return modes; 325 | } 326 | 327 | /* 328 | * fnotify_dump_files() 329 | * dump out fnotify file access stats 330 | */ 331 | static void fnotify_dump_files( 332 | json_object *j_tests, 333 | const double duration) 334 | { 335 | list_t sorted; 336 | link_t *l; 337 | int count; 338 | uint64_t total; 339 | const int pid_size = pid_max_digits(); 340 | 341 | #ifndef JSON_OUTPUT 342 | (void)j_tests; 343 | (void)duration; 344 | #endif 345 | list_init(&sorted); 346 | for (l = fnotify_files.head; l; l = l->next) { 347 | fnotify_fileinfo_t *info = (fnotify_fileinfo_t *)l->data; 348 | if (list_add_ordered(&sorted, info, fnotify_event_cmp_count) == NULL) 349 | goto out; 350 | } 351 | if (fnotify_files.head && !(opt_flags & OPT_BRIEF)) { 352 | printf(" %*s Process Count Op Filename\n", 353 | pid_size, "PID"); 354 | for (count = 0, total = 0, l = sorted.head; l; l = l->next) { 355 | fnotify_fileinfo_t *info = (fnotify_fileinfo_t *)l->data; 356 | 357 | printf(" %*d %-20.20s %6" PRIu64 " %4s %s\n", 358 | pid_size, info->proc->pid, 359 | info->proc->cmdline, 360 | info->count, 361 | fnotify_mask_to_str(info->mask), 362 | info->filename); 363 | total += info->count; 364 | count++; 365 | } 366 | if (count > 1) 367 | printf(" %-25.25s%8" PRIu64 "\n", "Total", total); 368 | printf(" Op: O=Open, R=Read, W=Write, C=Close\n\n"); 369 | } 370 | 371 | #ifdef JSON_OUTPUT 372 | if (j_tests) { 373 | json_object *j_fnotify_test, *j_accesses, *j_access; 374 | 375 | if ((j_fnotify_test = j_obj_new_obj()) == NULL) 376 | goto out; 377 | j_obj_obj_add(j_tests, "file-access", j_fnotify_test); 378 | if ((j_accesses = j_obj_new_array()) == NULL) 379 | goto out; 380 | j_obj_obj_add(j_fnotify_test, "file-access-per-process", j_accesses); 381 | 382 | for (total = 0, l = sorted.head; l; l = l->next) { 383 | fnotify_fileinfo_t *info = (fnotify_fileinfo_t *)l->data; 384 | 385 | if ((j_access = j_obj_new_obj()) == NULL) 386 | goto out; 387 | j_obj_new_int32_add(j_access, "pid", info->proc->pid); 388 | j_obj_new_int32_add(j_access, "ppid", info->proc->ppid); 389 | j_obj_new_int32_add(j_access, "is-thread", info->proc->is_thread); 390 | j_obj_new_string_add(j_access, "name", info->proc->cmdline); 391 | j_obj_new_string_add(j_access, "access-mode", fnotify_mask_to_str(info->mask)); 392 | j_obj_new_string_add(j_access, "filename", info->filename); 393 | j_obj_new_int64_add(j_access, "accesses-count", info->count); 394 | j_obj_new_double_add(j_access, "accesses-count-rate", (double)info->count / duration); 395 | j_obj_array_add(j_accesses, j_access); 396 | total += info->count; 397 | } 398 | if ((j_access = j_obj_new_obj()) == NULL) 399 | goto out; 400 | j_obj_obj_add(j_fnotify_test, "file-access-total", j_access); 401 | j_obj_new_int64_add(j_access, "access-count-total", total); 402 | j_obj_new_double_add(j_access, "access-count-total-rate", (double)total / duration); 403 | } 404 | #endif 405 | 406 | out: 407 | list_free(&sorted, NULL); 408 | 409 | } 410 | 411 | /* 412 | * fnotify_dump_io_ops() 413 | * dump out fnotify I/O operations 414 | */ 415 | static void fnotify_dump_io_ops( 416 | json_object *j_tests, 417 | const double duration, 418 | const list_t *pids) 419 | { 420 | link_t *l, *lp; 421 | list_t sorted; 422 | int count; 423 | uint64_t read_total, write_total, open_total, close_total; 424 | #ifndef JSON_OUTPUT 425 | (void)j_tests; 426 | #endif 427 | 428 | list_init(&sorted); 429 | for (lp = pids->head; lp; lp = lp->next) { 430 | proc_info_t *p = (proc_info_t*)lp->data; 431 | io_ops_t io_ops; 432 | 433 | memset(&io_ops, 0, sizeof(io_ops)); 434 | io_ops.proc = p; 435 | 436 | for (l = fnotify_files.head; l; l = l->next) { 437 | fnotify_fileinfo_t *info = (fnotify_fileinfo_t *)l->data; 438 | 439 | if (info->proc->pid != p->pid) 440 | continue; 441 | if (info->mask & FAN_OPEN) 442 | io_ops.open_total += info->count; 443 | if (info->mask & (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE)) 444 | io_ops.close_total += info->count; 445 | if (info->mask & FAN_ACCESS) 446 | io_ops.read_total += info->count; 447 | if (info->mask & (FAN_MODIFY | FAN_CLOSE_WRITE)) 448 | io_ops.write_total += info->count; 449 | } 450 | io_ops.total = io_ops.open_total + io_ops.close_total + 451 | io_ops.read_total + io_ops.write_total; 452 | 453 | if (io_ops.total) { 454 | io_ops_t *new_io_ops; 455 | 456 | if ((new_io_ops = calloc(1, sizeof(*new_io_ops))) == NULL) { 457 | health_check_out_of_memory("allocating fnotify I/O ops information"); 458 | goto out; 459 | } 460 | *new_io_ops = io_ops; 461 | if (list_add_ordered(&sorted, new_io_ops, fnotify_event_cmp_io_ops) == NULL) { 462 | free(new_io_ops); 463 | goto out; 464 | } 465 | } 466 | } 467 | 468 | open_total = close_total = read_total = write_total = 0; 469 | if (fnotify_files.head) { 470 | if (opt_flags & OPT_BRIEF) { 471 | for (l = sorted.head; l; l = l->next) { 472 | io_ops_t *io_ops = (io_ops_t *)l->data; 473 | open_total += io_ops->open_total; 474 | close_total += io_ops->close_total; 475 | read_total += io_ops->read_total; 476 | write_total += io_ops->write_total; 477 | } 478 | printf(" I/O Operations per second: %.2f open, %.2f close, %.2f read, %.2f write\n", 479 | (double)open_total / duration, 480 | (double)close_total / duration, 481 | (double)read_total / duration, 482 | (double)write_total / duration); 483 | printf("\n"); 484 | } else { 485 | const int pid_size = pid_max_digits(); 486 | 487 | printf("File I/O Operations per second:\n"); 488 | printf(" %*s Process Open Close Read Write\n", 489 | pid_size, "PID"); 490 | for (count = 0, l = sorted.head; l; l = l->next) { 491 | io_ops_t *io_ops = (io_ops_t *)l->data; 492 | 493 | printf(" %*d %-20.20s %7.2f %7.2f %7.2f %7.2f\n", 494 | pid_size, io_ops->proc->pid, 495 | io_ops->proc->cmdline, 496 | (double)io_ops->open_total / duration, 497 | (double)io_ops->close_total / duration, 498 | (double)io_ops->read_total / duration, 499 | (double)io_ops->write_total / duration); 500 | 501 | open_total += io_ops->open_total; 502 | close_total += io_ops->close_total; 503 | read_total += io_ops->read_total; 504 | write_total += io_ops->write_total; 505 | count++; 506 | } 507 | if (count > 1) { 508 | printf(" %-27.27s%7.2f %7.2f %7.2f %7.2f\n", 509 | "Total", 510 | (double)open_total / duration, 511 | (double)close_total / duration, 512 | (double)read_total / duration, 513 | (double)write_total / duration); 514 | } 515 | printf("\n"); 516 | } 517 | } 518 | 519 | #ifdef JSON_OUTPUT 520 | if (j_tests) { 521 | json_object *j_fnotify_test, *j_io_ops, *j_io_op; 522 | 523 | if ((j_fnotify_test = j_obj_new_obj()) == NULL) 524 | goto out; 525 | j_obj_obj_add(j_tests, "file-io-operations", j_fnotify_test); 526 | if ((j_io_ops = j_obj_new_array()) == NULL) 527 | goto out; 528 | j_obj_obj_add(j_fnotify_test, "file-io-operations-per-process", j_io_ops); 529 | 530 | for (l = sorted.head; l; l = l->next) { 531 | io_ops_t *io_ops = (io_ops_t *)l->data; 532 | 533 | if ((j_io_op = j_obj_new_obj()) == NULL) 534 | goto out; 535 | j_obj_new_int32_add(j_io_op, "pid", io_ops->proc->pid); 536 | j_obj_new_int32_add(j_io_op, "ppid", io_ops->proc->ppid); 537 | j_obj_new_int32_add(j_io_op, "is-thread", io_ops->proc->is_thread); 538 | j_obj_new_string_add(j_io_op, "name", io_ops->proc->cmdline); 539 | j_obj_new_int64_add(j_io_op, "open-call-count", io_ops->open_total); 540 | j_obj_new_int64_add(j_io_op, "close-call-count", io_ops->close_total); 541 | j_obj_new_int64_add(j_io_op, "read-call-count", io_ops->read_total); 542 | j_obj_new_int64_add(j_io_op, "write-call-count", io_ops->write_total); 543 | 544 | j_obj_new_double_add(j_io_op, "open-call-rate", (double)io_ops->open_total / duration); 545 | j_obj_new_double_add(j_io_op, "close-call-rate", (double)io_ops->close_total / duration); 546 | j_obj_new_double_add(j_io_op, "read-call-rate", (double)io_ops->read_total / duration); 547 | j_obj_new_double_add(j_io_op, "write-call-rate", (double)io_ops->write_total / duration); 548 | j_obj_array_add(j_io_ops, j_io_op); 549 | } 550 | if ((j_io_op = j_obj_new_obj()) == NULL) 551 | goto out; 552 | j_obj_obj_add(j_fnotify_test, "file-io-operations-total", j_io_op); 553 | 554 | j_obj_new_int64_add(j_io_op, "open-call-total", open_total); 555 | j_obj_new_int64_add(j_io_op, "close-call-total", close_total); 556 | j_obj_new_int64_add(j_io_op, "read-total", read_total); 557 | j_obj_new_int64_add(j_io_op, "write-call-total", write_total); 558 | 559 | j_obj_new_double_add(j_io_op, "open-call-total-rate", (double)open_total / duration); 560 | j_obj_new_double_add(j_io_op, "close-call-total-rate", (double)close_total / duration); 561 | j_obj_new_double_add(j_io_op, "read-call-total-rate", (double)read_total / duration); 562 | j_obj_new_double_add(j_io_op, "write-call-total-rate", (double)write_total / duration); 563 | } 564 | #endif 565 | 566 | out: 567 | list_free(&sorted, free); 568 | } 569 | 570 | /* 571 | * fnotify_dump_wakelocks() 572 | * dump out fnotify wakelock operations 573 | */ 574 | void fnotify_dump_wakelocks( 575 | json_object *j_tests, 576 | const double duration) 577 | { 578 | list_t sorted; 579 | link_t *l; 580 | 581 | (void)j_tests; 582 | (void)duration; 583 | 584 | if (!(opt_flags & OPT_WAKELOCKS_LIGHT)) 585 | return; 586 | 587 | printf("Wakelock operations:\n"); 588 | 589 | list_init(&sorted); 590 | for (l = fnotify_wakelocks.head; l; l = l->next) { 591 | fnotify_wakelock_info_t *info = (fnotify_wakelock_info_t *)l->data; 592 | if (list_add_ordered(&sorted, info, fnotify_wakelock_cmp_count) == NULL) 593 | goto out; 594 | } 595 | 596 | if (!fnotify_wakelocks.head) { 597 | printf(" None.\n\n"); 598 | } else { 599 | if (fnotify_wakelocks.head && !(opt_flags & OPT_BRIEF)) { 600 | const int pid_size = pid_max_digits(); 601 | 602 | printf(" %*s Process Locks Unlocks\n", 603 | pid_size, "PID"); 604 | 605 | for (l = sorted.head; l; l = l->next) { 606 | fnotify_wakelock_info_t *info = (fnotify_wakelock_info_t *)l->data; 607 | printf(" %*d %-20.20s %8" PRIu64 " %8" PRIu64 "\n", 608 | pid_size, info->proc->pid, 609 | info->proc->cmdline, 610 | info->locked, info->unlocked); 611 | } 612 | } 613 | printf("\n"); 614 | } 615 | 616 | #ifdef JSON_OUTPUT 617 | if (j_tests) { 618 | json_object *j_wakelock_test, *j_wakelock_infos, *j_wakelock_info; 619 | uint64_t locked_total = 0, unlocked_total = 0; 620 | 621 | if ((j_wakelock_test = j_obj_new_obj()) == NULL) 622 | goto out; 623 | j_obj_obj_add(j_tests, "wakelock-operations-light", j_wakelock_test); 624 | if ((j_wakelock_infos = j_obj_new_array()) == NULL) 625 | goto out; 626 | j_obj_obj_add(j_wakelock_test, "wakelock-operations-light-per-process", j_wakelock_infos); 627 | 628 | for (l = sorted.head; l; l = l->next) { 629 | fnotify_wakelock_info_t *info = (fnotify_wakelock_info_t *)l->data; 630 | 631 | if ((j_wakelock_info = j_obj_new_obj()) == NULL) 632 | goto out; 633 | j_obj_new_int32_add(j_wakelock_info, "pid", info->proc->pid); 634 | j_obj_new_int32_add(j_wakelock_info, "ppid", info->proc->ppid); 635 | j_obj_new_int32_add(j_wakelock_info, "is-thread", info->proc->is_thread); 636 | j_obj_new_string_add(j_wakelock_info, "name", info->proc->cmdline); 637 | j_obj_new_int64_add(j_wakelock_info, "wakelock-locked", info->locked); 638 | j_obj_new_double_add(j_wakelock_info, "wakelock-locked-rate", (double)info->locked / duration); 639 | j_obj_new_int64_add(j_wakelock_info, "wakelock-unlocked", info->unlocked); 640 | j_obj_new_double_add(j_wakelock_info, "wakelock-unlocked-rate", (double)info->unlocked / duration); 641 | j_obj_array_add(j_wakelock_infos, j_wakelock_info); 642 | 643 | locked_total += info->locked; 644 | unlocked_total += info->unlocked; 645 | } 646 | if ((j_wakelock_info = j_obj_new_obj()) == NULL) 647 | goto out; 648 | j_obj_obj_add(j_wakelock_test, "wakelock-operations-light-total", j_wakelock_info); 649 | j_obj_new_int64_add(j_wakelock_info, "wakelock-locked-total", locked_total); 650 | j_obj_new_double_add(j_wakelock_info, "wakelock-locked-total-rate", (double)locked_total / duration); 651 | j_obj_new_int64_add(j_wakelock_info, "wakelock-unlocked-total", unlocked_total); 652 | j_obj_new_double_add(j_wakelock_info, "wakelock-unlocked-total-rate", (double)unlocked_total / duration); 653 | } 654 | #endif 655 | out: 656 | list_free(&sorted, NULL); 657 | } 658 | 659 | 660 | /* 661 | * fnotify_dump_events() 662 | * dump out fnotify file access events 663 | */ 664 | void fnotify_dump_events( 665 | json_object *j_tests, 666 | const double duration, 667 | const list_t *pids) 668 | { 669 | printf("File I/O operations:\n"); 670 | if (!fnotify_files.head) 671 | printf(" No file I/O operations detected.\n\n"); 672 | 673 | fnotify_dump_files(j_tests, duration); 674 | fnotify_dump_io_ops(j_tests, duration, pids); 675 | } 676 | 677 | /* 678 | * fnotify_init() 679 | * initialize fnotify lists 680 | */ 681 | void fnotify_init(void) 682 | { 683 | list_init(&fnotify_files); 684 | list_init(&fnotify_wakelocks); 685 | } 686 | 687 | /* 688 | * fnotify_cleanup() 689 | * free fnotify lists 690 | */ 691 | void fnotify_cleanup(void) 692 | { 693 | list_free(&fnotify_files, fnotify_event_free); 694 | list_free(&fnotify_wakelocks, free); 695 | } 696 | -------------------------------------------------------------------------------- /net.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * Copyright (C) 2021-2025 Colin Ian King 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | * 19 | * Author: Colin Ian King 20 | */ 21 | #define _GNU_SOURCE 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include "list.h" 39 | #include "proc.h" 40 | #include "json.h" 41 | #include "health-check.h" 42 | #include "net.h" 43 | 44 | #define NET_HASH_SIZE (1993) 45 | 46 | typedef struct { 47 | uint64_t call_count; 48 | uint64_t data_total; 49 | } net_stats_t; 50 | 51 | typedef struct { 52 | proc_info_t *proc; 53 | uint64_t inode; 54 | uint32_t fd; 55 | net_stats_t send; 56 | net_stats_t recv; 57 | char path[PATH_MAX + 1]; 58 | } net_hash_t; 59 | 60 | typedef enum { 61 | NET_TCP, 62 | NET_TCP6, 63 | NET_UDP, 64 | NET_UDP6, 65 | NET_UNIX, 66 | NET_NETLINK, 67 | NET_UNKNOWN, 68 | } net_type_t; 69 | 70 | typedef struct { 71 | net_type_t type; 72 | union { 73 | struct sockaddr_in addr4; 74 | struct sockaddr_in6 addr6; 75 | char path[PATH_MAX + 1]; 76 | } u; 77 | int family; 78 | uint64_t inode; 79 | } net_addr_info_t; 80 | 81 | typedef struct { 82 | net_addr_info_t *addr_info; 83 | net_hash_t *nh; 84 | uint64_t send_recv_total; 85 | } net_dump_info_t; 86 | 87 | static const char *net_types[] = { 88 | "TCP", 89 | "TCP6", 90 | "UDP", 91 | "UDP6", 92 | "UNIX", 93 | "NETLINK", 94 | "", 95 | }; 96 | 97 | /* 98 | * Cache of addresses used by the applications. 99 | * This shouldn't be too large, so O(n) lookup is 100 | * just bearable for now. 101 | */ 102 | static list_t net_cached_addrs; 103 | 104 | /* 105 | * Hash table of inode to process mappings. 106 | */ 107 | static list_t net_hash_table[NET_HASH_SIZE]; 108 | 109 | /* 110 | * net_hash() 111 | * hash an inode, just modulo the table size for now 112 | */ 113 | static inline unsigned long net_hash(const uint64_t inode) 114 | { 115 | return inode % NET_HASH_SIZE; 116 | } 117 | 118 | /* 119 | * net_hash_add() 120 | * add inode, pid and fd to inode hash table 121 | */ 122 | static net_hash_t *net_hash_add(const char *path, const uint64_t inode, const pid_t pid, const uint32_t fd) 123 | { 124 | net_hash_t *n; 125 | link_t *l; 126 | unsigned long h = net_hash(inode); 127 | 128 | /* Don't add it we have it already */ 129 | for (l = net_hash_table[h].head; l; l = l->next) { 130 | n = (net_hash_t *)l->data; 131 | if (n->proc->pid == pid && n->inode == inode) 132 | return n; 133 | } 134 | 135 | if ((n = calloc(1, sizeof(*n))) == NULL) { 136 | health_check_out_of_memory("allocating net hash data"); 137 | return NULL; 138 | } 139 | 140 | strlcpy(n->path, path, PATH_MAX); 141 | n->inode = inode; 142 | n->proc = proc_cache_find_by_pid(pid); 143 | n->fd = fd; 144 | 145 | if (list_append(&net_hash_table[h], n) == NULL) { 146 | free(n); 147 | return NULL; 148 | } 149 | 150 | return n; 151 | } 152 | 153 | /* 154 | * net_get_inode() 155 | * find inode in given readlink data, return -1 fail, 0 OK 156 | */ 157 | static int net_get_inode(const char *str, uint64_t *inode) 158 | { 159 | size_t len = strlen(str); 160 | 161 | /* Likely */ 162 | if (!strncmp(str, "socket:[", 8) && str[len - 1] == ']') 163 | return sscanf(str + 8, "%" SCNu64, inode) == 1 ? 0 : -1; 164 | 165 | /* Less likely */ 166 | if (!strncmp(str, "[0000]:", 7)) 167 | return sscanf(str + 7, "%" SCNu64, inode) == 1 ? 0 : -1; 168 | 169 | return -1; 170 | } 171 | 172 | /* 173 | * net_get_inode_by_path() 174 | * given a /proc/$pid/fd/fdnum path, look up a network inode 175 | */ 176 | static int net_get_inode_by_path(const char *path, uint64_t *inode, char *link, const size_t link_len) 177 | { 178 | ssize_t len; 179 | *inode = 0; 180 | 181 | if ((len = readlink(path, link, link_len - 1)) < 0) { 182 | *link = '\0'; 183 | return -1; 184 | } 185 | link[len] = '\0'; 186 | return net_get_inode(link, inode); 187 | } 188 | 189 | /* 190 | * net_cache_inode_by_pid_and_fd() 191 | * get a net hash given a file's owner pid and the fd 192 | */ 193 | static net_hash_t *net_cache_inode_by_pid_and_fd(const pid_t pid, const int fd) 194 | { 195 | char path[PATH_MAX], link[PATH_MAX + 1]; 196 | uint64_t inode; 197 | net_hash_t *nh = NULL; 198 | 199 | snprintf(path, sizeof(path), "/proc/%i/fd/%i", pid, fd); 200 | if (net_get_inode_by_path(path, &inode, link, sizeof(link)) != -1) 201 | nh = net_hash_add(link, inode, pid, fd); 202 | 203 | return nh; 204 | } 205 | 206 | /* 207 | * net_account_send() 208 | * account for net send transfers 209 | */ 210 | void net_account_send(const pid_t pid, const int fd, size_t size) 211 | { 212 | net_hash_t *nh = net_cache_inode_by_pid_and_fd(pid, fd); 213 | 214 | if (nh != NULL) { 215 | nh->send.call_count++; 216 | nh->send.data_total += size; 217 | } 218 | } 219 | 220 | /* 221 | * net_account_recv() 222 | * account for net receive transfers 223 | */ 224 | void net_account_recv(const pid_t pid, const int fd, size_t size) 225 | { 226 | net_hash_t *nh = net_cache_inode_by_pid_and_fd(pid, fd); 227 | 228 | if (nh != NULL) { 229 | nh->recv.call_count++; 230 | nh->recv.data_total += size; 231 | } 232 | } 233 | 234 | /* 235 | * net_cache_inodes_pid() 236 | * given a pid, find all the network inodes associated 237 | * with it's current file descriptors 238 | */ 239 | static int net_cache_inodes_pid(const pid_t pid) 240 | { 241 | char path[PATH_MAX]; 242 | DIR *fds; 243 | struct dirent *d; 244 | 245 | snprintf(path, sizeof(path), "/proc/%i/fd", pid); 246 | if ((fds = opendir(path)) == NULL) 247 | return -1; 248 | 249 | while ((d = readdir(fds)) != NULL) { 250 | uint64_t inode; 251 | char tmp[PATH_MAX + sizeof(d->d_name) + 2]; 252 | char link[PATH_MAX + 1]; 253 | uint32_t fd; 254 | 255 | if (d->d_name[0] == '.') 256 | continue; 257 | if (strlen(path) + strlen(d->d_name) + 2 > sizeof(tmp)) 258 | continue; 259 | snprintf(tmp, sizeof(tmp), "%s/%s", path, d->d_name); 260 | 261 | if (net_get_inode_by_path(tmp, &inode, link, sizeof(link)) != -1) { 262 | sscanf(d->d_name, "%" SCNu32, &fd); 263 | if (net_hash_add(link, inode, pid, fd) == NULL) { 264 | (void)closedir(fds); 265 | return -1; 266 | } 267 | } 268 | } 269 | (void)closedir(fds); 270 | 271 | return 0; 272 | } 273 | 274 | /* 275 | * net_cache_inodes() 276 | * given a list of pidis, find all the network inodes associated 277 | * with the processes' current file descriptors 278 | */ 279 | static int net_cache_inodes(list_t *pids) 280 | { 281 | link_t *l; 282 | 283 | for (l = pids->head; l; l = l->next) { 284 | proc_info_t *p = (proc_info_t *)l->data; 285 | if (net_cache_inodes_pid(p->pid) < 0) 286 | return -1; 287 | } 288 | return 0; 289 | } 290 | 291 | /* 292 | * net_inet4_resolve() 293 | * turn ipv4 addr to human readable address 294 | */ 295 | static void net_inet4_resolve(char *name, const size_t len, struct sockaddr_in *sin) 296 | { 297 | if ((opt_flags & OPT_ADDR_RESOLVE) && 298 | (sin->sin_addr.s_addr != INADDR_ANY) && 299 | (getnameinfo((struct sockaddr *)sin, sizeof(*sin), name, len, 300 | NULL, 0, NI_NAMEREQD) == 0)) 301 | return; 302 | if (getnameinfo((struct sockaddr *)sin, sizeof(*sin), name, len, 303 | NULL, 0, NI_NUMERICHOST) == 0) 304 | return; 305 | strlcpy(name, "", len); 306 | return; 307 | } 308 | 309 | /* 310 | * net_inet6_resolve() 311 | * turn ipv6 addr to human readable address 312 | */ 313 | static void net_inet6_resolve(char *name, const size_t len, struct sockaddr_in6 *sin6) 314 | { 315 | if ((opt_flags & OPT_ADDR_RESOLVE) && 316 | (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) && 317 | (getnameinfo((struct sockaddr *)sin6, sizeof(*sin6), name, len, 318 | NULL, 0, NI_NAMEREQD) == 0)) 319 | return; 320 | if (getnameinfo((struct sockaddr *)sin6, sizeof(*sin6), name, len, 321 | NULL, 0, NI_NUMERICHOST) == 0) 322 | return; 323 | strlcpy(name, "", len); 324 | return; 325 | } 326 | 327 | /* 328 | * net_addr_add() 329 | * Add a new address to the cached list of addresses. 330 | * This is an O(n) search and add, so we may need to 331 | * re-work this if the number of addresses gets too large. 332 | */ 333 | static net_addr_info_t *net_addr_add(net_addr_info_t *addr) 334 | { 335 | link_t *l; 336 | net_addr_info_t *new_addr; 337 | 338 | for (l = net_cached_addrs.head; l; l = l->next) { 339 | net_addr_info_t *old_addr = (net_addr_info_t *)l->data; 340 | 341 | if (memcmp(addr, old_addr, sizeof(*addr)) == 0) 342 | return old_addr; /* Duplicate, ignore */ 343 | } 344 | 345 | if ((new_addr = calloc(1, sizeof(*new_addr))) == NULL) { 346 | health_check_out_of_memory("allocating net address information"); 347 | return NULL; 348 | } 349 | memcpy(new_addr, addr, sizeof(*addr)); 350 | if (list_append(&net_cached_addrs, new_addr) == NULL) { 351 | free(new_addr); 352 | return NULL; 353 | } 354 | return new_addr; 355 | } 356 | 357 | /* 358 | * net_get_addr() 359 | * turn the addr info into human readable form 360 | */ 361 | static char *net_get_addr(net_addr_info_t *addr_info) 362 | { 363 | char buf[4096]; 364 | static char tmp[sizeof(buf) + 16]; 365 | in_port_t port; 366 | 367 | switch (addr_info->type) { 368 | case NET_TCP: 369 | case NET_TCP6: 370 | case NET_UDP: 371 | case NET_UDP6: 372 | switch (addr_info->family) { 373 | case AF_INET6: 374 | net_inet6_resolve(buf, sizeof(buf), &addr_info->u.addr6); 375 | port = addr_info->u.addr6.sin6_port; 376 | break; 377 | case AF_INET: 378 | net_inet4_resolve(buf, sizeof(buf), &addr_info->u.addr4); 379 | port = addr_info->u.addr4.sin_port; 380 | break; 381 | default: 382 | /* No idea what it is */ 383 | return NULL; 384 | } 385 | snprintf(tmp, sizeof(tmp), "%s:%d", buf, port); 386 | return tmp; 387 | case NET_UNIX: 388 | case NET_UNKNOWN: 389 | case NET_NETLINK: 390 | return addr_info->u.path; 391 | default: 392 | break; 393 | } 394 | return NULL; 395 | } 396 | 397 | /* 398 | * net_add_dump_info() 399 | * either accumulate existing send/recv net info, or add it if not 400 | * already unique 401 | */ 402 | static void net_add_dump_info(list_t *list, net_dump_info_t *new_dump_info) 403 | { 404 | link_t *l; 405 | 406 | for (l = list->head; l; l = l->next) { 407 | net_dump_info_t *dump_info = (net_dump_info_t *)l->data; 408 | 409 | if (dump_info->nh->proc == new_dump_info->nh->proc && 410 | dump_info->addr_info->type == new_dump_info->addr_info->type && 411 | memcmp(&dump_info->addr_info->u, &new_dump_info->addr_info->u, sizeof(dump_info->addr_info->u)) == 0 && 412 | dump_info->addr_info->family == new_dump_info->addr_info->family) { 413 | dump_info->nh->send.call_count += new_dump_info->nh->send.call_count; 414 | dump_info->nh->send.data_total += new_dump_info->nh->send.data_total; 415 | dump_info->nh->recv.call_count += new_dump_info->nh->recv.call_count; 416 | dump_info->nh->recv.data_total += new_dump_info->nh->recv.data_total; 417 | dump_info->send_recv_total += new_dump_info->send_recv_total; 418 | free(new_dump_info); 419 | return; 420 | } 421 | } 422 | list_append(list, new_dump_info); 423 | } 424 | 425 | /* 426 | * net_dump_info_cmp() 427 | * Sort for dumping net send/recv stats, sorted on total 428 | * data send/recv and then if no difference on pid order 429 | */ 430 | static int net_dump_info_cmp(const void *p1, const void *p2) 431 | { 432 | const net_dump_info_t *d1 = (const net_dump_info_t *)p1; 433 | const net_dump_info_t *d2 = (const net_dump_info_t *)p2; 434 | if (d2->send_recv_total - d1->send_recv_total == 0) 435 | return d1->nh->proc->pid - d2->nh->proc->pid; 436 | else 437 | return d2->send_recv_total - d1->send_recv_total; 438 | 439 | } 440 | 441 | /* 442 | * net_size_to_str() 443 | * turn transfer size in bytes to a more human readable form 444 | */ 445 | static void net_size_to_str(char *buf, size_t buf_len, uint64_t size) 446 | { 447 | double s; 448 | char unit; 449 | 450 | if (size < 1024) { 451 | s = (double)size; 452 | unit = 'B'; 453 | } else if (size < 1024 * 1024) { 454 | s = (double)size / 1024.0; 455 | unit = 'K'; 456 | } else { 457 | s = (double)size / (1024 * 1024); 458 | unit = 'M'; 459 | } 460 | snprintf(buf, buf_len, "%7.2f %c", s, unit); 461 | } 462 | 463 | /* 464 | * net_connection_dump() 465 | * dump out network connections 466 | */ 467 | void net_connection_dump(json_object *j_tests, double duration) 468 | { 469 | link_t *ln, *l; 470 | list_t dump_info_list; 471 | list_t sorted; 472 | int i; 473 | uint64_t send_total = 0, recv_total = 0; 474 | #ifdef JSON_OUTPUT 475 | json_object *j_net_test = NULL, *j_net_infos = NULL, *j_net_info; 476 | #else 477 | (void)j_tests; 478 | (void)duration; 479 | #endif 480 | 481 | printf("Open Network Connections:\n"); 482 | 483 | list_init(&dump_info_list); 484 | list_init(&sorted); 485 | 486 | #ifdef JSON_OUTPUT 487 | if (j_tests) { 488 | if ((j_net_test = j_obj_new_obj()) == NULL) 489 | goto out; 490 | j_obj_obj_add(j_tests, "network-connections", j_net_test); 491 | if ((j_net_infos = j_obj_new_array()) == NULL) 492 | goto out; 493 | j_obj_obj_add(j_net_test, "network-connections-per-process", j_net_infos); 494 | } 495 | #endif 496 | 497 | /* 498 | * Collate data 499 | */ 500 | for (i = 0; i < NET_HASH_SIZE; i++) { 501 | for (ln = net_hash_table[i].head; ln; ln = ln->next) { 502 | bool found = false; 503 | net_hash_t *nh = (net_hash_t *)ln->data; 504 | net_dump_info_t *dump_info; 505 | net_addr_info_t *addr_info = NULL; 506 | 507 | /* Is there cached info about this? */ 508 | for (l = net_cached_addrs.head; l; l = l->next) { 509 | addr_info = (net_addr_info_t *)l->data; 510 | 511 | if (nh->inode == addr_info->inode) { 512 | found = true; 513 | break; 514 | } 515 | } 516 | 517 | /* Skip threads that do nothing */ 518 | if ((nh->send.data_total + nh->recv.data_total == 0) && nh->proc->is_thread) 519 | continue; 520 | 521 | if ((addr_info == NULL) || !found) { 522 | /* Not found in cache, no idea what it is */ 523 | net_addr_info_t new_addr; 524 | 525 | memset(&new_addr, 0, sizeof(new_addr)); 526 | new_addr.inode = nh->inode; 527 | new_addr.type = NET_UNKNOWN; 528 | strlcpy(new_addr.u.path, nh->path, sizeof(new_addr.u.path)); 529 | if ((addr_info = net_addr_add(&new_addr)) == NULL) 530 | goto out; 531 | } 532 | 533 | if ((dump_info = calloc(1, sizeof(net_dump_info_t))) == NULL) 534 | goto out; 535 | dump_info->addr_info = addr_info; 536 | dump_info->nh = nh; 537 | dump_info->send_recv_total = nh->send.data_total + nh->recv.data_total; 538 | net_add_dump_info(&dump_info_list, dump_info); 539 | } 540 | } 541 | 542 | /* 543 | * We've now got a list of useful data, so now sort it 544 | */ 545 | for (l = dump_info_list.head; l; l = l->next) { 546 | net_dump_info_t *dump_info = l->data; 547 | list_add_ordered(&sorted, dump_info, net_dump_info_cmp); 548 | } 549 | 550 | if (!dump_info_list.head) { 551 | printf(" None.\n\n"); 552 | } else { 553 | char sendbuf[64], recvbuf[64]; 554 | const int pid_size = pid_max_digits(); 555 | 556 | printf(" %*s Process Proto Send Receive Address\n", 557 | pid_size, "PID"); 558 | for (l = sorted.head; l; l = l->next) { 559 | net_dump_info_t *dump_info = (net_dump_info_t *)l->data; 560 | char *addr = net_get_addr(dump_info->addr_info); 561 | 562 | net_size_to_str(sendbuf, sizeof(sendbuf), dump_info->nh->send.data_total); 563 | net_size_to_str(recvbuf, sizeof(recvbuf), dump_info->nh->recv.data_total); 564 | 565 | printf(" %*d %-20.20s %-7.7s %s %s %s\n", 566 | pid_size, dump_info->nh->proc->pid, 567 | dump_info->nh->proc->cmdline, 568 | net_types[dump_info->addr_info->type], 569 | sendbuf, recvbuf, addr); 570 | 571 | send_total += dump_info->nh->send.data_total; 572 | recv_total += dump_info->nh->recv.data_total; 573 | 574 | #ifdef JSON_OUTPUT 575 | if (j_tests) { 576 | if ((j_net_info = j_obj_new_obj()) == NULL) 577 | goto out; 578 | j_obj_new_int32_add(j_net_info, "pid", dump_info->nh->proc->pid); 579 | j_obj_new_int32_add(j_net_info, "ppid", dump_info->nh->proc->ppid); 580 | j_obj_new_int32_add(j_net_info, "is-thread", dump_info->nh->proc->is_thread); 581 | j_obj_new_string_add(j_net_info, "name", dump_info->nh->proc->cmdline); 582 | j_obj_new_string_add(j_net_info, "protocol", net_types[dump_info->addr_info->type]); 583 | j_obj_new_string_add(j_net_info, "address", addr); 584 | j_obj_new_int64_add(j_net_info, "send", dump_info->nh->send.data_total); 585 | j_obj_new_int64_add(j_net_info, "receive", dump_info->nh->recv.data_total); 586 | j_obj_array_add(j_net_infos, j_net_info); 587 | } 588 | #endif 589 | } 590 | net_size_to_str(sendbuf, sizeof(sendbuf), send_total); 591 | net_size_to_str(recvbuf, sizeof(recvbuf), recv_total); 592 | printf(" Total%31s%s %s\n\n", "", sendbuf, recvbuf); 593 | } 594 | #ifdef JSON_OUTPUT 595 | if (j_tests) { 596 | if ((j_net_info = j_obj_new_obj()) == NULL) 597 | goto out; 598 | j_obj_obj_add(j_net_test, "network-connections-total", j_net_info); 599 | j_obj_new_int64_add(j_net_info, "send-total", send_total); 600 | j_obj_new_int64_add(j_net_info, "receive-total", recv_total); 601 | j_obj_new_double_add(j_net_info, "send-total-rate", (double)send_total / duration); 602 | j_obj_new_double_add(j_net_info, "receive-total-rate", (double)recv_total / duration); 603 | } 604 | #endif 605 | out: 606 | list_free(&sorted, NULL); 607 | list_free(&dump_info_list, free); 608 | } 609 | 610 | /* 611 | * net_unix_parse() 612 | * parse /proc/net/unix and cache data 613 | */ 614 | static int net_unix_parse(void) 615 | { 616 | FILE *fp; 617 | char buf[4096]; 618 | int i; 619 | 620 | if ((fp = fopen("/proc/net/unix", "r")) == NULL) { 621 | fprintf(stderr, "Cannot open /proc/net/unix\n"); 622 | return -1; 623 | } 624 | 625 | for (i = 0; fgets(buf, sizeof(buf), fp) != NULL; i++) { 626 | uint64_t inode; 627 | char path[4096]; 628 | net_addr_info_t new_addr; 629 | 630 | if (i == 0) /* Skip header */ 631 | continue; 632 | 633 | sscanf(buf, "%*x: %*x %*x %*x %*x %*x %" SCNu64 " %s\n", 634 | &inode, path); 635 | 636 | memset(&new_addr, 0, sizeof(new_addr)); 637 | new_addr.inode = inode; 638 | new_addr.type = NET_UNIX; 639 | strlcpy(new_addr.u.path, path, PATH_MAX); 640 | net_addr_add(&new_addr); 641 | } 642 | (void)fclose(fp); 643 | 644 | return 0; 645 | } 646 | 647 | /* 648 | * net_tcp_udp_parse() 649 | * parse /proc/net/{tcp,udp} and cache data for 650 | * faster lookup 651 | */ 652 | static int net_tcp_udp_parse(const net_type_t type) 653 | { 654 | FILE *fp; 655 | char *procfile; 656 | char buf[4096]; 657 | char addr_str[128]; 658 | in_port_t port; 659 | int i; 660 | uint64_t inode; 661 | 662 | switch (type) { 663 | case NET_TCP: 664 | procfile = "/proc/net/tcp"; 665 | break; 666 | case NET_TCP6: 667 | procfile = "/proc/net/tcp6"; 668 | break; 669 | case NET_UDP: 670 | procfile = "/proc/net/udp"; 671 | break; 672 | case NET_UDP6: 673 | procfile = "/proc/net/udp6"; 674 | break; 675 | default: 676 | fprintf(stderr, "net_parse given bad net type.\n"); 677 | return -1; 678 | } 679 | 680 | if ((fp = fopen(procfile, "r")) == NULL) 681 | return -1; 682 | 683 | for (i = 0; fgets(buf, sizeof(buf), fp) != NULL; i++) { 684 | net_addr_info_t new_addr; 685 | 686 | if (i == 0) /* Skip header */ 687 | continue; 688 | 689 | sscanf(buf, 690 | "%*d: %*64[0-9A-Fa-f]:%*X %64[0-9A-Fa-f]:%" SCNx16 691 | " %*X %*X:%*X %*X:%*X %*X %*d %*d %" SCNu64, 692 | addr_str, &port, &inode); 693 | 694 | memset(&new_addr, 0, sizeof(new_addr)); 695 | new_addr.inode = inode; 696 | new_addr.type = type; 697 | if (strlen(addr_str) > 8) { 698 | new_addr.family = new_addr.u.addr6.sin6_family = AF_INET6; 699 | new_addr.u.addr6.sin6_port = port; 700 | sscanf(addr_str, "%08X%08X%08X%08X", 701 | &new_addr.u.addr6.sin6_addr.s6_addr32[0], 702 | &new_addr.u.addr6.sin6_addr.s6_addr32[1], 703 | &new_addr.u.addr6.sin6_addr.s6_addr32[2], 704 | &new_addr.u.addr6.sin6_addr.s6_addr32[3]); 705 | } else { 706 | new_addr.family = new_addr.u.addr4.sin_family = AF_INET; 707 | new_addr.u.addr4.sin_port = port; 708 | sscanf(addr_str, "%8X", &new_addr.u.addr4.sin_addr.s_addr); 709 | } 710 | net_addr_add(&new_addr); 711 | } 712 | (void)fclose(fp); 713 | 714 | return 0; 715 | } 716 | 717 | /* 718 | * net_netlink_parse() 719 | * parse /proc/net/netlink and cache data for 720 | * faster lookup 721 | */ 722 | static int net_netlink_parse(void) 723 | { 724 | FILE *fp; 725 | char buf[4096]; 726 | int i; 727 | uint64_t inode; 728 | 729 | if ((fp = fopen("/proc/net/netlink", "r")) == NULL) { 730 | fprintf(stderr, "Cannot open /proc/net/netlink.\n"); 731 | return -1; 732 | } 733 | 734 | for (i = 0; fgets(buf, sizeof(buf), fp) != NULL; i++) { 735 | net_addr_info_t new_addr; 736 | 737 | if (i == 0) /* Skip header */ 738 | continue; 739 | 740 | if (sscanf(buf, 741 | "%*X %*d %*d %*x %*d %*d %*d %*d %*d %" SCNu64, &inode) != 1) 742 | continue; 743 | 744 | memset(&new_addr, 0, sizeof(new_addr)); 745 | new_addr.inode = inode; 746 | new_addr.type = NET_NETLINK; 747 | snprintf(new_addr.u.path, PATH_MAX, "netlink:[%" PRIu64 "]", inode); 748 | net_addr_add(&new_addr); 749 | } 750 | (void)fclose(fp); 751 | 752 | return 0; 753 | } 754 | /* 755 | * net_parse() 756 | * parse various /proc net files 757 | */ 758 | static int net_parse(void) 759 | { 760 | if (net_tcp_udp_parse(NET_TCP) < 0) 761 | return -1; 762 | if (net_tcp_udp_parse(NET_TCP6) < 0) 763 | return -1; 764 | if (net_tcp_udp_parse(NET_UDP) < 0) 765 | return -1; 766 | if (net_tcp_udp_parse(NET_UDP6) < 0) 767 | return -1; 768 | if (net_unix_parse() < 0) 769 | return -1; 770 | if (net_netlink_parse() < 0) 771 | return -1; 772 | 773 | return 0; 774 | } 775 | 776 | /* 777 | * net_connection_pids() 778 | * find network inodes assocated with given 779 | * pids and find network addresses 780 | */ 781 | int net_connection_pids(list_t *pids) 782 | { 783 | if (net_cache_inodes(pids) < 0) 784 | return -1; 785 | return net_parse(); 786 | } 787 | 788 | /* 789 | * net_connection_pid() 790 | * find network inodes assocated with given 791 | * pid and find network addresses 792 | */ 793 | int net_connection_pid(const pid_t pid) 794 | { 795 | if (net_cache_inodes_pid(pid) < 0) 796 | return -1; 797 | return net_parse(); 798 | } 799 | 800 | /* 801 | * net_connection_init() 802 | * initialise 803 | */ 804 | void net_connection_init(void) 805 | { 806 | int i; 807 | 808 | list_init(&net_cached_addrs); 809 | for (i = 0; i < NET_HASH_SIZE; i++) 810 | list_init(&net_hash_table[i]); 811 | } 812 | 813 | /* 814 | * net_connection_cleanup() 815 | * tidy up behind ourselves 816 | */ 817 | void net_connection_cleanup(void) 818 | { 819 | int i; 820 | 821 | list_free(&net_cached_addrs, free); 822 | for (i = 0; i < NET_HASH_SIZE; i++) 823 | list_free(&net_hash_table[i], free); 824 | } 825 | -------------------------------------------------------------------------------- /mem.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2021 Canonical, Ltd. 3 | * Copyright (C) 2021-2025 Colin Ian King 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | * 19 | * Author: Colin Ian King 20 | */ 21 | #define _GNU_SOURCE 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "list.h" 34 | #include "mem.h" 35 | #include "health-check.h" 36 | 37 | static list_t mem_info_old, mem_info_new; 38 | static list_t mem_brk_info; 39 | static list_t mem_mmap_info; 40 | 41 | static const char *mem_types[] = { 42 | "Stack", 43 | "Heap", 44 | "Mapped", 45 | }; 46 | 47 | #ifdef JSON_OUTPUT 48 | /* 49 | * mem_tolower_str() 50 | * string to lower case 51 | */ 52 | static void mem_tolower_str(char *str) 53 | { 54 | for (;*str; str++) 55 | *str = tolower(*str); 56 | } 57 | #endif 58 | 59 | /* 60 | * mem_loading() 61 | * convert heath growth rate into human readable form 62 | */ 63 | static const char *mem_loading(const double mem_rate) 64 | { 65 | char *verb, *adverb; 66 | static char buffer[64]; 67 | double rate = mem_rate; 68 | 69 | if (FLOAT_CMP(rate, 0.0)) 70 | return "no change"; 71 | if (rate < 0) { 72 | verb = "shrinking"; 73 | rate = -mem_rate; 74 | } else 75 | verb = "growing"; 76 | 77 | if (rate < 1024.0) 78 | adverb = " slowly"; 79 | else if (rate >= 2.0 * 1024.0 * 1024.0) 80 | adverb = " very fast"; 81 | else if (rate >= 256.0 * 1024.0) 82 | adverb = " fast"; 83 | else if (rate >= 8.0 * 1024.0) 84 | adverb = " moderately fast"; 85 | else 86 | adverb = ""; 87 | 88 | sprintf(buffer, "%s%s", verb, adverb); 89 | return buffer; 90 | } 91 | 92 | /* 93 | * mem_mmap_account() 94 | * do mmap/munmap accounting on pid of map size length. 95 | */ 96 | int mem_mmap_account(const pid_t pid, const size_t length, const bool mmap) 97 | { 98 | link_t *l; 99 | bool found = false; 100 | 101 | mem_mmap_info_t *info = NULL; 102 | 103 | for (l = mem_mmap_info.head; l; l = l->next) { 104 | info = (mem_mmap_info_t *)l->data; 105 | if (info->pid == pid) { 106 | found = true; 107 | break; 108 | } 109 | } 110 | if (!found) { 111 | if ((info = calloc(1, sizeof(*info))) == NULL) { 112 | health_check_out_of_memory("allocating memory tracking brk() information"); 113 | return -1; 114 | } 115 | info->pid = pid; 116 | if (list_append(&mem_mmap_info, info) == NULL) { 117 | free(info); 118 | return -1; 119 | } 120 | } 121 | 122 | if (mmap) { 123 | info->mmap_count++; 124 | info->mmap_length += length; 125 | } else { 126 | info->munmap_count++; 127 | info->munmap_length += length; 128 | } 129 | return 0; 130 | } 131 | 132 | /* 133 | * mem_mmap_cmp() 134 | * list sorting based on total mmap size 135 | */ 136 | static int mem_mmap_cmp(const void *data1, const void *data2) 137 | { 138 | const mem_mmap_info_t *m1 = (const mem_mmap_info_t *)data1; 139 | const mem_mmap_info_t *m2 = (const mem_mmap_info_t *)data2; 140 | const int64_t d1 = m1->mmap_length - m1->munmap_length; 141 | const int64_t d2 = m2->mmap_length - m2->munmap_length; 142 | 143 | return d2 - d1; 144 | } 145 | 146 | /* 147 | * mem_dump_mmap() 148 | * dump mmap changes 149 | */ 150 | void mem_dump_mmap(json_object *j_tests, const double duration) 151 | { 152 | list_t sorted; 153 | link_t *l; 154 | mem_mmap_info_t *info; 155 | 156 | #if !defined(JSON_OUTPUT) 157 | (void)j_tests; 158 | #endif 159 | 160 | printf("Memory Change via mmap() and munmap():\n"); 161 | 162 | list_init(&sorted); 163 | for (l = mem_mmap_info.head; l; l = l->next) { 164 | info = (mem_mmap_info_t *)l->data; 165 | if (list_add_ordered(&sorted, info, mem_mmap_cmp) == NULL) 166 | goto out; 167 | } 168 | 169 | if (mem_mmap_info.head == NULL) { 170 | printf(" None.\n\n"); 171 | } else { 172 | const int pid_size = pid_max_digits(); 173 | 174 | printf(" %*s mmaps munmaps Change (K) Rate (K/Sec)\n", 175 | pid_size, "PID"); 176 | for (l = sorted.head; l; l = l->next) { 177 | info = (mem_mmap_info_t *)l->data; 178 | proc_info_t *p = proc_cache_find_by_pid(info->pid); 179 | int64_t delta = info->mmap_length - info->munmap_length; 180 | double rate = ((double)delta) / duration; 181 | 182 | printf(" %*d %-20.20s %8" PRIu64 " %8" PRIu64 " %8" PRIi64 " %8.2f (%s)\n", 183 | pid_size, info->pid, 184 | p ? p->cmdline : "", 185 | info->mmap_count, info->munmap_count, 186 | delta / 1024, rate / 1024.0, mem_loading(rate)); 187 | } 188 | printf("\n"); 189 | } 190 | 191 | #ifdef JSON_OUTPUT 192 | if (j_tests) { 193 | json_object *j_mem_test, *j_mem_infos, *j_mem_info; 194 | uint64_t total_mmap_count = 0, total_munmap_count = 0, total_delta = 0; 195 | 196 | if ((j_mem_test = j_obj_new_obj()) == NULL) 197 | goto out; 198 | j_obj_obj_add(j_tests, "memory-usage-via-mmap", j_mem_test); 199 | if ((j_mem_infos = j_obj_new_array()) == NULL) 200 | goto out; 201 | j_obj_obj_add(j_mem_test, "memory-usage-via-mmap-per-process", j_mem_infos); 202 | for (l = sorted.head; l; l = l->next) { 203 | info = (mem_mmap_info_t *)l->data; 204 | proc_info_t *p = proc_cache_find_by_pid(info->pid); 205 | int64_t delta = info->mmap_length - info->munmap_length; 206 | 207 | if ((j_mem_info = j_obj_new_obj()) == NULL) 208 | goto out; 209 | j_obj_new_int32_add(j_mem_info, "pid", info->pid); 210 | if (p) { 211 | j_obj_new_int32_add(j_mem_info, "ppid", p->ppid); 212 | j_obj_new_int32_add(j_mem_info, "is-thread", p->is_thread); 213 | j_obj_new_string_add(j_mem_info, "name", p->cmdline); 214 | } 215 | j_obj_new_int64_add(j_mem_info, "mmap-count", info->mmap_count); 216 | j_obj_new_int64_add(j_mem_info, "munmap-count", info->munmap_count); 217 | j_obj_new_int64_add(j_mem_info, "mmap-total-Kbytes", (uint64_t)delta / 1024); 218 | j_obj_new_double_add(j_mem_info, "mmap-total-Kbytes-rate", ((double)delta / 1024.0) / duration ); 219 | j_obj_array_add(j_mem_infos, j_mem_info); 220 | 221 | total_mmap_count += info->mmap_count; 222 | total_munmap_count += info->munmap_count; 223 | total_delta += delta; 224 | } 225 | 226 | if ((j_mem_info = j_obj_new_obj()) == NULL) 227 | goto out; 228 | j_obj_obj_add(j_mem_test, "memory-usage-via-mmap-total", j_mem_info); 229 | j_obj_new_int64_add(j_mem_info, "mmap-count-total", total_mmap_count); 230 | j_obj_new_int64_add(j_mem_info, "munmap-count-total", total_munmap_count); 231 | j_obj_new_int64_add(j_mem_info, "mmap-total-Kbytes", total_delta / 1024); 232 | j_obj_new_double_add(j_mem_info, "mmap-total-Kbytes-rate", ((double)total_delta / 1024.0) / duration); 233 | } 234 | #endif 235 | 236 | out: 237 | list_free(&sorted, NULL); 238 | } 239 | 240 | /* 241 | * mem_brk_account() 242 | * sys_brk memory accouting, used in syscall.c 243 | */ 244 | int mem_brk_account(const pid_t pid, const void *addr) 245 | { 246 | link_t *l; 247 | 248 | mem_brk_info_t *info = NULL; 249 | 250 | if (!addr) 251 | return 0; 252 | 253 | for (l = mem_brk_info.head; l; l = l->next) { 254 | info = (mem_brk_info_t *)l->data; 255 | if (info->pid == pid) { 256 | info->brk_current = addr; 257 | info->brk_count++; 258 | return 0; 259 | } 260 | } 261 | 262 | if ((info = calloc(1, sizeof(*info))) == NULL) { 263 | health_check_out_of_memory("allocating memory tracking brk() information"); 264 | return -1; 265 | } 266 | info->pid = pid; 267 | info->brk_start = addr; 268 | info->brk_current = addr; 269 | info->brk_count = 1; 270 | if (list_append(&mem_brk_info, info) == NULL) { 271 | free(info); 272 | return -1; 273 | } 274 | 275 | return 0; 276 | } 277 | 278 | /* 279 | * mem_brk_cmp() 280 | * list sorting based on total brk size 281 | */ 282 | static int mem_brk_cmp(const void *data1, const void *data2) 283 | { 284 | const mem_brk_info_t *m1 = (const mem_brk_info_t *)data1; 285 | const mem_brk_info_t *m2 = (const mem_brk_info_t *)data2; 286 | 287 | const ptrdiff_t p1 = (const char *)m1->brk_current - (const char *)m1->brk_start; 288 | const ptrdiff_t p2 = (const char *)m2->brk_current - (const char *)m2->brk_start; 289 | 290 | return p2 - p1; 291 | } 292 | 293 | /* 294 | * mem_dump_brk() 295 | * dump brk heap changes 296 | */ 297 | void mem_dump_brk(json_object *j_tests, const double duration) 298 | { 299 | list_t sorted; 300 | link_t *l; 301 | mem_brk_info_t *info; 302 | 303 | #if !defined(JSON_OUTPUT) 304 | (void)j_tests; 305 | #endif 306 | 307 | printf("Heap Change via brk():\n"); 308 | list_init(&sorted); 309 | 310 | for (l = mem_brk_info.head; l; l = l->next) { 311 | info = (mem_brk_info_t *)l->data; 312 | if (list_add_ordered(&sorted, info, mem_brk_cmp) == NULL) 313 | goto out; 314 | } 315 | 316 | if (mem_brk_info.head == NULL) { 317 | printf(" None.\n\n"); 318 | } else { 319 | const int pid_size = pid_max_digits(); 320 | 321 | printf(" %*s brk Count Change (K) Rate (K/Sec)\n", 322 | pid_size, "PID"); 323 | for (l = sorted.head; l; l = l->next) { 324 | info = (mem_brk_info_t *)l->data; 325 | const proc_info_t *p = proc_cache_find_by_pid(info->pid); 326 | const ptrdiff_t delta = ((const char *)info->brk_current - (const char *)info->brk_start); 327 | const double rate = ((double)delta) / duration; 328 | 329 | printf(" %*d %-20.20s %8" PRIu64 " %12td %8.2f (%s)\n", 330 | pid_size, info->pid, 331 | p ? p->cmdline : "", info->brk_count, 332 | delta / 1024, 333 | rate / 1024.0, mem_loading(rate)); 334 | } 335 | printf("\n"); 336 | } 337 | 338 | #ifdef JSON_OUTPUT 339 | if (j_tests) { 340 | json_object *j_mem_test, *j_mem_infos, *j_mem_info; 341 | uint64_t total_brk_count = 0, total_delta = 0; 342 | 343 | if ((j_mem_test = j_obj_new_obj()) == NULL) 344 | goto out; 345 | j_obj_obj_add(j_tests, "heap-usage-via-brk", j_mem_test); 346 | if ((j_mem_infos = j_obj_new_array()) == NULL) 347 | goto out; 348 | j_obj_obj_add(j_mem_test, "heap-usage-via-brk-per-process", j_mem_infos); 349 | for (l = sorted.head; l; l = l->next) { 350 | info = (mem_brk_info_t *)l->data; 351 | const proc_info_t *p = proc_cache_find_by_pid(info->pid); 352 | const ptrdiff_t delta = ((const char *)info->brk_current - (const char *)info->brk_start); 353 | 354 | if ((j_mem_info = j_obj_new_obj()) == NULL) 355 | goto out; 356 | j_obj_new_int32_add(j_mem_info, "pid", info->pid); 357 | if (p) { 358 | j_obj_new_int32_add(j_mem_info, "ppid", p->ppid); 359 | j_obj_new_int32_add(j_mem_info, "is-thread", p->is_thread); 360 | j_obj_new_string_add(j_mem_info, "name", p->cmdline); 361 | } 362 | j_obj_new_int64_add(j_mem_info, "brk-count", info->brk_count); 363 | j_obj_new_int64_add(j_mem_info, "brk-size-Kbytes", (uint64_t)delta / 1024); 364 | j_obj_new_double_add(j_mem_info, "brk-size-Kbytes-rate", ((double)delta / 1024.0) / duration); 365 | j_obj_array_add(j_mem_infos, j_mem_info); 366 | 367 | total_brk_count += info->brk_count; 368 | total_delta += (uint64_t)delta; 369 | } 370 | if ((j_mem_info = j_obj_new_obj()) == NULL) 371 | goto out; 372 | j_obj_obj_add(j_mem_test, "heap-usage-via-brk-total", j_mem_info); 373 | j_obj_new_int64_add(j_mem_info, "brk-count-total", total_brk_count); 374 | j_obj_new_int64_add(j_mem_info, "brk-size-total-Kbytes", total_delta / 1024); 375 | j_obj_new_double_add(j_mem_info, "brk-size-total-Kbytes-rate", ((double)total_delta / 1024.0) / duration); 376 | } 377 | #endif 378 | 379 | out: 380 | list_free(&sorted, NULL); 381 | } 382 | 383 | /* 384 | * mem_cmp() 385 | * list sorting based on total memory used 386 | */ 387 | static int mem_cmp(const void *data1, const void *data2) 388 | { 389 | const mem_info_t *m1 = (const mem_info_t *)data1; 390 | const mem_info_t *m2 = (const mem_info_t *)data2; 391 | 392 | return m2->grand_total - m1->grand_total; 393 | } 394 | 395 | /* 396 | * mem_get_size() 397 | * parse proc sizes in K bytes 398 | */ 399 | static int mem_get_size(FILE *fp, const char *field, uint64_t *size) 400 | { 401 | char tmp[4096]; 402 | uint64_t size_k; 403 | 404 | *size = 0; 405 | 406 | while (!feof(fp)) { 407 | if (fscanf(fp, "%4095[^:]: %" SCNi64 "%*[^\n]%*c", tmp, &size_k) == 2) { 408 | if (strcmp(tmp, field) == 0) { 409 | *size = size_k * 1024; 410 | return 0; 411 | } 412 | } 413 | } 414 | return -1; 415 | } 416 | 417 | /* 418 | * mem_get_entry() 419 | * parse a single memory mapping entry 420 | */ 421 | static int mem_get_entry(FILE *fp, mem_info_t *mem) 422 | { 423 | uint64_t addr_start, addr_end, addr_offset; 424 | int major, minor; 425 | mem_type_t type; 426 | char path[PATH_MAX]; 427 | uint64_t size, rss, pss; 428 | 429 | for (;;) { 430 | char buffer[4096]; 431 | 432 | if (fgets(buffer, sizeof(buffer), fp) == NULL) 433 | return -1; 434 | if (sscanf(buffer, "%" SCNx64 "-%" SCNx64 " %*s %" SCNx64 " %x:%x %*u %s", 435 | &addr_start, &addr_end, &addr_offset, &major, &minor, path) == 6) 436 | break; 437 | if (sscanf(buffer, "%" SCNx64 "-%" SCNx64 " %*s %" SCNx64 " %x:%x %*u", 438 | &addr_start, &addr_end, &addr_offset, &major, &minor) == 5) { 439 | *path = '\0'; 440 | break; 441 | } 442 | } 443 | 444 | 445 | if (strncmp(path, "[stack", 6) == 0) 446 | type = MEM_STACK; 447 | else if (strncmp(path, "[heap", 5) == 0) 448 | type = MEM_HEAP; 449 | else if (!*path && addr_offset == 0 && major == 0 && minor == 0) 450 | type = MEM_HEAP; 451 | else 452 | type = MEM_MAPPED; 453 | 454 | if (mem_get_size(fp, "Size", &size) < 0) 455 | return -1; 456 | if (mem_get_size(fp, "Rss", &rss) < 0) 457 | return -1; 458 | if (mem_get_size(fp, "Pss", &pss) < 0) 459 | return -1; 460 | 461 | mem->size[type] += size; 462 | mem->rss[type] += rss; 463 | mem->pss[type] += pss; 464 | mem->total[type] += size + rss + pss; 465 | return 0; 466 | } 467 | 468 | /* 469 | * mem_get_by_proc() 470 | * get mem info for a specific proc 471 | */ 472 | int mem_get_by_proc(proc_info_t *p, const proc_state state) 473 | { 474 | FILE *fp; 475 | char path[PATH_MAX]; 476 | mem_info_t *m; 477 | list_t *mem = (state == PROC_START) ? &mem_info_old : &mem_info_new; 478 | 479 | if (p->is_thread) 480 | return 0; 481 | 482 | snprintf(path, sizeof(path), "/proc/%i/smaps", p->pid); 483 | 484 | if ((fp = fopen(path, "r")) == NULL) 485 | return 0; 486 | 487 | if ((m = calloc(1, sizeof(*m))) == NULL) { 488 | health_check_out_of_memory("allocating memory tracking information"); 489 | (void)fclose(fp); 490 | return -1; 491 | } 492 | m->proc = p; 493 | 494 | while (mem_get_entry(fp, m) != -1) 495 | ; 496 | 497 | if (list_append(mem, m) == NULL) { 498 | free(m); 499 | (void)fclose(fp); 500 | return -1; 501 | } 502 | (void)fclose(fp); 503 | 504 | return 0; 505 | } 506 | 507 | /* 508 | * mem_get_all_pids() 509 | * scan mem and get mmap info 510 | */ 511 | int mem_get_all_pids(const list_t *pids, const proc_state state) 512 | { 513 | link_t *l; 514 | 515 | for (l = pids->head; l; l = l->next) { 516 | proc_info_t *p = (proc_info_t *)l->data; 517 | if (mem_get_by_proc(p, state) < 0) { 518 | return -1; 519 | } 520 | } 521 | return 0; 522 | } 523 | 524 | /* 525 | * mem_delta() 526 | * compute memory size change 527 | */ 528 | static mem_info_t *mem_delta(mem_info_t *mem_new, const list_t *mem_old_list) 529 | { 530 | link_t *l; 531 | int i; 532 | mem_info_t *delta; 533 | 534 | if ((delta = calloc(1, sizeof(*delta))) == NULL) { 535 | health_check_out_of_memory("allocating memory delta tracking information"); 536 | return NULL; 537 | } 538 | 539 | memset(delta, 0, sizeof(*delta)); 540 | 541 | for (l = mem_old_list->head; l; l = l->next) { 542 | mem_info_t *mem_old = (mem_info_t *)l->data; 543 | if (mem_new->proc == mem_old->proc) { 544 | for (i = 0; i < MEM_MAX; i++) { 545 | delta->proc = mem_new->proc; 546 | delta->size[i] = mem_new->size[i] - mem_old->size[i]; 547 | delta->rss[i] = mem_new->rss[i] - mem_old->rss[i]; 548 | delta->pss[i] = mem_new->pss[i] - mem_old->pss[i]; 549 | delta->total[i] = mem_new->total[i] - mem_old->total[i]; 550 | } 551 | return delta; 552 | } 553 | } 554 | /* Old not found, return new */ 555 | memcpy(delta, mem_new, sizeof(*delta)); 556 | return delta; 557 | } 558 | 559 | /* 560 | * mem_dump_diff() 561 | * dump differences between old and new events 562 | */ 563 | int mem_dump_diff( 564 | json_object *j_tests, 565 | const double duration) 566 | { 567 | list_t sorted, sorted_delta; 568 | link_t *l; 569 | bool deltas = false; 570 | 571 | #ifndef JSON_OUTPUT 572 | (void)j_tests; 573 | #endif 574 | 575 | printf("Memory:\n"); 576 | 577 | list_init(&sorted); 578 | list_init(&sorted_delta); 579 | 580 | for (l = mem_info_new.head; l; l = l->next) { 581 | mem_type_t type; 582 | mem_info_t *mem_new = (mem_info_t *)l->data; 583 | 584 | for (type = MEM_STACK; type < MEM_MAX; type++) 585 | mem_new->grand_total += mem_new->total[type]; 586 | 587 | if (list_add_ordered(&sorted, mem_new, mem_cmp) == NULL) 588 | goto out; 589 | } 590 | 591 | for (l = mem_info_new.head; l; l = l->next) { 592 | mem_info_t *delta, *mem_new = (mem_info_t *)l->data; 593 | 594 | if ((delta = mem_delta(mem_new, &mem_info_old)) == NULL) 595 | return -1; 596 | if (list_add_ordered(&sorted_delta, delta, mem_cmp) == NULL) { 597 | free(delta); 598 | goto out; 599 | } 600 | } 601 | 602 | if (!(opt_flags & OPT_BRIEF)) { 603 | printf("Per Process Memory (K):\n"); 604 | if (mem_info_new.head == NULL) { 605 | printf(" No memory detected.\n\n"); 606 | } else { 607 | const int pid_size = pid_max_digits(); 608 | 609 | printf(" %*s Process Type Size RSS PSS\n", 610 | pid_size, "PID"); 611 | for (l = sorted.head; l; l = l->next) { 612 | mem_info_t *delta = (mem_info_t *)l->data; 613 | mem_type_t type; 614 | 615 | for (type = MEM_STACK; type < MEM_MAX; type++) { 616 | printf(" %*d %-20.20s %-6.6s %9" PRIi64 " %9" PRIi64 " %9" PRIi64 "\n", 617 | pid_size, delta->proc->pid, 618 | delta->proc->cmdline, 619 | mem_types[type], 620 | delta->size[type] / 1024, 621 | delta->rss[type] / 1024, 622 | delta->pss[type] / 1024); 623 | } 624 | } 625 | printf("\n"); 626 | } 627 | } 628 | 629 | printf("Change in memory (K/second):\n"); 630 | if (mem_info_new.head == NULL) { 631 | printf(" No memory detected.\n\n"); 632 | } else { 633 | const int pid_size = pid_max_digits(); 634 | 635 | for (l = sorted_delta.head; l; l = l->next) { 636 | mem_info_t *delta = (mem_info_t *)l->data; 637 | mem_type_t type; 638 | 639 | for (type = MEM_STACK; type < MEM_MAX; type++) { 640 | if (delta->total[type]) { 641 | if (!deltas) { 642 | printf(" %*s Process Type Size RSS PSS\n", 643 | pid_size, "PID"); 644 | deltas = true; 645 | } 646 | printf(" %*d %-20.20s %-6.6s %9.2f %9.2f %9.2f (%s)\n", 647 | pid_size, delta->proc->pid, 648 | delta->proc->cmdline, 649 | mem_types[type], 650 | (double)(delta->size[type] / 1024.0) / duration, 651 | (double)(delta->rss[type] / 1024.0) / duration, 652 | (double)(delta->pss[type] / 1024.0) / duration, 653 | mem_loading((double)(delta->total[type] / duration))); 654 | } 655 | } 656 | } 657 | } 658 | if (!deltas) 659 | printf(" No changes found.\n"); 660 | printf("\n"); 661 | 662 | #ifdef JSON_OUTPUT 663 | if (j_tests) { 664 | json_object *j_mem_test, *j_mem_infos, *j_mem_info; 665 | char label[128]; 666 | mem_type_t type; 667 | double rate; 668 | int64_t total_size[MEM_MAX], total_rss[MEM_MAX], total_pss[MEM_MAX]; 669 | 670 | memset(total_size, 0, sizeof(total_size)); 671 | memset(total_rss, 0, sizeof(total_rss)); 672 | memset(total_pss, 0, sizeof(total_pss)); 673 | 674 | if ((j_mem_test = j_obj_new_obj()) == NULL) 675 | goto out; 676 | j_obj_obj_add(j_tests, "memory-usage", j_mem_test); 677 | if ((j_mem_infos = j_obj_new_array()) == NULL) 678 | goto out; 679 | j_obj_obj_add(j_mem_test, "memory-usage-per-process", j_mem_infos); 680 | for (l = sorted.head; l; l = l->next) { 681 | mem_info_t *delta = (mem_info_t *)l->data; 682 | 683 | for (type = MEM_STACK; type < MEM_MAX; type++) { 684 | if ((j_mem_info = j_obj_new_obj()) == NULL) 685 | goto out; 686 | j_obj_new_int32_add(j_mem_info, "pid", delta->proc->pid); 687 | j_obj_new_int32_add(j_mem_info, "ppid", delta->proc->ppid); 688 | j_obj_new_int32_add(j_mem_info, "is-thread", delta->proc->is_thread); 689 | j_obj_new_string_add(j_mem_info, "name", delta->proc->cmdline); 690 | /* Size */ 691 | snprintf(label, sizeof(label), "%s-size-Kbytes", mem_types[type]); 692 | mem_tolower_str(label); 693 | j_obj_new_int64_add(j_mem_info, label, delta->size[type] / 1024); 694 | /* RSS */ 695 | snprintf(label, sizeof(label), "%s-rss-Kbytes", mem_types[type]); 696 | mem_tolower_str(label); 697 | j_obj_new_int64_add(j_mem_info, label, delta->rss[type] / 1024); 698 | /* PSS */ 699 | snprintf(label, sizeof(label), "%s-pss-Kbytes", mem_types[type]); 700 | mem_tolower_str(label); 701 | j_obj_new_int64_add(j_mem_info, label, delta->pss[type] / 1024); 702 | 703 | j_obj_array_add(j_mem_infos, j_mem_info); 704 | 705 | total_size[type] += delta->size[type]; 706 | total_rss[type] += delta->rss[type]; 707 | total_pss[type] += delta->pss[type]; 708 | } 709 | } 710 | if ((j_mem_info = j_obj_new_obj()) == NULL) 711 | goto out; 712 | j_obj_obj_add(j_mem_test, "memory-usage-total", j_mem_info); 713 | for (type = MEM_STACK; type < MEM_MAX; type++) { 714 | /* Size */ 715 | snprintf(label, sizeof(label), "%s-size-total-Kbytes", mem_types[type]); 716 | mem_tolower_str(label); 717 | j_obj_new_int64_add(j_mem_info, label, total_size[type] / 1024); 718 | /* RSS */ 719 | snprintf(label, sizeof(label), "%s-rss-total-Kbytes", mem_types[type]); 720 | mem_tolower_str(label); 721 | j_obj_new_int64_add(j_mem_info, label, total_rss[type] / 1024); 722 | /* PSS */ 723 | snprintf(label, sizeof(label), "%s-pss-total-Kbytes", mem_types[type]); 724 | mem_tolower_str(label); 725 | j_obj_new_int64_add(j_mem_info, label, total_pss[type] / 1024); 726 | } 727 | 728 | memset(total_size, 0, sizeof(total_size)); 729 | memset(total_rss, 0, sizeof(total_rss)); 730 | memset(total_pss, 0, sizeof(total_pss)); 731 | 732 | if ((j_mem_test = j_obj_new_obj()) == NULL) 733 | goto out; 734 | j_obj_obj_add(j_tests, "memory-change", j_mem_test); 735 | if ((j_mem_infos = j_obj_new_array()) == NULL) 736 | goto out; 737 | j_obj_obj_add(j_mem_test, "memory-change-per-process", j_mem_infos); 738 | for (l = sorted_delta.head; l; l = l->next) { 739 | mem_info_t *delta = (mem_info_t *)l->data; 740 | 741 | for (type = MEM_STACK; type < MEM_MAX; type++) { 742 | if ((j_mem_info = j_obj_new_obj()) == NULL) 743 | goto out; 744 | j_obj_new_int32_add(j_mem_info, "pid", delta->proc->pid); 745 | j_obj_new_int32_add(j_mem_info, "ppid", delta->proc->ppid); 746 | j_obj_new_int32_add(j_mem_info, "is-thread", delta->proc->is_thread); 747 | j_obj_new_string_add(j_mem_info, "name", delta->proc->cmdline); 748 | /* Size */ 749 | rate = (double)(delta->size[type] / 1024.0) / duration; 750 | snprintf(label, sizeof(label), "%s-change-size-Kbytes", mem_types[type]); 751 | mem_tolower_str(label); 752 | j_obj_new_int64_add(j_mem_info, label, delta->size[type] / 1024); 753 | snprintf(label, sizeof(label), "%s-change-size-Kbytes-rate", mem_types[type]); 754 | mem_tolower_str(label); 755 | j_obj_new_double_add(j_mem_info, label, rate); 756 | snprintf(label, sizeof(label), "%s-change-size-Kbytes-hint", mem_types[type]); 757 | mem_tolower_str(label); 758 | j_obj_new_string_add(j_mem_info, label, mem_loading(rate)); 759 | /* RSS */ 760 | rate = (double)(delta->rss[type] / 1024.0) / duration; 761 | snprintf(label, sizeof(label), "%s-change-rss-Kbytes", mem_types[type]); 762 | mem_tolower_str(label); 763 | j_obj_new_int64_add(j_mem_info, label, delta->rss[type] / 1024); 764 | snprintf(label, sizeof(label), "%s-change-rss-Kbytes-rate", mem_types[type]); 765 | mem_tolower_str(label); 766 | j_obj_new_double_add(j_mem_info, label, rate); 767 | snprintf(label, sizeof(label), "%s-change-rss-Kbytes-hint", mem_types[type]); 768 | mem_tolower_str(label); 769 | j_obj_new_string_add(j_mem_info, label, mem_loading(rate)); 770 | /* PSS */ 771 | rate = (double)(delta->pss[type] / 1024.0) / duration; 772 | snprintf(label, sizeof(label), "%s-change-pss-Kbytes", mem_types[type]); 773 | mem_tolower_str(label); 774 | j_obj_new_int64_add(j_mem_info, label, delta->pss[type] / 1024); 775 | snprintf(label, sizeof(label), "%s-change-pss-Kbytes-rate", mem_types[type]); 776 | mem_tolower_str(label); 777 | j_obj_new_double_add(j_mem_info, label, rate); 778 | snprintf(label, sizeof(label), "%s-change-pss-Kbytes-hint", mem_types[type]); 779 | mem_tolower_str(label); 780 | j_obj_new_string_add(j_mem_info, label, mem_loading(rate)); 781 | 782 | j_obj_array_add(j_mem_infos, j_mem_info); 783 | 784 | total_size[type] += delta->size[type]; 785 | total_rss[type] += delta->rss[type]; 786 | total_pss[type] += delta->pss[type]; 787 | } 788 | } 789 | if ((j_mem_info = j_obj_new_obj()) == NULL) 790 | goto out; 791 | j_obj_obj_add(j_mem_test, "memory-change-total", j_mem_info); 792 | for (type = MEM_STACK; type < MEM_MAX; type++) { 793 | /* Size */ 794 | snprintf(label, sizeof(label), "%s-change-size-total-Kbytes", mem_types[type]); 795 | mem_tolower_str(label); 796 | j_obj_new_int64_add(j_mem_info, label, total_size[type] / 1024); 797 | /* RSS */ 798 | snprintf(label, sizeof(label), "%s-change-rss-total-Kbytes", mem_types[type]); 799 | mem_tolower_str(label); 800 | j_obj_new_int64_add(j_mem_info, label, total_rss[type] / 1024); 801 | /* PSS */ 802 | snprintf(label, sizeof(label), "%s-change-pss-total-Kbytes", mem_types[type]); 803 | mem_tolower_str(label); 804 | j_obj_new_int64_add(j_mem_info, label, total_pss[type] / 1024); 805 | } 806 | } 807 | #endif 808 | 809 | out: 810 | list_free(&sorted, NULL); 811 | list_free(&sorted_delta, free); 812 | 813 | return 0; 814 | } 815 | 816 | /* 817 | * mem_init() 818 | * initialise mem lists 819 | */ 820 | void mem_init(void) 821 | { 822 | list_init(&mem_info_old); 823 | list_init(&mem_info_new); 824 | list_init(&mem_brk_info); 825 | list_init(&mem_mmap_info); 826 | } 827 | 828 | /* 829 | * mem_cleanup() 830 | * free mem lists 831 | */ 832 | void mem_cleanup(void) 833 | { 834 | list_free(&mem_info_old, free); 835 | list_free(&mem_info_new, free); 836 | list_free(&mem_brk_info, free); 837 | list_free(&mem_mmap_info, free); 838 | } 839 | --------------------------------------------------------------------------------