#! /usr/bin/env python # bootlog.py - generate an illustration of the Linux boot process # # Copyright 2004 Jochen Voss # # 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 # $Id: bootlog.py 6039 2004-11-20 13:27:32Z voss $ from sys import argv from __future__ import division from matplotlib.matlab import * from matplotlib.patches import Rectangle from matplotlib.lines import Line2D from matplotlib.ticker import MultipleLocator, NullLocator, FormatStrFormatter fps=10 if len(argv)>1: nmax = fps*float(argv[1]) else: nmax = fps*75 ###################################################################### def parse_perc(s): assert s[-1]=="%" return float(s[:-1])/100 def parse_mem(s): if s[-1]=="k": return float(s[:-1])*1024 elif s[-1]=="m": return float(s[:-1])*1024*1024 else: assert str(int(s))==s return int(s) def parse_cpu_line(s): w=s.split() user=parse_perc(w[1]) system=parse_perc(w[3]) nice=parse_perc(w[5]) idle=parse_perc(w[7]) wait=parse_perc(w[9]) hi=parse_perc(w[11]) si=parse_perc(w[13]) return user,system,nice,idle,wait,hi,si def parse_mem_lines(s1,s2): w1=s1.split() w2=s2.split() mem_total=parse_mem(w1[1]) mem_used=parse_mem(w1[3]) mem_free=parse_mem(w1[5]) buffers=parse_mem(w1[7]) cached=parse_mem(w2[7]) return mem_total, mem_used, mem_free, buffers, cached def parse_swap_lines(s1,s2): w2=s2.split() swap_total=parse_mem(w2[1]) swap_used=parse_mem(w2[3]) swap_free=parse_mem(w2[5]) return swap_total, swap_used, swap_free def parse_proc_line(s): w=s.split() pid=int(w[0]) ppid=int(w[1]) virt=parse_mem(w[2]) res=parse_mem(w[3]) shared=parse_mem(w[4]) state=w[5] cpu=float(w[6])/100 cmd=" ".join(w[10:]) return pid,ppid,state,cpu,cmd ###################################################################### class Process: def __init__(self, cmd, pid, ppid, start): self.states=[] self.cpu=[] if cmd.startswith("/bin/sh -e "): cmd=cmd.replace("/bin/sh -e ","",1) if cmd.startswith("/bin/sh -c "): cmd=cmd.replace("/bin/sh -c ","",1) if cmd.startswith("/bin/sh "): cmd=cmd.replace("/bin/sh ","",1) if cmd.startswith("./"): cmd=cmd[2:] n=cmd.find(" ") if n>=0: cmd=cmd[:n] n=cmd.rfind("/") if n>=0 and not (cmd.startswith("[") or cmd.startswith("/top")): cmd=cmd[n+1:] self.cmd=cmd self.pid=pid self.ppid=ppid self.start=start self.stop=start-1 ###################################################################### n=0 data_time=[] data_load=[] data_io=[] data_mem_used=[] data_mem_cached=[] procdata={} piddata={} def parse_frame(f): global n if not f: return data_time.append(n/fps) user,system,nice,idle,wait,hi,si = parse_cpu_line(f[2]) data_load.append(user+system) data_io.append(wait) mem_total, mem_used, mem_free, buffers, cached = parse_mem_lines(f[3],f[4]) data_mem_used.append(mem_used) data_mem_cached.append(cached+buffers) swap_total, swap_used, swap_free = parse_swap_lines(f[3],f[4]) assert swap_used==0 piddata[n]=[] for l in f[7:]: if not l: continue pid,ppid,state,cpu,cmd = parse_proc_line(l) piddata[n].append(pid) if not pid in procdata: procdata[pid] = Process(cmd,pid,ppid,n) p=procdata[pid] p.states.append(state) p.cpu.append(cpu) assert p.stop==n-1 p.stop=n n+=1 frame=[] for l in open("bootlog").readlines(): l=l.rstrip() if l.startswith("top - "): parse_frame(frame) frame=[] if n>=nmax: break frame.append(l) ###################################################################### pids=procdata.keys() pids.sort() procs=[procdata[pid] for pid in pids] def slot_busy(slot,p,sl,gap=fps): """Return True, iff SLOT is unavailable for P under layout SL""" start=p.start stop=p.stop tenants=[procdata[pid] for pid in sl if sl[pid]==slot] for q in tenants: if not ((q.stop+gap < start and q.start+1.2*len(q.cmd) < start) or (stop+gap < q.start and start+1.2*len(p.cmd) < q.start)): return True return False def layout_collides(sl, slc, offset): for pid in slc: if slot_busy(slc[pid]+offset,procdata[pid],sl): return True return False def calculate_layout(p): sl={} sl[p.pid]=0 children=[q for q in procs if q.ppid==p.pid] children.reverse() for child in children: if child.cmd.startswith("[") or child.cmd.startswith("/top"): continue slc=calculate_layout(child) offset=1 while layout_collides(sl,slc,offset): offset += 1 for pid in slc: sl[pid]=slc[pid]+offset return sl init=procdata[1] sl=calculate_layout(init) #maxslot=max([procdata[pid].slot for pid in pids]) maxslot=0 for pid in pids[1:]: if pid in sl: if sl[pid] > maxslot: maxslot=sl[pid] else: p=procdata[pid] print "ignoring "+p.cmd ###################################################################### figure(figsize=(26,15)) axes([0.05, 0.85, 0.93, 0.1]) title("memory usage (bytes)") ax=gca() ax.xaxis.set_minor_locator(MultipleLocator(1)) ax.xaxis.set_major_locator(MultipleLocator(5)) ax.xaxis.grid(True, which="minor") ax.xaxis.grid(True, which="major") ax.xaxis.set_major_formatter(FormatStrFormatter('%ds')) ax.yaxis.grid(False) x=map(lambda x,y:x-y,data_mem_used,data_mem_cached) semilogy(data_time,x,"g",basey=2) l=gca().get_xlim() axes([0.05, 0.7, 0.93, 0.1]) grid(True) ax=gca() ax.xaxis.set_minor_locator(MultipleLocator(1)) ax.xaxis.set_major_locator(MultipleLocator(5)) ax.xaxis.grid(True, which="minor") ax.xaxis.set_major_formatter(FormatStrFormatter('%ds')) ax.yaxis.grid(False) title("CPU usage (blue=user+sys, red=I/O wait)") x=map(lambda x,y:x+y,data_load,data_io) fill([0]+data_time+[data_time[-1]],[0]+x+[0], facecolor=(0.96,0.39,0.47)) fill([0]+data_time+[data_time[-1]],[0]+data_load+[0], facecolor=(0.64,0.27,0.93)) axis([l[0],l[1],0,1]) axes([0.05, 0.05, 0.93, 0.6]) axis([l[0],l[1],0,maxslot+1]) grid(True) ax=gca() ax.xaxis.set_minor_locator(MultipleLocator(1)) ax.xaxis.set_major_locator(MultipleLocator(5)) ax.xaxis.grid(True, which="minor") ax.xaxis.set_major_formatter(FormatStrFormatter('%ds')) ax.yaxis.set_major_locator(NullLocator()) for pid in pids: p=procdata[pid] if pid>1 and pid not in sl: continue rect = Rectangle((p.start/fps, sl[pid]), (p.stop-p.start+1)/fps, 0.85, fill=False) ax.add_patch(rect) for i in range(p.start,p.stop+1): state=p.states[i-p.start] cpu=p.cpu[i-p.start] if state=="D": col=0.7 elif state=="S": col=0.93 elif state=="Z": col=0.4 elif state=="R": col=(cpu*0.94 + (1-cpu)*0.98, cpu*0.88 + (1-cpu)*1, cpu*0.0 + (1-cpu)*0.67) else: print state assert(0) rect = Rectangle((i/fps, sl[pid]), 1/fps, 0.85, linewidth=0, fill=True, fc=col) ax.add_patch(rect) for pid in pids: p=procdata[pid] if pid not in sl: continue if not p.ppid: continue x=p.start/fps y1=sl[pid]+0.5 y0=sl[p.ppid]+0.5 l = Line2D([x+0.06,x],[y0,y1],color="r") ax.add_line(l) for pid in pids: p=procdata[pid] if pid>1 and pid not in sl: continue text(p.start/fps+0.1,sl[pid]+0.5,p.cmd, verticalalignment='center',fontsize=8) savefig("bootlog.png") #show()