|  | /* | 
|  | * 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 <stdio.h> | 
|  | #include <time.h> | 
|  | #include <dirent.h> | 
|  | #include <unistd.h> | 
|  | #include <fcntl.h> | 
|  | #include <unistd.h> | 
|  | #include <fcntl.h> | 
|  | #include <unistd.h> | 
|  | #include <fcntl.h> | 
|  | #include <errno.h> | 
|  | #include <stdlib.h> | 
|  | #include <sys/stat.h> | 
|  | #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=<timeout> | 
|  | * 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); | 
|  | } |