Deleted Added
sdiff udiff text old ( 9935:cc9dc514036e ) new ( 10016:dffa80408656 )
full compact
1#!/usr/bin/env python
2
3# Copyright (c) 2012 ARM Limited
4# All rights reserved
5#
6# The license below extends only to copyright in the software and shall
7# not be construed as granting a license to any other intellectual
8# property including but not limited to intellectual property relating
9# to a hardware implementation of the functionality of the software
10# licensed hereunder. You may use the software subject to the license
11# terms below provided that you ensure that this notice is replicated
12# unmodified and in its entirety in all distributions of the software,
13# modified or unmodified, in source code or in binary form.
14#
15# Redistribution and use in source and binary forms, with or without
16# modification, are permitted provided that the following conditions are
17# met: redistributions of source code must retain the above copyright
18# notice, this list of conditions and the following disclaimer;
19# redistributions in binary form must reproduce the above copyright
20# notice, this list of conditions and the following disclaimer in the
21# documentation and/or other materials provided with the distribution;
22# neither the name of the copyright holders nor the names of its
23# contributors may be used to endorse or promote products derived from
24# this software without specific prior written permission.
25#
26# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37#
38# Author: Dam Sunwoo
39#
40
41# This script converts gem5 output to ARM DS-5 Streamline .apc project file
42# (Requires the gem5 runs to be run with ContextSwitchStatsDump enabled and
43# some patches applied to target Linux kernel.)
44# Visit http://www.gem5.org/Streamline for more details.
45#
46# Usage:
47# m5stats2streamline.py <stat_config.ini> <gem5 run folder> <dest .apc folder>
48#
49# <stat_config.ini>: .ini file that describes which stats to be included
50# in conversion. Sample .ini files can be found in
51# util/streamline.
52# NOTE: this is NOT the gem5 config.ini file.
53#
54# <gem5 run folder>: Path to gem5 run folder (must contain config.ini,
55# stats.txt[.gz], and system.tasks.txt.)
56#
57# <dest .apc folder>: Destination .apc folder path
58#
59# APC project generation based on Gator v12 (DS-5 v5.13)
60# Subsequent versions should be backward compatible
61
62import re, sys, os
63from ConfigParser import ConfigParser
64import gzip
65import xml.etree.ElementTree as ET
66import xml.dom.minidom as minidom
67import shutil
68import zlib
69
70import argparse
71
72parser = argparse.ArgumentParser(
73 formatter_class=argparse.RawDescriptionHelpFormatter,
74 description="""
75 Converts gem5 runs to ARM DS-5 Streamline .apc project file.
76 (NOTE: Requires gem5 runs to be run with ContextSwitchStatsDump
77 enabled and some patches applied to the target Linux kernel.)
78
79 Visit http://www.gem5.org/Streamline for more details.
80
81 APC project generation based on Gator v12 (DS-5 v5.13)
82 Subsequent versions should be backward compatible
83 """)
84
85parser.add_argument("stat_config_file", metavar="<stat_config.ini>",
86 help=".ini file that describes which stats to be included \
87 in conversion. Sample .ini files can be found in \
88 util/streamline. NOTE: this is NOT the gem5 config.ini \
89 file.")
90
91parser.add_argument("input_path", metavar="<gem5 run folder>",
92 help="Path to gem5 run folder (must contain config.ini, \
93 stats.txt[.gz], and system.tasks.txt.)")
94
95parser.add_argument("output_path", metavar="<dest .apc folder>",
96 help="Destination .apc folder path")
97
98parser.add_argument("--num-events", action="store", type=int,
99 default=1000000,
100 help="Maximum number of scheduling (context switch) \
101 events to be processed. Set to truncate early. \
102 Default=1000000")
103
104parser.add_argument("--gzipped-bmp-not-supported", action="store_true",
105 help="Do not use gzipped .bmp files for visual annotations. \
106 This option is only required when using Streamline versions \
107 older than 5.14")
108
109parser.add_argument("--verbose", action="store_true",
110 help="Enable verbose output")
111
112args = parser.parse_args()
113
114if not re.match("(.*)\.apc", args.output_path):
115 print "ERROR: <dest .apc folder> should end with '.apc'!"
116 sys.exit(1)
117
118# gzipped BMP files for visual annotation is supported in Streamline 5.14.
119# Setting this to True will significantly compress the .apc binary file that
120# includes frame buffer snapshots.
121gzipped_bmp_supported = not args.gzipped_bmp_not_supported
122
123ticks_in_ns = -1
124
125# Default max # of events. Increase this for longer runs.
126num_events = args.num_events
127
128start_tick = -1
129end_tick = -1
130
131# Parse gem5 config.ini file to determine some system configurations.
132# Number of CPUs, L2s, etc.
133def parseConfig(config_file):
134 global num_cpus, num_l2
135
136 print "\n==============================="
137 print "Parsing gem5 config.ini file..."
138 print config_file
139 print "===============================\n"
140 config = ConfigParser()
141 if not config.read(config_file):
142 print "ERROR: config file '", config_file, "' not found"
143 sys.exit(1)
144
145 if config.has_section("system.cpu"):
146 num_cpus = 1
147 else:
148 num_cpus = 0
149 while config.has_section("system.cpu" + str(num_cpus)):
150 num_cpus += 1
151
152 if config.has_section("system.l2"):
153 num_l2 = 1
154 else:
155 num_l2 = 0
156 while config.has_section("system.l2" + str(num_l2)):
157 num_l2 += 1
158
159 print "Num CPUs:", num_cpus
160 print "Num L2s:", num_l2
161 print ""
162
163 return (num_cpus, num_l2)
164
165
166process_dict = {}
167thread_dict = {}
168
169process_list = []
170
171idle_uid = -1
172kernel_uid = -1
173
174class Task(object):
175 def __init__(self, uid, pid, tgid, task_name, is_process, tick):
176 if pid == 0: # Idle
177 self.uid = 0
178 elif pid == -1: # Kernel
179 self.uid = 0
180 else:
181 self.uid = uid
182 self.pid = pid
183 self.tgid = tgid
184 self.is_process = is_process
185 self.task_name = task_name
186 self.children = []
187 self.tick = tick # time this task first appeared
188
189class Event(object):
190 def __init__(self, tick, task):
191 self.tick = tick
192 self.task = task
193
194############################################################
195# Types used in APC Protocol
196# - packed32, packed64
197# - int32
198# - string
199############################################################
200
201# variable length packed 4-byte signed value
202def packed32(x):
203 ret = []
204 if ((x & 0xffffff80) == 0):
205 ret.append(x & 0x7f)
206 elif ((x & 0xffffc000) == 0):
207 ret.append((x | 0x80) & 0xff)
208 ret.append((x >> 7) & 0x7f)
209 elif ((x & 0xffe00000) == 0):
210 ret.append((x | 0x80) & 0xff)
211 ret.append(((x >> 7) | 0x80) & 0xff)
212 ret.append((x >> 14) & 0x7f)
213 elif ((x & 0xf0000000) == 0):
214 ret.append((x | 0x80) & 0xff)
215 ret.append(((x >> 7) | 0x80) & 0xff)
216 ret.append(((x >> 14) | 0x80) & 0xff)
217 ret.append((x >> 21) & 0x7f)
218 else:
219 ret.append((x | 0x80) & 0xff)
220 ret.append(((x >> 7) | 0x80) & 0xff)
221 ret.append(((x >> 14) | 0x80) & 0xff)
222 ret.append(((x >> 21) | 0x80) & 0xff)
223 ret.append((x >> 28) & 0x0f)
224 return ret
225
226# variable length packed 8-byte signed value
227def packed64(x):
228 ret = []
229 if ((x & 0xffffffffffffff80) == 0):
230 ret.append(x & 0x7f)
231 elif ((x & 0xffffffffffffc000) == 0):
232 ret.append((x | 0x80) & 0xff)
233 ret.append((x >> 7) & 0x7f)
234 elif ((x & 0xffffffffffe00000) == 0):
235 ret.append((x | 0x80) & 0xff)
236 ret.append(((x >> 7) | 0x80) & 0xff)
237 ret.append((x >> 14) & 0x7f)
238 elif ((x & 0xfffffffff0000000) == 0):
239 ret.append((x | 0x80) & 0xff)
240 ret.append(((x >> 7) | 0x80) & 0xff)
241 ret.append(((x >> 14) | 0x80) & 0xff)
242 ret.append((x >> 21) & 0x7f)
243 elif ((x & 0xfffffff800000000) == 0):
244 ret.append((x | 0x80) & 0xff)
245 ret.append(((x >> 7) | 0x80) & 0xff)
246 ret.append(((x >> 14) | 0x80) & 0xff)
247 ret.append(((x >> 21) | 0x80) & 0xff)
248 ret.append((x >> 28) & 0x7f)
249 elif ((x & 0xfffffc0000000000) == 0):
250 ret.append((x | 0x80) & 0xff)
251 ret.append(((x >> 7) | 0x80) & 0xff)
252 ret.append(((x >> 14) | 0x80) & 0xff)
253 ret.append(((x >> 21) | 0x80) & 0xff)
254 ret.append(((x >> 28) | 0x80) & 0xff)
255 ret.append((x >> 35) & 0x7f)
256 elif ((x & 0xfffe000000000000) == 0):
257 ret.append((x | 0x80) & 0xff)
258 ret.append(((x >> 7) | 0x80) & 0xff)
259 ret.append(((x >> 14) | 0x80) & 0xff)
260 ret.append(((x >> 21) | 0x80) & 0xff)
261 ret.append(((x >> 28) | 0x80) & 0xff)
262 ret.append(((x >> 35) | 0x80) & 0xff)
263 ret.append((x >> 42) & 0x7f)
264 elif ((x & 0xff00000000000000) == 0):
265 ret.append((x | 0x80) & 0xff)
266 ret.append(((x >> 7) | 0x80) & 0xff)
267 ret.append(((x >> 14) | 0x80) & 0xff)
268 ret.append(((x >> 21) | 0x80) & 0xff)
269 ret.append(((x >> 28) | 0x80) & 0xff)
270 ret.append(((x >> 35) | 0x80) & 0xff)
271 ret.append(((x >> 42) | 0x80) & 0xff)
272 ret.append((x >> 49) & 0x7f)
273 elif ((x & 0x8000000000000000) == 0):
274 ret.append((x | 0x80) & 0xff)
275 ret.append(((x >> 7) | 0x80) & 0xff)
276 ret.append(((x >> 14) | 0x80) & 0xff)
277 ret.append(((x >> 21) | 0x80) & 0xff)
278 ret.append(((x >> 28) | 0x80) & 0xff)
279 ret.append(((x >> 35) | 0x80) & 0xff)
280 ret.append(((x >> 42) | 0x80) & 0xff)
281 ret.append(((x >> 49) | 0x80) & 0xff)
282 ret.append((x >> 56) & 0x7f)
283 else:
284 ret.append((x | 0x80) & 0xff)
285 ret.append(((x >> 7) | 0x80) & 0xff)
286 ret.append(((x >> 14) | 0x80) & 0xff)
287 ret.append(((x >> 21) | 0x80) & 0xff)
288 ret.append(((x >> 28) | 0x80) & 0xff)
289 ret.append(((x >> 35) | 0x80) & 0xff)
290 ret.append(((x >> 42) | 0x80) & 0xff)
291 ret.append(((x >> 49) | 0x80) & 0xff)
292 ret.append(((x >> 56) | 0x80) & 0xff)
293 ret.append((x >> 63) & 0x7f)
294 return ret
295
296# 4-byte signed little endian
297def int32(x):
298 ret = []
299 ret.append(x & 0xff)
300 ret.append((x >> 8) & 0xff)
301 ret.append((x >> 16) & 0xff)
302 ret.append((x >> 24) & 0xff)
303 return ret
304
305# 2-byte signed little endian
306def int16(x):
307 ret = []
308 ret.append(x & 0xff)
309 ret.append((x >> 8) & 0xff)
310 return ret
311
312# a packed32 length followed by the specified number of characters
313def stringList(x):
314 ret = []
315 ret += packed32(len(x))
316 for i in x:
317 ret.append(i)
318 return ret
319
320def utf8StringList(x):
321 ret = []
322 for i in x:
323 ret.append(ord(i))
324 return ret
325
326# packed64 time value in nanoseconds relative to the uptime from the
327# Summary message.
328def timestampList(x):
329 ret = packed64(x)
330 return ret
331
332
333############################################################
334# Write binary
335############################################################
336
337def writeBinary(outfile, binary_list):
338 for i in binary_list:
339 outfile.write("%c" % i)
340
341############################################################
342# APC Protocol Frame Types
343############################################################
344
345def addFrameHeader(frame_type, body, core):
346 ret = []
347
348 if frame_type == "Summary":
349 code = 1
350 elif frame_type == "Backtrace":
351 code = 2
352 elif frame_type == "Name":
353 code = 3
354 elif frame_type == "Counter":
355 code = 4
356 elif frame_type == "Block Counter":
357 code = 5
358 elif frame_type == "Annotate":
359 code = 6
360 elif frame_type == "Sched Trace":
361 code = 7
362 elif frame_type == "GPU Trace":
363 code = 8
364 elif frame_type == "Idle":
365 code = 9
366 else:
367 print "ERROR: Unknown frame type:", frame_type
368 sys.exit(1)
369
370 packed_code = packed32(code)
371
372 packed_core = packed32(core)
373
374 length = int32(len(packed_code) + len(packed_core) + len(body))
375
376 ret = length + packed_code + packed_core + body
377 return ret
378
379
380# Summary frame
381# - timestamp: packed64
382# - uptime: packed64
383def summaryFrame(timestamp, uptime):
384 frame_type = "Summary"
385 body = packed64(timestamp) + packed64(uptime)
386 ret = addFrameHeader(frame_type, body, 0)
387 return ret
388
389# Backtrace frame
390# - not implemented yet
391def backtraceFrame():
392 pass
393
394# Cookie name message
395# - cookie: packed32
396# - name: string
397def cookieNameFrame(cookie, name):
398 frame_type = "Name"
399 packed_code = packed32(1)
400 body = packed_code + packed32(cookie) + stringList(name)
401 ret = addFrameHeader(frame_type, body, 0)
402 return ret
403
404# Thread name message
405# - timestamp: timestamp
406# - thread id: packed32
407# - name: string
408def threadNameFrame(timestamp, thread_id, name):
409 frame_type = "Name"
410 packed_code = packed32(2)
411 body = packed_code + timestampList(timestamp) + \
412 packed32(thread_id) + stringList(name)
413 ret = addFrameHeader(frame_type, body, 0)
414 return ret
415
416# Core name message
417# - name: string
418def coreNameFrame(name):
419 frame_type = "Name"
420 packed_code = packed32(3)
421 body = packed_code + stringList(name)
422 ret = addFrameHeader(frame_type, body, 0)
423 return ret
424
425# Counter frame message
426# - timestamp: timestamp
427# - core: packed32
428# - key: packed32
429# - value: packed64
430def counterFrame(timestamp, core, key, value):
431 frame_type = "Counter"
432 body = timestampList(timestamp) + packed32(core) + packed32(key) + \
433 packed64(value)
434 ret = addFrameHeader(frame_type, body, core)
435 return ret
436
437# Block Counter frame message
438# - key: packed32
439# - value: packed64
440def blockCounterFrame(core, key, value):
441 frame_type = "Block Counter"
442 body = packed32(key) + packed64(value)
443 ret = addFrameHeader(frame_type, body, core)
444 return ret
445
446# Annotate frame messages
447# - core: packed32
448# - tid: packed32
449# - timestamp: timestamp
450# - size: packed32
451# - body
452def annotateFrame(core, tid, timestamp, size, userspace_body):
453 frame_type = "Annotate"
454 body = packed32(core) + packed32(tid) + timestampList(timestamp) + \
455 packed32(size) + userspace_body
456 ret = addFrameHeader(frame_type, body, core)
457 return ret
458
459# Scheduler Trace frame messages
460# Sched Switch
461# - Code: 1
462# - timestamp: timestamp
463# - pid: packed32
464# - tid: packed32
465# - cookie: packed32
466# - state: packed32
467def schedSwitchFrame(core, timestamp, pid, tid, cookie, state):
468 frame_type = "Sched Trace"
469 body = packed32(1) + timestampList(timestamp) + packed32(pid) + \
470 packed32(tid) + packed32(cookie) + packed32(state)
471 ret = addFrameHeader(frame_type, body, core)
472 return ret
473
474# Sched Thread Exit
475# - Code: 2
476# - timestamp: timestamp
477# - tid: packed32
478def schedThreadExitFrame(core, timestamp, pid, tid, cookie, state):
479 frame_type = "Sched Trace"
480 body = packed32(2) + timestampList(timestamp) + packed32(tid)
481 ret = addFrameHeader(frame_type, body, core)
482 return ret
483
484# GPU Trace frame messages
485# - Not implemented yet
486def gpuTraceFrame():
487 pass
488
489# Idle frame messages
490# Enter Idle
491# - code: 1
492# - timestamp: timestamp
493# - core: packed32
494def enterIdleFrame(timestamp, core):
495 frame_type = "Idle"
496 body = packed32(1) + timestampList(timestamp) + packed32(core)
497 ret = addFrameHeader(frame_type, body, core)
498 return ret
499
500# Exit Idle
501# - code: 2
502# - timestamp: timestamp
503# - core: packed32
504def exitIdleFrame(timestamp, core):
505 frame_type = "Idle"
506 body = packed32(2) + timestampList(timestamp) + packed32(core)
507 ret = addFrameHeader(frame_type, body, core)
508 return ret
509
510
511####################################################################
512def parseProcessInfo(task_file):
513 print "\n==============================="
514 print "Parsing Task file..."
515 print task_file
516 print "===============================\n"
517
518 global start_tick, end_tick, num_cpus
519 global process_dict, thread_dict, process_list
520 global event_list, unified_event_list
521 global idle_uid, kernel_uid
522
523 event_list = []
524 unified_event_list = []
525 for cpu in range(num_cpus):
526 event_list.append([])
527
528 uid = 1 # uid 0 is reserved for idle
529
530 # Dummy Tasks for frame buffers and system diagrams
531 process = Task(uid, 9999, 9999, "framebuffer", True, 0)
532 process_list.append(process)
533 uid += 1
534 thread = Task(uid, 9999, 9999, "framebuffer", False, 0)
535 process.children.append(thread)
536 uid += 1
537 process = Task(uid, 9998, 9998, "System", True, 0)
538 process_list.append(process)
539 # if we don't find the real kernel, use this to keep things going
540 kernel_uid = uid
541 uid += 1
542 thread = Task(uid, 9998, 9998, "System", False, 0)
543 process.children.append(thread)
544 uid += 1
545
546 ext = os.path.splitext(task_file)[1]
547
548 try:
549 if ext == ".gz":
550 process_file = gzip.open(task_file, 'rb')
551 else:
552 process_file = open(task_file, 'rb')
553 except:
554 print "ERROR opening task file:", task_file
555 print "Make sure context switch task dumping is enabled in gem5."
556 sys.exit(1)
557
558 process_re = re.compile("tick=(\d+)\s+(\d+)\s+cpu_id=(\d+)\s+" +
559 "next_pid=([-\d]+)\s+next_tgid=([-\d]+)\s+next_task=(.*)")
560
561 task_name_failure_warned = False
562
563 for line in process_file:
564 match = re.match(process_re, line)
565 if match:
566 tick = int(match.group(1))
567 if (start_tick < 0):
568 start_tick = tick
569 cpu_id = int(match.group(3))
570 pid = int(match.group(4))
571 tgid = int(match.group(5))
572 task_name = match.group(6)
573
574 if not task_name_failure_warned:
575 if task_name == "FailureIn_curTaskName":
576 print "-------------------------------------------------"
577 print "WARNING: Task name not set correctly!"
578 print "Process/Thread info will not be displayed correctly"
579 print "Perhaps forgot to apply m5struct.patch to kernel?"
580 print "-------------------------------------------------"
581 task_name_failure_warned = True
582
583 if not tgid in process_dict:
584 if tgid == pid:
585 # new task is parent as well
586 if args.verbose:
587 print "new process", uid, pid, tgid, task_name
588 if tgid == 0:
589 # new process is the "idle" task
590 process = Task(uid, pid, tgid, "idle", True, tick)
591 idle_uid = 0
592 else:
593 process = Task(uid, pid, tgid, task_name, True, tick)
594 else:
595 if tgid == 0:
596 process = Task(uid, tgid, tgid, "idle", True, tick)
597 idle_uid = 0
598 else:
599 # parent process name not known yet
600 process = Task(uid, tgid, tgid, "_Unknown_", True, tick)
601 if tgid == -1: # kernel
602 kernel_uid = 0
603 uid += 1
604 process_dict[tgid] = process
605 process_list.append(process)
606 else:
607 if tgid == pid:
608 if process_dict[tgid].task_name == "_Unknown_":
609 if args.verbose:
610 print "new process", \
611 process_dict[tgid].uid, pid, tgid, task_name
612 process_dict[tgid].task_name = task_name
613 if process_dict[tgid].task_name != task_name and tgid != 0:
614 process_dict[tgid].task_name = task_name
615
616 if not pid in thread_dict:
617 if args.verbose:
618 print "new thread", \
619 uid, process_dict[tgid].uid, pid, tgid, task_name
620 thread = Task(uid, pid, tgid, task_name, False, tick)
621 uid += 1
622 thread_dict[pid] = thread
623 process_dict[tgid].children.append(thread)
624 else:
625 if thread_dict[pid].task_name != task_name:
626 thread_dict[pid].task_name = task_name
627
628 if args.verbose:
629 print tick, uid, cpu_id, pid, tgid, task_name
630
631 task = thread_dict[pid]
632 event = Event(tick, task)
633 event_list[cpu_id].append(event)
634 unified_event_list.append(event)
635
636 if len(unified_event_list) == num_events:
637 print "Truncating at", num_events, "events!"
638 break
639 print "Found %d events." % len(unified_event_list)
640
641 for process in process_list:
642 if process.pid > 9990: # fix up framebuffer ticks
643 process.tick = start_tick
644 print process.uid, process.pid, process.tgid, \
645 process.task_name, str(process.tick)
646 for thread in process.children:
647 if thread.pid > 9990:
648 thread.tick = start_tick
649 print "\t", thread.uid, thread.pid, thread.tgid, \
650 thread.task_name, str(thread.tick)
651
652 end_tick = tick
653
654 print "Start tick:", start_tick
655 print "End tick: ", end_tick
656 print ""
657
658 return
659
660
661def initOutput(output_path):
662 if not os.path.exists(output_path):
663 os.mkdir(output_path)
664
665def ticksToNs(tick):
666 if ticks_in_ns < 0:
667 print "ticks_in_ns not set properly!"
668 sys.exit(1)
669
670 return tick / ticks_in_ns
671
672def writeXmlFile(xml, filename):
673 f = open(filename, "w")
674 txt = ET.tostring(xml)
675 f.write(minidom.parseString(txt).toprettyxml())
676 f.close()
677
678
679# StatsEntry that contains individual statistics
680class StatsEntry(object):
681 def __init__(self, name, group, group_index, per_cpu, per_switchcpu, key):
682
683 # Full name of statistics
684 self.name = name
685
686 # Streamline group name that statistic will belong to
687 self.group = group
688
689 # Index of statistics within group (used to change colors within groups)
690 self.group_index = group_index
691
692 # Shorter name with "system" stripped off
693 # and symbols converted to alphanumerics
694 self.short_name = re.sub("system\.", "", name)
695 self.short_name = re.sub(":", "_", name)
696
697 # Regex for this stat (string version used to construct union regex)
698 self.regex_string = "^" + name + "\s+([\d\.]+)"
699 self.regex = re.compile("^" + name + "\s+([\d\.e\-]+)\s+# (.*)$", re.M)
700 self.description = ""
701
702 # Whether this stat is use per CPU or not
703 self.per_cpu = per_cpu
704 self.per_switchcpu = per_switchcpu
705
706 # Key used in .apc protocol (as described in captured.xml)
707 self.key = key
708
709 # List of values of stat per timestamp
710 self.values = []
711
712 # Whether this stat has been found for the current timestamp
713 self.found = False
714
715 # Whether this stat has been found at least once
716 # (to suppress too many warnings)
717 self.not_found_at_least_once = False
718
719 # Field used to hold ElementTree subelement for this stat
720 self.ET_element = None
721
722 # Create per-CPU stat name and regex, etc.
723 if self.per_cpu:
724 self.per_cpu_regex_string = []
725 self.per_cpu_regex = []
726 self.per_cpu_name = []
727 self.per_cpu_found = []
728 for i in range(num_cpus):
729 # Resuming from checkpoints results in using "switch_cpus"
730 if per_switchcpu:
731 per_cpu_name = "system.switch_cpus"
732 else:
733 per_cpu_name = "system.cpu"
734
735 # No CPU number appends if num_cpus == 1
736 if num_cpus > 1:
737 per_cpu_name += str(i)
738 per_cpu_name += "." + self.name
739 self.per_cpu_name.append(per_cpu_name)
740 print "\t", per_cpu_name
741
742 self.per_cpu_regex_string.\
743 append("^" + per_cpu_name + "\s+[\d\.]+")
744 self.per_cpu_regex.append(re.compile("^" + per_cpu_name + \
745 "\s+([\d\.e\-]+)\s+# (.*)$", re.M))
746 self.values.append([])
747 self.per_cpu_found.append(False)
748
749 def append_value(self, val, per_cpu_index = None):
750 if self.per_cpu:
751 self.values[per_cpu_index].append(str(val))
752 else:
753 self.values.append(str(val))
754
755# Global stats object that contains the list of stats entries
756# and other utility functions
757class Stats(object):
758 def __init__(self):
759 self.stats_list = []
760 self.tick_list = []
761 self.next_key = 1
762
763 def register(self, name, group, group_index, per_cpu, per_switchcpu):
764 print "registering stat:", name, "group:", group, group_index
765 self.stats_list.append(StatsEntry(name, group, group_index, per_cpu, \
766 per_switchcpu, self.next_key))
767 self.next_key += 1
768
769 # Union of all stats to accelerate parsing speed
770 def createStatsRegex(self):
771 regex_strings = [];
772 print "\nnum entries in stats_list", len(self.stats_list)
773 for entry in self.stats_list:
774 if entry.per_cpu:
775 for i in range(num_cpus):
776 regex_strings.append(entry.per_cpu_regex_string[i])
777 else:
778 regex_strings.append(entry.regex_string)
779
780 self.regex = re.compile('|'.join(regex_strings))
781
782
783def registerStats(config_file):
784 print "==============================="
785 print "Parsing stats config.ini file..."
786 print config_file
787 print "==============================="
788
789 config = ConfigParser()
790 if not config.read(config_file):
791 print "ERROR: config file '", config_file, "' not found!"
792 sys.exit(1)
793
794 print "\nRegistering Stats..."
795
796 stats = Stats()
797
798 per_cpu_stat_groups = config.options('PER_CPU_STATS')
799 for group in per_cpu_stat_groups:
800 i = 0
801 per_cpu_stats_list = config.get('PER_CPU_STATS', group).split('\n')
802 for item in per_cpu_stats_list:
803 if item:
804 stats.register(item, group, i, True, False)
805 i += 1
806
807 per_cpu_stat_groups = config.options('PER_SWITCHCPU_STATS')
808 for group in per_cpu_stat_groups:
809 i = 0
810 per_cpu_stats_list = \
811 config.get('PER_SWITCHCPU_STATS', group).split('\n')
812 for item in per_cpu_stats_list:
813 if item:
814 stats.register(item, group, i, True, True)
815 i += 1
816
817 per_l2_stat_groups = config.options('PER_L2_STATS')
818 for group in per_l2_stat_groups:
819 i = 0
820 per_l2_stats_list = config.get('PER_L2_STATS', group).split('\n')
821 for item in per_l2_stats_list:
822 if item:
823 for l2 in range(num_l2):
824 name = item
825 prefix = "system.l2"
826 if num_l2 > 1:
827 prefix += str(l2)
828 prefix += "."
829 name = prefix + name
830 stats.register(name, group, i, False, False)
831 i += 1
832
833 other_stat_groups = config.options('OTHER_STATS')
834 for group in other_stat_groups:
835 i = 0
836 other_stats_list = config.get('OTHER_STATS', group).split('\n')
837 for item in other_stats_list:
838 if item:
839 stats.register(item, group, i, False, False)
840 i += 1
841
842 stats.createStatsRegex()
843
844 return stats
845
846# Parse and read in gem5 stats file
847# Streamline counters are organized per CPU
848def readGem5Stats(stats, gem5_stats_file):
849 print "\n==============================="
850 print "Parsing gem5 stats file..."
851 print gem5_stats_file
852 print "===============================\n"
853 ext = os.path.splitext(gem5_stats_file)[1]
854
855 window_start_regex = \
856 re.compile("^---------- Begin Simulation Statistics ----------")
857 window_end_regex = \
858 re.compile("^---------- End Simulation Statistics ----------")
859 final_tick_regex = re.compile("^final_tick\s+(\d+)")
860
861 global ticks_in_ns
862 sim_freq_regex = re.compile("^sim_freq\s+(\d+)")
863 sim_freq = -1
864
865 try:
866 if ext == ".gz":
867 f = gzip.open(gem5_stats_file, "r")
868 else:
869 f = open(gem5_stats_file, "r")
870 except:
871 print "ERROR opening stats file", gem5_stats_file, "!"
872 sys.exit(1)
873
874 stats_not_found_list = stats.stats_list[:]
875 window_num = 0
876
877 while (True):
878 error = False
879 try:
880 line = f.readline()
881 except IOError:
882 print ""
883 print "WARNING: IO error in stats file"
884 print "(gzip stream not closed properly?)...continuing for now"
885 error = True
886 if not line:
887 break
888
889 # Find out how many gem5 ticks in 1ns
890 if sim_freq < 0:
891 m = sim_freq_regex.match(line)
892 if m:
893 sim_freq = int(m.group(1)) # ticks in 1 sec
894 ticks_in_ns = int(sim_freq / 1e9)
895 print "Simulation frequency found! 1 tick == %e sec\n" \
896 % (1.0 / sim_freq)
897
898 # Final tick in gem5 stats: current absolute timestamp
899 m = final_tick_regex.match(line)
900 if m:
901 tick = int(m.group(1))
902 if tick > end_tick:
903 break
904 stats.tick_list.append(tick)
905
906
907 if (window_end_regex.match(line) or error):
908 if args.verbose:
909 print "new window"
910 for stat in stats.stats_list:
911 if stat.per_cpu:
912 for i in range(num_cpus):
913 if not stat.per_cpu_found[i]:
914 if not stat.not_found_at_least_once:
915 print "WARNING: stat not found in window #", \
916 window_num, ":", stat.per_cpu_name[i]
917 print "suppressing further warnings for " + \
918 "this stat"
919 stat.not_found_at_least_once = True
920 stat.values[i].append(str(0))
921 stat.per_cpu_found[i] = False
922 else:
923 if not stat.found:
924 if not stat.not_found_at_least_once:
925 print "WARNING: stat not found in window #", \
926 window_num, ":", stat.name
927 print "suppressing further warnings for this stat"
928 stat.not_found_at_least_once = True
929 stat.values.append(str(0))
930 stat.found = False
931 stats_not_found_list = stats.stats_list[:]
932 window_num += 1
933 if error:
934 break
935
936 # Do a single regex of the union of all stats first for speed
937 if stats.regex.match(line):
938 # Then loop through only the stats we haven't seen in this window
939 for stat in stats_not_found_list[:]:
940 if stat.per_cpu:
941 for i in range(num_cpus):
942 m = stat.per_cpu_regex[i].match(line)
943 if m:
944 if stat.name == "ipc":
945 value = str(int(float(m.group(1)) * 1000))
946 else:
947 value = str(int(float(m.group(1))))
948 if args.verbose:
949 print stat.per_cpu_name[i], value
950 stat.values[i].append(value)
951 stat.per_cpu_found[i] = True
952 all_found = True
953 for j in range(num_cpus):
954 if not stat.per_cpu_found[j]:
955 all_found = False
956 if all_found:
957 stats_not_found_list.remove(stat)
958 if stat.description == "":
959 stat.description = m.group(2)
960 else:
961 m = stat.regex.match(line)
962 if m:
963 value = str(int(float(m.group(1))))
964 if args.verbose:
965 print stat.name, value
966 stat.values.append(value)
967 stat.found = True
968 stats_not_found_list.remove(stat)
969 if stat.description == "":
970 stat.description = m.group(2)
971 f.close()
972
973
974# Create session.xml file in .apc folder
975def doSessionXML(output_path):
976 session_file = output_path + "/session.xml"
977
978 xml = ET.Element("session")
979
980 xml.set("version", "1")
981 xml.set("call_stack_unwinding", "no")
982 xml.set("parse_debug_info", "no")
983 xml.set("high_resolution", "yes")
984 xml.set("buffer_mode", "streaming")
985 xml.set("sample_rate", "low")
986
987 # Setting duration to zero for now. Doesn't affect visualization.
988 xml.set("duration", "0")
989
990 xml.set("target_host", "")
991 xml.set("target_port", "8080")
992
993 writeXmlFile(xml, session_file)
994
995
996# Create captured.xml file in .apc folder
997def doCapturedXML(output_path, stats):
998 captured_file = output_path + "/captured.xml"
999
1000 xml = ET.Element("captured")
1001 xml.set("version", "1")
1002 xml.set("protocol", "12")
1003
1004 target = ET.SubElement(xml, "target")
1005 target.set("name", "gem5")
1006 target.set("sample_rate", "1000")
1007 target.set("cores", str(num_cpus))
1008
1009 counters = ET.SubElement(xml, "counters")
1010 for stat in stats.stats_list:
1011 s = ET.SubElement(counters, "counter")
1012 stat_name = re.sub("\.", "_", stat.short_name)
1013 s.set("title", stat.group)
1014 s.set("name", stat_name)
1015 s.set("color", "0x00000000")
1016 s.set("key", "0x%08x" % stat.key)
1017 s.set("type", stat_name)
1018 s.set("event", "0x00000000")
1019 if stat.per_cpu:
1020 s.set("per_cpu", "yes")
1021 else:
1022 s.set("per_cpu", "no")
1023 s.set("display", "")
1024 s.set("units", "")
1025 s.set("average_selection", "no")
1026 s.set("description", stat.description)
1027
1028 writeXmlFile(xml, captured_file)
1029
1030# Writes out Streamline cookies (unique IDs per process/thread)
1031def writeCookiesThreads(blob):
1032 thread_list = []
1033 for process in process_list:
1034 if process.uid > 0:
1035 print "cookie", process.task_name, process.uid
1036 writeBinary(blob, cookieNameFrame(process.uid, process.task_name))
1037
1038 # pid and tgid need to be positive values -- no longer true?
1039 for thread in process.children:
1040 thread_list.append(thread)
1041
1042 # Threads need to be sorted in timestamp order
1043 thread_list.sort(key = lambda x: x.tick)
1044 for thread in thread_list:
1045 print "thread", thread.task_name, (ticksToNs(thread.tick)),\
1046 thread.tgid, thread.pid
1047 writeBinary(blob, threadNameFrame(ticksToNs(thread.tick),\
1048 thread.pid, thread.task_name))
1049
1050# Writes context switch info as Streamline scheduling events
1051def writeSchedEvents(blob):
1052 for cpu in range(num_cpus):
1053 for event in event_list[cpu]:
1054 timestamp = ticksToNs(event.tick)
1055 pid = event.task.tgid
1056 tid = event.task.pid
1057 if process_dict.has_key(event.task.tgid):
1058 cookie = process_dict[event.task.tgid].uid
1059 else:
1060 cookie = 0
1061
1062 # State:
1063 # 0: waiting on other event besides I/O
1064 # 1: Contention/pre-emption
1065 # 2: Waiting on I/O
1066 # 3: Waiting on mutex
1067 # Hardcoding to 0 for now. Other states not implemented yet.
1068 state = 0
1069
1070 if args.verbose:
1071 print cpu, timestamp, pid, tid, cookie
1072
1073 writeBinary(blob,\
1074 schedSwitchFrame(cpu, timestamp, pid, tid, cookie, state))
1075
1076# Writes selected gem5 statistics as Streamline counters
1077def writeCounters(blob, stats):
1078 timestamp_list = []
1079 for tick in stats.tick_list:
1080 if tick > end_tick:
1081 break
1082 timestamp_list.append(ticksToNs(tick))
1083
1084 for stat in stats.stats_list:
1085 if stat.per_cpu:
1086 stat_length = len(stat.values[0])
1087 else:
1088 stat_length = len(stat.values)
1089
1090 for n in range(len(timestamp_list)):
1091 for stat in stats.stats_list:
1092 if stat.per_cpu:
1093 for i in range(num_cpus):
1094 writeBinary(blob, counterFrame(timestamp_list[n], i, \
1095 stat.key, int(float(stat.values[i][n]))))
1096 else:
1097 writeBinary(blob, counterFrame(timestamp_list[n], 0, \
1098 stat.key, int(float(stat.values[n]))))
1099
1100# Streamline can display LCD frame buffer dumps (gzipped bmp)
1101# This function converts the frame buffer dumps to the Streamline format
1102def writeVisualAnnotations(blob, input_path, output_path):
1103 frame_path = input_path + "/frames_system.vncserver"
1104 if not os.path.exists(frame_path):
1105 return
1106
1107 frame_count = 0
1108 file_list = os.listdir(frame_path)
1109 file_list.sort()
1110 re_fb = re.compile("fb\.(\d+)\.(\d+)\.bmp.gz")
1111
1112 # Use first non-negative pid to tag visual annotations
1113 annotate_pid = -1
1114 for e in unified_event_list:
1115 pid = e.task.pid
1116 if pid >= 0:
1117 annotate_pid = pid
1118 break
1119
1120 for fn in file_list:
1121 m = re_fb.match(fn)
1122 if m:
1123 seq = m.group(1)
1124 tick = int(m.group(2))
1125 if tick > end_tick:
1126 break
1127 frame_count += 1
1128
1129 userspace_body = []
1130 userspace_body += packed32(0x1C) # escape code
1131 userspace_body += packed32(0x04) # visual code
1132
1133 text_annotation = "image_" + str(ticksToNs(tick)) + ".bmp.gz"
1134 userspace_body += int16(len(text_annotation))
1135 userspace_body += utf8StringList(text_annotation)
1136
1137 if gzipped_bmp_supported:
1138 # copy gzipped bmp directly
1139 bytes_read = open(frame_path + "/" + fn, "rb").read()
1140 else:
1141 # copy uncompressed bmp
1142 bytes_read = gzip.open(frame_path + "/" + fn, "rb").read()
1143
1144 userspace_body += int32(len(bytes_read))
1145 userspace_body += bytes_read
1146
1147 writeBinary(blob, annotateFrame(0, annotate_pid, ticksToNs(tick), \
1148 len(userspace_body), userspace_body))
1149
1150 print "\nfound", frame_count, "frames for visual annotation.\n"
1151
1152
1153def createApcProject(input_path, output_path, stats):
1154 initOutput(output_path)
1155
1156 blob = open(output_path + "/0000000000", "wb")
1157
1158 # Summary frame takes current system time and system uptime.
1159 # Filling in with random values for now.
1160 writeBinary(blob, summaryFrame(1234, 5678))
1161
1162 writeCookiesThreads(blob)
1163
1164 print "writing Events"
1165 writeSchedEvents(blob)
1166
1167 print "writing Counters"
1168 writeCounters(blob, stats)
1169
1170 print "writing Visual Annotations"
1171 writeVisualAnnotations(blob, input_path, output_path)
1172
1173 doSessionXML(output_path)
1174 doCapturedXML(output_path, stats)
1175
1176 blob.close()
1177
1178
1179
1180#######################
1181# Main Routine
1182
1183input_path = args.input_path
1184output_path = args.output_path
1185
1186####
1187# Make sure input path exists
1188####
1189if not os.path.exists(input_path):
1190 print "ERROR: Input path %s does not exist!" % input_path
1191 sys.exit(1)
1192
1193####
1194# Parse gem5 configuration file to find # of CPUs and L2s
1195####
1196(num_cpus, num_l2) = parseConfig(input_path + "/config.ini")
1197
1198####
1199# Parse task file to find process/thread info
1200####
1201parseProcessInfo(input_path + "/system.tasks.txt")
1202
1203####
1204# Parse stat config file and register stats
1205####
1206stat_config_file = args.stat_config_file
1207stats = registerStats(stat_config_file)
1208
1209####
1210# Parse gem5 stats
1211####
1212# Check if both stats.txt and stats.txt.gz exist and warn if both exist
1213if os.path.exists(input_path + "/stats.txt") and \
1214 os.path.exists(input_path + "/stats.txt.gz"):
1215 print "WARNING: Both stats.txt.gz and stats.txt exist. \
1216 Using stats.txt.gz by default."
1217
1218gem5_stats_file = input_path + "/stats.txt.gz"
1219if not os.path.exists(gem5_stats_file):
1220 gem5_stats_file = input_path + "/stats.txt"
1221if not os.path.exists(gem5_stats_file):
1222 print "ERROR: stats.txt[.gz] file does not exist in %s!" % input_path
1223 sys.exit(1)
1224
1225readGem5Stats(stats, gem5_stats_file)
1226
1227####
1228# Create Streamline .apc project folder
1229####
1230createApcProject(input_path, output_path, stats)
1231
1232print "All done!"