diff --git a/README.md b/README.md index 8a73191..c12efce 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ # bootchart +Bootchart is a tool for performance analysis and visualization of the GNU/Linux boot process. +Resource utilization and process information are collected during the boot process and are later rendered in a PNG, SVG or EPS encoded chart. +Bootchart provides a shell script to be run by the kernel in the init phase. +The script will run in background and collect process information, CPU statistics and disk usage statistics from the /proc file system. +The performance data are stored in memory and are written to disk once the boot process completes. +The boot log file is later processed using a Java application (or the web form) which builds the process tree and renders a performance chart in different formats: SVG e EPS. +The chart can then be analyzed to examine process dependency and overall resource utilization. + diff --git a/bootchart-0.9-nojavafunctions.patch b/bootchart-0.9-nojavafunctions.patch new file mode 100644 index 0000000..10d4b94 --- /dev/null +++ b/bootchart-0.9-nojavafunctions.patch @@ -0,0 +1,37 @@ +--- bootchart-0.9/script/bootchart.orig 2006-06-26 16:03:51.000000000 +0200 ++++ bootchart-0.9/script/bootchart 2006-06-26 16:07:21.000000000 +0200 +@@ -4,21 +4,23 @@ + # JPackage Project + + # Source functions library +-if [ -f /usr/share/java-utils/java-functions ] ; then +- . /usr/share/java-utils/java-functions +-else +- echo "Can't find functions library, aborting" +- exit 1 +-fi ++#if [ -f /usr/share/java-utils/java-functions ] ; then ++# . /usr/share/java-utils/java-functions ++#else ++# echo "Can't find functions library, aborting" ++# exit 1 ++#fi + + # Configuration + MAIN_CLASS="org.bootchart.Main" +-BASE_JARS="commons-cli.jar bootchart.jar" +-FLAGS="-Djava.awt.headless=true -Dgnu.java.awt.peer.gtk.Graphics=Graphics2D" ++BASE_JARS="/usr/share/java/bootchart.jar" ++#BASE_JARS="commons-cli.jar bootchart.jar" ++#FLAGS="-Djava.awt.headless=true -Dgnu.java.awt.peer.gtk.Graphics=Graphics2D" + + # Set parameters +-set_jvm +-set_classpath $BASE_JARS ++#set_jvm ++#set_classpath $BASE_JARS + + # Let's start +-run "$@" ++#run "$@" ++java -cp $BASE_JARS $MAIN_CLASS $@ diff --git a/bootchart-0.9-svg_path.patch b/bootchart-0.9-svg_path.patch new file mode 100644 index 0000000..4c58f9e --- /dev/null +++ b/bootchart-0.9-svg_path.patch @@ -0,0 +1,15 @@ +--- bootchart-0.9/bootchart.pl.orig 2006-06-26 11:33:31.000000000 +0200 ++++ bootchart-0.9/bootchart.pl 2006-06-26 11:35:22.000000000 +0200 +@@ -37,9 +37,9 @@ + my %ps_info; + my %ps_by_parent; + +-my $chart_svg_template = `cat svg/bootchart.svg.template` || die; +-my $process_svg_template = `cat svg/process.svg.template` || die; +-my $svg_css_file = `cat svg/style.css` || die; ++my $chart_svg_template = `cat /usr/share/bootchart/svg/bootchart.svg.template` || die; ++my $process_svg_template = `cat /usr/share/bootchart/svg/process.svg.template` || die; ++my $svg_css_file = `cat /usr/share/bootchart/svg/style.css` || die; + $svg_css_file =~ s/^/\t\t\t/mg; + $svg_css_file =~ s/^\s+//g; + diff --git a/bootchart-grub2 b/bootchart-grub2 new file mode 100644 index 0000000..a417be3 --- /dev/null +++ b/bootchart-grub2 @@ -0,0 +1,120 @@ +#! /bin/sh -e + +# update-grub helper script, modified for bootchart entry. +# Copyright (C) 2006,2007,2008 Free Software Foundation, Inc. +# +# GRUB 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, either version 3 of the License, or +# (at your option) any later version. +# +# GRUB 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 GRUB. If not, see . + +prefix=/usr +exec_prefix=/usr +libdir=/usr/lib +. ${libdir}/grub/grub-mkconfig_lib + +if [ "x${GRUB_DISTRIBUTOR}" = "x" ] ; then + OS=GNU/Linux +else + OS="${GRUB_DISTRIBUTOR} GNU/Linux" +fi + +test_numeric () +{ + local a=$1 + local cmp=$2 + local b=$3 + if [ "$a" = "$b" ] ; then + case $cmp in + ge|eq|le) return 0 ;; + gt|lt) return 1 ;; + esac + fi + if [ "$cmp" = "lt" ] ; then + c=$a + a=$b + b=$c + fi + if (echo $a ; echo $b) | sort -n | head -n 1 | grep -qx $b ; then + return 0 + else + return 1 + fi +} + +test_gt () +{ + local a=`echo $1 | sed -e "s/vmlinu[zx]-//g"` + local b=`echo $2 | sed -e "s/vmlinu[zx]-//g"` + local cmp=gt + if [ "x$b" = "x" ] ; then + return 0 + fi + case $a:$b in + *.old:*.old) ;; + *.old:*) a=`echo -n $a | sed -e s/\.old$//g` ; cmp=gt ;; + *:*.old) b=`echo -n $b | sed -e s/\.old$//g` ; cmp=ge ;; + esac + test_numeric $a $cmp $b + return $? +} + +find_latest () +{ + local a="" + for i in $@ ; do + if test_gt "$i" "$a" ; then + a="$i" + fi + done + echo "$a" +} + +list=`for i in /boot/vmlinu[xz]-* /vmlinu[xz]-* ; do + if grub_file_is_not_garbage "$i" ; then echo -n "$i " ; fi + done` + +while [ "x$list" != "x" ] ; do + linux=`find_latest $list` + echo "Found linux image: $linux" >&2 + basename=`basename $linux` + dirname=`dirname $linux` + grub_dirname=`echo ${dirname} | sed -e "s%^/boot%${GRUB_DRIVE_BOOT}%g"` + version=`echo $basename | sed -e "s,^[^0-9]*-,,g"` + alt_version=`echo $version | sed -e "s,\.old$,,g"` + + initrd= + for i in "initramfs.img-${version}" "initramfs-${version}.img" \ + "initramfs.img-${alt_version}" "initramfs-${alt_version}.img"; do + if test -e "${dirname}/${i}" ; then + initrd="$i" + break + fi + done + if test -n "${initrd}" ; then + echo "Found initrd image: ${dirname}/${initrd}" >&2 + fi + + cat << EOF +menuentry "${OS}, linux ${version} (bootchart)" { + linux ${grub_dirname}/${basename} root=${GRUB_DEVICE} ro ${GRUB_CMDLINE_LINUX} init=/sbin/bootchartd +EOF + if test -n "${initrd}" ; then + cat << EOF + initrd ${grub_dirname}/${initrd} +EOF + fi + cat << EOF +} +EOF + + list=`echo $list | tr ' ' '\n' | grep -vx $linux | tr '\n' ' '` +done diff --git a/bootchart.sh b/bootchart.sh new file mode 100644 index 0000000..e991be1 --- /dev/null +++ b/bootchart.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# +# Bootchart shell script - use the web renderer for bootchart +# +# Copyright (c) 2006 by Stefano Cotta Ramusino + +if [ $# -eq 0 ]; then + format="png" + output_dir="." +else + if [[ "$1" == "png" || "$1" == "svg" || "$1" == "eps" ]]; then + format=$1 + else + echo "$0 : invalid format. Try: png, svg or eps" + exit 1 + fi + if [[ -d "$2" ]]; then + output_dir=$2 + else + echo "$0: output directory not found" + exit 1 + fi +fi + +bootchart_log="/var/log/bootchart.tgz" +webrenderer_url="http://render.bootchart.org:8080/bootchart/render" + +if [ ! -f "$bootchart_log" ]; then + echo "$0: bootchart log not found." + echo "Try adding \`init=/sbin/bootchartd' to your kernel entries in grub configuration file" + exit 1 +fi + +if [ $format == "svg" ]; then + output_ext_file="svgz" +else + if [ $format == "eps" ]; then + output_ext_file="eps.gz" + else + output_ext_file=$format + fi +fi + +curl \ + --form format=$format \ + --form log=@$bootchart_log \ + $webrenderer_url > $output_dir/bootchart.$output_ext_file + +exit $? diff --git a/bootchart.spec b/bootchart.spec new file mode 100644 index 0000000..5286894 --- /dev/null +++ b/bootchart.spec @@ -0,0 +1,203 @@ +Name: bootchart +Version: 1.20 +Release: 1mamba +Summary: Bootchart is a tool for performance analysis and visualization of the GNU/Linux boot process +Group: System/Benchmarks +Vendor: openmamba +Distribution: openmamba +Packager: Stefano Cotta Ramusino +URL: http://www.bootchart.org/ +Source: http://foo-projects.org/~sofar/bootchart/bootchart-%{version}.tar.gz +#Source: http://downloads.sourceforge.net/sourceforge/bootchart/bootchart-%{version}.tar.bz2 +Source1: http://zerodeux.net/misc/bootchart/bootchart_pl +Source2: bootchart.sh +Source3: kbootchart +Source4: kbootchart.png +Source5: bootchart-grub2 +Patch0: %{name}-0.9-nojavafunctions.patch +Patch1: %{name}-0.9-svg_path.patch +License: GPL +Requires: curl +Requires: %{name}-logger +BuildRequires: apache-ant +BuildRequires: jdk +BuildRoot: %{_tmppath}/%{name}-%{version}-root + +%description +Bootchart is a tool for performance analysis and visualization of the GNU/Linux boot process. +Resource utilization and process information are collected during the boot process and are later rendered in a PNG, SVG or EPS encoded chart. +Bootchart provides a shell script to be run by the kernel in the init phase. +The script will run in background and collect process information, CPU statistics and disk usage statistics from the /proc file system. +The performance data are stored in memory and are written to disk once the boot process completes. +The boot log file is later processed using a Java application (or the web form) which builds the process tree and renders a performance chart in different formats: SVG e EPS. +The chart can then be analyzed to examine process dependency and overall resource utilization. + +%package logger +Summary: Boot logging script for %{name} +Group: System/Kernel and Hardware + +%description logger +Boot logging script for %{name}. + +%prep +%setup -q +#%patch0 -p1 + +cp %{S:1} %{name}.pl +%patch1 -p1 + +%build +#JAVA_HOME=%{_jvmdir}/jdk ANT_HOME=/opt/java/ant ant +%configure +%make + +%install +[ "%{buildroot}" != / ] && rm -rf "%{buildroot}" +%makeinstall + +## install scripts +#install -D -m 755 script/%{name} \ +# %{buildroot}%{_bindir}/%{name} +#install -D -m 755 %{name}.pl \ +# %{buildroot}%{_bindir}/%{name}.pl +#install -D -m 755 %{S:2} \ +# %{buildroot}%{_bindir}/%{name}.sh +#install -d %{buildroot}%{_datadir}/%{name}/svg +#install -m 644 svg/* \ +# %{buildroot}%{_datadir}/%{name}/svg +# +## install java files +#install -D -m 644 %{name}.jar \ +# %{buildroot}%{_javadir}/%{name}-%{version}.jar +#ln -s %{name}-%{version}.jar %{buildroot}%{_javadir}/%{name}.jar +#install -d -m 755 %{buildroot}%{_javadocdir}/%{name}-%{version} +#cp -pr javadoc/* \ +# %{buildroot}%{_javadocdir}/%{name}-%{version} +#ln -s %{name}-%{version} %{buildroot}%{_javadocdir}/%{name} + +## install logger +#install -D -m 755 script/bootchartd \ +# %{buildroot}/sbin/bootchartd +##install -D -m 644 script/bootchartd.conf \ +# %{buildroot}%{_sysconfdir}/bootchartd.conf +# +# install kde frontend +install -D -m 755 %{S:3} \ + %{buildroot}%{_bindir}/k%{name} +install -D -m 644 %{S:4} \ + %{buildroot}%{_datadir}/icons/crystalsvg/32x32/apps/k%{name}.png + +# install grub.d file +install -D -m 0755 %{S:5} %{buildroot}%{_sysconfdir}/grub.d/15_bootchart + +# create KDE menu link +install -d %{buildroot}/%{_datadir}/applications +cat > %{buildroot}/%{_datadir}/applications/k%{name}.desktop </dev/null); do + if [ $(grep init=/sbin/%{name}d $i >> /dev/null; echo $?) -ne 0 ]; then + sed -i "s|^kernel\(.*\)|kernel \1 init=/sbin/%{name}d|" $i 2> /dev/null + fi + done + %endif + %if "%{_target_cpu}" == "ppc" + for i in $(ls %{_sysconfdir}/yaboot/conf.d/%{distribution}-* 2>/dev/null); do + if [ $(grep init=/sbin/%{name}d $i >> /dev/null; echo $?) -ne 0 ]; then + sed -i "s|^append=\"\(.*\)\"|append=\"\1 init=/sbin/%{name}d\"|" $i 2> /dev/null + fi + done + %endif +fi + +if [ $1 -eq 1 ]; then +# new install + %if "%{_target_cpu}" == "i586" + [ -x /usr/sbin/update-grub ] && /usr/sbin/update-grub + %endif + %if "%{_target_cpu}" == "ppc" + [ -x /usr/sbin/yaboot-update ] && /usr/sbin/yaboot-update + %endif +fi +exit 0 + +%postun logger +if [ $1 -eq 0 ]; then +# uninstall +%if "%{_target_cpu}" == "i586" +for i in $(ls %{_sysconfdir}/grub/conf.d/%{distribution}-* 2>/dev/null); do + if [ $(grep init=/sbin/%{name}d $i >> /dev/null; echo $?) -eq 0 ]; then + sed -i "s|^kernel\(.*\) init=/sbin/%{name}d\(.*\)|kernel \1\2|" $i 2> /dev/null + fi +done + +%endif +%if "%{_target_cpu}" == "ppc" +for i in $(ls %{_sysconfdir}/yaboot/conf.d/%{distribution}-* 2>/dev/null); do + if [ $(grep init=/sbin/%{name}d $i >> /dev/null; echo $?) -eq 0 ]; then + sed -i "s|^append=\"\(.*\) init=/sbin/%{name}d\(.*\)\"|append=\"\1\2\"|" $i 2> /dev/null + fi +done +%endif +fi +exit 0 + +%files +%defattr(-,root,root) +%{_bindir}/kbootchart +#%{_datadir}/%{name}/svg +#%{_javadir}/* +%{_datadir}/applications/k%{name}.desktop +%{_datadir}/icons/crystalsvg/32x32/apps/k%{name}.png +#%{_javadocdir}/* +%doc COPYING README +#%doc TODO + +%files logger +%defattr(-,root,root) +%{_sysconfdir}/grub.d/15_bootchart +%{_sbindir}/bootchartd +%{_datadir}/doc/bootchart/bootchartd.conf.example +#%config(noreplace) %{_sysconfdir}/%{name}d.conf +#%doc README.logger + +%changelog +* Sat Dec 01 2012 Automatic Build System 1.20-1mamba +- update to 1.20 + +* Mon May 09 2011 Silvan Calarco 0.9-7mamba +- updated grub2 configuration file to use grub-mkconfig_lib instead of deprecated update-grub_lib + +* Fri Feb 05 2010 Silvan Calarco 0.9-6mamba +- rebuilt to remove executable requirements + +* Tue Oct 14 2008 Silvan Calarco 0.9-5mamba +- updated for grub2 + +* Tue Oct 14 2008 Silvan Calarco 0.9-4mamba +- rebuilt + +* Mon Feb 19 2007 Silvan Calarco 0.9-3qilnx +- add an exit 0 to scripts + +* Wed Nov 15 2006 Stefano Cotta Ramusino 0.9-2qilnx +- added %%post and %%postun scriplets + +* Mon Jun 26 2006 Stefano Cotta Ramusino 0.9-1qilnx +- package created by autospec diff --git a/bootchart_pl b/bootchart_pl new file mode 100644 index 0000000..63860d6 --- /dev/null +++ b/bootchart_pl @@ -0,0 +1,467 @@ +#!/usr/bin/perl -w + +# A basic perl port of the bootchart renderer, SVG only +# (c) 2005 Vincent Caron + +# This program 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; either version 2 of the License, or +# (at your option) any later version. +# +# 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; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# BUGS +# - ignore number of CPUs (disk util.) + +# 2005-06-29 - fixes +# - process name is latest sample argv value +# - sibling processes are properly PID-sorted from top to bottom +# - process start is now parsed from proc_ps +# - hiding bootchartd-related processes +# +# 2005-06-28 +# - initial release + +use strict; + +my %headers; +my @disk_samples; +my @cpu_samples; +my %ps_info; +my %ps_by_parent; + +my $chart_svg_template = `cat svg/bootchart.svg.template` || die; +my $process_svg_template = `cat svg/process.svg.template` || die; +my $svg_css_file = `cat svg/style.css` || die; +$svg_css_file =~ s/^/\t\t\t/mg; +$svg_css_file =~ s/^\s+//g; + +my $HZ = 100; +my $min_img_w = 800; +my $sec_w = 25; +my $proc_h = 16; +my $header_h = 300; +my $img_w; +my $img_h; +my $t_begin; +my $t_end; +my $t_dur; + + +sub parse_logs { + my $logfile = shift; + my $tmp = "/tmp/bootchart-$$"; + + mkdir $tmp or die; + system "cd $tmp && tar xzf $logfile"; + + parse_log_header($tmp); + parse_log_cpu($tmp); + parse_log_disk($tmp); + parse_log_ps($tmp); + + system('rm', '-rf', $tmp); + print STDERR "Disk samples: ".scalar(@disk_samples)."\n"; + print STDERR "CPU samples : ".scalar(@cpu_samples)."\n"; + print STDERR "Processes : ".scalar(keys %ps_info)."\n"; +} + +sub parse_log_header { + my $tmp = shift; + + open(HEADER, "<$tmp/header"); + while (
) { + chomp; + my ($key, $val) = split /\s*=\s*/, $_, 2; + $headers{$key} = $val; + } + close HEADER; +} + +sub parse_log_cpu { + my $tmp = shift; + + # Reads in: time, user, system, iowait + my ($time, $ltime); + my (@timings, @ltimings); + + open(PROC_STAT, "<$tmp/proc_stat.log"); + while () { + chomp; + $time = $1, next if /^(\d+)$/; + + if (/^$/) { + if (defined $ltime) { + my $dtime = ($time - $ltime) / $HZ; # in seconds + my $user = ($timings[0] + $timings[1]) - ($ltimings[0] + $ltimings[1]); + my $system = ($timings[2] + $timings[5] + $timings[6]) - ($ltimings[2] + $ltimings[5] + $ltimings[6]); + my $idle = $timings[3] - $ltimings[3]; + my $iowait = $timings[4] - $ltimings[4]; + my $sum = $user + $system + $idle + $iowait; + my %sample; + $sample{time} = $time; + $sample{user} = $user / $sum; + $sample{system} = $system / $sum; + $sample{iowait} = $iowait / $sum; + push @cpu_samples, \%sample; + } + + $ltime = $time; + @ltimings = @timings; + next; + } + + @timings = split /\s+/ if s/^cpu\s+//; + } + close(PROC_STAT); +} + +sub parse_log_disk { + my $tmp = shift; + + # Reads in: time, read, write, use + my ($time, $ltime); + my ($read, $write, $util) = (0, 0, 0); + my ($lread, $lwrite, $lutil); + + open(DISK_STAT, "<$tmp/proc_diskstats.log"); + while () { + chomp; + $time = $1, next if /^(\d+)$/; + + if (/^$/) { + if (defined $ltime) { + my $dtime = ($time - $ltime) / $HZ; # in seconds + my $dutil = ($util - $lutil) / (1000 * $dtime); + + my %sample; + $sample{time} = $time; + $sample{read} = ($read - $lread) / 2 / $dtime; # in KB/s + $sample{write} = ($write - $lwrite) / 2 / $dtime; # in KB/s + $sample{util} = ($dutil > 1 ? 1 : $dutil); + push @disk_samples, \%sample; + } + + $ltime = $time; + $lread = $read, $read = 0; + $lwrite = $write, $write = 0; + $lutil = $util, $util = 0; + next; + } + + s/\s+//; + my @tokens = split /\s+/; + next if scalar(@tokens) != 14 || not $tokens[2] =~ /hd|sd/; + + $read += $tokens[5]; + $write += $tokens[9]; + $util += $tokens[12]; + } + close(DISK_STAT); +} + +sub parse_log_ps { + my $tmp = shift; + + my $time; + + open(PS_STAT, "<$tmp/proc_ps.log"); + while () { + chomp; + next if /^$/; + $time = $1, next if /^(\d+)$/; + + my @tokens = split /\s+/; + my $pid = $tokens[0]; + + if (!defined $ps_info{$pid}) { + my %info; + my @empty; + my $ppid = $tokens[3]; + my $start = $tokens[21]; + $t_begin = $start if !defined $t_begin || $t_begin > $start; + + $info{ppid} = $ppid; + $info{start} = $start; + $info{samples} = \@empty; + $ps_info{$pid} = \%info; + + if (!defined $ps_by_parent{$ppid}) { + my @pidlist = ($pid); + $ps_by_parent{$ppid} = \@pidlist; + } else { + push @{$ps_by_parent{$ppid}}, $pid; + } + } + + # argv may change, we'll store here the last sampled value + my $comm = $tokens[1]; + $comm =~ s/[()]//g; + $ps_info{$pid}->{comm} = $comm; + + my %sample; + $sample{time} = $time; + $sample{state} = $tokens[2]; + $sample{sys} = $tokens[13]; + $sample{user} = $tokens[14]; + push @{$ps_info{$pid}->{samples}}, \%sample; + } + close PS_STAT; +} + +sub unxml { + my $x = shift; + $x =~ s//>/g; + + return $x; +} + +sub render_svg { + my $t_init = $cpu_samples[0]->{time}; + $t_end = $cpu_samples[-1]->{time}; + $t_dur = $t_end - $t_begin; + $img_w = $t_dur / $HZ * $sec_w; + + my @subst; + $subst[1] = $svg_css_file; + + # + # Headers + # + + my $cpu = $headers{'system.cpu'}; + $cpu =~ s/model name\s*:\s*//; + $subst[2] = unxml($headers{title}); + $subst[3] = "uname: ".unxml($headers{'system.uname'}); + $subst[4] = "release: ".unxml($headers{'system.release'}); + $subst[5] = "CPU: ".unxml($cpu); + $subst[6] = "kernel options: ".unxml($headers{'system.kernel.options'}); + $subst[7] = "boot time: ".int($t_end / $HZ)." seconds"; + + # + # CPU I/O chart + # + + my $bar_h = 50; + my $cpu_ticks = ''; + for (my $i = 0; $i < $t_dur / $HZ; $i++) { + my $x = $i * $sec_w; + $cpu_ticks .= "\n"; + } + $cpu_ticks =~ s/^/\t\t\t/mg; $cpu_ticks =~ s/^\s+//g; + + my $io_points = ''; + my $cpu_points = ''; + + if (@cpu_samples) { + my $last_x = 0; + + for (@cpu_samples) { + my $time = $_->{time}; + my $iowait = $_->{iowait}; + my $usersys = $_->{user} + $_->{system}; + + my $pos_x = int(($time - $t_begin) * $img_w / $t_dur); + my $pos_y = int($bar_h - ($usersys + $iowait) * $bar_h); + $io_points .= "$pos_x,$bar_h" if $io_points eq ''; + $io_points .= " $pos_x,$pos_y"; + + $pos_y = int($bar_h - $usersys * $bar_h); + $cpu_points .= "$pos_x,$bar_h" if $cpu_points eq ''; + $cpu_points .= " $pos_x,$pos_y"; + + $last_x = $pos_x; + } + $io_points .= " $last_x,$bar_h"; + $cpu_points .= " $last_x,$bar_h"; + } + + $subst[8] = $cpu_ticks; + $subst[9] = $io_points; + $subst[10] = $cpu_points; + + # + # Disk usage chart + # + + my $util_points = ''; + my $read_points = ''; + + if (@disk_samples) { + my $max_tput = 0; + my $max_tput_label = ''; + for (@disk_samples) { + my $put = $_->{read} + $_->{write}; + $max_tput = $put if $put > $max_tput; + } + + my $last_x = 0; + my $last_y = 0; + + for (@disk_samples) { + my $time = $_->{time}; + my $read = $_->{read}; + my $write = $_->{write}; + my $util = $_->{util}; + + my $pos_x = int(($time - $t_begin) * $img_w / $t_dur); + my $pos_y = int($bar_h - $util * $bar_h); + $util_points .= "$pos_x,$bar_h" if $util_points eq ''; + $util_points .= " $pos_x,$pos_y"; + + $pos_y = int($bar_h - ($read + $write) / $max_tput * $bar_h); + if ($read_points ne '') { + $read_points .= "\t\t\t"; + } + $read_points .= "\n"; + + if ($max_tput_label eq '' && $read + $write == $max_tput) { + my $label = int($max_tput / 1024)." MB/s"; + $max_tput_label = "\t\t\t$label\n"; + } + + $last_x = $pos_x; + $last_y = $pos_y; + } + $util_points .= " $last_x,$bar_h"; + $read_points .= $max_tput_label; + } + + $subst[11] = $cpu_ticks; + $subst[12] = $util_points; + $subst[13] = $read_points; + $subst[14] = ''; # open_points, no openfile parser implemented + + # + # Process tree + # + + my $tree_h = scalar(@cpu_samples) * $proc_h; + my $axis = ''; + my $proc_ticks = ''; + for (my $i = 0; $i < $t_dur / $HZ; $i++) { + my $x = $i * $sec_w; + $proc_ticks .= "\n"; + if ($i > 0 && $i % 5 == 0) { + my $label = $i.'s'; + $axis .= "$label\n"; + } + } + $proc_ticks =~ s/^/\t\t\t/mg; $proc_ticks =~ s/^\s+//g; + $axis =~ s/^/\t\t\t/mg; $axis =~ s/^\s+//g; + + my $proc_tree = ''; + my @init = (1); + my $ps_count = 0; + $proc_tree = render_proc_tree(\@init, \$ps_count); + + $subst[15] = $axis; + $subst[16] = $proc_ticks; + $subst[17] = $proc_tree; + + $img_h = $proc_h * $ps_count + $header_h; + $subst[0] = 'width="'.($img_w > $min_img_w ? $img_w : $min_img_w).'px" height="'.($img_h + 1).'px"'; + + print template_subst($chart_svg_template, @subst); +} + +sub render_proc_tree { + my $ps_list = shift; + my $ps_count = shift; + my $level = shift || 0; + + my $proc_tree = ''; + for (sort {$a <=> $b} @{$ps_list}) { + # Hide bootchartd and its children processes + next if $ps_info{$_}->{comm} eq 'bootchartd'; + + my $children = $ps_by_parent{$_}; + $proc_tree .= render_proc($_, $ps_count, $level); + $proc_tree .= render_proc_tree($children, $ps_count, $level + 1) if defined $children; + } + return $proc_tree; +} + +sub render_proc { + my $pid = shift; + my $ps_count = shift; + my $level = shift; + + my $info = $ps_info{$pid}; + return '' if defined $info->{done}; # Draw once + + my @samples = @{$info->{samples}}; + return '' if scalar(@samples) < 2; # Skip processes with only 1 sample + + my $p_begin = ($pid == 1) ? $samples[0]->{time} : $info->{start}; + my $p_period = $samples[1]->{time} - $p_begin; + my $p_end = $samples[-1]->{time}; + my $p_dur = $p_end - $p_begin + $p_period; + + my $x = ($p_begin - $t_begin) * $sec_w / $HZ; + my $y = ${$ps_count} * $proc_h; + my $w = $p_dur * $sec_w / $HZ; + + my $position = "$x,$y"; + my $border = "width=\"$w\" height=\"$proc_h\""; + my $timeline = "\n"; + + my ($last_tx, $last_sample); + for my $sample (@samples) { + my $time = $sample->{time}; + my $state = $sample->{state}; + my $tx = ($time - $p_begin) * $sec_w / $HZ; + $tx = 0 if $tx < 0; + + if (defined $last_sample) { + my $tw = $tx - $last_tx; + + if ($state ne 'S') { + my $sys = ($sample->{sys} - $last_sample->{sys}) / $HZ; + my $user = ($sample->{user} - $last_sample->{user}) / $HZ; + + my %class = ('D' => 'UnintSleep', 'R' => 'Running', 'T' => 'Traced', 'Z' => 'Zombie'); + my $opacity = ($state eq 'R') ? ' fill-opacity="'.($sys + $user).'"' : ''; + $timeline .= "\n"; + } + } + + $last_tx = $tx; + $last_sample = $sample; + } + $timeline =~ s/^/\t\t/mg; $timeline =~ s/^\s+//g; + + my $label = $info->{comm}; + my $label_pos = ($w < 200 && ($x + $w + 200) < $img_w) ? + "dx=\"2\" dy=\"".($proc_h - 1)."\" x=\"".($w + 1)."\" y=\"0\"" : + "dx=\"".($w / 2)."\" dy=\"".($proc_h - 1)."\" x=\"0\" y=\"0\""; + + my @subst = ($position, $timeline, $border, '', $label_pos, $label, ''); + + $info->{done} = 1; + ${$ps_count}++; + return template_subst($process_svg_template, @subst); +} + +sub template_subst { + my $template = shift; + + my $i = 0; + for(@_) { + $template =~ s/\Q{$i}\E/$_[$i]/g; + $i++; + } + return $template; +} + + +parse_logs('/var/log/bootchart.tgz'); +render_svg(); diff --git a/kbootchart b/kbootchart new file mode 100644 index 0000000..ab4cbb3 --- /dev/null +++ b/kbootchart @@ -0,0 +1,78 @@ +#!/bin/sh +# +# kbootchart - kde frontend for bootchart +# +# Copyright (c) 2006 by Stefano Cotta Ramusino + +lang="java" + +if [ -z "$JAVA_HOME" ]; then + JRE_HOME="/usr/java/jre" + JDK_HOME="/usr/java/jdk" + if [ -x "$JRE_HOME/bin/java" ]; then + JAVA_HOME=$JRE_HOME + elif [ -x "$JDK_HOME/bin/java" ]; then + JAVA_HOME=$JDK_HOME + elif [[ $(host www.qilinux.org &> /dev/null; echo $?) -eq 0 ]]; then + lang="bash" + elif [ -x "$(which perl 2> /dev/null)" ]; then + lang="perl" + else + kdialog --title "KBootchart" \ + --icon $(basename $0) \ + --error "Perl is missing in your system.\nTo create the bootchart image you need to install perl." + exit 1 + fi +fi + +if [[ "$lang" == "java" || "$lang" == "bash" ]]; then + format="$(kdialog --title "KBootchart" \ + --icon $(basename $0) \ + --radiolist "Select the output image format:" \ + png "PNG" on svg "SVG" off eps "EPS" off)" + + if [ -z "$format" ]; then + exit 1 + fi +else + format="svg" +fi + +output_dir="$(kdialog --title "Select the directory where the image will be created" \ + --icon $(basename $0) \ + --getexistingdirectory ~)" + +if [ -z "$output_dir" ]; then + exit 1 +fi + +if [ $format == "svg" && $lang != "perl" ]]; then + output_ext_file="svgz" +else + if [ $format == "eps" ]; then + output_ext_file="eps.gz" + else + output_ext_file=$format + fi +fi + +if [ "$lang" == "java" ]; then + PATH=$JAVA_HOME/bin:$PATH bootchart -f $format -o $output_dir &> /dev/null +elif [ "$lang" == "bash" ]; then + bootchart.sh $format $output_dir &> /dev/null +else + bootchart.pl > $output_dir/bootchart.$output_ext_file 2> /dev/null +fi + +if [ $? -ne 0 ]; then + kdialog --title "KBootchart" \ + --icon $(basename $0) \ + --error "Error during creation of image file." + exit 1 +fi + +kdialog --title "KBootchart" \ + --icon $(basename $0) \ + --msgbox "File bootchart.$output_ext_file successfully created" + +exit $? diff --git a/kbootchart.png b/kbootchart.png new file mode 100644 index 0000000..c735df6 Binary files /dev/null and b/kbootchart.png differ