Skip to content
Open
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
2 changes: 1 addition & 1 deletion .idea/chronus.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions .idea/runConfigurations/chronos.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions assets/images/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion chronus/SystemIntegration/FileRepository.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import io
from pprint import pprint

from chronus.model.Run import Run
from chronus.domain.Run import Run
from chronus.SystemIntegration.repository import Repository


Expand Down
61 changes: 61 additions & 0 deletions chronus/SystemIntegration/cpu_info_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from typing import List

import re
import subprocess

from chronus.domain.interfaces.cpu_info_service_interface import CpuInfo, CpuInfoServiceInterface


class LsCpuInfoService(CpuInfoServiceInterface):
def get_cpu_info(self) -> CpuInfo:
# Run lscpu and parse the model name
output = subprocess.run("lscpu", stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

if output.returncode != 0:
print(output.returncode)
raise RuntimeError("Failed to run lscpu")
# Regex that finds Model Name: <model name>
model_name = re.search(r"Model name:\s+(.*)", output.stdout)

if model_name is None:
return CpuInfo("Unknown")

return CpuInfo(model_name.group(1))

def get_frequencies(self) -> List[float]:
output = subprocess.run(
"cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_available_frequencies",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)

if output.returncode != 0:
raise RuntimeError(
"Failed to read /sys/devices/system/cpu/cpu*/cpufreq/scaling_available_frequencies"
)

rows = output.stdout.split("\n")
frequencies_str = [row.split(" ") for row in rows]

# find the intersection of all the frequencies
intersection = set.intersection(*map(set, frequencies_str))

frequencies = [float(frequency) for frequency in intersection]
frequencies.sort()

return frequencies

def get_cores(self) -> int:
output = subprocess.run("lscpu", stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

if output.returncode != 0:
raise RuntimeError("Failed to run lscpu")

# Regex that finds Model Name: <model name>
cores = re.search(r"CPU\(s\):\s+(.*)", output.stdout)

if cores is None:
return 0

return int(cores.group(1))
2 changes: 1 addition & 1 deletion chronus/SystemIntegration/hpcg.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
from pprint import pprint

from chronus.model.Run import Run
from chronus.domain.Run import Run


class HpcgService:
Expand Down
2 changes: 1 addition & 1 deletion chronus/SystemIntegration/repository.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from chronus.model import Run
from chronus.domain.Run import Run


class Repository:
Expand Down
16 changes: 13 additions & 3 deletions chronus/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
from rich.pretty import pprint

from chronus import version
from chronus.cli import fake_data, plot_energy
from chronus.model.svm import config_model
from chronus.SystemIntegration.cpu_info_service import LsCpuInfoService
from chronus.SystemIntegration.FileRepository import FileRepository
from chronus.SystemIntegration.hpcg import HpcgService
from chronus.SystemIntegration.repository import Repository
from chronus.vis import plot_energy, fake_data

name_as_grad = "^[[38;2;244;59;71mc^[[39m^[[38;2;215;59;84mh^[[39m^[[38;2;186;59;97mr^[[39m^[[38;2;157;59;110mo^[[39m^[[38;2;127;58;122mn^[[39m^[[38;2;98;58;135mu^[[39m^[[38;2;69;58;148ms^[[39m"
name = "chronus"
Expand Down Expand Up @@ -138,15 +139,24 @@ def solver():

pprint(best_config)

@app.command(name="slurm-config-json")
def get_config(cpu: str = typer.Argument(..., help="The cpu model to get the config for")):

@app.command(name="slurm-config")
def get_config(cpu: str = typer.Argument(..., help="The cpu model to get the config for")):
config = {
"cores": 2,
"frequency": 2_200_000,
}

print(json.dumps(config))


@app.command(name="cpu")
def debug():
cpu_service = LsCpuInfoService()
pprint(f"CPU: {cpu_service.get_cpu_info().cpu}")
pprint(f"Frequencies: {cpu_service.get_frequencies()}")
pprint(f"Cores: {cpu_service.get_cores()}")


if __name__ == "__main__":
app()
2 changes: 1 addition & 1 deletion chronus/vis/__init__.py → chronus/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import numpy as np
from mpl_toolkits.mplot3d import Axes3D

from chronus.model.Run import Run
from chronus.domain.Run import Run


def fake_data() -> list[Run]:
Expand Down
40 changes: 40 additions & 0 deletions chronus/domain/Run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from dataclasses import dataclass

from dataclasses_json import dataclass_json

from chronus.domain.system_sample import SystemSample


@dataclass_json
@dataclass(init=True, repr=True, eq=True, order=True)
class Run:
_samples: list[SystemSample] = None
cpu: str = ""
cores: int = 0
frequency: float = 0.0
gflops: float = 0.0
_joules_used: float = 0.0

def __post_init__(self):
self._samples = []

@property
def gflops_per_watt(self) -> float:
return self.gflops / self.watts

@property
def energy_used(self) -> float:
energy = 0.0
previous_timestamp = self._samples[0].timestamp if len(self._samples) > 0 else None
previous_power_draw = (
self._samples[0].current_power_draw if len(self._samples) > 0 else None
)
for sample in self._samples:
time_delta = (sample.timestamp - previous_timestamp).total_seconds()
average_power = (sample.current_power_draw + previous_power_draw) / 2
energy += average_power * time_delta
previous_timestamp = sample.timestamp
return energy

def add_sample(self, sample: SystemSample):
self._samples.append(sample)
52 changes: 52 additions & 0 deletions chronus/domain/benchmark_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import time

from chronus.domain.configuration import Configurations
from chronus.domain.interfaces.application_runner_interface import ApplicationRunnerInterface
from chronus.domain.interfaces.benchmark_run_repository_interface import (
BenchmarkRunRepositoryInterface,
)
from chronus.domain.interfaces.cpu_info_service_interface import CpuInfoServiceInterface
from chronus.domain.interfaces.system_service_interface import SystemServiceInterface
from chronus.domain.Run import Run


class BenchmarkService:
cpu: str
gflops: float
cpu_info_service: CpuInfoServiceInterface
application_runner: ApplicationRunnerInterface
system_service: SystemServiceInterface
run_repository: BenchmarkRunRepositoryInterface
frequency = 20

def __init__(
self,
cpu_info_service: CpuInfoServiceInterface,
application_runner: ApplicationRunnerInterface,
system_service: SystemServiceInterface,
benchmark_repository: BenchmarkRunRepositoryInterface,
):
self.energy_used = 0.0
self.cpu_info_service = cpu_info_service
self.application_runner = application_runner
self.system_service = system_service
self.run_repository = benchmark_repository
self.gflops = 0.0

def run(self):
cpu = self.cpu_info_service.get_cpu_info().cpu
cores = self.cpu_info_service.get_cores()
frequencies = self.cpu_info_service.get_frequencies()

configurations = Configurations(cores, frequencies)
for configuration in configurations:
run = Run(cpu=cpu, cores=configuration.cores, frequency=configuration.frequency)
self.application_runner.run(configuration.cores, configuration.frequency)
while self.application_runner.is_running():
sample = self.system_service.sample()
run.add_sample(sample)
time.sleep(1)

run.add_sample(self.system_service.sample())
run.gflops = self.application_runner.gflops
self.run_repository.save(run)
43 changes: 43 additions & 0 deletions chronus/domain/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import List

from dataclasses import dataclass


@dataclass
class Configuration:
cores: int
frequency: float


def make_core_interval(cores_number: int):
cores = []
counter = 1
while counter <= cores_number:
cores.append(counter)
counter *= 2

return cores


def make_configurations(cores_number: int, frequencies: List[float]):
cores = make_core_interval(cores_number)

configurations = []
for core in cores:
for frequency in frequencies:
configurations.append(Configuration(core, frequency))
return configurations


class Configurations:
def __init__(self, cores: int, frequencies: List[float]):
self.__configurations = make_configurations(cores, frequencies)

def __iter__(self):
return iter(self.__configurations)

def __len__(self):
return len(self.__configurations)

def __getitem__(self, index):
return self.__configurations[index]
Empty file.
8 changes: 8 additions & 0 deletions chronus/domain/interfaces/application_runner_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class ApplicationRunnerInterface:
gflops: float

def run(self, cores: int, frequency: float):
pass

def is_running(self) -> bool:
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class BenchmarkRunRepositoryInterface:
def save(self, benchmark) -> None:
pass
20 changes: 20 additions & 0 deletions chronus/domain/interfaces/cpu_info_service_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import List

from dataclasses import dataclass


@dataclass
class CpuInfo:
cpu: str
pass


class CpuInfoServiceInterface:
def get_cpu_info(self) -> CpuInfo:
pass

def get_frequencies(self) -> List[float]:
pass

def get_cores(self) -> int:
pass
6 changes: 6 additions & 0 deletions chronus/domain/interfaces/system_service_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from chronus.domain.system_sample import SystemSample


class SystemServiceInterface:
def sample(self) -> SystemSample:
pass
8 changes: 8 additions & 0 deletions chronus/domain/system_sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import datetime
from dataclasses import dataclass


@dataclass
class SystemSample:
timestamp: datetime.datetime
current_power_draw: float
18 changes: 0 additions & 18 deletions chronus/model/Run.py

This file was deleted.

2 changes: 1 addition & 1 deletion chronus/model/svm.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.svm import SVR

from chronus.model.Run import Run
from chronus.domain.Run import Run

param_grid = {
"C": [0.1, 1, 10],
Expand Down
Loading