source: trunk/benchmarks/conftest.py

Last change on this file was 0c2db2d5, checked in by Itamar Turner-Trauring <itamar@…>, at 2023-12-06T19:46:37Z

Make sure FEC does some work

  • Property mode set to 100644
File size: 4.5 KB
Line 
1"""
2pytest infrastructure for benchmarks.
3
4The number of nodes is parameterized via a --number-of-nodes CLI option added
5to pytest.
6"""
7
8import os
9from shutil import which, rmtree
10from tempfile import mkdtemp
11from contextlib import contextmanager
12from time import time
13
14import pytest
15import pytest_twisted
16
17from twisted.internet import reactor
18from twisted.internet.defer import DeferredList, succeed
19
20from allmydata.util.iputil import allocate_tcp_port
21
22from integration.grid import Client, create_grid, create_flog_gatherer
23
24
25def pytest_addoption(parser):
26    parser.addoption(
27        "--number-of-nodes",
28        action="append",
29        default=[],
30        type=int,
31        help="list of number_of_nodes to benchmark against",
32    )
33    # Required to be compatible with integration.util code that we indirectly
34    # depend on, but also might be useful.
35    parser.addoption(
36        "--force-foolscap",
37        action="store_true",
38        default=False,
39        dest="force_foolscap",
40        help=(
41            "If set, force Foolscap only for the storage protocol. "
42            + "Otherwise HTTP will be used."
43        ),
44    )
45
46
47def pytest_generate_tests(metafunc):
48    # Make number_of_nodes accessible as a parameterized fixture:
49    if "number_of_nodes" in metafunc.fixturenames:
50        metafunc.parametrize(
51            "number_of_nodes",
52            metafunc.config.getoption("number_of_nodes"),
53            scope="session",
54        )
55
56
57def port_allocator():
58    port = allocate_tcp_port()
59    return succeed(port)
60
61
62@pytest.fixture(scope="session")
63def grid(request):
64    """
65    Provides a new Grid with a single Introducer and flog-gathering process.
66
67    Notably does _not_ provide storage servers; use the storage_nodes
68    fixture if your tests need a Grid that can be used for puts / gets.
69    """
70    tmp_path = mkdtemp(prefix="tahoe-benchmark")
71    request.addfinalizer(lambda: rmtree(tmp_path))
72    flog_binary = which("flogtool")
73    flog_gatherer = pytest_twisted.blockon(
74        create_flog_gatherer(reactor, request, tmp_path, flog_binary)
75    )
76    g = pytest_twisted.blockon(
77        create_grid(reactor, request, tmp_path, flog_gatherer, port_allocator)
78    )
79    return g
80
81
82@pytest.fixture(scope="session")
83def storage_nodes(grid, number_of_nodes):
84    nodes_d = []
85    for _ in range(number_of_nodes):
86        nodes_d.append(grid.add_storage_node())
87
88    nodes_status = pytest_twisted.blockon(DeferredList(nodes_d))
89    for ok, value in nodes_status:
90        assert ok, "Storage node creation failed: {}".format(value)
91    return grid.storage_servers
92
93
94@pytest.fixture(scope="session")
95def client_node(request, grid, storage_nodes, number_of_nodes) -> Client:
96    """
97    Create a grid client node with number of shares matching number of nodes.
98    """
99    client_node = pytest_twisted.blockon(
100        grid.add_client(
101            "client_node",
102            needed=number_of_nodes,
103            happy=number_of_nodes,
104            total=number_of_nodes + 3,  # Make sure FEC does some work
105        )
106    )
107    print(f"Client node pid: {client_node.process.transport.pid}")
108    return client_node
109
110def get_cpu_time_for_cgroup():
111    """
112    Get how many CPU seconds have been used in current cgroup so far.
113
114    Assumes we're running in a v2 cgroup.
115    """
116    with open("/proc/self/cgroup") as f:
117        cgroup = f.read().strip().split(":")[-1]
118        assert cgroup.startswith("/")
119        cgroup = cgroup[1:]
120    cpu_stat = os.path.join("/sys/fs/cgroup", cgroup, "cpu.stat")
121    with open(cpu_stat) as f:
122        for line in f.read().splitlines():
123            if line.startswith("usage_usec"):
124                return int(line.split()[1]) / 1_000_000
125    raise ValueError("Failed to find usage_usec")
126
127
128class Benchmarker:
129    """Keep track of benchmarking results."""
130
131    @contextmanager
132    def record(self, capsys: pytest.CaptureFixture[str], name, **parameters):
133        """Record the timing of running some code, if it succeeds."""
134        start_cpu = get_cpu_time_for_cgroup()
135        start = time()
136        yield
137        elapsed = time() - start
138        end_cpu = get_cpu_time_for_cgroup()
139        elapsed_cpu = end_cpu - start_cpu
140        # FOR now we just print the outcome:
141        parameters = " ".join(f"{k}={v}" for (k, v) in parameters.items())
142        with capsys.disabled():
143            print(
144                f"\nBENCHMARK RESULT: {name} {parameters} elapsed={elapsed:.3} (secs) CPU={elapsed_cpu:.3} (secs)\n"
145            )
146
147
148@pytest.fixture(scope="session")
149def tahoe_benchmarker():
150    return Benchmarker()
Note: See TracBrowser for help on using the repository browser.