378 lines
9.5 KiB
C
378 lines
9.5 KiB
C
/*
|
|
* 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);
|
|
}
|