# ppamltracer -- Python bindings to ppamltracer # Copyright (C) 2013 Galois, Inc. # # This library is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this library. If not, see . # # To contact Galois, complete the Web form at # or write to Galois, Inc., 421 Southwest 6th Avenue, Suite 300, Portland, # Oregon, 97204-1622. """A tracing library for explicit instrumentation of generated code. ppamltracer is a lightweight, portable tracing library designed for explicit instrumention of generated code. If you're writing a compiler and need hard data on your optimizer's efficacy, ppamltracer is the library for you. ppamltracer-python provides a high-level Python API on top of the libppamltracer C API. ppamltracer-python's usage can be summed up in a couple lines: from ppamltracer import Tracer with Tracer("/tmp/my_report") as tracer: with tracer.create_phase("phase 1") as phase: with phase.running(): do_stuff() with tracer.create_phase("phase 2") as phase: with phase.running(): do_other_stuff() with phase.running(): do_yet_more_stuff() This creates a report with the total runtime of do_stuff recorded as "phase 1", and the total runtime of do_other_stuff and do_yet_more_stuff combined as "phase 2". The ppamltracer-python distribution also contains a more lengthy example in the "examples" directory. ppamltracer writes trace logs in the Open Trace Format [1], a free and open standard developed by the Zentrum fuer Informationsdienste und Hochleistungsrechnen (Center for Information Services and High-Performance Computing) at the Technical University of Dresden. We developed ppamltracer and ppamltracer-python as part of DARPA's Probabilistic Programming for Advancing Machine Learning (PPAML) project [2]. References: [1] http://tu-dresden.de/zih/otf/ [2] http://darpa.mil/Our_Work/I2O/Programs/Probabilistic_Programming_for_Advanced_Machine_Learning_(PPAML).aspx """ # TODO: Replace "ue" in "fuer" with Unicode u-with-diaresis (blocking on # deployment of fix to Python #1065986, "Fix pydoc crashing on unicode # strings"). import ctypes import ctypes.util import warnings ################################ Low-level API ################################ _lib = ctypes.cdll.LoadLibrary(ctypes.util.find_library("ppamltracer")) """ctypes binding to the ppamltracer C library.""" # The code in this section binds the C API using ctypes, translating # return codes into the exception hierarchy defined below. Since these # functions are not intended to be used externally, they're not # documented using pydoc; see the ppamltracer C API documentation for # information on their behavior. _sizeof_ppaml_tracer_t = \ ctypes.c_size_t.in_dll(_lib, "ppaml_tracer_t_size").value _sizeof_ppaml_phase_t = ctypes.c_size_t.in_dll(_lib, "ppaml_phase_t_size").value def _ppaml_tracer_init(tracer, report_name_base): result = _lib.ppaml_tracer_init(tracer, report_name_base) if result == 0: return None elif result == 1: raise OTFManagerInitializationError() elif result == 2: raise OTFWriterInitializationError() elif result == 3: raise OTFWriterResolutionError() elif result == 4: raise OTFWriterProcessDefinitionError("main") else: _warn_unexpected_return_code() raise TracerError() def _ppaml_tracer_done(tracer): result = _lib.ppaml_tracer_done(tracer) if result == 0: return None elif result == 1: raise OTFWriterCloseError() else: _warn_unexpected_return_code() raise TracerError() def _ppaml_phase_init(tracer, phase, name): result = _lib.ppaml_phase_init(tracer, phase, name) if result == 0: return None elif result == 1: raise OTFWriterPhaseDefinitionError() else: _warn_unexpected_return_code() raise TracerError() def _ppaml_phase_start(phase): result = _lib.ppaml_phase_start(phase) if result == 0: return None elif result == 1: raise ClockAcquisitionError() elif result == 2: raise OTFWriterEntryError() else: _warn_unexpected_return_code() raise TracerError() def _ppaml_phase_stop(phase): result = _lib.ppaml_phase_stop(phase) if result == 0: return None elif result == 1: raise ClockAcquisitionError() elif result == 2: raise OTFWriterExitError() else: _warn_unexpected_return_code() raise TracerError() def _ppaml_phase_done(phase): result = _lib.ppaml_phase_done(phase) if result == 0: return None else: _warn_unexpected_return_code() raise TracerError() ############################# Resource-based API ############################## class Tracer(object): """A tracer for programs, which records execution timing information. This class is designed to be used with Python's "with" statement-- e.g., with Tracer("/tmp/my_report") as tracer: main(tracer) """ def __init__(self, report_name_base): """Create a new tracer. The tracer will create an Open Trace Format report during program execution. The multiple files of the report will all start with the specified base name. """ self._report_name_base = report_name_base self._underlying = None def __enter__(self): if self._underlying is None: self._underlying = \ ctypes.create_string_buffer(_sizeof_ppaml_tracer_t) _ppaml_tracer_init(self._underlying, self._report_name_base) return self def __exit__(self, *exception_info): if self._underlying is not None: _ppaml_tracer_done(self._underlying) self._underlying = None def create_phase(self, name): """Construct a Phase associated with this Tracer. This function is merely a convenience function; internally, it shells out to the Phase constructor. """ return Phase(self, name) class Phase(object): """A phase of computation traced by ppamltracer. This class is designed to be used with Python's "with" statement-- e.g., with Phase(tracer, "my phase") as phase: with phase.running(): do_stuff() with phase.running(): do_stuff_again() Note the double use of "with". The outer "with" manages the lifetime of a Phase; the inner "with" actually starts and stops the phase timer. """ def __init__(self, tracer, name): """Define a new phase tracked by a given tracer.""" self._tracer = tracer self._name = name self._underlying = None def __enter__(self): if self._underlying is None: self._underlying = \ ctypes.create_string_buffer(_sizeof_ppaml_phase_t) _ppaml_phase_init( self._tracer._underlying, self._underlying, self._name) return self def __exit__(self, *exception_info): if self._underlying is not None: _ppaml_phase_done(self._underlying) self._underlying = None def _start(self): _ppaml_phase_start(self._underlying) def _stop(self): _ppaml_phase_stop(self._underlying) def running(self): """ Create a resource manager for the phase timer. This manager handles starting and stopping the phase, allowing you to time operations by saying, e.g., with phase.running(): do_stuff() """ return _PhaseTimerManager(self) class _PhaseTimerManager(object): """Manage entry and exit from a Phase using the "with" statement.""" def __init__(self, phase): self._phase = phase def __enter__(self): self._phase._start() def __exit__(self, *exception_info): self._phase._stop() ############################# Exception hierarchy ############################# class TracerError(Exception): """A generic ppamltracer error.""" pass class OTFError(TracerError): """An error related to Open Trace Format input and output.""" pass class OTFManagerError(OTFError): """An error caused by the Open Trace Format manager.""" pass class OTFManagerInitializationError(OTFManagerError): """Failure to initialize the Open Trace Format manager.""" def __init__(self): super.__init__( self, "could not initialize Open Trace Format file manager") class OTFWriterError(OTFError): """An error caused by the Open Trace Format writer.""" pass class OTFWriterInitializationError(OTFWriterError): """Failure to initialize the Open Trace Format writer.""" def __init__(self): super.__init__(self, "could not open Open Trace Format writer") class OTFWriterPhaseDefinitionError(OTFWriterError): """Failure to define a phase. """ def __init__(self): super.__init__(self, "could not define phase") class OTFWriterEntryError(OTFWriterError): """Failure to record entry into a phase.""" def __init__(self): super.__init__(self, "could not record phase start") class OTFWriterExitError(OTFWriterError): """Failure to record exit from a phase.""" def __init__(self): super.__init__(self, "could not record phase end") class OTFWriterCloseError(OTFWriterError): """Failure to close the Open Trace Format writer.""" def __init__(self): super.__init__(self, "could not close Open Trace Format writer") class OTFWriterResolutionError(OTFWriterError): """Failure to set the tracer resolution.""" def __init__(self): super.__init__(self, "could not set trace resolution") class OTFWriterProcessDefinitionError(OTFWriterError): """Failure to define an Open Trace Format process.""" def __init__(self, process_name=None): if process_name is None: super.__init__(self, "could not define Open Trace Format process") else: super.__init__(self, ("could not define Open Trace Format process \"" + process_name + "\"")) class TimingError(TracerError): """An error related to system timers.""" pass class ClockAcquisitionError(TimingError): """A failure to get the current clock time.""" def __init__(self): super.__init__(self, "could not get current time") ############################### Warnings ############################### class UnwrappedCErrorWarning(Warning): """A warning indicating a failure to correctly wrap a C API.""" pass def _warn_unexpected_return_code(): warnings.warn(("Unexpected C return code\n" + "*** This is a bug in ppamltracer-python! Report it to the maintainers."), UnwrappedCErrorWarning, 2)