| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 1 | /* | 
|  | 2 | * Copyright (C) 2008 The Android Open Source Project | 
|  | 3 | * | 
|  | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | 5 | * you may not use this file except in compliance with the License. | 
|  | 6 | * You may obtain a copy of the License at | 
|  | 7 | * | 
|  | 8 | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | 9 | * | 
|  | 10 | * Unless required by applicable law or agreed to in writing, software | 
|  | 11 | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | 13 | * See the License for the specific language governing permissions and | 
|  | 14 | * limitations under the License. | 
|  | 15 | */ | 
|  | 16 |  | 
|  | 17 | /* this code is used to generate a boot sequence profile that can be used | 
|  | 18 | * with the 'bootchart' graphics generation tool. see www.bootchart.org | 
|  | 19 | * note that unlike the original bootchartd, this is not a Bash script but | 
|  | 20 | * some C code that is run right from the init script. | 
|  | 21 | */ | 
|  | 22 |  | 
|  | 23 | #include <stdio.h> | 
|  | 24 | #include <time.h> | 
|  | 25 | #include <dirent.h> | 
|  | 26 | #include <unistd.h> | 
|  | 27 | #include <fcntl.h> | 
|  | 28 | #include <unistd.h> | 
|  | 29 | #include <fcntl.h> | 
|  | 30 | #include <unistd.h> | 
|  | 31 | #include <fcntl.h> | 
|  | 32 | #include <errno.h> | 
|  | 33 | #include <stdlib.h> | 
|  | 34 | #include <sys/stat.h> | 
|  | 35 | #include "bootchart.h" | 
|  | 36 |  | 
|  | 37 | #define VERSION         "0.8" | 
|  | 38 | #define SAMPLE_PERIOD   0.2 | 
|  | 39 | #define LOG_ROOT        "/data/bootchart" | 
|  | 40 | #define LOG_STAT        LOG_ROOT"/proc_stat.log" | 
|  | 41 | #define LOG_PROCS       LOG_ROOT"/proc_ps.log" | 
|  | 42 | #define LOG_DISK        LOG_ROOT"/proc_diskstats.log" | 
|  | 43 | #define LOG_HEADER      LOG_ROOT"/header" | 
|  | 44 | #define LOG_ACCT        LOG_ROOT"/kernel_pacct" | 
|  | 45 |  | 
|  | 46 | #define LOG_STARTFILE   "/data/bootchart-start" | 
|  | 47 | #define LOG_STOPFILE    "/data/bootchart-stop" | 
|  | 48 |  | 
|  | 49 | static int | 
|  | 50 | unix_read(int  fd, void*  buff, int  len) | 
|  | 51 | { | 
|  | 52 | int  ret; | 
|  | 53 | do { ret = read(fd, buff, len); } while (ret < 0 && errno == EINTR); | 
|  | 54 | return ret; | 
|  | 55 | } | 
|  | 56 |  | 
|  | 57 | static int | 
|  | 58 | unix_write(int  fd, const void*  buff, int  len) | 
|  | 59 | { | 
|  | 60 | int  ret; | 
|  | 61 | do { ret = write(fd, buff, len); } while (ret < 0 && errno == EINTR); | 
|  | 62 | return ret; | 
|  | 63 | } | 
|  | 64 |  | 
|  | 65 | static int | 
|  | 66 | proc_read(const char*  filename, char* buff, size_t  buffsize) | 
|  | 67 | { | 
|  | 68 | int  len = 0; | 
|  | 69 | int  fd  = open(filename, O_RDONLY); | 
|  | 70 | if (fd >= 0) { | 
|  | 71 | len = unix_read(fd, buff, buffsize-1); | 
|  | 72 | close(fd); | 
|  | 73 | } | 
|  | 74 | buff[len > 0 ? len : 0] = 0; | 
|  | 75 | return len; | 
|  | 76 | } | 
|  | 77 |  | 
|  | 78 | #define FILE_BUFF_SIZE    65536 | 
|  | 79 |  | 
|  | 80 | typedef struct { | 
|  | 81 | int   count; | 
|  | 82 | int   fd; | 
|  | 83 | char  data[FILE_BUFF_SIZE]; | 
|  | 84 | } FileBuffRec, *FileBuff; | 
|  | 85 |  | 
|  | 86 | static void | 
|  | 87 | file_buff_open( FileBuff  buff, const char*  path ) | 
|  | 88 | { | 
|  | 89 | buff->count = 0; | 
|  | 90 | buff->fd    = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0755); | 
|  | 91 | } | 
|  | 92 |  | 
|  | 93 | static void | 
|  | 94 | file_buff_write( FileBuff  buff, const void*  src, int  len ) | 
|  | 95 | { | 
|  | 96 | while (len > 0) { | 
|  | 97 | int  avail = sizeof(buff->data) - buff->count; | 
|  | 98 | if (avail > len) | 
|  | 99 | avail = len; | 
|  | 100 |  | 
|  | 101 | memcpy( buff->data + buff->count, src, avail ); | 
|  | 102 | len -= avail; | 
|  | 103 | src  = (char*)src + avail; | 
|  | 104 |  | 
|  | 105 | buff->count += avail; | 
|  | 106 | if (buff->count == FILE_BUFF_SIZE) { | 
|  | 107 | unix_write( buff->fd, buff->data, buff->count ); | 
|  | 108 | buff->count = 0; | 
|  | 109 | } | 
|  | 110 | } | 
|  | 111 | } | 
|  | 112 |  | 
|  | 113 | static void | 
|  | 114 | file_buff_done( FileBuff  buff ) | 
|  | 115 | { | 
|  | 116 | if (buff->count > 0) { | 
|  | 117 | unix_write( buff->fd, buff->data, buff->count ); | 
|  | 118 | buff->count = 0; | 
|  | 119 | } | 
|  | 120 | } | 
|  | 121 |  | 
|  | 122 | static void | 
|  | 123 | log_header(void) | 
|  | 124 | { | 
|  | 125 | FILE*      out; | 
|  | 126 | char       cmdline[1024]; | 
|  | 127 | char       uname[128]; | 
|  | 128 | char       cpuinfo[128]; | 
|  | 129 | char*      cpu; | 
|  | 130 | char       date[32]; | 
|  | 131 | time_t     now_t = time(NULL); | 
|  | 132 | struct tm  now = *localtime(&now_t); | 
|  | 133 | strftime(date, sizeof(date), "%x %X", &now); | 
|  | 134 |  | 
|  | 135 | out = fopen( LOG_HEADER, "w" ); | 
|  | 136 | if (out == NULL) | 
|  | 137 | return; | 
|  | 138 |  | 
|  | 139 | proc_read("/proc/cmdline", cmdline, sizeof(cmdline)); | 
|  | 140 | proc_read("/proc/version", uname, sizeof(uname)); | 
|  | 141 | proc_read("/proc/cpuinfo", cpuinfo, sizeof(cpuinfo)); | 
|  | 142 |  | 
|  | 143 | cpu = strchr( cpuinfo, ':' ); | 
|  | 144 | if (cpu) { | 
|  | 145 | char*  p = strchr(cpu, '\n'); | 
|  | 146 | cpu += 2; | 
|  | 147 | if (p) | 
|  | 148 | *p = 0; | 
|  | 149 | } | 
|  | 150 |  | 
|  | 151 | fprintf(out, "version = %s\n", VERSION); | 
|  | 152 | fprintf(out, "title = Boot chart for Android ( %s )\n", date); | 
|  | 153 | fprintf(out, "system.uname = %s\n", uname); | 
|  | 154 | fprintf(out, "system.release = 0.0\n"); | 
|  | 155 | fprintf(out, "system.cpu = %s\n", cpu); | 
|  | 156 | fprintf(out, "system.kernel.options = %s\n", cmdline); | 
|  | 157 | fclose(out); | 
|  | 158 | } | 
|  | 159 |  | 
|  | 160 | static void | 
|  | 161 | close_on_exec(int  fd) | 
|  | 162 | { | 
|  | 163 | fcntl(fd, F_SETFD, FD_CLOEXEC); | 
|  | 164 | } | 
|  | 165 |  | 
|  | 166 | static void | 
|  | 167 | open_log_file(int*  plogfd, const char*  logfile) | 
|  | 168 | { | 
|  | 169 | int    logfd = *plogfd; | 
|  | 170 |  | 
|  | 171 | /* create log file if needed */ | 
|  | 172 | if (logfd < 0) | 
|  | 173 | { | 
|  | 174 | logfd = open(logfile,O_WRONLY|O_CREAT|O_TRUNC,0755); | 
|  | 175 | if (logfd < 0) { | 
|  | 176 | *plogfd = -2; | 
|  | 177 | return; | 
|  | 178 | } | 
|  | 179 | close_on_exec(logfd); | 
|  | 180 | *plogfd = logfd; | 
|  | 181 | } | 
|  | 182 | } | 
|  | 183 |  | 
|  | 184 | static void | 
|  | 185 | do_log_uptime(FileBuff  log) | 
|  | 186 | { | 
|  | 187 | char  buff[65]; | 
|  | 188 | int   fd, ret, len; | 
|  | 189 |  | 
|  | 190 | fd = open("/proc/uptime",O_RDONLY); | 
|  | 191 | if (fd >= 0) { | 
|  | 192 | int  ret; | 
|  | 193 | ret = unix_read(fd, buff, 64); | 
|  | 194 | close(fd); | 
|  | 195 | buff[64] = 0; | 
|  | 196 | if (ret >= 0) { | 
|  | 197 | long long  jiffies = 100LL*strtod(buff,NULL); | 
|  | 198 | int        len; | 
|  | 199 | snprintf(buff,sizeof(buff),"%lld\n",jiffies); | 
|  | 200 | len = strlen(buff); | 
|  | 201 | file_buff_write(log, buff, len); | 
|  | 202 | } | 
|  | 203 | } | 
|  | 204 | } | 
|  | 205 |  | 
|  | 206 | static void | 
|  | 207 | do_log_ln(FileBuff  log) | 
|  | 208 | { | 
|  | 209 | file_buff_write(log, "\n", 1); | 
|  | 210 | } | 
|  | 211 |  | 
|  | 212 |  | 
|  | 213 | static void | 
|  | 214 | do_log_file(FileBuff  log, const char*  procfile) | 
|  | 215 | { | 
|  | 216 | char   buff[1024]; | 
|  | 217 | int    fd; | 
|  | 218 |  | 
|  | 219 | do_log_uptime(log); | 
|  | 220 |  | 
|  | 221 | /* append file content */ | 
|  | 222 | fd = open(procfile,O_RDONLY); | 
|  | 223 | if (fd >= 0) { | 
|  | 224 | close_on_exec(fd); | 
|  | 225 | for (;;) { | 
|  | 226 | int  ret; | 
|  | 227 | ret = unix_read(fd, buff, sizeof(buff)); | 
|  | 228 | if (ret <= 0) | 
|  | 229 | break; | 
|  | 230 |  | 
|  | 231 | file_buff_write(log, buff, ret); | 
|  | 232 | if (ret < (int)sizeof(buff)) | 
|  | 233 | break; | 
|  | 234 | } | 
|  | 235 | close(fd); | 
|  | 236 | } | 
|  | 237 |  | 
|  | 238 | do_log_ln(log); | 
|  | 239 | } | 
|  | 240 |  | 
|  | 241 | static void | 
|  | 242 | do_log_procs(FileBuff  log) | 
|  | 243 | { | 
|  | 244 | DIR*  dir = opendir("/proc"); | 
|  | 245 | struct dirent*  entry; | 
|  | 246 |  | 
|  | 247 | do_log_uptime(log); | 
|  | 248 |  | 
|  | 249 | while ((entry = readdir(dir)) != NULL) { | 
|  | 250 | /* only match numeric values */ | 
|  | 251 | char*  end; | 
|  | 252 | int    pid = strtol( entry->d_name, &end, 10); | 
|  | 253 | if (end != NULL && end > entry->d_name && *end == 0) { | 
|  | 254 | char  filename[32]; | 
|  | 255 | char  buff[1024]; | 
|  | 256 | char  cmdline[1024]; | 
|  | 257 | int   len; | 
|  | 258 | int   fd; | 
|  | 259 |  | 
|  | 260 | /* read command line and extract program name */ | 
|  | 261 | snprintf(filename,sizeof(filename),"/proc/%d/cmdline",pid); | 
|  | 262 | proc_read(filename, cmdline, sizeof(cmdline)); | 
|  | 263 |  | 
|  | 264 | /* read process stat line */ | 
|  | 265 | snprintf(filename,sizeof(filename),"/proc/%d/stat",pid); | 
|  | 266 | fd = open(filename,O_RDONLY); | 
|  | 267 | if (fd >= 0) { | 
|  | 268 | len = unix_read(fd, buff, sizeof(buff)-1); | 
|  | 269 | close(fd); | 
|  | 270 | if (len > 0) { | 
|  | 271 | int  len2 = strlen(cmdline); | 
|  | 272 | if (len2 > 0) { | 
|  | 273 | /* we want to substitute the process name with its real name */ | 
|  | 274 | const char*  p1; | 
|  | 275 | const char*  p2; | 
|  | 276 | buff[len] = 0; | 
|  | 277 | p1 = strchr(buff, '('); | 
|  | 278 | p2 = strchr(p1, ')'); | 
|  | 279 | file_buff_write(log, buff, p1+1-buff); | 
|  | 280 | file_buff_write(log, cmdline, strlen(cmdline)); | 
|  | 281 | file_buff_write(log, p2, strlen(p2)); | 
|  | 282 | } else { | 
|  | 283 | /* no substitution */ | 
|  | 284 | file_buff_write(log,buff,len); | 
|  | 285 | } | 
|  | 286 | } | 
|  | 287 | } | 
|  | 288 | } | 
|  | 289 | } | 
|  | 290 | closedir(dir); | 
|  | 291 | do_log_ln(log); | 
|  | 292 | } | 
|  | 293 |  | 
|  | 294 | static FileBuffRec  log_stat[1]; | 
|  | 295 | static FileBuffRec  log_procs[1]; | 
|  | 296 | static FileBuffRec  log_disks[1]; | 
|  | 297 |  | 
|  | 298 | /* called to setup bootcharting */ | 
|  | 299 | int   bootchart_init( void ) | 
|  | 300 | { | 
|  | 301 | int  ret; | 
|  | 302 | char buff[4]; | 
|  | 303 | int  timeout = 0, count = 0; | 
|  | 304 |  | 
|  | 305 | buff[0] = 0; | 
|  | 306 | proc_read( LOG_STARTFILE, buff, sizeof(buff) ); | 
|  | 307 | if (buff[0] != 0) { | 
|  | 308 | timeout = atoi(buff); | 
|  | 309 | } | 
|  | 310 | else { | 
|  | 311 | /* when running with emulator, androidboot.bootchart=<timeout> | 
|  | 312 | * might be passed by as kernel parameters to specify the bootchart | 
|  | 313 | * timeout. this is useful when using -wipe-data since the /data | 
|  | 314 | * partition is fresh | 
|  | 315 | */ | 
|  | 316 | char  cmdline[1024]; | 
|  | 317 | char* s; | 
|  | 318 | #define  KERNEL_OPTION  "androidboot.bootchart=" | 
|  | 319 | proc_read( "/proc/cmdline", cmdline, sizeof(cmdline) ); | 
|  | 320 | s = strstr(cmdline, KERNEL_OPTION); | 
|  | 321 | if (s) { | 
|  | 322 | s      += sizeof(KERNEL_OPTION)-1; | 
|  | 323 | timeout = atoi(s); | 
|  | 324 | } | 
|  | 325 | } | 
|  | 326 | if (timeout == 0) | 
|  | 327 | return 0; | 
|  | 328 |  | 
|  | 329 | if (timeout > BOOTCHART_MAX_TIME_SEC) | 
|  | 330 | timeout = BOOTCHART_MAX_TIME_SEC; | 
|  | 331 |  | 
|  | 332 | count = (timeout*1000 + BOOTCHART_POLLING_MS-1)/BOOTCHART_POLLING_MS; | 
|  | 333 |  | 
|  | 334 | do {ret=mkdir(LOG_ROOT,0755);}while (ret < 0 && errno == EINTR); | 
|  | 335 |  | 
|  | 336 | file_buff_open(log_stat,  LOG_STAT); | 
|  | 337 | file_buff_open(log_procs, LOG_PROCS); | 
|  | 338 | file_buff_open(log_disks, LOG_DISK); | 
|  | 339 |  | 
|  | 340 | /* create kernel process accounting file */ | 
|  | 341 | { | 
|  | 342 | int  fd = open( LOG_ACCT, O_WRONLY|O_CREAT|O_TRUNC,0644); | 
|  | 343 | if (fd >= 0) { | 
|  | 344 | close(fd); | 
|  | 345 | acct( LOG_ACCT ); | 
|  | 346 | } | 
|  | 347 | } | 
|  | 348 |  | 
|  | 349 | log_header(); | 
|  | 350 | return count; | 
|  | 351 | } | 
|  | 352 |  | 
|  | 353 | /* called each time you want to perform a bootchart sampling op */ | 
|  | 354 | int  bootchart_step( void ) | 
|  | 355 | { | 
|  | 356 | do_log_file(log_stat,   "/proc/stat"); | 
|  | 357 | do_log_file(log_disks,  "/proc/diskstats"); | 
|  | 358 | do_log_procs(log_procs); | 
|  | 359 |  | 
|  | 360 | /* we stop when /data/bootchart-stop contains 1 */ | 
|  | 361 | { | 
|  | 362 | char  buff[2]; | 
|  | 363 | if (proc_read(LOG_STOPFILE,buff,sizeof(buff)) > 0 && buff[0] == '1') { | 
|  | 364 | return -1; | 
|  | 365 | } | 
|  | 366 | } | 
|  | 367 |  | 
|  | 368 | return 0; | 
|  | 369 | } | 
|  | 370 |  | 
|  | 371 | void  bootchart_finish( void ) | 
|  | 372 | { | 
|  | 373 | unlink( LOG_STOPFILE ); | 
|  | 374 | file_buff_done(log_stat); | 
|  | 375 | file_buff_done(log_disks); | 
|  | 376 | file_buff_done(log_procs); | 
|  | 377 | acct(NULL); | 
|  | 378 | } |