<reponame>uraimo/swift-llbuild
#!/usr/bin/env python

# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors

import datetime
import inspect
import json
import optparse
import os
import re
import subprocess
import tempfile

###

def _write_message(kind, message):
    # Get the file/line where this message was generated.
    f = inspect.currentframe()
    # Step out of _write_message, and then out of wrapper.
    f = f.f_back.f_back
    file,line,_,_,_ = inspect.getframeinfo(f)
    location = '%s:%d' % (os.path.basename(file), line)

    print >>sys.stderr, '%s: %s: %s' % (location, kind, message)

note = lambda message: _write_message('note', message)
warning = lambda message: _write_message('warning', message)
error = lambda message: _write_message('error', message)
fatal = lambda message: (_write_message('fatal error', message), sys.exit(1))

###

class MultiTool(object):
    """
    This object defines a generic command line tool instance, which dynamically
    builds its commands from a module dictionary.

    Example usage::

      import multitool

      def action_foo(name, args):
          "the foo command"

          ... 

      tool = multitool.MultiTool(locals())
      if __name__ == '__main__':
        tool.main(sys.argv)

    Any function beginning with "action_" is considered a tool command. It's
    name is defined by the function name suffix. Underscores in the function
    name are converted to '-' in the command line syntax. Actions ending ith
    "-debug" are not listed in the help.
    """

    def __init__(self, locals, version=None):
        self.version = version

        # Create the list of commands.
        self.commands = dict((name[7:].replace('_','-'), f)
                             for name,f in locals.items()
                             if name.startswith('action_'))

    def usage(self, name):
        print >>sys.stderr, "Usage: %s <command> [options] ... arguments ..." %(
            os.path.basename(name),)
        print >>sys.stderr
        print >>sys.stderr, """\
Use ``%s <command> --help`` for more information on a specific command.\n""" % (
            os.path.basename(name),)
        print >>sys.stderr, "Available commands:"
        cmds_width = max(map(len, self.commands))
        for name,func in sorted(self.commands.items()):
            if name.endswith("-debug"):
                continue

            print >>sys.stderr, "  %-*s - %s" % (cmds_width, name, func.__doc__)
        sys.exit(1)

    def main(self, args=None):
        if args is None:
            args = sys.argv

        progname = os.path.basename(args.pop(0))

        # Parse immediate command line options.
        while args and args[0].startswith("-"):
            option = args.pop(0)
            if option in ("-h", "--help"):
                self.usage(progname)
            elif option in ("-v", "--version") and self.version is not None:
                print self.version
                return
            else:
                print >>sys.stderr, "error: invalid option %r\n" % (option,)
                self.usage(progname)

        if not args:
            self.usage(progname)

        cmd = args.pop(0)
        if cmd not in self.commands:
            print >>sys.stderr,"error: invalid command %r\n" % cmd
            self.usage(progname)

        self.commands[cmd]('%s %s' % (progname, cmd), args)

###
# Event descriptions

class PTreeTimeEvent(object):
    pass

class PTreeStartEvent(PTreeTimeEvent):
    def __init__(self, ts, target):
        self.timestamp = ts
        self.target = target

class PTreeStopEvent(PTreeTimeEvent):
    def __init__(self, ts):
        self.timestamp = ts

class PTreeProcCreateEvent(PTreeTimeEvent):
    def __init__(self, ts, pid, parent):
        self.timestamp = ts
        self.pid = pid
        self.parent = parent

class PTreeUserStartupEvent(PTreeTimeEvent):
    def __init__(self, ts, pid, parent, utime, stime):
        self.timestamp = ts
        self.pid = pid
        self.parent = parent
        self.user_time = utime
        self.system_time = stime

class PTreeUserExecEvent(PTreeTimeEvent):
    def __init__(self, ts, pid, parent, utime, stime, args):
        self.timestamp = ts
        self.pid = pid
        self.parent = parent
        self.user_time = utime
        self.system_time = stime
        self.args = args

class PTreeUserSpawnEvent(PTreeTimeEvent):
    def __init__(self, ts, pid, parent, utime, stime, args):
        self.timestamp = ts
        self.pid = pid
        self.parent = parent
        self.args = args

class PTreeUserExitEvent(PTreeTimeEvent):
    def __init__(self, ts, pid, parent, utime, stime):
        self.timestamp = ts
        self.pid = pid
        self.parent = parent
        self.user_time = utime
        self.system_time = stime

class PTreeProcExitEvent(PTreeTimeEvent):
    def __init__(self, ts, name, pid, parent):
        self.timestamp = ts
        self.name = name
        self.pid = pid
        self.parent = parent

###

_event_classes = {
    'START' : PTreeStartEvent,
    'proc:::create' : PTreeProcCreateEvent,
    'user startup' : PTreeUserStartupEvent,
    'user exec' : PTreeUserExecEvent,
    'user spawn' : PTreeUserSpawnEvent,
    'user exit' : PTreeUserExitEvent,
    'proc:::exit' : PTreeProcExitEvent,
    'END' : PTreeStopEvent }

###

class ProcessInfo(object):
    def __init__(self, pid):
        self.pid = pid
        self.children = []
        self.name = None
        self.args = None
        self.start_timestamp = None
        self.exit_timestamp = None
        self.user_start_timestamp = None
        self.user_exit_timestamp = None
        self.user_time = None
        self.system_time = None
        self.startup_user_time = None
        self.startup_system_time = None
        self.parent = None

    @property
    def total_wall_time(self):
        return (self.exit_timestamp - self.start_timestamp) / 1000000.
    @property
    def total_user_time(self):
        if self.user_time is None:
            return 0
        return (self.user_time[0] + self.user_time[1] / 1000000.)
    @property
    def total_system_time(self):
        if self.system_time is None:
            return 0
        return (self.system_time[0] + self.system_time[1] / 1000000.)
    @property
    def startlag(self):
        if self.user_start_timestamp is None:
            return 0
        return (self.user_start_timestamp - self.start_timestamp) / 1000000.
    @property
    def exitlag(self):
        if self.user_exit_timestamp is None:
            return 0
        return (self.exit_timestamp - self.user_exit_timestamp) / 1000000.

    @property
    def start_timestamp_in_seconds(self):
        return self.start_timestamp / 1000000.
    @property
    def exit_timestamp_in_seconds(self):
        return self.exit_timestamp / 1000000.

    def todata(self):
        return { 'pid' : self.pid,
                 'name' : self.name,
                 'children' : [c.todata()
                               for c in self.children],
                 'start_timestamp' : self.start_timestamp,
                 'exit_timestamp' : self.exit_timestamp,
                 'user_start_timestamp' : self.user_start_timestamp,
                 'user_exit_timestamp' : self.user_exit_timestamp,
                 'user_time' : self.user_time,
                 'system_time' : self.system_time }

###


def _parse_event_log(path):
    # Load the file as JSON.
    with open(path) as f:
        data = json.load(f)
        command = data['command']
        events_data = data['events']

    # Convert the events data into events.
    def convert_event(data):
        evt_name = data.pop('evt', '').strip()
        evt_class = _event_classes.get(evt_name)
        if evt_class is None:
            raise ValueError("unknown event: %r" % ((evt_name, data),))

        try:
            return evt_class(**data)
        except TypeError:
            raise ValueError("invalid event: %r (unable to instantiate %r)" % (
                    (evt_name, data), evt_class))
    events = [convert_event(d)
              for d in events_data]

    # Order all of the events by timestamp.
    events.sort(key = lambda event: event.timestamp)

    # Check that the file is complete.
    if not events:
        raise ValueError("data file has no events: %r" % (path,))
    if not isinstance(events[0], PTreeStartEvent):
        raise ValueError("data file %r starts with unexpected event %r" % (
              path, events[0]))
    if not isinstance(events[-1], PTreeStopEvent):
        raise ValueError("data file %r ends with unexpected event %r" % (
              path, events[-1]))

    # Get the target PID.
    target = events[0].target
    target_start_timestamp = events[0].timestamp

    # Drop the start and stop events.
    events = events[1:-1]

    # Build the process tree list.
    def get_record_for_pid(pid):
        # Lookup the PID.
        record = active_processes.get(pid)
        if record is not None:
            return record

        # The PID was unknown, create a record for it.
        active_processes[pid] = record = ProcessInfo(pid)

        # Add to the list of all records.
        all_records.append(record)
        roots.add(record)

        return record

    all_records = []
    roots = set()
    target_root = None
    active_processes = {}
    active_spawn_args = {}
    for event in events:
        # Handle the exit of the target process specially.
        if event.pid == target:
            if not isinstance(event, PTreeProcExitEvent):
                raise RuntimeError("unexpected event for target %r" % (
                        event,))

            assert isinstance(event, PTreeProcExitEvent)
            target_root = get_record_for_pid(event.pid)
            roots.remove(target_root)
            target_root.start_timestamp = target_start_timestamp
            target_root.exit_timestamp = event.timestamp
            target_root.name = event.name
            del active_processes[event.pid]
            continue

        # First, lookup or create an entry for the parent process.
        parent_process = get_record_for_pid(event.parent)

        # Get the record for this PID.
        child_process = get_record_for_pid(event.pid)

        # If this is a create event, store the timestamp and add it to the
        # parent.
        if isinstance(event, PTreeProcCreateEvent):
            if child_process.parent:
                raise RuntimeError("multiple create events for %r" % (
                        event,))
            child_process.parent = parent_process
            child_process.start_timestamp = event.timestamp
            parent_process.children.append(child_process)
            roots.remove(child_process)

            child_process.args = active_spawn_args.pop(event.parent, None)
            if child_process.args is not None:
                child_process.name = os.path.basename(child_process.args[0])
            continue

        # Otherwise, handle the various "user" events.
        if isinstance(event, PTreeUserStartupEvent):
            child_process.user_start_timestamp = event.timestamp
            child_process.startup_user_time = event.user_time
            child_process.startup_system_time = event.system_time
            continue
        if isinstance(event, PTreeUserExecEvent):
            # If the child process already has arguments defined, then the
            # process is re-exec'ing itself. Create a new process entry.
            if child_process.args is not None:
                child_process.exit_timestamp = \
                    child_process.user_exit_timestamp = event.timestamp
                child_process.user_time = event.user_time
                child_process.system_time = event.system_time
                # FIXME: Get utime and stime available here, and copy.
                del active_processes[event.pid]

                child_process = get_record_for_pid(event.pid)
                child_process.parent = parent_process
                child_process.start_timestamp = event.timestamp
                child_process.args = event.args
                child_process.name = os.path.basename(child_process.args[0])
                parent_process.children.append(child_process)
                roots.remove(child_process)
            else:
                child_process.args = event.args
                child_process.name = os.path.basename(child_process.args[0])
            continue
        if isinstance(event, PTreeUserSpawnEvent):
            active_spawn_args[event.pid] = event.args
            continue
        if isinstance(event, PTreeUserExitEvent):
            child_process.user_exit_timestamp = event.timestamp
            child_process.user_time = event.user_time
            child_process.system_time = event.system_time
            continue

        # Otherwise, this is an exit event, store the event information and
        # update the parent and active process list.
        if child_process.parent is None:
            warning('found stray exit: %r' % (
                    (event.timestamp, event.pid, event.name),))
            child_process.parent = parent_process
            parent_process.children.append(child_process)
            roots.remove(child_process)
        assert isinstance(event, PTreeProcExitEvent)
        child_process.exit_timestamp = event.timestamp
        child_process.name = event.name
        del active_processes[event.pid]

    return target_root, roots, active_processes

def action_analyze(name, args):
    """analyze a ptreetime data file"""
    parser = optparse.OptionParser("""\
usage: %%prog %(name)s [options] <path>

Execute the given command and track the execution time of all the processes
which are created during the execution.""" % locals())
    parser.add_option("-f", "--focus", dest="focus",
                      help="focus on the given process name",
                      action="store", default=None)
    parser.add_option("--show-tree", dest="show_tree",
                      help="Print the entire call tree",
                      action="store_true")
    parser.add_option("", "--gen-report", dest="lnt_report_path",
                      help="generate an LNT report",
                      action="store", default=None)
    parser.add_option("", "--report-machine", dest="report_machine",
                      help="machine name to include in the LNT report",
                      action="store", default=None)
    parser.add_option("", "--report-run-order", dest="report_run_order",
                      help="run order to include in the LNT report",
                      action="store", default=None)
    parser.add_option("", "--report-sub-arg", dest="report_sub_args",
                      help="add an argument replacement pattern",
                      metavar="PATTERN=REPLACEMENT",
                      action="append", default=[])

    (opts, args) = parser.parse_args(args)

    if len(args) < 1:
       parser.error("invalid number of arguments")
    if opts.lnt_report_path:
        if opts.report_machine is None:
            parser.error("--report-machine is required with --gen-report")
        if opts.report_run_order is None:
            parser.error("--report-run-order is required with --gen-report")
    else:
        if opts.report_machine is not None:
            parser.error("--report-machine is unused without --gen-report")
        if opts.report_run_order is not None:
            parser.error("--report-run-order is unused without --gen-report")
        if opts.report_sub_args:
            parser.error("--report-sub-arg is unused without --gen-report")

    paths = args

    def find_focused_roots(node):
        # If this is a focused node, add it to the list.
        if opts.focus is None or node.name == opts.focus:
            focused_roots.append(node)
            return

        # Otherwise, recurse on each child.
        for child in node.children:
            find_focused_roots(child)
    focused_roots = []
    for path in paths:
        # Load each file.
        target_root,roots,active_processes = _parse_event_log(path)
        find_focused_roots(target_root)

    # For each root, show the flattened performance.
    def print_node(node, depth):
        # If this node isn't complete, ignore it.
        if node.start_timestamp is None:
            warning("ignoring incomplete node %r (didn't see create)" % (
                    (node.pid, node.name),))
            return
        if node.exit_timestamp is None:
            warning("ignoring incomplete node %r (didn't see exit)" % (
                    (node.pid,),))
            return

        indent = '  '*depth
        wall = node.total_wall_time
        user = node.total_user_time
        sys = node.total_system_time
        startlag = node.startlag
        exitlag = node.exitlag
        if opts.show_tree:
            print ('%s%s -- wall: %.4fs, user: %.4fs, sys: %.4fs, '
                   'startlag: %.4fs, exitlag: %.4fs') % (
                indent, node.name, wall, user, sys, startlag, exitlag)

        key = '%s @ %d' % (node.name, depth)
        aggregate_info = aggregate.get(key)
        if aggregate_info is None:
            aggregate[key] = aggregate_info = {
                'name' : key,
                'wall' : 0.0,
                'user' : 0.0,
                'sys' : 0.0,
                'startlag' : 0.0,
                'exitlag' : 0.0,
                'count' : 0 }
        aggregate_info['wall'] += wall
        aggregate_info['user'] += user
        aggregate_info['sys'] += sys
        aggregate_info['startlag'] += startlag
        aggregate_info['exitlag'] += exitlag
        aggregate_info['count'] += 1

        for c in node.children:
            print_node(c, depth+1)

    if not focused_roots:
        parser.error("no roots found!")

    aggregate = {}
    for node in focused_roots:
       print_node(node, depth=0)
       if node is not focused_roots[-1]:
          print

    print
    print 'Aggregate Times'
    print '---------------'
    items = aggregate.values()
    items.sort(key = lambda i: i['wall'],
               reverse = True)
    name_length = max(len(item['name'])
                      for item in items)
    for item in items:
       print ('%-*s -- wall: %8.4fs, user: %8.4fs, sys: %8.4fs, '
              'startlag: %8.4fs, exitlag: %.4fs, count: %d') % (
          name_length, item['name'], item['wall'], item['user'], item['sys'],
          item['startlag'], item['exitlag'], item['count'])

    # Write out an LNT test report with the data, if requested.
    if opts.lnt_report_path:
        import lnt.testing

        # Build the regular expression substitution arguments.
        substitution_args = []
        for value in opts.report_sub_args:
            if '=' in value:
                pattern,replacement = value.split('=',1)
            else:
                pattern,replacement = value,''
            substitution_args.append((re.compile(pattern), replacement))

        # First, organize all the nodes by deriving a test name key for them.
        def group_node_and_children(node, depth = 0):
            # Ignore incomplete nodes.
            if node.start_timestamp is None or node.exit_timestamp is None:
                return

            args = node.args
            if args is not None:
                # If this is a clang -cc1 invocation, do some special stuff to
                # normalize.
                if node.name == "clang" and args and args[1] == '-cc1':
                    triple_index = args.index('-triple')
                    main_file_name_index = args.index('-main-file-name')
                    args = (
                        args[:2] +
                        args[triple_index:triple_index+2] + \
                        args[main_file_name_index:main_file_name_index+2])


                # Apply substitutions to each argument.
                def substitute(arg):
                    for pattern_re,replacement in substitution_args:
                        arg = pattern_re.sub(replacement, arg)
                    return arg
                args = [substitute(arg)
                        for arg in args]

                # Eliminate empty arguments.
                args = filter(None, args)

            key = '%s(depth=%d,args=%r)' % (node.name, depth, args)
            items = nodes_by_key.get(key)
            if items is None:
                nodes_by_key[key] = items = []
            items.append(node)

            for child in node.children:
                group_node_and_children(child, depth+1)
        nodes_by_key = {}
        for node in focused_roots:
            group_node_and_children(node)

        tag = "compile"
        run_info = { "tag" : tag,
                     "run_order" : opts.report_run_order }
        machine = lnt.testing.Machine(opts.report_machine, {})
        run = lnt.testing.Run(datetime.datetime.fromtimestamp(
                target_root.start_timestamp_in_seconds).\
                                  strftime('%Y-%m-%d %H:%M:%S'),
                              datetime.datetime.fromtimestamp(
                target_root.exit_timestamp_in_seconds).\
                                  strftime('%Y-%m-%d %H:%M:%S'),
                              info=run_info)

        # We report in a scheme compatible with the compile time suite.
        testsamples = []
        for key,nodes in nodes_by_key.items():
            for subkey,accessor in (('wall', 'total_wall_time'),
                                    ('user', 'total_user_time'),
                                    ('sys', 'total_system_time')):
                values = [getattr(node, accessor)
                          for node in nodes]
                name = '%s.%s.%s' % (tag, key, subkey)
                testsamples.append(lnt.testing.TestSamples(
                        name, values))

        report = lnt.testing.Report(machine, run, testsamples)
        with open(opts.lnt_report_path, "w") as f:
            print >>f, report.render()

###

def action_profile(name, args):
    """time a process tree"""

    parser = optparse.OptionParser("""\
usage: %%prog %(name)s [options] ... test command args ...

Use dtrace and dyld interpositioning to collect precise time information on an
entire process tree.""" % locals())
    parser.add_option("-v", "--verbose", dest="verbose",
                      help="output more test information",
                      action="store_true", default=False)
    parser.add_option("-o", "--output", dest="output_path",
                      help="path for data output",
                      action="store", default=None)

    parser.disable_interspersed_args()

    (opts, args) = parser.parse_args(args)
    if opts.output_path is None:
        parser.error("--output argument is required")

    command_arguments = args

    # Check that the ptreetime interpose library has been built.
    basepath = os.path.dirname(os.path.realpath(__file__))
    ptreetime_dtrace_path = os.path.join(
        basepath, 'libptreetime', 'ptreetime.dtrace')
    interpose_path = os.path.join(
        basepath, 'libptreetime', 'libptreetime_interpose.dylib')
    interpose_enabled = os.path.exists(interpose_path)
    if not interpose_enabled:
        warning(("interpose library not built, please 'make' in directory: %r"
                 "(some information will not be gathered)") % (
                     os.path.dirname(interpose_path)))

    # First, establish temporary files to contain the dtrace log and the
    # interposed event log.
    ptreetime_dtrace_log = tempfile.NamedTemporaryFile(
        suffix='-ptreetime-dtrace.log')
    ptreetime_interpose_log = tempfile.NamedTemporaryFile(
        suffix='-ptreetime-interpose.log')

    # Write the environment overrides and execution command into a script.
    with tempfile.NamedTemporaryFile(suffix='-ptreetime.sh') as \
            ptreetime_script:
        os.chmod(ptreetime_script.name, 0755)
        print >>ptreetime_script, '#!/bin/sh'
        if interpose_enabled:
            print >>ptreetime_script, 'export PTREETIME_LOG_PATH="%s"' % (
                ptreetime_interpose_log.name)
            print >>ptreetime_script, 'export DYLD_INSERT_LIBRARIES="%s"' % (
                interpose_path)
        # FIXME: Quote arguments better.
        print >>ptreetime_script, ' '.join("'%s'" % arg
                                           for arg in command_arguments)
        ptreetime_script.flush()

        # Form the dtrace command to execute. Notably we:
        #   1. Force dtrace to set up probes at exec time.
        #   2. Extend the buffer size.
        #   3. Send dtrace logging output to the (shared) log file.
        #   4. Execute the script we created.
        dtrace_args = ['sudo', '/usr/sbin/dtrace',
                       '-xevaltime=exec', '-xbufsize=50m',
                       '-s', ptreetime_dtrace_path,
                       '-o', ptreetime_dtrace_log.name,
                       '-c', ptreetime_script.name]

        # Execute the dtrace command.
        note("executing dtrace (with sudo)...")
        if opts.verbose:
            note("executing: %r" % (' '.join(dtrace_args),))
        p = subprocess.Popen(dtrace_args)
        res = p.wait()
        if res != 0:
            warning("dtrace exited with a error (%d)" % (res,))

    # Read in the log file.
    #
    # We simply load all the events lines as text and order them by timestamp,
    # but otherwise we don't process them at all.
    prefix = "PTREETIME "
    prefix_len = len(prefix)
    note("reading dtrace event log...")
    with open(ptreetime_dtrace_log.name) as f:
        event_data_lines = [ln[prefix_len:-1]
                            for ln in f
                            if ln.startswith(prefix)]
    if interpose_enabled:
        note("reading interposed event log...")
        with open(ptreetime_interpose_log.name) as f:
            event_data_lines.extend([ln[prefix_len:-1]
                                     for ln in f
                                     if ln.startswith(prefix)])
    note("loaded %d events" % (len(event_data_lines),))

    # Delete the temporary log files.
    del ptreetime_interpose_log
    del ptreetime_dtrace_log

    # Sort the event lines. Note that this is purely to make for more readable
    # output files, the ptreetime parser will also order.
    def extract_timestamp(ln):
        # Extract the timestamp, if we can.
        timestamp = ln.split(':', 1)[1].split(',', 1)[0]
        if timestamp.isdigit():
            timestamp = int(timestamp)
        return timestamp
    event_data_lines.sort(key = extract_timestamp)

    # Write out the JSON file. We don't care to guarantee this file is well
    # formed (the consumer of the data files can handle that).
    note("writing output file: %r" % (opts.output_path,))
    with open(opts.output_path, "w") as f:
        print >>f, '{'
        print >>f, '  "command" : %s,' % (
            json.dumps(command_arguments),)
        print >>f, '  "events"  : ['
        last_line = event_data_lines[-1]
        for ln in event_data_lines:
            print >>f, '    %s%c' % (ln, ',]'[ln is last_line])
        print >>f, '}'

###

tool = MultiTool(locals(), "ptreetime")

if __name__ == '__main__':
    import sys
    tool.main(sys.argv)

#!/usr/bin/env python

# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors

import datetime
import inspect
import json
import optparse
import os
import re
import subprocess
import tempfile

###

def _write_message(kind, message):
    # Get the file/line where this message was generated.
    f = inspect.currentframe()
    # Step out of _write_message, and then out of wrapper.
    f = f.f_back.f_back
    file,line,_,_,_ = inspect.getframeinfo(f)
    location = '%s:%d' % (os.path.basename(file), line)

    print >>sys.stderr, '%s: %s: %s' % (location, kind, message)

note = lambda message: _write_message('note', message)
warning = lambda message: _write_message('warning', message)
error = lambda message: _write_message('error', message)
fatal = lambda message: (_write_message('fatal error', message), sys.exit(1))

###

class MultiTool(object):
    """
    This object defines a generic command line tool instance, which dynamically
    builds its commands from a module dictionary.

    Example usage::

      import multitool

      def action_foo(name, args):
          "the foo command"

          ... 

      tool = multitool.MultiTool(locals())
      if __name__ == '__main__':
        tool.main(sys.argv)

    Any function beginning with "action_" is considered a tool command. It's
    name is defined by the function name suffix. Underscores in the function
    name are converted to '-' in the command line syntax. Actions ending ith
    "-debug" are not listed in the help.
    """

    def __init__(self, locals, version=None):
        self.version = version

        # Create the list of commands.
        self.commands = dict((name[7:].replace('_','-'), f)
                             for name,f in locals.items()
                             if name.startswith('action_'))

    def usage(self, name):
        print >>sys.stderr, "Usage: %s <command> [options] ... arguments ..." %(
            os.path.basename(name),)
        print >>sys.stderr
        print >>sys.stderr, """\
Use ``%s <command> --help`` for more information on a specific command.\n""" % (
            os.path.basename(name),)
        print >>sys.stderr, "Available commands:"
        cmds_width = max(map(len, self.commands))
        for name,func in sorted(self.commands.items()):
            if name.endswith("-debug"):
                continue

            print >>sys.stderr, "  %-*s - %s" % (cmds_width, name, func.__doc__)
        sys.exit(1)

    def main(self, args=None):
        if args is None:
            args = sys.argv

        progname = os.path.basename(args.pop(0))

        # Parse immediate command line options.
        while args and args[0].startswith("-"):
            option = args.pop(0)
            if option in ("-h", "--help"):
                self.usage(progname)
            elif option in ("-v", "--version") and self.version is not None:
                print self.version
                return
            else:
                print >>sys.stderr, "error: invalid option %r\n" % (option,)
                self.usage(progname)

        if not args:
            self.usage(progname)

        cmd = args.pop(0)
        if cmd not in self.commands:
            print >>sys.stderr,"error: invalid command %r\n" % cmd
            self.usage(progname)

        self.commands[cmd]('%s %s' % (progname, cmd), args)

###
# Event descriptions

class PTreeTimeEvent(object):
    pass

class PTreeStartEvent(PTreeTimeEvent):
    def __init__(self, ts, target):
        self.timestamp = ts
        self.target = target

class PTreeStopEvent(PTreeTimeEvent):
    def __init__(self, ts):
        self.timestamp = ts

class PTreeProcCreateEvent(PTreeTimeEvent):
    def __init__(self, ts, pid, parent):
        self.timestamp = ts
        self.pid = pid
        self.parent = parent

class PTreeUserStartupEvent(PTreeTimeEvent):
    def __init__(self, ts, pid, parent, utime, stime):
        self.timestamp = ts
        self.pid = pid
        self.parent = parent
        self.user_time = utime
        self.system_time = stime

class PTreeUserExecEvent(PTreeTimeEvent):
    def __init__(self, ts, pid, parent, utime, stime, args):
        self.timestamp = ts
        self.pid = pid
        self.parent = parent
        self.user_time = utime
        self.system_time = stime
        self.args = args

class PTreeUserSpawnEvent(PTreeTimeEvent):
    def __init__(self, ts, pid, parent, utime, stime, args):
        self.timestamp = ts
        self.pid = pid
        self.parent = parent
        self.args = args

class PTreeUserExitEvent(PTreeTimeEvent):
    def __init__(self, ts, pid, parent, utime, stime):
        self.timestamp = ts
        self.pid = pid
        self.parent = parent
        self.user_time = utime
        self.system_time = stime

class PTreeProcExitEvent(PTreeTimeEvent):
    def __init__(self, ts, name, pid, parent):
        self.timestamp = ts
        self.name = name
        self.pid = pid
        self.parent = parent

###

_event_classes = {
    'START' : PTreeStartEvent,
    'proc:::create' : PTreeProcCreateEvent,
    'user startup' : PTreeUserStartupEvent,
    'user exec' : PTreeUserExecEvent,
    'user spawn' : PTreeUserSpawnEvent,
    'user exit' : PTreeUserExitEvent,
    'proc:::exit' : PTreeProcExitEvent,
    'END' : PTreeStopEvent }

###

class ProcessInfo(object):
    def __init__(self, pid):
        self.pid = pid
        self.children = []
        self.name = None
        self.args = None
        self.start_timestamp = None
        self.exit_timestamp = None
        self.user_start_timestamp = None
        self.user_exit_timestamp = None
        self.user_time = None
        self.system_time = None
        self.startup_user_time = None
        self.startup_system_time = None
        self.parent = None

    @property
    def total_wall_time(self):
        return (self.exit_timestamp - self.start_timestamp) / 1000000.
    @property
    def total_user_time(self):
        if self.user_time is None:
            return 0
        return (self.user_time[0] + self.user_time[1] / 1000000.)
    @property
    def total_system_time(self):
        if self.system_time is None:
            return 0
        return (self.system_time[0] + self.system_time[1] / 1000000.)
    @property
    def startlag(self):
        if self.user_start_timestamp is None:
            return 0
        return (self.user_start_timestamp - self.start_timestamp) / 1000000.
    @property
    def exitlag(self):
        if self.user_exit_timestamp is None:
            return 0
        return (self.exit_timestamp - self.user_exit_timestamp) / 1000000.

    @property
    def start_timestamp_in_seconds(self):
        return self.start_timestamp / 1000000.
    @property
    def exit_timestamp_in_seconds(self):
        return self.exit_timestamp / 1000000.

    def todata(self):
        return { 'pid' : self.pid,
                 'name' : self.name,
                 'children' : [c.todata()
                               for c in self.children],
                 'start_timestamp' : self.start_timestamp,
                 'exit_timestamp' : self.exit_timestamp,
                 'user_start_timestamp' : self.user_start_timestamp,
                 'user_exit_timestamp' : self.user_exit_timestamp,
                 'user_time' : self.user_time,
                 'system_time' : self.system_time }

###


def _parse_event_log(path):
    # Load the file as JSON.
    with open(path) as f:
        data = json.load(f)
        command = data['command']
        events_data = data['events']

    # Convert the events data into events.
    def convert_event(data):
        evt_name = data.pop('evt', '').strip()
        evt_class = _event_classes.get(evt_name)
        if evt_class is None:
            raise ValueError("unknown event: %r" % ((evt_name, data),))

        try:
            return evt_class(**data)
        except TypeError:
            raise ValueError("invalid event: %r (unable to instantiate %r)" % (
                    (evt_name, data), evt_class))
    events = [convert_event(d)
              for d in events_data]

    # Order all of the events by timestamp.
    events.sort(key = lambda event: event.timestamp)

    # Check that the file is complete.
    if not events:
        raise ValueError("data file has no events: %r" % (path,))
    if not isinstance(events[0], PTreeStartEvent):
        raise ValueError("data file %r starts with unexpected event %r" % (
              path, events[0]))
    if not isinstance(events[-1], PTreeStopEvent):
        raise ValueError("data file %r ends with unexpected event %r" % (
              path, events[-1]))

    # Get the target PID.
    target = events[0].target
    target_start_timestamp = events[0].timestamp

    # Drop the start and stop events.
    events = events[1:-1]

    # Build the process tree list.
    def get_record_for_pid(pid):
        # Lookup the PID.
        record = active_processes.get(pid)
        if record is not None:
            return record

        # The PID was unknown, create a record for it.
        active_processes[pid] = record = ProcessInfo(pid)

        # Add to the list of all records.
        all_records.append(record)
        roots.add(record)

        return record

    all_records = []
    roots = set()
    target_root = None
    active_processes = {}
    active_spawn_args = {}
    for event in events:
        # Handle the exit of the target process specially.
        if event.pid == target:
            if not isinstance(event, PTreeProcExitEvent):
                raise RuntimeError("unexpected event for target %r" % (
                        event,))

            assert isinstance(event, PTreeProcExitEvent)
            target_root = get_record_for_pid(event.pid)
            roots.remove(target_root)
            target_root.start_timestamp = target_start_timestamp
            target_root.exit_timestamp = event.timestamp
            target_root.name = event.name
            del active_processes[event.pid]
            continue

        # First, lookup or create an entry for the parent process.
        parent_process = get_record_for_pid(event.parent)

        # Get the record for this PID.
        child_process = get_record_for_pid(event.pid)

        # If this is a create event, store the timestamp and add it to the
        # parent.
        if isinstance(event, PTreeProcCreateEvent):
            if child_process.parent:
                raise RuntimeError("multiple create events for %r" % (
                        event,))
            child_process.parent = parent_process
            child_process.start_timestamp = event.timestamp
            parent_process.children.append(child_process)
            roots.remove(child_process)

            child_process.args = active_spawn_args.pop(event.parent, None)
            if child_process.args is not None:
                child_process.name = os.path.basename(child_process.args[0])
            continue

        # Otherwise, handle the various "user" events.
        if isinstance(event, PTreeUserStartupEvent):
            child_process.user_start_timestamp = event.timestamp
            child_process.startup_user_time = event.user_time
            child_process.startup_system_time = event.system_time
            continue
        if isinstance(event, PTreeUserExecEvent):
            # If the child process already has arguments defined, then the
            # process is re-exec'ing itself. Create a new process entry.
            if child_process.args is not None:
                child_process.exit_timestamp = \
                    child_process.user_exit_timestamp = event.timestamp
                child_process.user_time = event.user_time
                child_process.system_time = event.system_time
                # FIXME: Get utime and stime available here, and copy.
                del active_processes[event.pid]

                child_process = get_record_for_pid(event.pid)
                child_process.parent = parent_process
                child_process.start_timestamp = event.timestamp
                child_process.args = event.args
                child_process.name = os.path.basename(child_process.args[0])
                parent_process.children.append(child_process)
                roots.remove(child_process)
            else:
                child_process.args = event.args
                child_process.name = os.path.basename(child_process.args[0])
            continue
        if isinstance(event, PTreeUserSpawnEvent):
            active_spawn_args[event.pid] = event.args
            continue
        if isinstance(event, PTreeUserExitEvent):
            child_process.user_exit_timestamp = event.timestamp
            child_process.user_time = event.user_time
            child_process.system_time = event.system_time
            continue

        # Otherwise, this is an exit event, store the event information and
        # update the parent and active process list.
        if child_process.parent is None:
            warning('found stray exit: %r' % (
                    (event.timestamp, event.pid, event.name),))
            child_process.parent = parent_process
            parent_process.children.append(child_process)
            roots.remove(child_process)
        assert isinstance(event, PTreeProcExitEvent)
        child_process.exit_timestamp = event.timestamp
        child_process.name = event.name
        del active_processes[event.pid]

    return target_root, roots, active_processes

def action_analyze(name, args):
    """analyze a ptreetime data file"""
    parser = optparse.OptionParser("""\
usage: %%prog %(name)s [options] <path>

Execute the given command and track the execution time of all the processes
which are created during the execution.""" % locals())
    parser.add_option("-f", "--focus", dest="focus",
                      help="focus on the given process name",
                      action="store", default=None)
    parser.add_option("--show-tree", dest="show_tree",
                      help="Print the entire call tree",
                      action="store_true")
    parser.add_option("", "--gen-report", dest="lnt_report_path",
                      help="generate an LNT report",
                      action="store", default=None)
    parser.add_option("", "--report-machine", dest="report_machine",
                      help="machine name to include in the LNT report",
                      action="store", default=None)
    parser.add_option("", "--report-run-order", dest="report_run_order",
                      help="run order to include in the LNT report",
                      action="store", default=None)
    parser.add_option("", "--report-sub-arg", dest="report_sub_args",
                      help="add an argument replacement pattern",
                      metavar="PATTERN=REPLACEMENT",
                      action="append", default=[])

    (opts, args) = parser.parse_args(args)

    if len(args) < 1:
       parser.error("invalid number of arguments")
    if opts.lnt_report_path:
        if opts.report_machine is None:
            parser.error("--report-machine is required with --gen-report")
        if opts.report_run_order is None:
            parser.error("--report-run-order is required with --gen-report")
    else:
        if opts.report_machine is not None:
            parser.error("--report-machine is unused without --gen-report")
        if opts.report_run_order is not None:
            parser.error("--report-run-order is unused without --gen-report")
        if opts.report_sub_args:
            parser.error("--report-sub-arg is unused without --gen-report")

    paths = args

    def find_focused_roots(node):
        # If this is a focused node, add it to the list.
        if opts.focus is None or node.name == opts.focus:
            focused_roots.append(node)
            return

        # Otherwise, recurse on each child.
        for child in node.children:
            find_focused_roots(child)
    focused_roots = []
    for path in paths:
        # Load each file.
        target_root,roots,active_processes = _parse_event_log(path)
        find_focused_roots(target_root)

    # For each root, show the flattened performance.
    def print_node(node, depth):
        # If this node isn't complete, ignore it.
        if node.start_timestamp is None:
            warning("ignoring incomplete node %r (didn't see create)" % (
                    (node.pid, node.name),))
            return
        if node.exit_timestamp is None:
            warning("ignoring incomplete node %r (didn't see exit)" % (
                    (node.pid,),))
            return

        indent = '  '*depth
        wall = node.total_wall_time
        user = node.total_user_time
        sys = node.total_system_time
        startlag = node.startlag
        exitlag = node.exitlag
        if opts.show_tree:
            print ('%s%s -- wall: %.4fs, user: %.4fs, sys: %.4fs, '
                   'startlag: %.4fs, exitlag: %.4fs') % (
                indent, node.name, wall, user, sys, startlag, exitlag)

        key = '%s @ %d' % (node.name, depth)
        aggregate_info = aggregate.get(key)
        if aggregate_info is None:
            aggregate[key] = aggregate_info = {
                'name' : key,
                'wall' : 0.0,
                'user' : 0.0,
                'sys' : 0.0,
                'startlag' : 0.0,
                'exitlag' : 0.0,
                'count' : 0 }
        aggregate_info['wall'] += wall
        aggregate_info['user'] += user
        aggregate_info['sys'] += sys
        aggregate_info['startlag'] += startlag
        aggregate_info['exitlag'] += exitlag
        aggregate_info['count'] += 1

        for c in node.children:
            print_node(c, depth+1)

    if not focused_roots:
        parser.error("no roots found!")

    aggregate = {}
    for node in focused_roots:
       print_node(node, depth=0)
       if node is not focused_roots[-1]:
          print

    print
    print 'Aggregate Times'
    print '---------------'
    items = aggregate.values()
    items.sort(key = lambda i: i['wall'],
               reverse = True)
    name_length = max(len(item['name'])
                      for item in items)
    for item in items:
       print ('%-*s -- wall: %8.4fs, user: %8.4fs, sys: %8.4fs, '
              'startlag: %8.4fs, exitlag: %.4fs, count: %d') % (
          name_length, item['name'], item['wall'], item['user'], item['sys'],
          item['startlag'], item['exitlag'], item['count'])

    # Write out an LNT test report with the data, if requested.
    if opts.lnt_report_path:
        import lnt.testing

        # Build the regular expression substitution arguments.
        substitution_args = []
        for value in opts.report_sub_args:
            if '=' in value:
                pattern,replacement = value.split('=',1)
            else:
                pattern,replacement = value,''
            substitution_args.append((re.compile(pattern), replacement))

        # First, organize all the nodes by deriving a test name key for them.
        def group_node_and_children(node, depth = 0):
            # Ignore incomplete nodes.
            if node.start_timestamp is None or node.exit_timestamp is None:
                return

            args = node.args
            if args is not None:
                # If this is a clang -cc1 invocation, do some special stuff to
                # normalize.
                if node.name == "clang" and args and args[1] == '-cc1':
                    triple_index = args.index('-triple')
                    main_file_name_index = args.index('-main-file-name')
                    args = (
                        args[:2] +
                        args[triple_index:triple_index+2] + \
                        args[main_file_name_index:main_file_name_index+2])


                # Apply substitutions to each argument.
                def substitute(arg):
                    for pattern_re,replacement in substitution_args:
                        arg = pattern_re.sub(replacement, arg)
                    return arg
                args = [substitute(arg)
                        for arg in args]

                # Eliminate empty arguments.
                args = filter(None, args)

            key = '%s(depth=%d,args=%r)' % (node.name, depth, args)
            items = nodes_by_key.get(key)
            if items is None:
                nodes_by_key[key] = items = []
            items.append(node)

            for child in node.children:
                group_node_and_children(child, depth+1)
        nodes_by_key = {}
        for node in focused_roots:
            group_node_and_children(node)

        tag = "compile"
        run_info = { "tag" : tag,
                     "run_order" : opts.report_run_order }
        machine = lnt.testing.Machine(opts.report_machine, {})
        run = lnt.testing.Run(datetime.datetime.fromtimestamp(
                target_root.start_timestamp_in_seconds).\
                                  strftime('%Y-%m-%d %H:%M:%S'),
                              datetime.datetime.fromtimestamp(
                target_root.exit_timestamp_in_seconds).\
                                  strftime('%Y-%m-%d %H:%M:%S'),
                              info=run_info)

        # We report in a scheme compatible with the compile time suite.
        testsamples = []
        for key,nodes in nodes_by_key.items():
            for subkey,accessor in (('wall', 'total_wall_time'),
                                    ('user', 'total_user_time'),
                                    ('sys', 'total_system_time')):
                values = [getattr(node, accessor)
                          for node in nodes]
                name = '%s.%s.%s' % (tag, key, subkey)
                testsamples.append(lnt.testing.TestSamples(
                        name, values))

        report = lnt.testing.Report(machine, run, testsamples)
        with open(opts.lnt_report_path, "w") as f:
            print >>f, report.render()

###

def action_profile(name, args):
    """time a process tree"""

    parser = optparse.OptionParser("""\
usage: %%prog %(name)s [options] ... test command args ...

Use dtrace and dyld interpositioning to collect precise time information on an
entire process tree.""" % locals())
    parser.add_option("-v", "--verbose", dest="verbose",
                      help="output more test information",
                      action="store_true", default=False)
    parser.add_option("-o", "--output", dest="output_path",
                      help="path for data output",
                      action="store", default=None)

    parser.disable_interspersed_args()

    (opts, args) = parser.parse_args(args)
    if opts.output_path is None:
        parser.error("--output argument is required")

    command_arguments = args

    # Check that the ptreetime interpose library has been built.
    basepath = os.path.dirname(os.path.realpath(__file__))
    ptreetime_dtrace_path = os.path.join(
        basepath, 'libptreetime', 'ptreetime.dtrace')
    interpose_path = os.path.join(
        basepath, 'libptreetime', 'libptreetime_interpose.dylib')
    interpose_enabled = os.path.exists(interpose_path)
    if not interpose_enabled:
        warning(("interpose library not built, please 'make' in directory: %r"
                 "(some information will not be gathered)") % (
                     os.path.dirname(interpose_path)))

    # First, establish temporary files to contain the dtrace log and the
    # interposed event log.
    ptreetime_dtrace_log = tempfile.NamedTemporaryFile(
        suffix='-ptreetime-dtrace.log')
    ptreetime_interpose_log = tempfile.NamedTemporaryFile(
        suffix='-ptreetime-interpose.log')

    # Write the environment overrides and execution command into a script.
    with tempfile.NamedTemporaryFile(suffix='-ptreetime.sh') as \
            ptreetime_script:
        os.chmod(ptreetime_script.name, 0755)
        print >>ptreetime_script, '#!/bin/sh'
        if interpose_enabled:
            print >>ptreetime_script, 'export PTREETIME_LOG_PATH="%s"' % (
                ptreetime_interpose_log.name)
            print >>ptreetime_script, 'export DYLD_INSERT_LIBRARIES="%s"' % (
                interpose_path)
        # FIXME: Quote arguments better.
        print >>ptreetime_script, ' '.join("'%s'" % arg
                                           for arg in command_arguments)
        ptreetime_script.flush()

        # Form the dtrace command to execute. Notably we:
        #   1. Force dtrace to set up probes at exec time.
        #   2. Extend the buffer size.
        #   3. Send dtrace logging output to the (shared) log file.
        #   4. Execute the script we created.
        dtrace_args = ['sudo', '/usr/sbin/dtrace',
                       '-xevaltime=exec', '-xbufsize=50m',
                       '-s', ptreetime_dtrace_path,
                       '-o', ptreetime_dtrace_log.name,
                       '-c', ptreetime_script.name]

        # Execute the dtrace command.
        note("executing dtrace (with sudo)...")
        if opts.verbose:
            note("executing: %r" % (' '.join(dtrace_args),))
        p = subprocess.Popen(dtrace_args)
        res = p.wait()
        if res != 0:
            warning("dtrace exited with a error (%d)" % (res,))

    # Read in the log file.
    #
    # We simply load all the events lines as text and order them by timestamp,
    # but otherwise we don't process them at all.
    prefix = "PTREETIME "
    prefix_len = len(prefix)
    note("reading dtrace event log...")
    with open(ptreetime_dtrace_log.name) as f:
        event_data_lines = [ln[prefix_len:-1]
                            for ln in f
                            if ln.startswith(prefix)]
    if interpose_enabled:
        note("reading interposed event log...")
        with open(ptreetime_interpose_log.name) as f:
            event_data_lines.extend([ln[prefix_len:-1]
                                     for ln in f
                                     if ln.startswith(prefix)])
    note("loaded %d events" % (len(event_data_lines),))

    # Delete the temporary log files.
    del ptreetime_interpose_log
    del ptreetime_dtrace_log

    # Sort the event lines. Note that this is purely to make for more readable
    # output files, the ptreetime parser will also order.
    def extract_timestamp(ln):
        # Extract the timestamp, if we can.
        timestamp = ln.split(':', 1)[1].split(',', 1)[0]
        if timestamp.isdigit():
            timestamp = int(timestamp)
        return timestamp
    event_data_lines.sort(key = extract_timestamp)

    # Write out the JSON file. We don't care to guarantee this file is well
    # formed (the consumer of the data files can handle that).
    note("writing output file: %r" % (opts.output_path,))
    with open(opts.output_path, "w") as f:
        print >>f, '{'
        print >>f, '  "command" : %s,' % (
            json.dumps(command_arguments),)
        print >>f, '  "events"  : ['
        last_line = event_data_lines[-1]
        for ln in event_data_lines:
            print >>f, '    %s%c' % (ln, ',]'[ln is last_line])
        print >>f, '}'

###

tool = MultiTool(locals(), "ptreetime")

if __name__ == '__main__':
    import sys
    tool.main(sys.argv)
#!/usr/bin/env python # This source file is part of the Swift.org open source project # # Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors # Licensed under Apache License v2.0 with Runtime Library Exception # # See http://swift.org/LICENSE.txt for license information # See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors ### # Get the file/line where this message was generated. # Step out of _write_message, and then out of wrapper. ### This object defines a generic command line tool instance, which dynamically     builds its commands from a module dictionary.      Example usage::        import multitool        def action_foo(name, args):           "the foo command"            ...         tool = multitool.MultiTool(locals())       if __name__ == '__main__':         tool.main(sys.argv)      Any function beginning with "action_" is considered a tool command. It's     name is defined by the function name suffix. Underscores in the function     name are converted to '-' in the command line syntax. Actions ending ith     "-debug" are not listed in the help. # Create the list of commands. \ Use ``%s <command> --help`` for more information on a specific command.\n # Parse immediate command line options. ### # Event descriptions ### ### ### # Load the file as JSON. # Convert the events data into events. # Order all of the events by timestamp. # Check that the file is complete. # Get the target PID. # Drop the start and stop events. # Build the process tree list. # Lookup the PID. # The PID was unknown, create a record for it. # Add to the list of all records. # Handle the exit of the target process specially. # First, lookup or create an entry for the parent process. # Get the record for this PID. # If this is a create event, store the timestamp and add it to the # parent. # Otherwise, handle the various "user" events. # If the child process already has arguments defined, then the # process is re-exec'ing itself. Create a new process entry. # FIXME: Get utime and stime available here, and copy. # Otherwise, this is an exit event, store the event information and # update the parent and active process list. analyze a ptreetime data file \ usage: %%prog %(name)s [options] <path>  Execute the given command and track the execution time of all the processes which are created during the execution. # If this is a focused node, add it to the list. # Otherwise, recurse on each child. # Load each file. # For each root, show the flattened performance. # If this node isn't complete, ignore it. # Write out an LNT test report with the data, if requested. # Build the regular expression substitution arguments. # First, organize all the nodes by deriving a test name key for them. # Ignore incomplete nodes. # If this is a clang -cc1 invocation, do some special stuff to # normalize. # Apply substitutions to each argument. # Eliminate empty arguments. # We report in a scheme compatible with the compile time suite. ### time a process tree \ usage: %%prog %(name)s [options] ... test command args ...  Use dtrace and dyld interpositioning to collect precise time information on an entire process tree. # Check that the ptreetime interpose library has been built. # First, establish temporary files to contain the dtrace log and the # interposed event log. # Write the environment overrides and execution command into a script. # FIXME: Quote arguments better. # Form the dtrace command to execute. Notably we: #   1. Force dtrace to set up probes at exec time. #   2. Extend the buffer size. #   3. Send dtrace logging output to the (shared) log file. #   4. Execute the script we created. # Execute the dtrace command. # Read in the log file. # # We simply load all the events lines as text and order them by timestamp, # but otherwise we don't process them at all. # Delete the temporary log files. # Sort the event lines. Note that this is purely to make for more readable # output files, the ptreetime parser will also order. # Extract the timestamp, if we can. # Write out the JSON file. We don't care to guarantee this file is well # formed (the consumer of the data files can handle that). ###