Skip to content

Commit 464a6a8

Browse files
committedJan 9, 2025
Add minimum tox + unit tests for wrapper
1 parent f994b8f commit 464a6a8

File tree

10 files changed

+2742
-42
lines changed

10 files changed

+2742
-42
lines changed
 

‎poetry.lock

+2,333
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎pyproject.toml

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Copyright 2023 Canonical Ltd.
2+
# See LICENSE file for licensing details.
3+
4+
[tool.poetry]
5+
package-mode = false
6+
requires-poetry = ">=2.0.0"
7+
8+
[tool.poetry.dependencies]
9+
python = "^3.10"
10+
ops = "^2.17.0"
11+
tenacity = "^9.0.0"
12+
jinja2 = "^3.1.4"
13+
overrides = "7.7.0"
14+
requests = "2.32.3"
15+
shortuuid = "1.0.13"
16+
cryptography = "^43.0.1"
17+
jsonschema = "^4.23.0"
18+
prometheus-client = ">=0.19.0"
19+
pydantic = "^1.10.18, <2"
20+
# pydantic = ">=2.0,<3.0"
21+
fastapi = ">=0.115.0"
22+
uvicorn = ">0.11.5"
23+
kafka-python = ">=2.0"
24+
25+
[tool.poetry.group.charm-libs.dependencies]
26+
# data_platform_libs/v0/data_interfaces.py
27+
ops = "^2.17"
28+
# data_platform_libs/v0/upgrade.py
29+
# grafana_agent/v0/cos_agent.py requires pydantic <2
30+
pydantic = "^1.10, <2"
31+
# tls_certificates_interface/v1/tls_certificates.py
32+
cryptography = "^43.0.0"
33+
jsonschema = "^4.23.0"
34+
# grafana_agent/v0/cos_agent.py
35+
cosl = "^0.0.41"
36+
bcrypt = "^4.1.3"
37+
38+
[tool.poetry.group.format]
39+
optional = true
40+
41+
[tool.poetry.group.format.dependencies]
42+
ruff = "^0.6.8"
43+
44+
[tool.poetry.group.lint]
45+
optional = true
46+
47+
[tool.poetry.group.lint.dependencies]
48+
codespell = "^2.3.0"
49+
shellcheck-py = "^0.10.0.1"
50+
51+
[tool.poetry.group.unit.dependencies]
52+
pytest = "^8.3.3"
53+
pytest-asyncio = "^0.21.2"
54+
coverage = {extras = ["toml"], version = "^7.6.1"}
55+
parameterized = "^0.9.0"
56+
57+
[tool.poetry.group.integration.dependencies]
58+
pytest = "^8.3.3"
59+
pytest-github-secrets = {git = "https://github.com/canonical/data-platform-workflows", tag = "v24.0.6", subdirectory = "python/pytest_plugins/github_secrets"}
60+
pytest-operator = "^0.37.0"
61+
pytest-operator-cache = {git = "https://github.com/canonical/data-platform-workflows", tag = "v24.0.6", subdirectory = "python/pytest_plugins/pytest_operator_cache"}
62+
pytest-operator-groups = {git = "https://github.com/canonical/data-platform-workflows", tag = "v24.0.6", subdirectory = "python/pytest_plugins/pytest_operator_groups"}
63+
pytest-microceph = {git = "https://github.com/canonical/data-platform-workflows", tag = "v24.0.6", subdirectory = "python/pytest_plugins/microceph"}
64+
juju = "^3.5.2"
65+
ops = "^2.17.0"
66+
tenacity = "^9.0.0"
67+
pyyaml = "^6.0.2"
68+
urllib3 = "^2.2.3"
69+
protobuf = "5.28.2"
70+
71+
[tool.coverage.run]
72+
branch = true
73+
74+
[tool.coverage.report]
75+
show_missing = true
76+
77+
[tool.pytest.ini_options]
78+
minversion = "6.0"
79+
log_cli_level = "INFO"
80+
markers = ["unstable"]
81+
asyncio_mode = "auto"
82+
83+
# Formatting tools configuration
84+
[tool.black]
85+
line-length = 99
86+
target-version = ["py310"]
87+
88+
# Linting tools configuration
89+
[tool.ruff]
90+
# preview and explicit preview are enabled for CPY001
91+
preview = true
92+
target-version = "py310"
93+
src = ["src", "."]
94+
line-length = 99
95+
96+
[tool.ruff.lint]
97+
explicit-preview-rules = true
98+
select = ["A", "E", "W", "F", "C", "N", "D", "I001", "CPY001"]
99+
extend-ignore = [
100+
"D203",
101+
"D204",
102+
"D213",
103+
"D215",
104+
"D400",
105+
"D404",
106+
"D406",
107+
"D407",
108+
"D408",
109+
"D409",
110+
"D413",
111+
]
112+
# Ignore E501 because using black creates errors with this
113+
# Ignore D107 Missing docstring in __init__
114+
ignore = ["E501", "D107"]
115+
116+
[tool.ruff.lint.per-file-ignores]
117+
"tests/*" = ["D100", "D101", "D102", "D103", "D104"]
118+
119+
[tool.ruff.lint.flake8-copyright]
120+
# Check for properly formatted copyright header in each file
121+
author = "Canonical Ltd."
122+
notice-rgx = "Copyright\\s\\d{4}([-,]\\d{4})*\\s+"
123+
min-file-size = 1
124+
125+
[tool.ruff.lint.mccabe]
126+
max-complexity = 10
127+
128+
[tool.ruff.lint.pydocstyle]
129+
convention = "google"

‎src/benchmark/literals.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright 2024 Canonical Ltd.
2+
# See LICENSE file for licensing details.
3+
4+
"""This module contains the constants and models used by the sysbench charm."""
5+
6+
BENCHMARK_WORKLOAD_PATH = "/root/.benchmark/charmed_parameters"

‎src/benchmark/wrapper/core.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,9 @@ class WorkloadCLIArgsModel(BaseModel):
6060
run_count: int
6161
report_interval: int
6262
extra_labels: str
63-
log_file: str = "/var/log/dpe_benchmark_workload.log"
6463
peers: str
64+
log_file: str = "/var/log/dpe_benchmark_workload.log"
65+
is_coordinator: bool
6566

6667

6768
class BenchmarkMetrics:
@@ -88,7 +89,6 @@ def add(self, sample: BaseModel):
8889
)
8990

9091

91-
9292
class KafkaBenchmarkSample(BaseModel):
9393
"""Sample from the benchmark tool."""
9494

@@ -130,4 +130,4 @@ class KafkaBenchmarkSampleMatcher(Enum):
130130

131131
consume_rate: str = r"Cons rate\s+(.*?)\s+msg/s"
132132
consume_throughput: str = r"Cons rate\s+\d+.\d+\s+msg/s\s+/\s+(.*?)\s+MB/s"
133-
consume_backlog: str = r"Backlog:\s+(.*?)\s+K"
133+
consume_backlog: str = r"Backlog:\s+(.*?)\s+K"

‎src/benchmark/wrapper/main.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,14 @@ def run(self):
2424
"""Prepares the workload and runs the benchmark."""
2525
manager, _ = self.mapping.map(self.args.command)
2626

27-
logging.basicConfig(
28-
filename=self.args.log_file, encoding="utf-8", level=logging.INFO
29-
)
27+
logging.basicConfig(filename=self.args.log_file, encoding="utf-8", level=logging.INFO)
3028

3129
def _exit(*args, **kwargs):
3230
manager.stop()
3331

3432
signal.signal(signal.SIGINT, _exit)
3533
signal.signal(signal.SIGTERM, _exit)
36-
start_http_server(8088)
34+
start_http_server(8008)
3735

3836
# Start the manager and process the output
3937
manager.start()
@@ -62,14 +60,14 @@ def _exit(*args, **kwargs):
6260
# "--target_hosts", type=str, default="", help="comma-separated list of target hosts"
6361
# )
6462
# parser.add_argument(
63+
# "--log_file", type=str, default="/var/log/dpe_benchmark_workload.log", help="Log file for all threads"
64+
# )
65+
# parser.add_argument(
6566
# "--extra_labels",
6667
# type=str,
6768
# help="comma-separated list of extra labels to be used.",
6869
# default="",
6970
# )
70-
# parser.add_argument(
71-
# "--log_file", type=str, default="/var/log/dpe_benchmark_workload.log", help="Log file for all threads"
72-
# )
7371
# # Parse the arguments as dictionary, using the same logic as:
7472
# # https://github.com/python/cpython/blob/ \
7573
# # 47c5a0f307cff3ed477528536e8de095c0752efa/Lib/argparse.py#L134

‎src/benchmark/wrapper/process.py

+27-16
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727

2828

2929
logger = logging.getLogger(__name__)
30+
logging.basicConfig(
31+
filename="/var/log/dpe_benchmark_workload.log", encoding="utf-8", level=logging.INFO
32+
)
3033

3134

3235
class BenchmarkProcess:
@@ -42,7 +45,7 @@ class BenchmarkProcess:
4245

4346
def __init__(
4447
self,
45-
model: ProcessModel,
48+
model: ProcessModel | None,
4649
args: WorkloadCLIArgsModel,
4750
metrics: BenchmarkMetrics,
4851
):
@@ -53,6 +56,8 @@ def __init__(
5356

5457
def start(self):
5558
"""Start the process."""
59+
if not self.model:
60+
return
5661
self._proc = subprocess.Popen(
5762
self.model.cmd,
5863
user=self.model.user,
@@ -73,6 +78,10 @@ def start(self):
7378
def status(self) -> ProcessStatus:
7479
"""Return the status of the process."""
7580
stat = ProcessStatus.STOPPED
81+
if not self._proc:
82+
# We are managing only, we do not run a process
83+
return ProcessStatus.RUNNING
84+
7685
if self._proc.poll() is None:
7786
stat = ProcessStatus.RUNNING
7887
elif self._proc.returncode != 0:
@@ -95,20 +104,21 @@ async def process(
95104
or (self.status() == ProcessStatus.RUNNING and self.args.duration == 0)
96105
):
97106
to_wait = True
98-
for line in iter(self._proc.stdout.readline, ""):
99-
if output := self.process_line(line):
100-
self.metrics.add(output)
107+
if self._proc:
108+
for line in iter(self._proc.stdout.readline, ""):
109+
if output := self.process_line(line):
110+
self.metrics.add(output)
101111

102-
if self.status() != ProcessStatus.RUNNING:
103-
# Process has finished
104-
break
112+
if self.status() != ProcessStatus.RUNNING:
113+
# Process has finished
114+
break
105115

106-
to_wait = False
116+
to_wait = False
107117

108-
# Log the output.
109-
# This way, an user can see what the process is doing and
110-
# some of the metrics will be readily available without COS.
111-
logger.info(f"[workload pid {self._proc.pid}] " + line.rstrip())
118+
# Log the output.
119+
# This way, an user can see what the process is doing and
120+
# some of the metrics will be readily available without COS.
121+
logger.info(f"[workload pid {self._proc.pid}] " + line.rstrip())
112122

113123
if to_wait:
114124
# In case the stdout is empty, we ensure we sleep anyways
@@ -126,7 +136,8 @@ async def process(
126136
def stop(self):
127137
"""Stop the process."""
128138
try:
129-
self._proc.kill()
139+
if self._proc:
140+
self._proc.kill()
130141
except Exception as e:
131142
logger.warning(f"Error stopping worker: {e}")
132143
self.model.status = ProcessStatus.STOPPED
@@ -214,16 +225,16 @@ def map(self, cmd: BenchmarkCommand) -> tuple[BenchmarkManager, list[BenchmarkPr
214225
raise ValueError(f"Invalid command: {cmd}")
215226

216227
@abstractmethod
217-
def _map_prepare(self) -> tuple[BenchmarkManager, list[BenchmarkProcess] | None]:
228+
def _map_prepare(self) -> tuple[BenchmarkManager | None, list[BenchmarkProcess] | None]:
218229
"""Returns the mapping for the prepare phase."""
219230
...
220231

221232
@abstractmethod
222-
def _map_run(self) -> tuple[BenchmarkManager, list[BenchmarkProcess] | None]:
233+
def _map_run(self) -> tuple[BenchmarkManager | None, list[BenchmarkProcess] | None]:
223234
"""Returns the mapping for the run phase."""
224235
...
225236

226237
@abstractmethod
227-
def _map_clean(self) -> tuple[BenchmarkManager, list[BenchmarkProcess] | None]:
238+
def _map_clean(self) -> tuple[BenchmarkManager | None, list[BenchmarkProcess] | None]:
228239
"""Returns the mapping for the clean phase."""
229240
...

0 commit comments

Comments
 (0)