3 # based on plot-timeline.py by Federico Mena-Quintero <federico at ximian dotcom>
5 # GST_DEBUG_COLOR_MODE=off GST_DEBUG="*:3" gst-launch-1.0 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 # how many pixels for a second on the timeline
19 PIXELS_PER_SECOND = 300
20 # how many pixels for one line of log
24 SYSCALL_MARKER_WIDTH = 20
27 BACKGROUND_COLOR = (0, 0, 0)
29 # assumes GST_DEBUG_LOG_COLOR=1
30 # timestamp pid thread level category,file,line,msg
31 mark_regex = re.compile (r'^(\d+:\d+:\d+\.\d+) +\d+ +0?x?[0-9a-f]+ [A-Z]+ +([-a-zA-Z0-9_]+ )(.*)')
32 mark_timestamp_group = 1
33 mark_program_group = 2
40 filter_regex = re.compile ('')
41 skip_regex = re.compile('')
45 def __init__(self, timestamp, log):
46 self.timestamp = timestamp
48 self.timestamp_ypos = 0
51 class AccessMark(BaseMark):
54 class LastMark(BaseMark):
57 class FirstMark(BaseMark):
60 class ExecMark(BaseMark):
61 # colors = 0.75, 0.33, 0.33
62 colors = (1.0, 0.0, 0.0)
63 def __init__(self, timestamp, log):
64 BaseMark.__init__(self, timestamp,
65 'execve: ' + os.path.basename(log))
72 # don't use black or red
87 def add_line (self, str):
88 m = mark_regex.search (str)
90 timestr = m.group (mark_timestamp_group).split(':')
91 timestamp = float (timestr[2]) + (float (timestr[1]) * 60.0) + (float (timestr[0]) * 3600.0)
92 program = m.group (mark_program_group)
93 text = program + m.group (mark_log_group)
95 self.syscalls.append (LastMark (timestamp, text))
97 self.syscalls.append (FirstMark (timestamp, text))
99 s = AccessMark (timestamp, text)
100 program_hash = program.__hash__ ()
101 s.colors = palette[program_hash % len (palette)]
102 self.syscalls.append (s)
104 print 'No log in %s' % str
107 def parse_strace(filename):
108 parser = SyscallParser ()
116 for line in file(filename, "r").readlines():
121 if skip_regex.search(line):
130 if len(parser.syscalls) >= max_lines:
133 if filter_regex.search(line):
134 parser.add_line (line)
136 return parser.syscalls
138 def normalize_timestamps(syscalls):
140 first_timestamp = syscalls[0].timestamp
142 for syscall in syscalls:
143 syscall.timestamp -= first_timestamp
145 def compute_syscall_metrics(syscalls):
146 global PIXELS_PER_SECOND
147 global PIXELS_PER_LINE
149 num_syscalls = len(syscalls)
152 metrics.width = PLOT_WIDTH
154 last_timestamp = syscalls[num_syscalls - 1].timestamp
156 time_height = int(math.ceil(last_timestamp * PIXELS_PER_SECOND))
157 line_height = num_syscalls * PIXELS_PER_LINE
159 if time_height > line_height:
160 metrics.height = time_height
161 print "Adjusting PIXELS_PER_LINE = %d" % PIXELS_PER_LINE
162 PIXELS_PER_LINE = metrics.height / num_syscalls
163 print " PIXELS_PER_LINE = %d" % PIXELS_PER_LINE
165 metrics.height = line_height
166 print "Adjusting PIXELS_PER_SECOND %d" % PIXELS_PER_SECOND
167 PIXELS_PER_SECOND = int(math.ceil(metrics.height / last_timestamp))
168 print " PIXELS_PER_SECOND %d" % PIXELS_PER_SECOND
172 for syscall in syscalls:
173 syscall.timestamp_ypos = syscall.timestamp * PIXELS_PER_SECOND
174 syscall.log_ypos = text_ypos + FONT_SIZE
176 text_ypos += PIXELS_PER_LINE
180 def plot_time_scale(surface, ctx, metrics):
181 num_seconds = (metrics.height + PIXELS_PER_SECOND - 1) / PIXELS_PER_SECOND
183 ctx.set_source_rgb(0.5, 0.5, 0.5)
184 ctx.set_line_width(1.0)
186 for i in range(num_seconds):
187 ypos = i * PIXELS_PER_SECOND
189 ctx.move_to(0, ypos + 0.5)
190 ctx.line_to(TIME_SCALE_WIDTH, ypos + 0.5)
193 ctx.move_to(0, ypos + 2 + FONT_SIZE)
194 ctx.show_text("%d s" % i)
196 def plot_syscall(surface, ctx, syscall):
197 ctx.set_source_rgb(*syscall.colors)
201 ctx.move_to(TIME_SCALE_WIDTH, syscall.timestamp_ypos)
202 ctx.line_to(TIME_SCALE_WIDTH + SYSCALL_MARKER_WIDTH, syscall.timestamp_ypos)
203 ctx.line_to(LOG_TEXT_XPOS - LOG_MARKER_WIDTH, syscall.log_ypos - FONT_SIZE / 2 + 0.5)
204 ctx.line_to(LOG_TEXT_XPOS, syscall.log_ypos - FONT_SIZE / 2 + 0.5)
209 ctx.move_to(LOG_TEXT_XPOS, syscall.log_ypos)
210 ctx.show_text("%8.5f: %s" % (syscall.timestamp, syscall.log))
212 def plot_syscalls_to_surface(syscalls, metrics):
213 num_syscalls = len(syscalls)
215 print 'picture size: %d x %d' % (metrics.width, metrics.height);
217 surface = cairo.ImageSurface(cairo.FORMAT_RGB24,
218 metrics.width, metrics.height)
220 ctx = cairo.Context(surface)
221 ctx.select_font_face(FONT_NAME)
222 ctx.set_font_size(FONT_SIZE)
226 ctx.set_source_rgb (*BACKGROUND_COLOR)
227 ctx.rectangle(0, 0, metrics.width, metrics.height)
232 plot_time_scale(surface, ctx, metrics)
236 ctx.set_line_width(1.0)
238 for syscall in syscalls:
239 plot_syscall(surface, ctx, syscall)
250 option_parser = optparse.OptionParser(
251 usage="usage: %prog -o output.png <debug.log>")
252 option_parser.add_option("-o",
253 "--output", dest="output",
255 help="Name of output file (output is a PNG file)")
256 option_parser.add_option("-s",
257 "--skip", dest="skip",
259 help="Skip a number of loglines at the beginning of the file or wait till a regular expression happens")
260 option_parser.add_option("-m",
261 "--max-lines", dest="max",
262 help="max lines that need to be plotted")
263 option_parser.add_option("-f",
264 "--filter", dest="filter",
265 help="filter the log lines on a regular expression")
267 options, args = option_parser.parse_args()
269 if not options.output:
270 print 'Please specify an output filename with "-o file.png" or "--output=file.png".'
274 print 'Please specify only one input filename, which is an debug log taken with "GST_DEBUG_COLOR_MODE=off GST_DEBUG=XXX <application>"'
277 in_filename = args[0]
278 out_filename = options.output
282 skip_lines = int(options.skip)
284 skip_regex = re.compile(options.skip)
288 max_lines = int(options.max)
291 filter_regex = re.compile(options.filter)
294 for syscall in parse_strace(in_filename):
295 syscalls.append(syscall)
296 if isinstance(syscall, FirstMark):
298 elif isinstance(syscall, LastMark):
302 print 'No logs in %s' % in_filename
305 normalize_timestamps(syscalls)
306 metrics = compute_syscall_metrics(syscalls)
308 surface = plot_syscalls_to_surface(syscalls, metrics)
309 surface.write_to_png(out_filename)
313 if __name__ == "__main__":
314 sys.exit(main(sys.argv))