Source code for pex.tracer

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

from __future__ import absolute_import

import sys
import threading
import time
from contextlib import contextmanager

from pex.typing import TYPE_CHECKING, cast
from pex.variables import ENV

__all__ = ("TraceLogger",)

if TYPE_CHECKING:
    from typing import Any, Callable, IO, Iterator, List, Optional


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

    def __init__(self, msg, parent=None, verbosity=1, clock=time):
        # type: (str, Optional[Trace], int, Any) -> None
        self.msg = msg
        self.verbosity = verbosity
        self.parent = parent
        if parent is not None:
            parent.children.append(self)
        self.children = []  # type: List[Trace]
        self._clock = clock
        self._start = cast(float, self._clock.time())
        self._stop = cast("Optional[float]", None)

    def stop(self):
        # type: () -> None
        self._stop = self._clock.time()

    def duration(self):
        # type: () -> float
        assert self._stop is not None
        return self._stop - self._start


[docs]class TraceLogger(object): """A multi-threaded tracer.""" def __init__(self, predicate=None, output=sys.stderr, clock=time, prefix=""): # type: (Optional[Callable[[int], bool]], IO, Any, str) -> None """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 = cast("Optional[int]", 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): # type: (int) -> bool return self._predicate(V) def log(self, msg, V=1, end="\n"): # type: (str, int, str) -> None 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): # type: () -> None 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): # type: (int, Optional[Trace]) -> 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): # type: (str, int) -> Iterator[None] if not hasattr(self._local, "parent"): 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: ", )