Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stress-ng benchmark #95

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ RUN apt update -y \
netcat-openbsd \
nmap \
maven \
stress-ng \
&& apt clean

# Install MongoDB
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ load-memcached:
-p recordcount=1000000 \
-p memcached.hosts=localhost:$(MEMCACHED_PORT)

benchmark-stress-ng:
@python python/kernmlops collect -v \
-c ${KERNMLOPS_CONFIG_FILE} \
--benchmark stress_ng

benchmark-linux-build:
@python python/kernmlops collect -v \
-c ${KERNMLOPS_CONFIG_FILE} \
Expand Down
9 changes: 9 additions & 0 deletions defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ benchmark_config:
operation_count: 1000000
read_proportion: 0.99
update_proportion: 0.01
stress_ng:
stress_ng_benchmark: stress-ng
args:
- --vm
- "1"
- --vm-ops
- "1000000"
- --timeout
- 10s
collector_config:
generic:
poll_rate: 0.5
Expand Down
11 changes: 9 additions & 2 deletions python/kernmlops/cli/collect.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pathlib import Path
from queue import Queue
from threading import Event, Thread
from time import sleep
from time import sleep, time
from typing import cast

import data_collection
Expand Down Expand Up @@ -34,12 +34,16 @@ def poll_instrumentation(
) -> int:

return_code = None
loop_start = None
while return_code is None and run_event.is_set():
try:
for bpf_program in bpf_programs:
bpf_program.poll()
if poll_rate > 0:
sleep(poll_rate)
sleep_time = poll_rate - (time() - loop_start) if loop_start else poll_rate
if sleep_time > 0:
sleep(sleep_time)
loop_start = time()
return_code = benchmark.poll()
# clean data when missed samples - or detect?
except BenchmarkNotRunningError:
Expand Down Expand Up @@ -132,6 +136,9 @@ def run_collect(
pl.lit(benchmark.name()).alias("benchmark_name"),
pl.lit([hook.name() for hook in bpf_programs]).cast(pl.List(pl.String())).alias("hooks"),
])
),
data_schema.BenchmarkRunInfoTable.from_df(
pl.DataFrame(benchmark.to_run_info_dict())
)
]
for bpf_program in bpf_programs:
Expand Down
2 changes: 2 additions & 0 deletions python/kernmlops/data_schema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from data_schema.quanta_runtime import QuantaQueuedTable, QuantaRuntimeTable
from data_schema.schema import (
UPTIME_TIMESTAMP,
BenchmarkRunInfoTable,
CollectionData,
CollectionGraph,
CollectionTable,
Expand All @@ -24,6 +25,7 @@

table_types: list[type[CollectionTable]] = [
SystemInfoTable,
BenchmarkRunInfoTable,
QuantaRuntimeTable,
QuantaQueuedTable,
ProcessMetadataTable,
Expand Down
10 changes: 6 additions & 4 deletions python/kernmlops/data_schema/perf/tlb_perf.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import polars as pl
from bcc import PerfType
from data_schema.memory_usage import MemoryUsageGraph
Expand Down Expand Up @@ -45,7 +47,7 @@ def measured_event_name(cls) -> str:
return "Misses"

@classmethod
def from_df(cls, table: pl.DataFrame) -> "DTLBPerfTable":
def from_df(cls, table: pl.DataFrame) -> DTLBPerfTable:
return DTLBPerfTable(table=table.cast(cls.schema(), strict=True)) # pyright: ignore [reportArgumentType]

def __init__(self, table: pl.DataFrame):
Expand Down Expand Up @@ -133,7 +135,7 @@ def measured_event_name(cls) -> str:
return "Misses"

@classmethod
def from_df(cls, table: pl.DataFrame) -> "ITLBPerfTable":
def from_df(cls, table: pl.DataFrame) -> ITLBPerfTable:
return ITLBPerfTable(table=table.cast(cls.schema(), strict=True)) # pyright: ignore [reportArgumentType]

def __init__(self, table: pl.DataFrame):
Expand Down Expand Up @@ -220,7 +222,7 @@ def measured_event_name(cls) -> str:
return "Flushes"

@classmethod
def from_df(cls, table: pl.DataFrame) -> "TLBFlushPerfTable":
def from_df(cls, table: pl.DataFrame) -> TLBFlushPerfTable:
return TLBFlushPerfTable(table=table.cast(cls.schema(), strict=True)) # pyright: ignore [reportArgumentType]

def __init__(self, table: pl.DataFrame):
Expand Down Expand Up @@ -306,7 +308,7 @@ def measured_event_name(cls) -> str:
return "Walk Durations"

@classmethod
def from_df(cls, table: pl.DataFrame) -> "DTLBWalkDurationPerfTable":
def from_df(cls, table: pl.DataFrame) -> DTLBWalkDurationPerfTable:
return DTLBWalkDurationPerfTable(table=table.cast(cls.schema(), strict=True)) # pyright: ignore [reportArgumentType]

def __init__(self, table: pl.DataFrame):
Expand Down
29 changes: 29 additions & 0 deletions python/kernmlops/data_schema/schema.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Abstract definition of CollectionTable and logical collection
from __future__ import annotations

from pathlib import Path
from typing import Final, Mapping, cast
Expand Down Expand Up @@ -73,6 +74,34 @@ class CollectionsTable[T: CollectionTable]:
pass


class BenchmarkRunInfoTable(CollectionTable):

@classmethod
def name(cls) -> str:
return "benchmark_run_info"

@classmethod
def schema(cls) -> pl.Schema:
return pl.Schema()

@classmethod
def from_df(cls, table: pl.DataFrame) -> BenchmarkRunInfoTable:
return BenchmarkRunInfoTable(table=table)

def __init__(self, table: pl.DataFrame):
self._table = table

@property
def table(self) -> pl.DataFrame:
return self._table

def filtered_table(self) -> pl.DataFrame:
return self.table

def graphs(self) -> list[type[CollectionGraph]]:
return []


class SystemInfoTable(CollectionTable):

@classmethod
Expand Down
4 changes: 3 additions & 1 deletion python/kernmlops/kernmlops_benchmark/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from kernmlops_benchmark.memcached import MemcachedBenchmark
from kernmlops_benchmark.mongodb import MongoDbBenchmark
from kernmlops_benchmark.redis import RedisBenchmark
from kernmlops_benchmark.stress_ng import StressNgBenchmark
from kernmlops_config import ConfigBase

benchmarks: Mapping[str, type[Benchmark]] = {
Expand All @@ -27,7 +28,8 @@
MongoDbBenchmark.name(): MongoDbBenchmark,
LinnosBenchmark.name(): LinnosBenchmark,
RedisBenchmark.name(): RedisBenchmark,
MemcachedBenchmark.name(): MemcachedBenchmark
MemcachedBenchmark.name(): MemcachedBenchmark,
StressNgBenchmark.name(): StressNgBenchmark,
}

BenchmarkConfig = make_dataclass(
Expand Down
18 changes: 14 additions & 4 deletions python/kernmlops/kernmlops_benchmark/benchmark.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Abstract definition of a benchmark."""
from __future__ import annotations

import os
import subprocess
Expand Down Expand Up @@ -62,8 +63,6 @@ def generic_setup(self):
)




@dataclass(frozen=True)
class FauxBenchmarkConfig(ConfigBase):
pass
Expand All @@ -72,14 +71,18 @@ class FauxBenchmarkConfig(ConfigBase):
class Benchmark(Protocol):
"""Runnable benchmark that terminates naturally in finite time."""

def __init__(self):
self.start_timestamp: int = 0
self.finish_timestamp: int = 0

@classmethod
def name(cls) -> str: ...

@classmethod
def default_config(cls) -> ConfigBase: ...

@classmethod
def from_config(cls, config: ConfigBase) -> "Benchmark": ...
def from_config(cls, config: ConfigBase) -> Benchmark: ...

def is_configured(self) -> bool:
"""Returns True if the environment has been setup to run the benchmark."""
Expand All @@ -102,6 +105,13 @@ def plot_events(cls, graph_engine: GraphEngine) -> None:
"""Given a collection of data, plot important events for this benchmark."""
...

def to_run_info_dict(self) -> dict[str, list]:
return {
"benchmark": [self.name()],
"start_ts_us": [self.start_timestamp],
"finish_ts_us": [self.finish_timestamp],
}


class FauxBenchmark(Benchmark):
"""Benchmark that does nothing and allows users to collect the running system's data."""
Expand All @@ -115,7 +125,7 @@ def default_config(cls) -> ConfigBase:
return FauxBenchmarkConfig()

@classmethod
def from_config(cls, config: ConfigBase) -> "Benchmark":
def from_config(cls, config: ConfigBase) -> Benchmark:
generic_config = cast(GenericBenchmarkConfig, getattr(config, "generic"))
faux_config = cast(FauxBenchmarkConfig, getattr(config, cls.name()))
return FauxBenchmark(generic_config=generic_config, config=faux_config)
Expand Down
12 changes: 12 additions & 0 deletions python/kernmlops/kernmlops_benchmark/gap.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import subprocess
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Literal, cast
Expand Down Expand Up @@ -41,6 +42,7 @@ def __init__(self, *, generic_config: GenericBenchmarkConfig, config: GapBenchma
self.config = config
self.benchmark_dir = self.generic_config.get_benchmark_dir() / self.name()
self.process: subprocess.Popen | None = None
super().__init__()

def get_input_file_path(self) -> Path:
return Path(self.benchmark_dir / "graphs" / f"kron{self.config.gap_benchmark_size}.sg")
Expand Down Expand Up @@ -77,10 +79,12 @@ def run(self) -> None:
preexec_fn=demote(),
stdout=subprocess.DEVNULL,
)
self.start_timestamp = int(time.clock_gettime_ns(time.CLOCK_BOOTTIME) / 1000)

def poll(self) -> int | None:
if self.process is None:
raise BenchmarkNotRunningError()
self.finish_timestamp = int(time.clock_gettime_ns(time.CLOCK_BOOTTIME) / 1000)
return self.process.poll()

def wait(self) -> None:
Expand All @@ -92,9 +96,17 @@ def kill(self) -> None:
if self.process is None:
raise BenchmarkNotRunningError()
self.process.terminate()
self.finish_timestamp = int(time.clock_gettime_ns(time.CLOCK_BOOTTIME) / 1000)

@classmethod
def plot_events(cls, graph_engine: GraphEngine) -> None:
if graph_engine.collection_data.benchmark != cls.name():
raise BenchmarkNotInCollectionData()
# TODO(Patrick): plot when a trial starts/ends

def to_run_info_dict(self) -> dict[str, list]:
return {
"benchmark": [self.name()],
"start_ts_us": [self.start_timestamp],
"finish_ts_us": [self.finish_timestamp],
}
8 changes: 7 additions & 1 deletion python/kernmlops/kernmlops_benchmark/linnos.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from __future__ import annotations

import subprocess
import time
from dataclasses import dataclass, field
from random import shuffle
from typing import Literal, cast
Expand Down Expand Up @@ -41,7 +44,7 @@ def default_config(cls) -> ConfigBase:
return LinnosBenchmarkConfig()

@classmethod
def from_config(cls, config: ConfigBase) -> "Benchmark":
def from_config(cls, config: ConfigBase) -> Benchmark:
generic_config = cast(GenericBenchmarkConfig, getattr(config, "generic"))
linnos_config = cast(LinnosBenchmarkConfig, getattr(config, cls.name()))
return LinnosBenchmark(generic_config=generic_config, config=linnos_config)
Expand Down Expand Up @@ -107,10 +110,12 @@ def run(self) -> None:
preexec_fn=demote(),
stdout=subprocess.DEVNULL,
)
self.finish_timestamp = int(time.clock_gettime_ns(time.CLOCK_BOOTTIME) / 1000)

def poll(self) -> int | None:
if self.process is None:
raise BenchmarkNotRunningError()
self.finish_timestamp = int(time.clock_gettime_ns(time.CLOCK_BOOTTIME) / 1000)
return self.process.poll()

def wait(self) -> None:
Expand All @@ -122,6 +127,7 @@ def kill(self) -> None:
if self.process is None:
raise BenchmarkNotRunningError()
self.process.terminate()
self.finish_timestamp = int(time.clock_gettime_ns(time.CLOCK_BOOTTIME) / 1000)

@classmethod
def plot_events(cls, graph_engine: GraphEngine) -> None:
Expand Down
10 changes: 10 additions & 0 deletions python/kernmlops/kernmlops_benchmark/linux_build.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import subprocess
import time
from dataclasses import dataclass
from typing import cast

Expand Down Expand Up @@ -73,10 +74,12 @@ def run(self) -> None:
preexec_fn=demote(),
stdout=subprocess.DEVNULL,
)
self.start_timestamp = int(time.clock_gettime_ns(time.CLOCK_BOOTTIME) / 1000)

def poll(self) -> int | None:
if self.process is None:
raise BenchmarkNotRunningError()
self.finish_timestamp = int(time.clock_gettime_ns(time.CLOCK_BOOTTIME) / 1000)
return self.process.poll()

def wait(self) -> None:
Expand All @@ -102,3 +105,10 @@ def plot_events(cls, graph_engine: GraphEngine) -> None:
graph_engine.plot_event_as_sec(ts_us=file_data.get_last_occurrence_us("vmlinux.bin"))
graph_engine.plot_event_as_sec(ts_us=file_data.get_last_occurrence_us("vmlinux.o"))
graph_engine.plot_event_as_sec(ts_us=file_data.get_last_occurrence_us("vmlinux"))

def to_run_info_dict(self) -> dict[str, list]:
return {
"benchmark": [self.name()],
"start_ts_us": [self.start_timestamp],
"finish_ts_us": [self.finish_timestamp],
}
5 changes: 5 additions & 0 deletions python/kernmlops/kernmlops_benchmark/memcached.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import signal
import subprocess
import time
Expand Down Expand Up @@ -172,6 +174,7 @@ def run(self) -> None:
f"target={self.config.target}"
]
self.process = subprocess.Popen(run_memcached, preexec_fn=demote())
self.finish_timestamp = int(time.clock_gettime_ns(time.CLOCK_BOOTTIME) / 1000)

def poll(self) -> int | None:
if self.process is None:
Expand All @@ -180,6 +183,7 @@ def poll(self) -> int | None:
if ret is None:
return ret
self.end_server()
self.finish_timestamp = int(time.clock_gettime_ns(time.CLOCK_BOOTTIME) / 1000)
return ret

def wait(self) -> None:
Expand All @@ -193,6 +197,7 @@ def kill(self) -> None:
raise BenchmarkNotRunningError()
self.process.terminate()
self.end_server()
self.finish_timestamp = int(time.clock_gettime_ns(time.CLOCK_BOOTTIME) / 1000)

def end_server(self) -> None:
if self.server is None:
Expand Down
Loading
Loading