# Source code for pex.tracer

    # Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
    # Licensed under the Apache License, Version 2.0 (see LICENSE).

    import sys
    import threading
    import time
    from contextlib import contextmanager

    from .variables import ENV

    __all__ = ('TraceLogger',)

    class Trace(object):
      __slots__ = ('msg', 'verbosity', 'parent', 'children', '_clock', '_start', '_stop')

      def __init__(self, msg, parent=None, verbosity=1, clock=time):
        self.msg = msg
        self.verbosity = verbosity
        self.parent = parent
        if parent is not None:
          parent.children.append(self)
        self.children = []
        self._clock = clock
        self._start = self._clock.time()
        self._stop = None

      def stop(self):
        self._stop = self._clock.time()

      def duration(self):
        assert self._stop is not None
        return self._stop - self._start

    class TraceLogger(object):
      """
        A multi-threaded tracer.
      """

      def __init__(self, predicate=None, output=sys.stderr, clock=time, prefix=''):
        """
          If predicate specified, it should take a "verbosity" integer and determine whether
          or not to log, e.g.

            def predicate(verbosity):
              try:
                return verbosity < int(os.environ.get('APP_VERBOSITY', 0))
              except ValueError:
                return False

          output defaults to sys.stderr, but can take any file-like object.
        """
        self._predicate = predicate or (lambda verbosity: True)
        self._length = None
        self._output = output
        self._isatty = getattr(output, 'isatty', False) and output.isatty()
        self._lock = threading.RLock()
        self._local = threading.local()
        self._clock = clock
        self._prefix = prefix

      def should_log(self, V):
        return self._predicate(V)

      def log(self, msg, V=1, end='\n'):
        if not self.should_log(V):
          return
        if not self._isatty and end == '\r':
          # force newlines if we're not a tty
          end = '\n'
        trailing_whitespace = ''
        with self._lock:
          if self._length and self._length > (len(self._prefix) + len(msg)):
            trailing_whitespace = ' ' * (self._length - len(msg) - len(self._prefix))
          self._output.write(''.join([self._prefix, msg, trailing_whitespace, end]))
          self._output.flush()
          self._length = (len(self._prefix) + len(msg)) if end == '\r' else 0

      def print_trace_snippet(self):
        parent = self._local.parent
        parent_verbosity = parent.verbosity
        if not self.should_log(parent_verbosity):
          return
        traces = []
        while parent:
          if self.should_log(parent.verbosity):
            traces.append(parent.msg)
          parent = parent.parent
        self.log(' :: '.join(reversed(traces)), V=parent_verbosity, end='\r')

      def print_trace(self, indent=0, node=None):
        node = node or self._local.parent
        with self._lock:
          self.log(' ' * indent + ('%s: %.1fms' % (node.msg, 1000.0 * node.duration())),
                   V=node.verbosity)
          for child in node.children:
            self.print_trace(indent=indent + 2, node=child)

      @contextmanager
      def timed(self, msg, V=1):
        if getattr(self._local, 'parent', None) is None:
          self._local.parent = Trace(msg, verbosity=V, clock=self._clock)
        else:
          parent = self._local.parent
          self._local.parent = Trace(msg, parent=parent, verbosity=V, clock=self._clock)
        self.print_trace_snippet()
        yield
        self._local.parent.stop()
        if self._local.parent.parent is not None:
          self._local.parent = self._local.parent.parent
        else:
          self.print_trace()
          self._local.parent = None

    TRACER = TraceLogger(
       predicate=lambda verbosity: verbosity <= ENV.PEX_VERBOSE,
       prefix='pex: '
    )