diff options
-rw-r--r-- | toys/pending/bootchartd.c | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/toys/pending/bootchartd.c b/toys/pending/bootchartd.c new file mode 100644 index 00000000..90f37cb5 --- /dev/null +++ b/toys/pending/bootchartd.c @@ -0,0 +1,334 @@ +/* bootchartd.c - bootchartd is commonly used to profile the boot process. + * + * Copyright 2014 Bilal Qureshi <bilal.jmi@gmail.com> + * Copyright 2014 Kyungwan Han <asura321@gmail.com> + * + * No Standard + +USE_BOOTCHARTD(NEWTOY(bootchartd, NULL, TOYFLAG_STAYROOT|TOYFLAG_USR|TOYFLAG_BIN)) + +config BOOTCHARTD + bool "bootchartd" + default n + help + usage: bootchartd start [PROG ARGS]|stop|init + + Create /var/log/bootlog.tgz with boot chart data + + start: start background logging; with PROG, run PROG, then kill logging with USR1 + stop: send USR1 to all bootchartd processes + init: start background logging; stop when getty/xdm is seen (for init scripts) + Under PID 1: as init, then exec $bootchart_init, /init, /sbin/init +*/ +#define FOR_bootchartd +#include "toys.h" +#include <signal.h> + +GLOBALS( + char buf[32]; + long smpl_period_usec; + int proc_accounting; + int is_login; + int got_signal; +) + +struct pid_list { + struct pid_list *next; + int pid; +}; + +static struct pid_list *head; + +static void insert_pid(struct pid_list **plist, int pid) +{ + struct pid_list *new_node = xmalloc(sizeof(struct pid_list)); + + new_node->pid = pid; + new_node->next = NULL; + if (!*plist) *plist = new_node; + else { + new_node->next = (*plist); + *plist = new_node; + } +} + +static int push_pids_in_list(pid_t pid, char *name) +{ + insert_pid(&head, pid); + return 0; +} + +static void dump_data_in_file(char *fname, int wfd) +{ + int rfd = open(fname, O_RDONLY); + + if (rfd != -1) { + xwrite(wfd, TT.buf, strlen(TT.buf)); + xsendfile(rfd, wfd); + close(rfd); + xwrite(wfd, "\n", 1); + } +} + +static int dump_proc_data(FILE *fp) +{ + struct dirent *pid_dir; + int login_flag = 0; + pid_t pid; + DIR *proc_dir = opendir("/proc"); + + fputs(TT.buf, fp); + while ((pid_dir = readdir(proc_dir))) { + char filename[64]; + int fd; + + if (!isdigit(pid_dir->d_name[0])) continue; + sscanf(pid_dir->d_name, "%d", &pid); + sprintf(filename, "/proc/%d/stat", pid); + if ((fd = open(filename, O_RDONLY)) != -1 ) { + char *ptr; + ssize_t len; + + if ((len = readall(fd, toybuf, sizeof(toybuf)-1)) < 0) continue; + toybuf[len] = '\0'; + close(fd); + fputs(toybuf, fp); + if (!TT.is_login) continue; + if ((ptr = strchr(toybuf, '('))) { + char *tmp = strchr(++ptr, ')'); + + if (tmp) *tmp = '\0'; + } + // Checks for gdm, kdm or getty + if (((ptr[0] == 'g' || ptr[0] == 'k' || ptr[0] == 'x') && ptr[1] == 'd' + && ptr[2] == 'm') || strstr(ptr, "getty")) login_flag = 1; + } + } + closedir(proc_dir); + fputc('\n', fp); + return login_flag; +} + +static int parse_config_file(char *fname) +{ + size_t len = 0; + char *line = NULL; + FILE *fp = fopen(fname, "r"); + + if (!fp) return 0; + for (;getline(&line, &len, fp) != -1; line = NULL) { + char *ptr = line; + + while (*ptr == ' ' || *ptr == '\t') ptr++; + if (!*ptr || *ptr == '#' || *ptr == '\n') continue; + if (!strncmp(ptr, "SAMPLE_PERIOD", strlen("SAMPLE_PERIOD"))) { + double smpl_val; + + if ((ptr = strchr(ptr, '='))) ptr += 1; + else continue; + sscanf(ptr, "%lf", &smpl_val); + TT.smpl_period_usec = smpl_val * 1000000; + if (TT.smpl_period_usec <= 0) TT.smpl_period_usec = 1; + } + if (!strncmp(ptr, "PROCESS_ACCOUNTING", strlen("PROCESS_ACCOUNTING"))) { + if ((ptr = strchr(ptr, '='))) ptr += 1; + else continue; + sscanf(ptr, "%s", toybuf); // string will come with double quotes. + if (!(strncmp(toybuf+1, "on", strlen("on"))) || + !(strncmp(toybuf+1, "yes", strlen("yes")))) TT.proc_accounting = 1; + } + free(line); + } + fclose(fp); + return 1; +} + +static char *create_tmp_dir() +{ + char *dir_list[] = {"/tmp", "/mnt", "/boot", "/proc"}, **target = dir_list; + char *dir, dir_path[] = "/tmp/bootchart.XXXXXX"; + + if ((dir = mkdtemp(dir_path))) { + xchdir((dir = xstrdup(dir))); + return dir; + } + while (mount("none", *target, "tmpfs", (1<<15), "size=16m")) //MS_SILENT + if (!++target) perror_exit("can't mount tmpfs"); + xchdir(*target); + if (umount2(*target, MNT_DETACH)) perror_exit("Can't unmount tmpfs"); + return *target; +} + +static void start_logging() +{ + int proc_stat_fd = xcreate("proc_stat.log", + O_WRONLY | O_CREAT | O_TRUNC, 0644); + int proc_diskstats_fd = xcreate("proc_diskstats.log", + O_WRONLY | O_CREAT | O_TRUNC, 0644); + FILE *proc_ps_fp = xfopen("proc_ps.log", "w"); + long tcnt = 60 * 1000 * 1000 / TT.smpl_period_usec; + + if (tcnt <= 0) tcnt = 1; + if (TT.proc_accounting) { + int kp_fd = xcreate("kernel_procs_acct", O_WRONLY | O_CREAT | O_TRUNC,0666); + + xclose(kp_fd); + acct("kernel_procs_acct"); + } + memset(TT.buf, 0, sizeof(TT.buf)); + while (--tcnt && !TT.got_signal) { + int i = 0, j = 0, fd = open("/proc/uptime", O_RDONLY); + if (fd < 0) goto wait_usec; + char *line = get_line(fd); + + if (!line) goto wait_usec; + while (line[i] != ' ') { + if (line[i] == '.') { + i++; + continue; + } + TT.buf[j++] = line[i++]; + } + TT.buf[j++] = '\n'; + TT.buf[j] = '\0'; + free(line); + close(fd); + dump_data_in_file("/proc/stat", proc_stat_fd); + dump_data_in_file("/proc/diskstats", proc_diskstats_fd); + // stop proc dumping in 2 secs if getty or gdm, kdm, xdm found + if (dump_proc_data(proc_ps_fp)) + if (tcnt > 2 * 1000 * 1000 / TT.smpl_period_usec) + tcnt = 2 * 1000 * 1000 / TT.smpl_period_usec; + fflush(NULL); +wait_usec: + usleep(TT.smpl_period_usec); + } + xclose(proc_stat_fd); + xclose(proc_diskstats_fd); + fclose(proc_ps_fp); +} + +static void stop_logging(char *tmp_dir, char *prog) +{ + char host_name[32]; + int kcmd_line_fd; + time_t t; + struct tm st; + struct utsname uts; + FILE *hdr_fp = xfopen("header", "w"); + + if (TT.proc_accounting) acct(NULL); + if (prog) fprintf(hdr_fp, "profile.process = %s\n", prog); + gethostname(host_name, sizeof(host_name)); + time(&t); + localtime_r(&t, &st); + memset(toybuf, 0, sizeof(toybuf)); + strftime(toybuf, sizeof(toybuf), "%a %b %e %H:%M:%S %Z %Y", &st); + fprintf(hdr_fp, "version = TBX_BCHARTD_VER 1.0.0\n"); + fprintf(hdr_fp, "title = Boot chart for %s (%s)\n", host_name, toybuf); + if (uname(&uts) < 0) perror_exit("uname"); + fprintf(hdr_fp, "system.uname = %s %s %s %s\n", uts.sysname, uts.release, + uts.version, uts.machine); + memset(toybuf, 0, sizeof(toybuf)); + if ((kcmd_line_fd = open("/proc/cmdline", O_RDONLY)) != -1) { + ssize_t len; + len = readall(kcmd_line_fd, toybuf, sizeof(toybuf)-1); + toybuf[len] = '\0'; + while (--len >= 0 && toybuf[len] == '\0') continue; + for (; len > 0; len--) + if ((unsigned char)toybuf[len] < ' ') toybuf[len] = ' '; + } + fprintf(hdr_fp, "system.kernel.options = %s", toybuf); + close(kcmd_line_fd); + fclose(hdr_fp); + memset(toybuf, 0, sizeof(toybuf)); + snprintf(toybuf, sizeof(toybuf), "tar -zcf /var/log/bootlog.tgz header %s *.log", + TT.proc_accounting ? "kernel_procs_acct" : ""); + system(toybuf); + if (tmp_dir) { + unlink("header"); + unlink("proc_stat.log"); + unlink("proc_diskstats.log"); + unlink("proc_ps.log"); + if (TT.proc_accounting) unlink("kernel_procs_acct"); + rmdir(tmp_dir); + } +} + +static void signal_handler(int sig) +{ + TT.got_signal = sig; +} + +void bootchartd_main() +{ + pid_t lgr_pid, self_pid = getpid(); + int bchartd_opt; // 1 -> start, 2 -> stop, 3 -> init, 4 -> PID1 + TT.smpl_period_usec = 200 * 1000; + + if (toys.optargs[0]) { + if (!strcmp("start", toys.optargs[0])) bchartd_opt = 1; + else if (!strcmp("stop", toys.optargs[0])) bchartd_opt = 2; + else if (!strcmp("init", toys.optargs[0])) bchartd_opt = 3; + else { + toys.exithelp++; + error_exit("Unknown option '%s'", toys.optargs[0]); + } + if (bchartd_opt == 2) { + struct pid_list *temp; + char *process_name[] = {"bootchartd", NULL}; + + names_to_pid(process_name, push_pids_in_list); + for (temp = head; temp; temp = temp->next) + if (temp->pid != self_pid) kill(temp->pid, SIGUSR1); + temp = head; + while (head) { + temp = head->next; + free(head); + head = temp; + } + return; + } + } else { + if (self_pid != 1) { + toys.exithelp++; + error_exit("Its Not PID1"); + } + bchartd_opt = 4; // PID1 case + } + // Execute the code below for start or init or PID1 + if (!parse_config_file("bootchartd.conf")) + parse_config_file("/etc/bootchartd.conf"); + + TT.is_login = (self_pid == 1); + memset(toybuf, 0, sizeof(toybuf)); + if (!(lgr_pid = fork())) { + char *tmp_dir = create_tmp_dir(); + + sigatexit(signal_handler); + raise(SIGSTOP); + if (bchartd_opt == 4 && !getenv("PATH")) + putenv((char*)"PATH=/sbin:/usr/sbin:/bin:/usr/bin"); + start_logging(); + stop_logging(tmp_dir, bchartd_opt == 1 ? toys.optargs[1] : NULL); + free(tmp_dir); + return; + } + waitpid(lgr_pid, NULL, WUNTRACED); + kill(lgr_pid, SIGCONT); + + if (bchartd_opt == 4) { + char *pbchart_init = getenv("bootchart_init"); + + if (pbchart_init) execl(pbchart_init, pbchart_init, NULL); + execl("/init", "init", (char *) 0); + execl("/sbin/init", "init", (char *) 0); + } + if (bchartd_opt == 1 && toys.optargs[1]) { + pid_t prog_pid; + + if (!(prog_pid = fork())) xexec_optargs(1); + waitpid(prog_pid, NULL, 0); + kill(lgr_pid, SIGUSR1); + } +} |