|
|
|
|
|
|
import math |
|
|
|
import sys |
|
|
|
import time |
|
|
|
import threading |
|
|
|
from typing import Any, Callable, Dict, Generator, TypeVar |
|
|
|
from typing import Any, Callable, Dict, Generator, Optional, TypeVar |
|
|
|
|
|
|
|
TIMER_FORMAT_VERSION = "0.1.0" |
|
|
|
|
|
|
|
|
|
|
Tracks the most recent value of a metric. This is analogous to gauges in statsd. |
|
|
|
""" |
|
|
|
|
|
|
|
__slots__ = ["value", "min_value", "max_value", "count"] |
|
|
|
__slots__ = ["value", "min_value", "max_value", "count", "_timestamp"] |
|
|
|
|
|
|
|
def __init__(self, value: float): |
|
|
|
self.value = value |
|
|
|
|
|
|
# Internal timestamp so we can determine priority. |
|
|
|
self._timestamp = time.time() |
|
|
|
|
|
|
|
def update(self, new_value: float) -> None: |
|
|
|
self.min_value = min(self.min_value, new_value) |
|
|
|
|
|
|
self._timestamp = time.time() |
|
|
|
|
|
|
|
def merge(self, other: "GaugeNode") -> None: |
|
|
|
if self._timestamp < other._timestamp: |
|
|
|
# Keep the "later" value |
|
|
|
self.value = other.value |
|
|
|
self._timestamp = other._timestamp |
|
|
|
self.min_value = min(self.min_value, other.min_value) |
|
|
|
self.max_value = max(self.max_value, other.max_value) |
|
|
|
self.count += other.count |
|
|
|
|
|
|
|
def as_dict(self) -> Dict[str, float]: |
|
|
|
return { |
|
|
|
|
|
|
self.metadata["command_line_arguments"] = " ".join(sys.argv) |
|
|
|
|
|
|
|
|
|
|
|
# Global instance of a TimerStack. This is generally all that we need for profiling, but you can potentially |
|
|
|
# create multiple instances and pass them to the contextmanager |
|
|
|
_global_timer_stack = TimerStack() |
|
|
|
# Maintain a separate "global" timer per thread, so that they don't accidentally conflict with each other. |
|
|
|
_thread_timer_stacks: Dict[int, TimerStack] = {} |
|
|
|
|
|
|
|
|
|
|
|
def _get_thread_timer() -> TimerStack: |
|
|
|
ident = threading.get_ident() |
|
|
|
if ident not in _thread_timer_stacks: |
|
|
|
timer_stack = TimerStack() |
|
|
|
_thread_timer_stacks[ident] = timer_stack |
|
|
|
return _thread_timer_stacks[ident] |
|
|
|
|
|
|
|
|
|
|
|
def get_timer_stack_for_thread(t: threading.Thread) -> Optional[TimerStack]: |
|
|
|
if t.ident is None: |
|
|
|
# Thread hasn't started, shouldn't ever happen |
|
|
|
return None |
|
|
|
return _thread_timer_stacks.get(t.ident) |
|
|
|
|
|
|
|
|
|
|
|
@contextmanager |
|
|
|
|
|
|
the context manager exits. |
|
|
|
""" |
|
|
|
timer_stack = timer_stack or _global_timer_stack |
|
|
|
timer_stack = timer_stack or _get_thread_timer() |
|
|
|
timer_node = timer_stack.push(name) |
|
|
|
start_time = time.perf_counter() |
|
|
|
|
|
|
|
|
|
|
""" |
|
|
|
Updates the value of the gauge (or creates it if it hasn't been set before). |
|
|
|
""" |
|
|
|
timer_stack = timer_stack or _global_timer_stack |
|
|
|
timer_stack = timer_stack or _get_thread_timer() |
|
|
|
def merge_gauges(gauges: Dict[str, GaugeNode], timer_stack: TimerStack = None) -> None: |
|
|
|
""" |
|
|
|
Merge the gauges from another TimerStack with the provided one (or the |
|
|
|
current thread's stack if none is provided). |
|
|
|
:param gauges: |
|
|
|
:param timer_stack: |
|
|
|
:return: |
|
|
|
""" |
|
|
|
timer_stack = timer_stack or _get_thread_timer() |
|
|
|
for n, g in gauges.items(): |
|
|
|
if n in timer_stack.gauges: |
|
|
|
timer_stack.gauges[n].merge(g) |
|
|
|
else: |
|
|
|
timer_stack.gauges[n] = g |
|
|
|
|
|
|
|
|
|
|
|
timer_stack = timer_stack or _global_timer_stack |
|
|
|
timer_stack = timer_stack or _get_thread_timer() |
|
|
|
Return the tree of timings from the TimerStack as a dictionary (or the global stack if none is provided) |
|
|
|
Return the tree of timings from the TimerStack as a dictionary (or the |
|
|
|
current thread's stack if none is provided) |
|
|
|
timer_stack = timer_stack or _global_timer_stack |
|
|
|
timer_stack = timer_stack or _get_thread_timer() |
|
|
|
Get the root TimerNode of the timer_stack (or the global TimerStack if not specified) |
|
|
|
Get the root TimerNode of the timer_stack (or the current thread's |
|
|
|
TimerStack if not specified) |
|
|
|
timer_stack = timer_stack or _global_timer_stack |
|
|
|
timer_stack = timer_stack or _get_thread_timer() |
|
|
|
Reset the timer_stack (or the global TimerStack if not specified) |
|
|
|
Reset the timer_stack (or the current thread's TimerStack if not specified) |
|
|
|
timer_stack = timer_stack or _global_timer_stack |
|
|
|
timer_stack = timer_stack or _get_thread_timer() |
|
|
|
timer_stack.reset() |