3700373c67
Ideally bootchart collects system data every BOOTCHART_POLLING_MS ms. However, the current bootchart pacing logic would collect data every time the main loop in init.c is executed. This results in: 1. Multiple data samples being taken consecutively or prematurely. In many cases, these data samples have the same timestamp (in jiffies). The off-line data processing tool would complain about dividing by zero "interval" and bail out. 2. Because of the ineffective pacing, bootchart data collection would terminate prematurely. The total duration of data collection is usually much shorter than what the user specifies. The fix is to check whether BOOTCHART_POLLING_MS ms has elapsed before taking a new data sample. For this purpose, /proc/uptime is used to get the time values, consistent with the precision of bootchart timestamps. Change-Id: I106bf91dbda01059b719df6c73b8bd1cd54a64f0 Signed-off-by: Bo (Andover) Zhang <zhang@broadcom.com>
385 lines
9.6 KiB
C
385 lines
9.6 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 long long
|
|
get_uptime_jiffies()
|
|
{
|
|
char buff[64];
|
|
long long jiffies = 0;
|
|
|
|
if (proc_read("/proc/uptime", buff, sizeof(buff)) > 0)
|
|
jiffies = 100LL*strtod(buff,NULL);
|
|
|
|
return jiffies;
|
|
}
|
|
|
|
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 len;
|
|
|
|
snprintf(buff,sizeof(buff),"%lld\n",get_uptime_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);
|
|
}
|
|
|
|
/* called to get time (in ms) used by bootchart */
|
|
long long bootchart_gettime( void )
|
|
{
|
|
return 10LL*get_uptime_jiffies();
|
|
}
|