/* * pstime - A mash-up of ps(1) and ptime(1) * * Tim.Cook@sun.com, 31 Mar 2009 * * Copyright (c) 2007, 2009 Tim Cook. Permission to use is granted to all. * This code is provided on an "as is" basis, with no warranties * expressed or implied. * * SYNOPSIS * pstime [ ...] * * DESCRIPTION * Lists the amount of USR and SYS CPU time consumed by PID's * (default is all PIDs running) during seconds. * NOTE: the USR and SYS values are not normalised to a * per-second value. */ #include #include #include #include #include #include #include #include #include #include struct psinf { struct psinf *next; pid_t pid; timestruc_t utime; timestruc_t stime; hrtime_t snaptime; char *psargs; char user[9]; }; typedef struct psinf psinf_t; static int g_opt_t = 0; /* Just print the absolute time delta */ static long g_nproc; /* Number of processors on-line */ /* * Load PID usage info into psinf_t. * Returns 0 if not found. */ static int load_usage(psinf_t *listp, char *pid_str) { char path[32]; int usage_fd; struct prusage pru; /* Read usage */ (void) sprintf(path, "/proc/%s/usage", pid_str); if ((usage_fd = open(path, O_RDONLY, 0)) < 0) return (0); if (read(usage_fd, &pru, sizeof pru) != sizeof (pru)) { (void) close(usage_fd); return (0); } if (! g_opt_t) listp->snaptime = gethrtime(); (void) close(usage_fd); /* Save the value that we want */ listp->pid = atoi(pid_str); listp->utime = pru.pr_utime; listp->stime = pru.pr_stime; return (1); } /* * Collects "before" values for all current processes, stored in a linked list */ static psinf_t * get_psinfo_all() { int procs = 0; DIR *dirp; struct dirent *dent; psinf_t *p, *firstp, *lastp; if ((dirp = opendir("/proc")) == NULL) { perror("readdir(/proc)"); return ((psinf_t *)NULL); } lastp = (psinf_t *)NULL; while (dent = readdir(dirp)) { if (strcmp(dent->d_name, ".") == 0) continue; if (strcmp(dent->d_name, "..") == 0) continue; /* malloc a new struct */ if (! (p = malloc(sizeof (psinf_t)))) { perror("malloc"); exit(1); } if (! lastp) firstp = p; else lastp->next = p; lastp = p; if (! load_usage(p, dent->d_name)) continue; procs++; } /* The last entry in the list is empty */ p->next = NULL; (void) closedir(dirp); return (firstp); } /* * Collects "before" values for a list of PIDs */ static psinf_t * get_ps_info_argv(int argind, char **argv) { int procs = 0; psinf_t *p, *firstp, *lastp; lastp = (psinf_t *)NULL; for (; argv[argind]; argind++) { if (! (p = malloc(sizeof (psinf_t)))) { perror("malloc"); exit(1); } if (! load_usage(p, argv[argind])) continue; if (! lastp) firstp = p; else lastp->next = p; lastp = p; procs++; } /* The last entry in the list is empty */ p->next = NULL; return (firstp); } /* * Load psinfo data */ static char * load_psinfo(psinf_t *pi_listp) { int psinfo_fd; char path[64]; struct psinfo psi; struct passwd *pwd; (void) sprintf(path, "/proc/%ld/psinfo", pi_listp->pid); if ((psinfo_fd = open(path, O_RDONLY, 0)) < 0) return (NULL); if (read(psinfo_fd, &psi, sizeof psi) != sizeof (psi)) { (void) close(psinfo_fd); return (NULL); } (void) close(psinfo_fd); /* Get pwnam */ if (pwd = getpwuid(psi.pr_uid)) { (void) strncpy(pi_listp->user, pwd->pw_name, 8); pi_listp->user[8] = '\0'; } else { (void) sprintf(pi_listp->user, "%8u", psi.pr_uid); } /* Copy psargs */ pi_listp->psargs = strdup(psi.pr_psargs); return (pi_listp->psargs); } /* * Print the delta between two timestruc_t's. * Field width of 5. */ static void print_ts_delta(timestruc_t *bef, timestruc_t *now) { time_t delta_sec; hrtime_t delta_nsec; if (now->tv_nsec < bef->tv_nsec) { /* Need to carry a second */ delta_sec = now->tv_sec - bef->tv_sec - 1; delta_nsec = 1000000000LL - bef->tv_nsec + now->tv_nsec; } else { delta_sec = now->tv_sec - bef->tv_sec; delta_nsec = now->tv_nsec - bef->tv_nsec; } /* How many columns? */ if (delta_sec < 10) (void) printf(" %ld.%03lld", delta_sec, delta_nsec / 1000000); else if (delta_sec < 100) (void) printf(" %ld.%02lld", delta_sec, delta_nsec / 10000000); else if (delta_sec < 1000) (void) printf(" %ld.%01lld", delta_sec, delta_nsec / 100000000); else (void) printf(" %5ld", delta_sec); } static inline int precision5(double val) { if (val < 10) return (3); else if (val < 100) return (2); else if (val < 1000) return (1); return (0); } /* * Print the delta between two timestruc_t's as a percentage of elapsed. * Field width of 5. */ static void print_ts_pct(timestruc_t *bef, timestruc_t *now, hrtime_t elapsed) { hrtime_t delta_nsec; double percent; delta_nsec = (now->tv_sec - bef->tv_sec) * 1000000000LL + now->tv_nsec - bef->tv_nsec; if (delta_nsec > 0) percent = delta_nsec * 100.0 / elapsed / g_nproc; else percent = 0.0; (void) printf(" %5.*f", precision5(percent), percent); } /* * Takes list of processes, prints deltas for those running now */ static void print_psinfo_list(psinf_t *listp) { psinf_t *pi_listp = listp; char path[32]; struct prusage pru; int usage_fd; psinf_t *pi_prev; size_t bytes; hrtime_t elapsed; if (g_opt_t) (void) printf(" UID PID USR SYS COMMAND\n"); else (void) printf(" UID PID %%USR %%SYS COMMAND\n"); while (pi_listp) { (void) sprintf(path, "/proc/%ld/usage", pi_listp->pid); usage_fd = open(path, O_RDONLY, 0); if (usage_fd >= 0) { bytes = read(usage_fd, &pru, sizeof (pru)); (void) close(usage_fd); } if (usage_fd < 0 || bytes != sizeof (pru)) { /* Process no longer there - drop from the list */ if (pi_listp->psargs) free(pi_listp->psargs); if (pi_listp == listp) { /* First entry */ (void) memcpy((void *) pi_listp, (void *) pi_listp->next, sizeof (psinf_t)); } else { pi_prev->next = pi_listp->next; free(pi_listp); } } else { /* Get user & psargs */ (void) load_psinfo(pi_listp); /* Print out USER and PID */ (void) printf("%8s %6ld", pi_listp->user, pi_listp->pid); if (g_opt_t) { print_ts_delta(&pi_listp->utime, &pru.pr_utime); print_ts_delta(&pi_listp->stime, &pru.pr_stime); } else { elapsed = gethrtime() - pi_listp->snaptime; print_ts_pct(&pi_listp->utime, &pru.pr_utime, elapsed); print_ts_pct(&pi_listp->stime, &pru.pr_stime, elapsed); } if (pi_listp->psargs) { /* Make it fit in 80 columns */ if (strlen(pi_listp->psargs) > 52) pi_listp->psargs[52] = '\0'; (void) printf(" %s\n", pi_listp->psargs); } } /* Ding */ pi_prev = pi_listp; pi_listp = pi_listp->next; } } int main(int argc, char **argv) { int interval; psinf_t *pi_list; int c; int opt_errors = 0; extern int optind; while ((c = getopt(argc, argv, "t")) != -1) { switch (c) { case 't': g_opt_t++; break; case '?': opt_errors = 0; } } /* Need a minimum of one argument */ if (argc - optind < 1) opt_errors++; else interval = atoi(argv[optind]); if (opt_errors || interval <= 0) { (void) fprintf(stderr, "usage: %s [-t] [ ...]\n", argv[0]); exit(1); } /* How many processors on-line? */ if (! g_opt_t) g_nproc = sysconf(_SC_NPROCESSORS_ONLN); /* Who is running now? */ if ((argc - optind) > 1) { if ((pi_list = get_ps_info_argv(optind + 1, argv)) == (psinf_t *)NULL) { (void) fprintf(stderr, "%s: no such process(es)\n", argv[0]); exit(2); } } else pi_list = get_psinfo_all(); /* Wait the required interval */ (void) sleep(interval); /* Print what has changed */ print_psinfo_list(pi_list); exit(0); }