File: stats_collector.py

package info (click to toggle)
python-b2sdk 2.8.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,020 kB
  • sloc: python: 30,902; sh: 13; makefile: 8
file content (91 lines) | stat: -rw-r--r-- 3,121 bytes parent folder | download
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}]'