greenmining 1.0.5__py3-none-any.whl → 1.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- greenmining/__init__.py +54 -2
- greenmining/analyzers/__init__.py +9 -0
- greenmining/analyzers/metrics_power_correlator.py +165 -0
- greenmining/analyzers/power_regression.py +212 -0
- greenmining/analyzers/version_power_analyzer.py +246 -0
- greenmining/config.py +46 -34
- greenmining/dashboard/__init__.py +5 -0
- greenmining/dashboard/app.py +200 -0
- greenmining/energy/__init__.py +8 -1
- greenmining/energy/base.py +45 -35
- greenmining/energy/carbon_reporter.py +242 -0
- greenmining/energy/codecarbon_meter.py +25 -24
- greenmining/energy/cpu_meter.py +144 -0
- greenmining/energy/rapl.py +30 -36
- greenmining/services/__init__.py +13 -3
- greenmining/services/commit_extractor.py +9 -5
- greenmining/services/local_repo_analyzer.py +325 -63
- greenmining/services/reports.py +5 -8
- {greenmining-1.0.5.dist-info → greenmining-1.0.7.dist-info}/METADATA +212 -43
- {greenmining-1.0.5.dist-info → greenmining-1.0.7.dist-info}/RECORD +23 -16
- {greenmining-1.0.5.dist-info → greenmining-1.0.7.dist-info}/WHEEL +0 -0
- {greenmining-1.0.5.dist-info → greenmining-1.0.7.dist-info}/licenses/LICENSE +0 -0
- {greenmining-1.0.5.dist-info → greenmining-1.0.7.dist-info}/top_level.txt +0 -0
greenmining/energy/rapl.py
CHANGED
|
@@ -13,9 +13,9 @@ from .base import EnergyMeter, EnergyMetrics, EnergyBackend
|
|
|
13
13
|
|
|
14
14
|
class RAPLEnergyMeter(EnergyMeter):
|
|
15
15
|
# Energy measurement using Intel RAPL on Linux.
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
RAPL_PATH = Path("/sys/class/powercap/intel-rapl")
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
def __init__(self):
|
|
20
20
|
# Initialize RAPL energy meter.
|
|
21
21
|
super().__init__(EnergyBackend.RAPL)
|
|
@@ -24,12 +24,12 @@ class RAPLEnergyMeter(EnergyMeter):
|
|
|
24
24
|
self._start_time: Optional[float] = None
|
|
25
25
|
self._power_samples: List[float] = []
|
|
26
26
|
self._discover_domains()
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
def _discover_domains(self) -> None:
|
|
29
29
|
# Discover available RAPL domains.
|
|
30
30
|
if not self.RAPL_PATH.exists():
|
|
31
31
|
return
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
# Find all RAPL domains (intel-rapl:0, intel-rapl:0:0, etc.)
|
|
34
34
|
for domain_path in self.RAPL_PATH.glob("intel-rapl:*"):
|
|
35
35
|
if (domain_path / "energy_uj").exists():
|
|
@@ -39,9 +39,9 @@ class RAPLEnergyMeter(EnergyMeter):
|
|
|
39
39
|
domain_name = name_file.read_text().strip()
|
|
40
40
|
else:
|
|
41
41
|
domain_name = domain_path.name
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
self._domains[domain_name] = domain_path / "energy_uj"
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
# Check for sub-domains (core, uncore, dram, etc.)
|
|
46
46
|
for subdomain_path in domain_path.glob("intel-rapl:*:*"):
|
|
47
47
|
if (subdomain_path / "energy_uj").exists():
|
|
@@ -50,24 +50,24 @@ class RAPLEnergyMeter(EnergyMeter):
|
|
|
50
50
|
subdomain_name = name_file.read_text().strip()
|
|
51
51
|
else:
|
|
52
52
|
subdomain_name = subdomain_path.name
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
self._domains[subdomain_name] = subdomain_path / "energy_uj"
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
def _read_energy(self, path: Path) -> int:
|
|
57
57
|
# Read energy value in microjoules from a RAPL file.
|
|
58
58
|
try:
|
|
59
59
|
return int(path.read_text().strip())
|
|
60
60
|
except (PermissionError, FileNotFoundError, ValueError):
|
|
61
61
|
return 0
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
def is_available(self) -> bool:
|
|
64
64
|
# Check if RAPL is available on this system.
|
|
65
65
|
if not self.RAPL_PATH.exists():
|
|
66
66
|
return False
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
if not self._domains:
|
|
69
69
|
return False
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
# Try to read at least one domain
|
|
72
72
|
for path in self._domains.values():
|
|
73
73
|
try:
|
|
@@ -75,68 +75,62 @@ class RAPLEnergyMeter(EnergyMeter):
|
|
|
75
75
|
return True
|
|
76
76
|
except Exception:
|
|
77
77
|
continue
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
return False
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
def start(self) -> None:
|
|
82
82
|
# Start energy measurement.
|
|
83
83
|
if self._is_measuring:
|
|
84
84
|
raise RuntimeError("Already measuring energy")
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
self._is_measuring = True
|
|
87
87
|
self._start_time = time.time()
|
|
88
88
|
self._power_samples = []
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
# Read starting energy values for all domains
|
|
91
|
-
self._start_energy = {
|
|
92
|
-
|
|
93
|
-
for name, path in self._domains.items()
|
|
94
|
-
}
|
|
95
|
-
|
|
91
|
+
self._start_energy = {name: self._read_energy(path) for name, path in self._domains.items()}
|
|
92
|
+
|
|
96
93
|
def stop(self) -> EnergyMetrics:
|
|
97
94
|
# Stop energy measurement and return results.
|
|
98
95
|
if not self._is_measuring:
|
|
99
96
|
raise RuntimeError("Not currently measuring energy")
|
|
100
|
-
|
|
97
|
+
|
|
101
98
|
end_time = time.time()
|
|
102
99
|
self._is_measuring = False
|
|
103
|
-
|
|
100
|
+
|
|
104
101
|
# Read ending energy values
|
|
105
|
-
end_energy = {
|
|
106
|
-
|
|
107
|
-
for name, path in self._domains.items()
|
|
108
|
-
}
|
|
109
|
-
|
|
102
|
+
end_energy = {name: self._read_energy(path) for name, path in self._domains.items()}
|
|
103
|
+
|
|
110
104
|
# Calculate energy consumption per domain (in joules)
|
|
111
105
|
duration = end_time - self._start_time
|
|
112
|
-
|
|
106
|
+
|
|
113
107
|
# Handle counter wrap-around (RAPL counters are typically 32-bit)
|
|
114
108
|
MAX_ENERGY_UJ = 2**32
|
|
115
|
-
|
|
109
|
+
|
|
116
110
|
domain_energy = {}
|
|
117
111
|
for name in self._domains:
|
|
118
112
|
start = self._start_energy.get(name, 0)
|
|
119
113
|
end = end_energy.get(name, 0)
|
|
120
|
-
|
|
114
|
+
|
|
121
115
|
if end >= start:
|
|
122
116
|
delta_uj = end - start
|
|
123
117
|
else:
|
|
124
118
|
# Counter wrapped around
|
|
125
119
|
delta_uj = (MAX_ENERGY_UJ - start) + end
|
|
126
|
-
|
|
120
|
+
|
|
127
121
|
domain_energy[name] = delta_uj / 1_000_000 # Convert to joules
|
|
128
|
-
|
|
122
|
+
|
|
129
123
|
# Aggregate metrics
|
|
130
124
|
total_joules = sum(domain_energy.values())
|
|
131
|
-
|
|
125
|
+
|
|
132
126
|
# Extract component-specific energy
|
|
133
127
|
cpu_energy = domain_energy.get("core", 0) or domain_energy.get("package-0", total_joules)
|
|
134
128
|
dram_energy = domain_energy.get("dram", 0)
|
|
135
129
|
gpu_energy = domain_energy.get("uncore", None) # Integrated GPU
|
|
136
|
-
|
|
130
|
+
|
|
137
131
|
# Calculate power
|
|
138
132
|
watts_avg = total_joules / duration if duration > 0 else 0
|
|
139
|
-
|
|
133
|
+
|
|
140
134
|
return EnergyMetrics(
|
|
141
135
|
joules=total_joules,
|
|
142
136
|
watts_avg=watts_avg,
|
|
@@ -151,7 +145,7 @@ class RAPLEnergyMeter(EnergyMeter):
|
|
|
151
145
|
start_time=datetime.fromtimestamp(self._start_time),
|
|
152
146
|
end_time=datetime.fromtimestamp(end_time),
|
|
153
147
|
)
|
|
154
|
-
|
|
148
|
+
|
|
155
149
|
def get_available_domains(self) -> List[str]:
|
|
156
150
|
# Get list of available RAPL domains.
|
|
157
151
|
return list(self._domains.keys())
|
greenmining/services/__init__.py
CHANGED
|
@@ -3,15 +3,25 @@
|
|
|
3
3
|
from .commit_extractor import CommitExtractor
|
|
4
4
|
from .data_aggregator import DataAggregator
|
|
5
5
|
from .data_analyzer import DataAnalyzer
|
|
6
|
-
from .
|
|
7
|
-
from .local_repo_analyzer import
|
|
6
|
+
from .github_graphql_fetcher import GitHubGraphQLFetcher
|
|
7
|
+
from .local_repo_analyzer import (
|
|
8
|
+
LocalRepoAnalyzer,
|
|
9
|
+
CommitAnalysis,
|
|
10
|
+
RepositoryAnalysis,
|
|
11
|
+
MethodMetrics,
|
|
12
|
+
SourceCodeChange,
|
|
13
|
+
)
|
|
8
14
|
from .reports import ReportGenerator
|
|
9
15
|
|
|
10
16
|
__all__ = [
|
|
11
|
-
"
|
|
17
|
+
"GitHubGraphQLFetcher",
|
|
12
18
|
"CommitExtractor",
|
|
13
19
|
"DataAnalyzer",
|
|
14
20
|
"DataAggregator",
|
|
15
21
|
"ReportGenerator",
|
|
16
22
|
"LocalRepoAnalyzer",
|
|
23
|
+
"CommitAnalysis",
|
|
24
|
+
"RepositoryAnalysis",
|
|
25
|
+
"MethodMetrics",
|
|
26
|
+
"SourceCodeChange",
|
|
17
27
|
]
|
|
@@ -41,7 +41,9 @@ class CommitExtractor:
|
|
|
41
41
|
self.github = Github(github_token) if github_token else None
|
|
42
42
|
self.timeout = timeout
|
|
43
43
|
|
|
44
|
-
def extract_from_repositories(
|
|
44
|
+
def extract_from_repositories(
|
|
45
|
+
self, repositories: list[dict[str, Any] | Repository]
|
|
46
|
+
) -> list[dict[str, Any]]:
|
|
45
47
|
# Extract commits from list of repositories.
|
|
46
48
|
all_commits = []
|
|
47
49
|
failed_repos = []
|
|
@@ -74,15 +76,17 @@ class CommitExtractor:
|
|
|
74
76
|
pbar.update(1)
|
|
75
77
|
except TimeoutError:
|
|
76
78
|
signal.alarm(0) # Cancel alarm
|
|
77
|
-
repo_name =
|
|
78
|
-
|
|
79
|
-
f"\nTimeout processing {repo_name} (>{self.timeout}s)", "yellow"
|
|
79
|
+
repo_name = (
|
|
80
|
+
repo.full_name if isinstance(repo, Repository) else repo["full_name"]
|
|
80
81
|
)
|
|
82
|
+
colored_print(f"\nTimeout processing {repo_name} (>{self.timeout}s)", "yellow")
|
|
81
83
|
failed_repos.append(repo_name)
|
|
82
84
|
pbar.update(1)
|
|
83
85
|
except Exception as e:
|
|
84
86
|
signal.alarm(0) # Cancel alarm
|
|
85
|
-
repo_name =
|
|
87
|
+
repo_name = (
|
|
88
|
+
repo.full_name if isinstance(repo, Repository) else repo["full_name"]
|
|
89
|
+
)
|
|
86
90
|
colored_print(f"\nError processing {repo_name}: {e}", "yellow")
|
|
87
91
|
failed_repos.append(repo_name)
|
|
88
92
|
pbar.update(1)
|