/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* this code is used to generate a boot sequence profile that can be used * with the 'bootchart' graphics generation tool. see www.bootchart.org * note that unlike the original bootchartd, this is not a Bash script but * some C code that is run right from the init script. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "bootchart.h" #define VERSION "0.8" #define SAMPLE_PERIOD 0.2 #define LOG_ROOT "/data/bootchart" #define LOG_STAT LOG_ROOT"/proc_stat.log" #define LOG_PROCS LOG_ROOT"/proc_ps.log" #define LOG_DISK LOG_ROOT"/proc_diskstats.log" #define LOG_HEADER LOG_ROOT"/header" #define LOG_ACCT LOG_ROOT"/kernel_pacct" #define LOG_STARTFILE "/data/bootchart-start" #define LOG_STOPFILE "/data/bootchart-stop" static int unix_read(int fd, void* buff, int len) { int ret; do { ret = read(fd, buff, len); } while (ret < 0 && errno == EINTR); return ret; } static int unix_write(int fd, const void* buff, int len) { int ret; do { ret = write(fd, buff, len); } while (ret < 0 && errno == EINTR); return ret; } static int proc_read(const char* filename, char* buff, size_t buffsize) { int len = 0; int fd = open(filename, O_RDONLY); if (fd >= 0) { len = unix_read(fd, buff, buffsize-1); close(fd); } buff[len > 0 ? len : 0] = 0; return len; } #define FILE_BUFF_SIZE 65536 typedef struct { int count; int fd; char data[FILE_BUFF_SIZE]; } FileBuffRec, *FileBuff; static void file_buff_open( FileBuff buff, const char* path ) { buff->count = 0; buff->fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0755); } static void file_buff_write( FileBuff buff, const void* src, int len ) { while (len > 0) { int avail = sizeof(buff->data) - buff->count; if (avail > len) avail = len; memcpy( buff->data + buff->count, src, avail ); len -= avail; src = (char*)src + avail; buff->count += avail; if (buff->count == FILE_BUFF_SIZE) { unix_write( buff->fd, buff->data, buff->count ); buff->count = 0; } } } static void file_buff_done( FileBuff buff ) { if (buff->count > 0) { unix_write( buff->fd, buff->data, buff->count ); buff->count = 0; } } static void log_header(void) { FILE* out; char cmdline[1024]; char uname[128]; char cpuinfo[128]; char* cpu; char date[32]; time_t now_t = time(NULL); struct tm now = *localtime(&now_t); strftime(date, sizeof(date), "%x %X", &now); out = fopen( LOG_HEADER, "w" ); if (out == NULL) return; proc_read("/proc/cmdline", cmdline, sizeof(cmdline)); proc_read("/proc/version", uname, sizeof(uname)); proc_read("/proc/cpuinfo", cpuinfo, sizeof(cpuinfo)); cpu = strchr( cpuinfo, ':' ); if (cpu) { char* p = strchr(cpu, '\n'); cpu += 2; if (p) *p = 0; } fprintf(out, "version = %s\n", VERSION); fprintf(out, "title = Boot chart for Android ( %s )\n", date); fprintf(out, "system.uname = %s\n", uname); fprintf(out, "system.release = 0.0\n"); fprintf(out, "system.cpu = %s\n", cpu); fprintf(out, "system.kernel.options = %s\n", cmdline); fclose(out); } static void close_on_exec(int fd) { fcntl(fd, F_SETFD, FD_CLOEXEC); } static void open_log_file(int* plogfd, const char* logfile) { int logfd = *plogfd; /* create log file if needed */ if (logfd < 0) { logfd = open(logfile,O_WRONLY|O_CREAT|O_TRUNC,0755); if (logfd < 0) { *plogfd = -2; return; } close_on_exec(logfd); *plogfd = logfd; } } static void do_log_uptime(FileBuff log) { char buff[65]; int fd, ret, len; fd = open("/proc/uptime",O_RDONLY); if (fd >= 0) { int ret; ret = unix_read(fd, buff, 64); close(fd); buff[64] = 0; if (ret >= 0) { long long jiffies = 100LL*strtod(buff,NULL); int len; snprintf(buff,sizeof(buff),"%lld\n",jiffies); len = strlen(buff); file_buff_write(log, buff, len); } } } static void do_log_ln(FileBuff log) { file_buff_write(log, "\n", 1); } static void do_log_file(FileBuff log, const char* procfile) { char buff[1024]; int fd; do_log_uptime(log); /* append file content */ fd = open(procfile,O_RDONLY); if (fd >= 0) { close_on_exec(fd); for (;;) { int ret; ret = unix_read(fd, buff, sizeof(buff)); if (ret <= 0) break; file_buff_write(log, buff, ret); if (ret < (int)sizeof(buff)) break; } close(fd); } do_log_ln(log); } static void do_log_procs(FileBuff log) { DIR* dir = opendir("/proc"); struct dirent* entry; do_log_uptime(log); while ((entry = readdir(dir)) != NULL) { /* only match numeric values */ char* end; int pid = strtol( entry->d_name, &end, 10); if (end != NULL && end > entry->d_name && *end == 0) { char filename[32]; char buff[1024]; char cmdline[1024]; int len; int fd; /* read command line and extract program name */ snprintf(filename,sizeof(filename),"/proc/%d/cmdline",pid); proc_read(filename, cmdline, sizeof(cmdline)); /* read process stat line */ snprintf(filename,sizeof(filename),"/proc/%d/stat",pid); fd = open(filename,O_RDONLY); if (fd >= 0) { len = unix_read(fd, buff, sizeof(buff)-1); close(fd); if (len > 0) { int len2 = strlen(cmdline); if (len2 > 0) { /* we want to substitute the process name with its real name */ const char* p1; const char* p2; buff[len] = 0; p1 = strchr(buff, '('); p2 = strchr(p1, ')'); file_buff_write(log, buff, p1+1-buff); file_buff_write(log, cmdline, strlen(cmdline)); file_buff_write(log, p2, strlen(p2)); } else { /* no substitution */ file_buff_write(log,buff,len); } } } } } closedir(dir); do_log_ln(log); } static FileBuffRec log_stat[1]; static FileBuffRec log_procs[1]; static FileBuffRec log_disks[1]; /* called to setup bootcharting */ int bootchart_init( void ) { int ret; char buff[4]; int timeout = 0, count = 0; buff[0] = 0; proc_read( LOG_STARTFILE, buff, sizeof(buff) ); if (buff[0] != 0) { timeout = atoi(buff); } else { /* when running with emulator, androidboot.bootchart= * might be passed by as kernel parameters to specify the bootchart * timeout. this is useful when using -wipe-data since the /data * partition is fresh */ char cmdline[1024]; char* s; #define KERNEL_OPTION "androidboot.bootchart=" proc_read( "/proc/cmdline", cmdline, sizeof(cmdline) ); s = strstr(cmdline, KERNEL_OPTION); if (s) { s += sizeof(KERNEL_OPTION)-1; timeout = atoi(s); } } if (timeout == 0) return 0; if (timeout > BOOTCHART_MAX_TIME_SEC) timeout = BOOTCHART_MAX_TIME_SEC; count = (timeout*1000 + BOOTCHART_POLLING_MS-1)/BOOTCHART_POLLING_MS; do {ret=mkdir(LOG_ROOT,0755);}while (ret < 0 && errno == EINTR); file_buff_open(log_stat, LOG_STAT); file_buff_open(log_procs, LOG_PROCS); file_buff_open(log_disks, LOG_DISK); /* create kernel process accounting file */ { int fd = open( LOG_ACCT, O_WRONLY|O_CREAT|O_TRUNC,0644); if (fd >= 0) { close(fd); acct( LOG_ACCT ); } } log_header(); return count; } /* called each time you want to perform a bootchart sampling op */ int bootchart_step( void ) { do_log_file(log_stat, "/proc/stat"); do_log_file(log_disks, "/proc/diskstats"); do_log_procs(log_procs); /* we stop when /data/bootchart-stop contains 1 */ { char buff[2]; if (proc_read(LOG_STOPFILE,buff,sizeof(buff)) > 0 && buff[0] == '1') { return -1; } } return 0; } void bootchart_finish( void ) { unlink( LOG_STOPFILE ); file_buff_done(log_stat); file_buff_done(log_disks); file_buff_done(log_procs); acct(NULL); }