1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
|
######################################################################
#
# File: b2sdk/_internal/transfer/inbound/downloader/stats_collector.py
#
# Copyright 2020 Backblaze Inc. All Rights Reserved.
#
# License https://www.backblaze.com/using_b2_code.html
#
######################################################################
from __future__ import annotations
import logging
from dataclasses import (
dataclass,
field,
)
from time import perf_counter_ns
from typing import (
Any,
)
logger = logging.getLogger(__name__)
class SingleStatsCollector:
TO_MS = 1_000_000
def __init__(self):
self.latest_entry: int | None = None
self.sum_of_all_entries: int = 0
self.started_perf_timer: int | None = None
def __enter__(self) -> None:
self.started_perf_timer = perf_counter_ns()
def __exit__(self, exc_type: type, exc_val: Exception, exc_tb: Any) -> None:
time_diff = perf_counter_ns() - self.started_perf_timer
self.latest_entry = time_diff
self.sum_of_all_entries += time_diff
self.started_perf_timer = None
@property
def sum_ms(self) -> float:
return self.sum_of_all_entries / self.TO_MS
@property
def latest_ms(self) -> float:
return self.latest_entry / self.TO_MS
@property
def has_any_entry(self) -> bool:
return self.latest_entry is not None
@dataclass
class StatsCollector:
name: str #: file name or object url
detail: str #: description of the thread, ex. "10000000:20000000" or "writer"
other_name: str #: other statistic, typically "seek" or "hash"
total: SingleStatsCollector = field(default_factory=SingleStatsCollector)
other: SingleStatsCollector = field(default_factory=SingleStatsCollector)
write: SingleStatsCollector = field(default_factory=SingleStatsCollector)
read: SingleStatsCollector = field(default_factory=SingleStatsCollector)
def report(self):
if self.read.has_any_entry:
logger.info('download stats | %s | TTFB: %.3f ms', self, self.read.latest_ms)
logger.info(
'download stats | %s | read() without TTFB: %.3f ms',
self,
(self.read.sum_of_all_entries - self.read.latest_entry) / self.read.TO_MS,
)
if self.other.has_any_entry:
logger.info(
'download stats | %s | %s total: %.3f ms', self, self.other_name, self.other.sum_ms
)
if self.write.has_any_entry:
logger.info('download stats | %s | write() total: %.3f ms', self, self.write.sum_ms)
if self.total.has_any_entry:
basic_operation_time = (
self.write.sum_of_all_entries
+ self.other.sum_of_all_entries
+ self.read.sum_of_all_entries
)
overhead = self.total.sum_of_all_entries - basic_operation_time
logger.info(
'download stats | %s | overhead: %.3f ms', self, overhead / self.total.TO_MS
)
def __str__(self):
return f'{self.name}[{self.detail}]'
|