3 # based on plot-timeline.py by Federico Mena-Quintero <federico at ximian dotcom>
5 # GST_DEBUG_NO_COLOR=1 GST_DEBUG="*:3" gst-launch-0.10 2>debug.log audiotestsrc num-buffers=10 ! audioconvert ! alsasink
6 # gst-plot-timeline.py debug.log --output=debug.png
16 FONT_NAME = "Bitstream Vera Sans"
18 PIXELS_PER_SECOND = 1000
22 SYSCALL_MARKER_WIDTH = 20
25 BACKGROUND_COLOR = (0, 0, 0)
27 # assumes GST_DEBUG_LOG_COLOR=1
28 mark_regex = re.compile (r'^(\d:\d\d:\d\d\.\d+) \d+ 0x[0-9a-f]+ [A-Z]+ +([a-zA-Z_]+ )(.*)')
29 mark_timestamp_group = 1
30 mark_program_group = 2
37 def __init__(self, timestamp, log):
38 self.timestamp = timestamp
40 self.timestamp_ypos = 0
43 class AccessMark(BaseMark):
46 class LastMark(BaseMark):
49 class FirstMark(BaseMark):
52 class ExecMark(BaseMark):
53 # colors = 0.75, 0.33, 0.33
54 colors = (1.0, 0.0, 0.0)
55 def __init__(self, timestamp, log):
56 BaseMark.__init__(self, timestamp,
57 'execve: ' + os.path.basename(log))
64 # don't use black or red
77 self.pending_execs = []
80 def search_pending_execs (self, search_pid):
81 n = len (self.pending_execs)
83 (pid, timestamp, command) = self.pending_execs[i]
85 return (i, timestamp, command)
87 return (None, None, None)
89 def add_line (self, str):
90 m = mark_regex.search (str)
92 timestr = m.group (mark_timestamp_group)
93 timestamp = float (timestr[5:]) + (float (timestr[2:3]) * 60.0) + (float (timestr[0]) * 60.0*60.0)
94 program = m.group (mark_program_group)
95 text = program + m.group (mark_log_group)
97 self.syscalls.append (LastMark (timestamp, text))
99 self.syscalls.append (FirstMark (timestamp, text))
101 s = AccessMark (timestamp, text)
102 program_hash = program.__hash__ ()
103 s.colors = palette[program_hash % len (palette)]
104 self.syscalls.append (s)
108 def parse_strace(filename):
109 parser = SyscallParser ()
111 for line in file(filename, "r").readlines():
115 parser.add_line (line)
117 return parser.syscalls
119 def normalize_timestamps(syscalls):
121 first_timestamp = syscalls[0].timestamp
123 for syscall in syscalls:
124 syscall.timestamp -= first_timestamp
126 def compute_syscall_metrics(syscalls):
127 num_syscalls = len(syscalls)
130 metrics.width = PLOT_WIDTH
132 last_timestamp = syscalls[num_syscalls - 1].timestamp
133 num_seconds = int(math.ceil(last_timestamp))
134 metrics.height = max(num_seconds * PIXELS_PER_SECOND,
135 num_syscalls * PIXELS_PER_LINE)
139 for syscall in syscalls:
140 syscall.timestamp_ypos = syscall.timestamp * PIXELS_PER_SECOND
141 syscall.log_ypos = text_ypos + FONT_SIZE
143 text_ypos += PIXELS_PER_LINE
147 def plot_time_scale(surface, ctx, metrics):
148 num_seconds = (metrics.height + PIXELS_PER_SECOND - 1) / PIXELS_PER_SECOND
150 ctx.set_source_rgb(0.5, 0.5, 0.5)
151 ctx.set_line_width(1.0)
153 for i in range(num_seconds):
154 ypos = i * PIXELS_PER_SECOND
156 ctx.move_to(0, ypos + 0.5)
157 ctx.line_to(TIME_SCALE_WIDTH, ypos + 0.5)
160 ctx.move_to(0, ypos + 2 + FONT_SIZE)
161 ctx.show_text("%d s" % i)
163 def plot_syscall(surface, ctx, syscall):
164 ctx.set_source_rgb(*syscall.colors)
168 ctx.move_to(TIME_SCALE_WIDTH, syscall.timestamp_ypos)
169 ctx.line_to(TIME_SCALE_WIDTH + SYSCALL_MARKER_WIDTH, syscall.timestamp_ypos)
170 ctx.line_to(LOG_TEXT_XPOS - LOG_MARKER_WIDTH, syscall.log_ypos - FONT_SIZE / 2 + 0.5)
171 ctx.line_to(LOG_TEXT_XPOS, syscall.log_ypos - FONT_SIZE / 2 + 0.5)
176 ctx.move_to(LOG_TEXT_XPOS, syscall.log_ypos)
177 ctx.show_text("%8.5f: %s" % (syscall.timestamp, syscall.log))
179 def plot_syscalls_to_surface(syscalls, metrics):
180 num_syscalls = len(syscalls)
182 surface = cairo.ImageSurface(cairo.FORMAT_RGB24,
183 metrics.width, metrics.height)
185 ctx = cairo.Context(surface)
186 ctx.select_font_face(FONT_NAME)
187 ctx.set_font_size(FONT_SIZE)
191 ctx.set_source_rgb (*BACKGROUND_COLOR)
192 ctx.rectangle(0, 0, metrics.width, metrics.height)
197 plot_time_scale(surface, ctx, metrics)
201 ctx.set_line_width(1.0)
203 for syscall in syscalls:
204 plot_syscall(surface, ctx, syscall)
209 option_parser = optparse.OptionParser(
210 usage="usage: %prog -o output.png <debug.log>")
211 option_parser.add_option("-o",
212 "--output", dest="output",
214 help="Name of output file (output is a PNG file)")
216 options, args = option_parser.parse_args()
218 if not options.output:
219 print 'Please specify an output filename with "-o file.png" or "--output=file.png".'
223 print 'Please specify only one input filename, which is an debug log taken with "GST_DEBUG_NO_COLOR=1 GST_DEBUG=XXX <application>"'
226 in_filename = args[0]
227 out_filename = options.output
230 for syscall in parse_strace(in_filename):
231 syscalls.append(syscall)
232 if isinstance(syscall, FirstMark):
234 elif isinstance(syscall, LastMark):
238 print 'No logs in %s' % in_filename
241 normalize_timestamps(syscalls)
242 metrics = compute_syscall_metrics(syscalls)
244 surface = plot_syscalls_to_surface(syscalls, metrics)
245 surface.write_to_png(out_filename)
249 if __name__ == "__main__":
250 sys.exit(main(sys.argv))