/*
* Copyright 2008, Intel Corporation
*
* This file is part of LatencyTOP
*
* This program file is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program in a file named COPYING; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* Authors:
* Arjan van de Ven <arjan@linux.intel.com>
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <glib.h>
#include "latencytop.h"
GList *lines;
GList *procs;
GList *allprocs;
int total_time = 0;
int total_count = 0;
unsigned int pid_with_max = 0;
unsigned int pidmax = 0;
int firsttime = 1;
int noui;
int dump_unknown;
char *prefered_process;
static void add_to_global(struct latency_line *line)
{
GList *item;
struct latency_line *line2;
item = g_list_first(lines);
while (item) {
line2 = item->data;
item = g_list_next(item);
if (strcmp(line->reason, line2->reason)==0) {
line2->count += line->count;
line2->time += line->time;
if (line->max > line2->max)
line2->max = line->max;
free(line);
return;
}
}
lines = g_list_append(lines, line);
}
static void add_to_process(struct process *process, struct latency_line *line)
{
GList *item;
struct latency_line *line2;
item = g_list_first(process->latencies);
while (item) {
line2 = item->data;
item = g_list_next(item);
if (strcmp(line->reason, line2->reason)==0) {
line2->count += line->count;
line2->time += line->time;
if (line->max > line2->max)
line2->max = line->max;
free(line);
return;
}
}
process->latencies = g_list_append(process->latencies, line);
}
static void fixup_reason(struct latency_line *line, char *c)
{
char *c2;
c2 = strchr(c, '\n');
if (c2)
*c2=0;
while (c[0]==' ')
c++;
strncpy(line->backtrace, c, 4096);
c2 = translate(c);
if (c2 == NULL) {
line->reason[0] = '[';
strncpy(line->reason + 1, c, 1022);
for (c2 = line->reason + 1; *c2 && (c2 - line->reason) < 1022; c2++)
if (*c2 == ' ')
break;
*(c2++) = ']';
*(c2++) = 0;
} else
strncpy(line->reason, c2, 1024);
}
void parse_global_list(void)
{
FILE *file;
char *ln;
size_t dummy;
file = fopen("/proc/latency_stats","r+");
if (!file) {
fprintf(stderr, "Please enable the CONFIG_LATENCYTOP configuration in your kernel.\n");
fprintf(stderr, "Exiting...\n");
exit(EXIT_FAILURE);
}
/* wipe first line */
ln = NULL;
if (getline(&ln, &dummy, file) < 0) {
free(ln);
fclose(file);
return;
}
free(ln);
total_time = 0;
total_count = 0;
while (!feof(file)) {
struct latency_line *line;
char *c;
ln = NULL;
if (getline(&ln, &dummy, file) < 0) {
free(ln);
break;
}
if (strlen(ln)<2) {
free(ln);
break;
}
line = malloc(sizeof(struct latency_line));
memset(line, 0, sizeof(struct latency_line));
line->count = strtoull(ln, &c, 10);
line->time = strtoull(c, &c, 10);
line->max = strtoull(c, &c, 10);
total_time += line->time;
total_count += line->count;
fixup_reason(line, c);
add_to_global(line);
free(ln);
ln = NULL;
}
/* reset for next time */
fprintf(file, "erase\n");
fclose(file);
}
gint comparef(gconstpointer A, gconstpointer B)
{
struct latency_line *a, *b;
a = (struct latency_line *)A;
b = (struct latency_line *)B;
if (a->max > b->max)
return -1;
if (a->max < b->max)
return 1;
if (a->time > b->time)
return -1;
if (a->time < b->time)
return 1;
return -1;
}
void sort_list(void)
{
GList *entry;
struct latency_line *line;
total_time = 0;
lines = g_list_sort(lines, comparef);
entry = g_list_first(lines);
while (entry) {
line = entry->data;
entry = g_list_next(entry);
total_time = total_time + line->time;
}
}
void delete_list(void)
{
GList *entry, *entry2,*next;
struct latency_line *line;
struct process *proc;
while (lines) {
entry = g_list_first(lines);
line = entry->data;
free(line);
lines = g_list_delete_link(lines, entry);
}
entry = g_list_first(allprocs);
while (entry) {
next = g_list_next(entry);
proc = entry->data;
while (proc->latencies) {
entry2 = g_list_first(proc->latencies);
line = entry2->data;
free(line);
proc->latencies = g_list_delete_link(proc->latencies, entry2);
}
proc->max = 0;
if (!proc->exists) {
free(proc);
allprocs = g_list_delete_link(allprocs, entry);
}
entry = next;
}
g_list_free(procs);
procs = NULL;
}
void prune_unused_procs(void)
{
GList *entry, *entry2;
struct latency_line *line;
struct process *proc;
entry = g_list_first(procs);
while (entry) {
proc = entry->data;
entry2 = g_list_next(entry);
if (!proc->used) {
while (proc->latencies) {
entry2 = g_list_first(proc->latencies);
line = entry2->data;
free(line);
proc->latencies = g_list_delete_link(proc->latencies, entry2);
}
free(proc);
procs = g_list_delete_link(procs, entry);
}
entry = entry2;
}
}
void parse_process(struct process *process)
{
DIR *dir;
struct dirent *dirent;
char filename[PATH_MAX];
sprintf(filename, "/proc/%i/task/", process->pid);
dir = opendir(filename);
if (!dir)
return;
while ((dirent = readdir(dir))) {
FILE *file;
char *line = NULL;
size_t dummy;
int pid;
if (dirent->d_name[0]=='.')
continue;
pid = strtoull(dirent->d_name, NULL, 10);
if (pid<=0) /* not a process */
continue;
sprintf(filename, "/proc/%i/task/%i/latency", process->pid, pid);
file = fopen(filename, "r+");
if (!file)
continue;
/* wipe first line*/
if (getline(&line, &dummy, file) < 0) {
free(line);
continue;
}
free(line);
while (!feof(file)) {
struct latency_line *ln;
char *c, *c2;
line = NULL;
if (getline(&line, &dummy, file) < 0) {
free(line);
break;
}
if (strlen(line)<2) {
free(line);
break;
}
ln = malloc(sizeof(struct latency_line));
memset(ln, 0, sizeof(struct latency_line));
ln->count = strtoull(line, &c, 10);
ln->time = strtoull(c, &c, 10);
ln->max = strtoull(c, &c, 10);
fixup_reason(ln, c);
if (ln->max > process->max)
process->max = ln->max;
add_to_process(process, ln);
process->used = 1;
free(line);
line = NULL;
}
/* reset for next time */
fprintf(file, "erase\n");
fclose(file);
}
/* 100 usec minimum */
if (!firsttime) {
struct latency_line *ln, *ln2;
ln = malloc(sizeof(struct latency_line));
ln2 = malloc(sizeof(struct latency_line));
memset(ln, 0, sizeof(struct latency_line));
if (process->delaycount)
ln->count = process->delaycount;
else
ln->count = 1;
if (process->totaldelay > 0.00001)
ln->time = process->totaldelay * 1000;
else
ln->time = process->maxdelay * 1000;
ln->max = process->maxdelay * 1000;
strcpy(ln->reason, "Scheduler: waiting for cpu");
if (ln->max > process->max)
process->max = ln->max;
memcpy(ln2, ln, sizeof(struct latency_line));
add_to_process(process, ln);
add_to_global(ln2);
process->used = 1;
}
closedir(dir);
}
struct process* find_create_process(unsigned int pid)
{
GList *entry;
struct process *proc;
entry = g_list_first(allprocs);
while (entry) {
proc = entry->data;
if (proc->pid == pid) {
return proc;
}
entry = g_list_next(entry);
}
proc = malloc(sizeof(struct process));
memset(proc, 0, sizeof(struct process));
proc->pid = pid;
allprocs = g_list_append(allprocs, proc);
return proc;
}
void parse_processes(void)
{
DIR *dir;
struct dirent *dirent;
char filename[PATH_MAX];
struct process *process;
pidmax = 0;
dir = opendir("/proc");
if (!dir)
return;
while ((dirent = readdir(dir))) {
FILE *file;
char *line;
size_t dummy;
int pid;
if (dirent->d_name[0]=='.')
continue;
pid = strtoull(dirent->d_name, NULL, 10);
if (pid<=0) /* not a process */
continue;
process = find_create_process(pid);
process->exists = 1;
sprintf(filename, "/proc/%i/status", pid);
file = fopen(filename, "r");
if (file) {
char *q;
line = NULL;
if (getline(&line, &dummy, file) >= 0) {
strncpy(&process->name[0], &line[6], 64);
q = strchr(process->name, '\n');
if (q) *q=0;
fclose(file);
}
free(line);
line = NULL;
}
if (process->name && prefered_process && strcmp(process->name, prefered_process)==0) {
pid_with_max = pid;
pidmax = INT_MAX;
}
sprintf(filename, "/proc/%i/sched", pid);
file = fopen(filename, "r+");
if (file) {
char *q;
double d;
while (!feof(file)) {
line = NULL;
if (getline(&line, &dummy, file) < 0) {
free(line);
break;
}
q = strchr(line, ':');
if (strstr(line, "se.wait_max") && q) {
sscanf(q+1,"%lf", &d);
process->maxdelay = d;
}
if (strstr(line, "se.wait_sum") && q) {
sscanf(q+1,"%lf", &d);
process->totaldelay = d;
}
if (strstr(line, "se.wait_count") && q) {
sscanf(q+1,"%lf", &d);
process->delaycount = d;
}
free(line);
line = NULL;
}
fprintf(file,"erase");
fclose(file);
}
sprintf(filename, "/proc/%i/statm", pid);
file = fopen(filename, "r");
if (file) {
line = NULL;
if (getline(&line, &dummy, file) >= 0) {
if (strcmp(line, "0 0 0 0 0 0 0\n")==0)
process->kernelthread = 1;
}
fclose(file);
free(line);
line = NULL;
}
parse_process(process);
if (process->latencies) {
process->latencies = g_list_sort(process->latencies, comparef);
if (!process->kernelthread && pidmax < process->max) {
pidmax = process->max;
pid_with_max = process->pid;
}
procs = g_list_append(procs, process);
};
}
closedir(dir);
}
void dump_global_to_console(void)
{
GList *item;
struct latency_line *line;
int i = 0;
item = g_list_first(lines);
while (item) {
line = item->data;
item = g_list_next(item);
printf( "[max %4.1fmsec] %40s - %5.2f msec (%3.1f%%)\n",
line->max * 0.001,
line->reason,
(line->time * 0.001 +0.0001) / line->count,
line->time * 100.0 / total_time );
i++;
}
}
static void enable_sysctl(void)
{
FILE *file;
file = fopen("/proc/sys/kernel/latencytop", "w");
if (!file)
return;
fprintf(file, "1");
fclose(file);
}
static void disable_sysctl(void)
{
FILE *file;
file = fopen("/proc/sys/kernel/latencytop", "w");
if (!file)
return;
fprintf(file, "0");
fclose(file);
}
void update_list(void)
{
delete_list();
parse_processes();
prune_unused_procs();
parse_global_list();
sort_list();
if (!total_time)
total_time = 1;
firsttime = 0;
}
static void cleanup_sysctl(void)
{
disable_sysctl();
disable_fsync_tracer();
}
int main(int argc, char **argv)
{
int i, use_gtk = 0;
enable_sysctl();
enable_fsync_tracer();
atexit(cleanup_sysctl);
#ifdef HAS_GTK_GUI
if (preinitialize_gtk_ui(&argc, &argv))
use_gtk = 1;
#endif
if (!use_gtk)
preinitialize_text_ui(&argc, &argv);
for (i = 1; i < argc; i++)
if (strcmp(argv[i],"-d") == 0) {
init_translations("latencytop.trans");
parse_global_list();
sort_list();
dump_global_to_console();
return EXIT_SUCCESS;
}
for (i = 1; i < argc; i++)
if (strcmp(argv[i], "--unknown") == 0) {
noui = 1;
dump_unknown = 1;
}
/* Allow you to specify a process name to track */
for (i = 1; i < argc; i++)
if (argv[i][0] != '-') {
prefered_process = strdup(argv[i]);
break;
}
init_translations("/usr/share/latencytop/latencytop.trans");
if (!translations)
init_translations("latencytop.trans"); /* for those who don't do make install */
while (noui) {
sleep(5);
fprintf(stderr, ".");
}
#ifdef HAS_GTK_GUI
if (use_gtk)
start_gtk_ui();
else
#endif
start_text_ui();
prune_unused_procs();
delete_list();
return EXIT_SUCCESS;
}