1 #!/usr/bin/env python2.7
3 # Copyright (c) 2012, 2014 ARM Limited
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.
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.
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.
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.)
46 # m5stats2streamline.py <stat_config.ini> <gem5 run folder> <dest .apc folder>
48 # <stat_config.ini>: .ini file that describes which stats to be included
49 # in conversion. Sample .ini files can be found in
51 # NOTE: this is NOT the gem5 config.ini file.
53 # <gem5 run folder>: Path to gem5 run folder (must contain config.ini,
54 # stats.txt[.gz], and system.tasks.txt.)
56 # <dest .apc folder>: Destination .apc folder path
58 # APC project generation based on Gator v17 (DS-5 v5.17)
59 # Subsequent versions should be backward compatible
62 from ConfigParser
import ConfigParser
64 import xml
.etree
.ElementTree
as ET
65 import xml
.dom
.minidom
as minidom
71 parser
= argparse
.ArgumentParser(
72 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
74 Converts gem5 runs to ARM DS-5 Streamline .apc project file.
75 (NOTE: Requires gem5 runs to be run with ContextSwitchStatsDump
76 enabled and some patches applied to the target Linux kernel.)
78 APC project generation based on Gator v17 (DS-5 v5.17)
79 Subsequent versions should be backward compatible
82 parser
.add_argument("stat_config_file", metavar
="<stat_config.ini>",
83 help=".ini file that describes which stats to be included \
84 in conversion. Sample .ini files can be found in \
85 util/streamline. NOTE: this is NOT the gem5 config.ini \
88 parser
.add_argument("input_path", metavar
="<gem5 run folder>",
89 help="Path to gem5 run folder (must contain config.ini, \
90 stats.txt[.gz], and system.tasks.txt.)")
92 parser
.add_argument("output_path", metavar
="<dest .apc folder>",
93 help="Destination .apc folder path")
95 parser
.add_argument("--num-events", action
="store", type=int,
97 help="Maximum number of scheduling (context switch) \
98 events to be processed. Set to truncate early. \
101 parser
.add_argument("--gzipped-bmp-not-supported", action
="store_true",
102 help="Do not use gzipped .bmp files for visual annotations. \
103 This option is only required when using Streamline versions \
106 parser
.add_argument("--verbose", action
="store_true",
107 help="Enable verbose output")
109 args
= parser
.parse_args()
111 if not re
.match("(.*)\.apc", args
.output_path
):
112 print "ERROR: <dest .apc folder> should end with '.apc'!"
115 # gzipped BMP files for visual annotation is supported in Streamline 5.14.
116 # Setting this to True will significantly compress the .apc binary file that
117 # includes frame buffer snapshots.
118 gzipped_bmp_supported
= not args
.gzipped_bmp_not_supported
122 # Default max # of events. Increase this for longer runs.
123 num_events
= args
.num_events
128 # Parse gem5 config.ini file to determine some system configurations.
129 # Number of CPUs, L2s, etc.
130 def parseConfig(config_file
):
131 global num_cpus
, num_l2
133 print "\n==============================="
134 print "Parsing gem5 config.ini file..."
136 print "===============================\n"
137 config
= ConfigParser()
138 if not config
.read(config_file
):
139 print "ERROR: config file '", config_file
, "' not found"
142 if config
.has_section("system.cpu"):
146 while config
.has_section("system.cpu" + str(num_cpus
)):
149 if config
.has_section("system.l2_cache"):
153 while config
.has_section("system.l2_cache" + str(num_l2
)):
156 print "Num CPUs:", num_cpus
157 print "Num L2s:", num_l2
160 return (num_cpus
, num_l2
)
172 def __init__(self
, uid
, pid
, tgid
, task_name
, is_process
, tick
):
175 elif pid
== -1: # Kernel
181 self
.is_process
= is_process
182 self
.task_name
= task_name
184 self
.tick
= tick
# time this task first appeared
187 def __init__(self
, tick
, task
):
191 ############################################################
192 # Types used in APC Protocol
193 # - packed32, packed64
196 ############################################################
204 if (((x
== 0) and ((b
& 0x40) == 0)) or \
205 ((x
== -1) and ((b
& 0x40) != 0))):
212 # For historical reasons, 32/64-bit versions of functions are presevered
216 # variable length packed 4-byte signed value
217 def unsigned_packed32(x
):
219 if ((x
& 0xffffff80) == 0):
221 elif ((x
& 0xffffc000) == 0):
222 ret
.append((x |
0x80) & 0xff)
223 ret
.append((x
>> 7) & 0x7f)
224 elif ((x
& 0xffe00000) == 0):
225 ret
.append((x |
0x80) & 0xff)
226 ret
.append(((x
>> 7) |
0x80) & 0xff)
227 ret
.append((x
>> 14) & 0x7f)
228 elif ((x
& 0xf0000000) == 0):
229 ret
.append((x |
0x80) & 0xff)
230 ret
.append(((x
>> 7) |
0x80) & 0xff)
231 ret
.append(((x
>> 14) |
0x80) & 0xff)
232 ret
.append((x
>> 21) & 0x7f)
234 ret
.append((x |
0x80) & 0xff)
235 ret
.append(((x
>> 7) |
0x80) & 0xff)
236 ret
.append(((x
>> 14) |
0x80) & 0xff)
237 ret
.append(((x
>> 21) |
0x80) & 0xff)
238 ret
.append((x
>> 28) & 0x0f)
241 # variable length packed 8-byte signed value
242 def unsigned_packed64(x
):
244 if ((x
& 0xffffffffffffff80) == 0):
246 elif ((x
& 0xffffffffffffc000) == 0):
247 ret
.append((x |
0x80) & 0xff)
248 ret
.append((x
>> 7) & 0x7f)
249 elif ((x
& 0xffffffffffe00000) == 0):
250 ret
.append((x |
0x80) & 0xff)
251 ret
.append(((x
>> 7) |
0x80) & 0xff)
252 ret
.append((x
>> 14) & 0x7f)
253 elif ((x
& 0xfffffffff0000000) == 0):
254 ret
.append((x |
0x80) & 0xff)
255 ret
.append(((x
>> 7) |
0x80) & 0xff)
256 ret
.append(((x
>> 14) |
0x80) & 0xff)
257 ret
.append((x
>> 21) & 0x7f)
258 elif ((x
& 0xfffffff800000000) == 0):
259 ret
.append((x |
0x80) & 0xff)
260 ret
.append(((x
>> 7) |
0x80) & 0xff)
261 ret
.append(((x
>> 14) |
0x80) & 0xff)
262 ret
.append(((x
>> 21) |
0x80) & 0xff)
263 ret
.append((x
>> 28) & 0x7f)
264 elif ((x
& 0xfffffc0000000000) == 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) & 0x7f)
271 elif ((x
& 0xfffe000000000000) == 0):
272 ret
.append((x |
0x80) & 0xff)
273 ret
.append(((x
>> 7) |
0x80) & 0xff)
274 ret
.append(((x
>> 14) |
0x80) & 0xff)
275 ret
.append(((x
>> 21) |
0x80) & 0xff)
276 ret
.append(((x
>> 28) |
0x80) & 0xff)
277 ret
.append(((x
>> 35) |
0x80) & 0xff)
278 ret
.append((x
>> 42) & 0x7f)
279 elif ((x
& 0xff00000000000000) == 0):
280 ret
.append((x |
0x80) & 0xff)
281 ret
.append(((x
>> 7) |
0x80) & 0xff)
282 ret
.append(((x
>> 14) |
0x80) & 0xff)
283 ret
.append(((x
>> 21) |
0x80) & 0xff)
284 ret
.append(((x
>> 28) |
0x80) & 0xff)
285 ret
.append(((x
>> 35) |
0x80) & 0xff)
286 ret
.append(((x
>> 42) |
0x80) & 0xff)
287 ret
.append((x
>> 49) & 0x7f)
288 elif ((x
& 0x8000000000000000) == 0):
289 ret
.append((x |
0x80) & 0xff)
290 ret
.append(((x
>> 7) |
0x80) & 0xff)
291 ret
.append(((x
>> 14) |
0x80) & 0xff)
292 ret
.append(((x
>> 21) |
0x80) & 0xff)
293 ret
.append(((x
>> 28) |
0x80) & 0xff)
294 ret
.append(((x
>> 35) |
0x80) & 0xff)
295 ret
.append(((x
>> 42) |
0x80) & 0xff)
296 ret
.append(((x
>> 49) |
0x80) & 0xff)
297 ret
.append((x
>> 56) & 0x7f)
299 ret
.append((x |
0x80) & 0xff)
300 ret
.append(((x
>> 7) |
0x80) & 0xff)
301 ret
.append(((x
>> 14) |
0x80) & 0xff)
302 ret
.append(((x
>> 21) |
0x80) & 0xff)
303 ret
.append(((x
>> 28) |
0x80) & 0xff)
304 ret
.append(((x
>> 35) |
0x80) & 0xff)
305 ret
.append(((x
>> 42) |
0x80) & 0xff)
306 ret
.append(((x
>> 49) |
0x80) & 0xff)
307 ret
.append(((x
>> 56) |
0x80) & 0xff)
308 ret
.append((x
>> 63) & 0x7f)
311 # 4-byte signed little endian
315 ret
.append((x
>> 8) & 0xff)
316 ret
.append((x
>> 16) & 0xff)
317 ret
.append((x
>> 24) & 0xff)
320 # 2-byte signed little endian
324 ret
.append((x
>> 8) & 0xff)
327 # a packed32 length followed by the specified number of characters
330 ret
+= packed32(len(x
))
335 def utf8StringList(x
):
341 # packed64 time value in nanoseconds relative to the uptime from the
343 def timestampList(x
):
348 ############################################################
350 ############################################################
352 def writeBinary(outfile
, binary_list
):
353 for i
in binary_list
:
354 outfile
.write("%c" % i
)
356 ############################################################
357 # APC Protocol Frame Types
358 ############################################################
360 def addFrameHeader(frame_type
, body
, core
):
363 if frame_type
== "Summary":
365 elif frame_type
== "Backtrace":
367 elif frame_type
== "Name":
369 elif frame_type
== "Counter":
371 elif frame_type
== "Block Counter":
373 elif frame_type
== "Annotate":
375 elif frame_type
== "Sched Trace":
377 elif frame_type
== "GPU Trace":
379 elif frame_type
== "Idle":
382 print "ERROR: Unknown frame type:", frame_type
385 packed_code
= packed32(code
)
387 packed_core
= packed32(core
)
389 length
= int32(len(packed_code
) + len(packed_core
) + len(body
))
391 ret
= length
+ packed_code
+ packed_core
+ body
396 # - timestamp: packed64
398 def summaryFrame(timestamp
, uptime
):
399 frame_type
= "Summary"
400 newline_canary
= stringList("1\n2\r\n3\r4\n\r5")
401 monotonic_delta
= packed64(0)
402 end_of_attr
= stringList("")
403 body
= newline_canary
+ packed64(timestamp
) + packed64(uptime
)
404 body
+= monotonic_delta
+ end_of_attr
405 ret
= addFrameHeader(frame_type
, body
, 0)
409 # - not implemented yet
410 def backtraceFrame():
413 # Cookie name message
416 def cookieNameFrame(cookie
, name
):
418 packed_code
= packed32(1)
419 body
= packed_code
+ packed32(cookie
) + stringList(name
)
420 ret
= addFrameHeader(frame_type
, body
, 0)
423 # Thread name message
424 # - timestamp: timestamp
425 # - thread id: packed32
427 def threadNameFrame(timestamp
, thread_id
, name
):
429 packed_code
= packed32(2)
430 body
= packed_code
+ timestampList(timestamp
) + \
431 packed32(thread_id
) + stringList(name
)
432 ret
= addFrameHeader(frame_type
, body
, 0)
437 # - core_id: packed32
439 def coreNameFrame(name
, core_id
, cpuid
):
441 packed_code
= packed32(3)
442 body
= packed_code
+ packed32(core_id
) + packed32(cpuid
) + stringList(name
)
443 ret
= addFrameHeader(frame_type
, body
, 0)
446 # IRQ Cookie name message
450 def irqCookieNameFrame(cookie
, name
, irq
):
452 packed_code
= packed32(5)
453 body
= packed_code
+ packed32(cookie
) + stringList(name
) + packed32(irq
)
454 ret
= addFrameHeader(frame_type
, body
, 0)
457 # Counter frame message
458 # - timestamp: timestamp
462 def counterFrame(timestamp
, core
, key
, value
):
463 frame_type
= "Counter"
464 body
= timestampList(timestamp
) + packed32(core
) + packed32(key
) + \
466 ret
= addFrameHeader(frame_type
, body
, core
)
469 # Block Counter frame message
472 def blockCounterFrame(core
, key
, value
):
473 frame_type
= "Block Counter"
474 body
= packed32(key
) + packed64(value
)
475 ret
= addFrameHeader(frame_type
, body
, core
)
478 # Annotate frame messages
481 # - timestamp: timestamp
484 def annotateFrame(core
, tid
, timestamp
, size
, userspace_body
):
485 frame_type
= "Annotate"
486 body
= packed32(core
) + packed32(tid
) + timestampList(timestamp
) + \
487 packed32(size
) + userspace_body
488 ret
= addFrameHeader(frame_type
, body
, core
)
491 # Scheduler Trace frame messages
494 # - timestamp: timestamp
499 def schedSwitchFrame(core
, timestamp
, pid
, tid
, cookie
, state
):
500 frame_type
= "Sched Trace"
501 body
= packed32(1) + timestampList(timestamp
) + packed32(pid
) + \
502 packed32(tid
) + packed32(cookie
) + packed32(state
)
503 ret
= addFrameHeader(frame_type
, body
, core
)
508 # - timestamp: timestamp
510 def schedThreadExitFrame(core
, timestamp
, pid
, tid
, cookie
, state
):
511 frame_type
= "Sched Trace"
512 body
= packed32(2) + timestampList(timestamp
) + packed32(tid
)
513 ret
= addFrameHeader(frame_type
, body
, core
)
516 # GPU Trace frame messages
517 # - Not implemented yet
521 # Idle frame messages
524 # - timestamp: timestamp
526 def enterIdleFrame(timestamp
, core
):
528 body
= packed32(1) + timestampList(timestamp
) + packed32(core
)
529 ret
= addFrameHeader(frame_type
, body
, core
)
534 # - timestamp: timestamp
536 def exitIdleFrame(timestamp
, core
):
538 body
= packed32(2) + timestampList(timestamp
) + packed32(core
)
539 ret
= addFrameHeader(frame_type
, body
, core
)
543 ####################################################################
544 def parseProcessInfo(task_file
):
545 print "\n==============================="
546 print "Parsing Task file..."
548 print "===============================\n"
550 global start_tick
, end_tick
, num_cpus
551 global process_dict
, thread_dict
, process_list
552 global event_list
, unified_event_list
553 global idle_uid
, kernel_uid
556 unified_event_list
= []
557 for cpu
in range(num_cpus
):
558 event_list
.append([])
560 uid
= 1 # uid 0 is reserved for idle
562 # Dummy Tasks for frame buffers and system diagrams
563 process
= Task(uid
, 9999, 9999, "framebuffer", True, 0)
564 process_list
.append(process
)
566 thread
= Task(uid
, 9999, 9999, "framebuffer", False, 0)
567 process
.children
.append(thread
)
569 process
= Task(uid
, 9998, 9998, "System", True, 0)
570 process_list
.append(process
)
571 # if we don't find the real kernel, use this to keep things going
574 thread
= Task(uid
, 9998, 9998, "System", False, 0)
575 process
.children
.append(thread
)
578 ext
= os
.path
.splitext(task_file
)[1]
582 process_file
= gzip
.open(task_file
, 'rb')
584 process_file
= open(task_file
, 'rb')
586 print "ERROR opening task file:", task_file
587 print "Make sure context switch task dumping is enabled in gem5."
590 process_re
= re
.compile("tick=(\d+)\s+(\d+)\s+cpu_id=(\d+)\s+" +
591 "next_pid=([-\d]+)\s+next_tgid=([-\d]+)\s+next_task=(.*)")
593 task_name_failure_warned
= False
595 for line
in process_file
:
596 match
= re
.match(process_re
, line
)
598 tick
= int(match
.group(1))
601 cpu_id
= int(match
.group(3))
602 pid
= int(match
.group(4))
603 tgid
= int(match
.group(5))
604 task_name
= match
.group(6)
606 if not task_name_failure_warned
:
607 if task_name
== "FailureIn_curTaskName":
608 print "-------------------------------------------------"
609 print "WARNING: Task name not set correctly!"
610 print "Process/Thread info will not be displayed correctly"
611 print "Perhaps forgot to apply m5struct.patch to kernel?"
612 print "-------------------------------------------------"
613 task_name_failure_warned
= True
615 if not tgid
in process_dict
:
617 # new task is parent as well
619 print "new process", uid
, pid
, tgid
, task_name
621 # new process is the "idle" task
622 process
= Task(uid
, pid
, tgid
, "idle", True, tick
)
625 process
= Task(uid
, pid
, tgid
, task_name
, True, tick
)
628 process
= Task(uid
, tgid
, tgid
, "idle", True, tick
)
631 # parent process name not known yet
632 process
= Task(uid
, tgid
, tgid
, "_Unknown_", True, tick
)
633 if tgid
== -1: # kernel
636 process_dict
[tgid
] = process
637 process_list
.append(process
)
640 if process_dict
[tgid
].task_name
== "_Unknown_":
642 print "new process", \
643 process_dict
[tgid
].uid
, pid
, tgid
, task_name
644 process_dict
[tgid
].task_name
= task_name
645 if process_dict
[tgid
].task_name
!= task_name
and tgid
!= 0:
646 process_dict
[tgid
].task_name
= task_name
648 if not pid
in thread_dict
:
650 print "new thread", \
651 uid
, process_dict
[tgid
].uid
, pid
, tgid
, task_name
652 thread
= Task(uid
, pid
, tgid
, task_name
, False, tick
)
654 thread_dict
[pid
] = thread
655 process_dict
[tgid
].children
.append(thread
)
657 if thread_dict
[pid
].task_name
!= task_name
:
658 thread_dict
[pid
].task_name
= task_name
661 print tick
, uid
, cpu_id
, pid
, tgid
, task_name
663 task
= thread_dict
[pid
]
664 event
= Event(tick
, task
)
665 event_list
[cpu_id
].append(event
)
666 unified_event_list
.append(event
)
668 if len(unified_event_list
) == num_events
:
669 print "Truncating at", num_events
, "events!"
671 print "Found %d events." % len(unified_event_list
)
673 for process
in process_list
:
674 if process
.pid
> 9990: # fix up framebuffer ticks
675 process
.tick
= start_tick
676 print process
.uid
, process
.pid
, process
.tgid
, \
677 process
.task_name
, str(process
.tick
)
678 for thread
in process
.children
:
679 if thread
.pid
> 9990:
680 thread
.tick
= start_tick
681 print "\t", thread
.uid
, thread
.pid
, thread
.tgid
, \
682 thread
.task_name
, str(thread
.tick
)
686 print "Start tick:", start_tick
687 print "End tick: ", end_tick
693 def initOutput(output_path
):
694 if not os
.path
.exists(output_path
):
695 os
.mkdir(output_path
)
699 print "ticks_in_ns not set properly!"
702 return tick
/ ticks_in_ns
704 def writeXmlFile(xml
, filename
):
705 f
= open(filename
, "w")
706 txt
= ET
.tostring(xml
)
707 f
.write(minidom
.parseString(txt
).toprettyxml())
711 # StatsEntry that contains individual statistics
712 class StatsEntry(object):
713 def __init__(self
, name
, group
, group_index
, per_cpu
, key
):
715 # Full name of statistics
718 # Streamline group name that statistic will belong to
721 # Index of statistics within group (used to change colors within groups)
722 self
.group_index
= group_index
724 # Shorter name with "system" stripped off
725 # and symbols converted to alphanumerics
726 self
.short_name
= re
.sub("system\.", "", name
)
727 self
.short_name
= re
.sub(":", "_", name
)
729 # Regex for this stat (string version used to construct union regex)
730 self
.regex_string
= "^" + name
+ "\s+([\d\.]+)"
731 self
.regex
= re
.compile("^" + name
+ "\s+([\d\.e\-]+)\s+# (.*)$", re
.M
)
732 self
.description
= ""
734 # Whether this stat is use per CPU or not
735 self
.per_cpu
= per_cpu
737 # Key used in .apc protocol (as described in captured.xml)
740 # List of values of stat per timestamp
743 # Whether this stat has been found for the current timestamp
746 # Whether this stat has been found at least once
747 # (to suppress too many warnings)
748 self
.not_found_at_least_once
= False
750 # Field used to hold ElementTree subelement for this stat
751 self
.ET_element
= None
753 # Create per-CPU stat name and regex, etc.
755 self
.per_cpu_regex_string
= []
756 self
.per_cpu_regex
= []
757 self
.per_cpu_name
= []
758 self
.per_cpu_found
= []
759 for i
in range(num_cpus
):
761 per_cpu_name
= re
.sub("#", str(i
), self
.name
)
763 per_cpu_name
= re
.sub("#", "", self
.name
)
765 self
.per_cpu_name
.append(per_cpu_name
)
766 print "\t", per_cpu_name
768 self
.per_cpu_regex_string
.\
769 append("^" + per_cpu_name
+ "\s+[\d\.]+")
770 self
.per_cpu_regex
.append(re
.compile("^" + per_cpu_name
+ \
771 "\s+([\d\.e\-]+)\s+# (.*)$", re
.M
))
772 self
.values
.append([])
773 self
.per_cpu_found
.append(False)
775 def append_value(self
, val
, per_cpu_index
= None):
777 self
.values
[per_cpu_index
].append(str(val
))
779 self
.values
.append(str(val
))
781 # Global stats object that contains the list of stats entries
782 # and other utility functions
789 def register(self
, name
, group
, group_index
, per_cpu
):
790 print "registering stat:", name
, "group:", group
, group_index
791 self
.stats_list
.append(StatsEntry(name
, group
, group_index
, per_cpu
, \
795 # Union of all stats to accelerate parsing speed
796 def createStatsRegex(self
):
798 print "\nnum entries in stats_list", len(self
.stats_list
)
799 for entry
in self
.stats_list
:
801 for i
in range(num_cpus
):
802 regex_strings
.append(entry
.per_cpu_regex_string
[i
])
804 regex_strings
.append(entry
.regex_string
)
806 self
.regex
= re
.compile('|'.join(regex_strings
))
809 def registerStats(config_file
):
810 print "==============================="
811 print "Parsing stats config.ini file..."
813 print "==============================="
815 config
= ConfigParser()
816 if not config
.read(config_file
):
817 print "ERROR: config file '", config_file
, "' not found!"
820 print "\nRegistering Stats..."
824 per_cpu_stat_groups
= config
.options('PER_CPU_STATS')
825 for group
in per_cpu_stat_groups
:
827 per_cpu_stats_list
= config
.get('PER_CPU_STATS', group
).split('\n')
828 for item
in per_cpu_stats_list
:
830 stats
.register(item
, group
, i
, True)
833 per_l2_stat_groups
= config
.options('PER_L2_STATS')
834 for group
in per_l2_stat_groups
:
836 per_l2_stats_list
= config
.get('PER_L2_STATS', group
).split('\n')
837 for item
in per_l2_stats_list
:
839 for l2
in range(num_l2
):
841 name
= re
.sub("#", str(l2
), item
)
843 name
= re
.sub("#", "", item
)
844 stats
.register(name
, group
, i
, False)
847 other_stat_groups
= config
.options('OTHER_STATS')
848 for group
in other_stat_groups
:
850 other_stats_list
= config
.get('OTHER_STATS', group
).split('\n')
851 for item
in other_stats_list
:
853 stats
.register(item
, group
, i
, False)
856 stats
.createStatsRegex()
860 # Parse and read in gem5 stats file
861 # Streamline counters are organized per CPU
862 def readGem5Stats(stats
, gem5_stats_file
):
863 print "\n==============================="
864 print "Parsing gem5 stats file..."
865 print gem5_stats_file
866 print "===============================\n"
867 ext
= os
.path
.splitext(gem5_stats_file
)[1]
869 window_start_regex
= \
870 re
.compile("^---------- Begin Simulation Statistics ----------")
872 re
.compile("^---------- End Simulation Statistics ----------")
873 final_tick_regex
= re
.compile("^final_tick\s+(\d+)")
876 sim_freq_regex
= re
.compile("^sim_freq\s+(\d+)")
881 f
= gzip
.open(gem5_stats_file
, "r")
883 f
= open(gem5_stats_file
, "r")
885 print "ERROR opening stats file", gem5_stats_file
, "!"
888 stats_not_found_list
= stats
.stats_list
[:]
897 print "WARNING: IO error in stats file"
898 print "(gzip stream not closed properly?)...continuing for now"
903 # Find out how many gem5 ticks in 1ns
905 m
= sim_freq_regex
.match(line
)
907 sim_freq
= int(m
.group(1)) # ticks in 1 sec
908 ticks_in_ns
= int(sim_freq
/ 1e9
)
909 print "Simulation frequency found! 1 tick == %e sec\n" \
912 # Final tick in gem5 stats: current absolute timestamp
913 m
= final_tick_regex
.match(line
)
915 tick
= int(m
.group(1))
918 stats
.tick_list
.append(tick
)
921 if (window_end_regex
.match(line
) or error
):
924 for stat
in stats
.stats_list
:
926 for i
in range(num_cpus
):
927 if not stat
.per_cpu_found
[i
]:
928 if not stat
.not_found_at_least_once
:
929 print "WARNING: stat not found in window #", \
930 window_num
, ":", stat
.per_cpu_name
[i
]
931 print "suppressing further warnings for " + \
933 stat
.not_found_at_least_once
= True
934 stat
.values
[i
].append(str(0))
935 stat
.per_cpu_found
[i
] = False
938 if not stat
.not_found_at_least_once
:
939 print "WARNING: stat not found in window #", \
940 window_num
, ":", stat
.name
941 print "suppressing further warnings for this stat"
942 stat
.not_found_at_least_once
= True
943 stat
.values
.append(str(0))
945 stats_not_found_list
= stats
.stats_list
[:]
950 # Do a single regex of the union of all stats first for speed
951 if stats
.regex
.match(line
):
952 # Then loop through only the stats we haven't seen in this window
953 for stat
in stats_not_found_list
[:]:
955 for i
in range(num_cpus
):
956 m
= stat
.per_cpu_regex
[i
].match(line
)
958 if stat
.name
== "ipc":
959 value
= str(int(float(m
.group(1)) * 1000))
961 value
= str(int(float(m
.group(1))))
963 print stat
.per_cpu_name
[i
], value
964 stat
.values
[i
].append(value
)
965 stat
.per_cpu_found
[i
] = True
967 for j
in range(num_cpus
):
968 if not stat
.per_cpu_found
[j
]:
971 stats_not_found_list
.remove(stat
)
972 if stat
.description
== "":
973 stat
.description
= m
.group(2)
975 m
= stat
.regex
.match(line
)
977 value
= str(int(float(m
.group(1))))
979 print stat
.name
, value
980 stat
.values
.append(value
)
982 stats_not_found_list
.remove(stat
)
983 if stat
.description
== "":
984 stat
.description
= m
.group(2)
988 # Create session.xml file in .apc folder
989 def doSessionXML(output_path
):
990 session_file
= output_path
+ "/session.xml"
992 xml
= ET
.Element("session")
994 xml
.set("version", "1")
995 xml
.set("call_stack_unwinding", "no")
996 xml
.set("parse_debug_info", "no")
997 xml
.set("high_resolution", "yes")
998 xml
.set("buffer_mode", "streaming")
999 xml
.set("sample_rate", "low")
1001 # Setting duration to zero for now. Doesn't affect visualization.
1002 xml
.set("duration", "0")
1004 xml
.set("target_host", "")
1005 xml
.set("target_port", "8080")
1007 writeXmlFile(xml
, session_file
)
1010 # Create captured.xml file in .apc folder
1011 def doCapturedXML(output_path
, stats
):
1012 captured_file
= output_path
+ "/captured.xml"
1014 xml
= ET
.Element("captured")
1015 xml
.set("version", "1")
1016 xml
.set("protocol", "17")
1017 xml
.set("backtrace_processing", "none")
1019 target
= ET
.SubElement(xml
, "target")
1020 target
.set("name", "gem5")
1021 target
.set("sample_rate", "1000")
1022 target
.set("cores", str(num_cpus
))
1024 counters
= ET
.SubElement(xml
, "counters")
1025 for stat
in stats
.stats_list
:
1026 s
= ET
.SubElement(counters
, "counter")
1027 stat_name
= re
.sub("\.", "_", stat
.short_name
)
1028 stat_name
= re
.sub("#", "", stat_name
)
1029 s
.set("title", stat
.group
)
1030 s
.set("name", stat_name
)
1031 s
.set("color", "0x00000000")
1032 s
.set("key", "0x%08x" % stat
.key
)
1033 s
.set("type", stat_name
)
1034 s
.set("event", "0x00000000")
1036 s
.set("per_cpu", "yes")
1038 s
.set("per_cpu", "no")
1039 s
.set("display", "")
1041 s
.set("average_selection", "no")
1042 s
.set("description", stat
.description
)
1044 writeXmlFile(xml
, captured_file
)
1046 # Writes out Streamline cookies (unique IDs per process/thread)
1047 def writeCookiesThreads(blob
):
1049 for process
in process_list
:
1051 print "cookie", process
.task_name
, process
.uid
1052 writeBinary(blob
, cookieNameFrame(process
.uid
, process
.task_name
))
1054 # pid and tgid need to be positive values -- no longer true?
1055 for thread
in process
.children
:
1056 thread_list
.append(thread
)
1058 # Threads need to be sorted in timestamp order
1059 thread_list
.sort(key
= lambda x
: x
.tick
)
1060 for thread
in thread_list
:
1061 print "thread", thread
.task_name
, (ticksToNs(thread
.tick
)),\
1062 thread
.tgid
, thread
.pid
1063 writeBinary(blob
, threadNameFrame(ticksToNs(thread
.tick
),\
1064 thread
.pid
, thread
.task_name
))
1066 # Writes context switch info as Streamline scheduling events
1067 def writeSchedEvents(blob
):
1068 for cpu
in range(num_cpus
):
1069 for event
in event_list
[cpu
]:
1070 timestamp
= ticksToNs(event
.tick
)
1071 pid
= event
.task
.tgid
1072 tid
= event
.task
.pid
1073 if process_dict
.has_key(event
.task
.tgid
):
1074 cookie
= process_dict
[event
.task
.tgid
].uid
1079 # 0: waiting on other event besides I/O
1080 # 1: Contention/pre-emption
1082 # 3: Waiting on mutex
1083 # Hardcoding to 0 for now. Other states not implemented yet.
1087 print cpu
, timestamp
, pid
, tid
, cookie
1090 schedSwitchFrame(cpu
, timestamp
, pid
, tid
, cookie
, state
))
1092 # Writes selected gem5 statistics as Streamline counters
1093 def writeCounters(blob
, stats
):
1095 for tick
in stats
.tick_list
:
1098 timestamp_list
.append(ticksToNs(tick
))
1100 for stat
in stats
.stats_list
:
1102 stat_length
= len(stat
.values
[0])
1104 stat_length
= len(stat
.values
)
1106 for n
in range(len(timestamp_list
)):
1107 for stat
in stats
.stats_list
:
1109 for i
in range(num_cpus
):
1110 writeBinary(blob
, counterFrame(timestamp_list
[n
], i
, \
1111 stat
.key
, int(float(stat
.values
[i
][n
]))))
1113 writeBinary(blob
, counterFrame(timestamp_list
[n
], 0, \
1114 stat
.key
, int(float(stat
.values
[n
]))))
1116 # Streamline can display LCD frame buffer dumps (gzipped bmp)
1117 # This function converts the frame buffer dumps to the Streamline format
1118 def writeVisualAnnotations(blob
, input_path
, output_path
):
1119 frame_path
= input_path
+ "/frames_system.vncserver"
1120 if not os
.path
.exists(frame_path
):
1124 file_list
= os
.listdir(frame_path
)
1126 re_fb
= re
.compile("fb\.(\d+)\.(\d+)\.bmp.gz")
1128 # Use first non-negative pid to tag visual annotations
1130 for e
in unified_event_list
:
1136 for fn
in file_list
:
1140 tick
= int(m
.group(2))
1146 userspace_body
+= packed32(0x1C) # escape code
1147 userspace_body
+= packed32(0x04) # visual code
1149 text_annotation
= "image_" + str(ticksToNs(tick
)) + ".bmp.gz"
1150 userspace_body
+= int16(len(text_annotation
))
1151 userspace_body
+= utf8StringList(text_annotation
)
1153 if gzipped_bmp_supported
:
1154 # copy gzipped bmp directly
1155 bytes_read
= open(frame_path
+ "/" + fn
, "rb").read()
1157 # copy uncompressed bmp
1158 bytes_read
= gzip
.open(frame_path
+ "/" + fn
, "rb").read()
1160 userspace_body
+= int32(len(bytes_read
))
1161 userspace_body
+= bytes_read
1163 writeBinary(blob
, annotateFrame(0, annotate_pid
, ticksToNs(tick
), \
1164 len(userspace_body
), userspace_body
))
1166 print "\nfound", frame_count
, "frames for visual annotation.\n"
1169 def createApcProject(input_path
, output_path
, stats
):
1170 initOutput(output_path
)
1172 blob
= open(output_path
+ "/0000000000", "wb")
1174 # Summary frame takes current system time and system uptime.
1175 # Filling in with random values for now.
1176 writeBinary(blob
, summaryFrame(1234, 5678))
1178 writeCookiesThreads(blob
)
1180 print "writing Events"
1181 writeSchedEvents(blob
)
1183 print "writing Counters"
1184 writeCounters(blob
, stats
)
1186 print "writing Visual Annotations"
1187 writeVisualAnnotations(blob
, input_path
, output_path
)
1189 doSessionXML(output_path
)
1190 doCapturedXML(output_path
, stats
)
1196 #######################
1199 input_path
= args
.input_path
1200 output_path
= args
.output_path
1203 # Make sure input path exists
1205 if not os
.path
.exists(input_path
):
1206 print "ERROR: Input path %s does not exist!" % input_path
1210 # Parse gem5 configuration file to find # of CPUs and L2s
1212 (num_cpus
, num_l2
) = parseConfig(input_path
+ "/config.ini")
1215 # Parse task file to find process/thread info
1217 parseProcessInfo(input_path
+ "/system.tasks.txt")
1220 # Parse stat config file and register stats
1222 stat_config_file
= args
.stat_config_file
1223 stats
= registerStats(stat_config_file
)
1228 # Check if both stats.txt and stats.txt.gz exist and warn if both exist
1229 if os
.path
.exists(input_path
+ "/stats.txt") and \
1230 os
.path
.exists(input_path
+ "/stats.txt.gz"):
1231 print "WARNING: Both stats.txt.gz and stats.txt exist. \
1232 Using stats.txt.gz by default."
1234 gem5_stats_file
= input_path
+ "/stats.txt.gz"
1235 if not os
.path
.exists(gem5_stats_file
):
1236 gem5_stats_file
= input_path
+ "/stats.txt"
1237 if not os
.path
.exists(gem5_stats_file
):
1238 print "ERROR: stats.txt[.gz] file does not exist in %s!" % input_path
1241 readGem5Stats(stats
, gem5_stats_file
)
1244 # Create Streamline .apc project folder
1246 createApcProject(input_path
, output_path
, stats
)