86 lines
2.3 KiB
Python
86 lines
2.3 KiB
Python
#
|
|
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
#
|
|
|
|
import datetime
|
|
import logging
|
|
import time
|
|
from contextlib import contextmanager
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
|
|
logger = logging.getLogger("airbyte")
|
|
|
|
|
|
class EventTimer:
|
|
"""Simple nanosecond resolution event timer for debugging, initially intended to be used to record streams execution
|
|
time for a source.
|
|
Event nesting follows a LIFO pattern, so finish will apply to the last started event.
|
|
"""
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.events = {}
|
|
self.count = 0
|
|
self.stack = []
|
|
|
|
def start_event(self, name):
|
|
"""
|
|
Start a new event and push it to the stack.
|
|
"""
|
|
self.events[name] = Event(name=name)
|
|
self.count += 1
|
|
self.stack.insert(0, self.events[name])
|
|
|
|
def finish_event(self):
|
|
"""
|
|
Finish the current event and pop it from the stack.
|
|
"""
|
|
|
|
if self.stack:
|
|
event = self.stack.pop(0)
|
|
event.finish()
|
|
else:
|
|
logger.warning(f"{self.name} finish_event called without start_event")
|
|
|
|
def report(self, order_by="name"):
|
|
"""
|
|
:param order_by: 'name' or 'duration'
|
|
"""
|
|
if order_by == "name":
|
|
events = sorted(self.events.values(), key=lambda event: event.name)
|
|
elif order_by == "duration":
|
|
events = sorted(self.events.values(), key=lambda event: event.duration)
|
|
text = f"{self.name} runtimes:\n"
|
|
text += "\n".join(str(event) for event in events)
|
|
return text
|
|
|
|
|
|
@dataclass
|
|
class Event:
|
|
name: str
|
|
start: float = field(default_factory=time.perf_counter_ns)
|
|
end: Optional[float] = field(default=None)
|
|
|
|
@property
|
|
def duration(self) -> float:
|
|
"""Returns the elapsed time in seconds or positive infinity if event was never finished"""
|
|
if self.end:
|
|
return (self.end - self.start) / 1e9
|
|
return float("+inf")
|
|
|
|
def __str__(self):
|
|
return f"{self.name} {datetime.timedelta(seconds=self.duration)}"
|
|
|
|
def finish(self):
|
|
self.end = time.perf_counter_ns()
|
|
|
|
|
|
@contextmanager
|
|
def create_timer(name):
|
|
"""
|
|
Creates a new EventTimer as a context manager to improve code readability.
|
|
"""
|
|
a_timer = EventTimer(name)
|
|
yield a_timer
|