greenmining 1.2.4__py3-none-any.whl → 1.2.5__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 +1 -1
- greenmining/analyzers/__init__.py +1 -1
- greenmining/analyzers/code_diff_analyzer.py +6 -6
- greenmining/analyzers/metrics_power_correlator.py +17 -17
- greenmining/analyzers/statistical_analyzer.py +5 -5
- greenmining/analyzers/temporal_analyzer.py +16 -17
- greenmining/controllers/repository_controller.py +1 -3
- greenmining/energy/__init__.py +3 -3
- greenmining/energy/base.py +15 -16
- greenmining/energy/carbon_reporter.py +10 -10
- greenmining/energy/codecarbon_meter.py +4 -6
- greenmining/energy/cpu_meter.py +6 -7
- greenmining/energy/rapl.py +6 -8
- greenmining/models/aggregated_stats.py +2 -3
- greenmining/models/commit.py +2 -2
- greenmining/models/repository.py +5 -6
- greenmining/services/__init__.py +2 -2
- greenmining/services/github_graphql_fetcher.py +8 -8
- greenmining/services/local_repo_analyzer.py +41 -39
- greenmining/services/reports.py +1 -1
- {greenmining-1.2.4.dist-info → greenmining-1.2.5.dist-info}/METADATA +1 -1
- greenmining-1.2.5.dist-info/RECORD +34 -0
- greenmining-1.2.4.dist-info/RECORD +0 -34
- {greenmining-1.2.4.dist-info → greenmining-1.2.5.dist-info}/WHEEL +0 -0
- {greenmining-1.2.4.dist-info → greenmining-1.2.5.dist-info}/licenses/LICENSE +0 -0
- {greenmining-1.2.4.dist-info → greenmining-1.2.5.dist-info}/top_level.txt +0 -0
greenmining/__init__.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Analyzers for GreenMining framework.
|
|
2
2
|
|
|
3
3
|
from .code_diff_analyzer import CodeDiffAnalyzer
|
|
4
|
+
from .metrics_power_correlator import CorrelationResult, MetricsPowerCorrelator
|
|
4
5
|
from .statistical_analyzer import StatisticalAnalyzer
|
|
5
6
|
from .temporal_analyzer import TemporalAnalyzer
|
|
6
|
-
from .metrics_power_correlator import MetricsPowerCorrelator, CorrelationResult
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
9
|
"CodeDiffAnalyzer",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Code diff analyzer for detecting green software patterns in code changes.
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
from pydriller import Commit, ModifiedFile
|
|
7
7
|
|
|
@@ -207,7 +207,7 @@ class CodeDiffAnalyzer:
|
|
|
207
207
|
},
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
def analyze_commit_diff(self, commit: Commit) ->
|
|
210
|
+
def analyze_commit_diff(self, commit: Commit) -> dict[str, Any]:
|
|
211
211
|
# Analyze code changes in a commit to detect green patterns.
|
|
212
212
|
patterns_detected = []
|
|
213
213
|
evidence = {}
|
|
@@ -244,12 +244,12 @@ class CodeDiffAnalyzer:
|
|
|
244
244
|
"metrics": metrics,
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
def _detect_patterns_in_line(self, code_line: str) ->
|
|
247
|
+
def _detect_patterns_in_line(self, code_line: str) -> list[str]:
|
|
248
248
|
# Detect patterns in a single line of code.
|
|
249
249
|
detected = []
|
|
250
250
|
|
|
251
251
|
for pattern_name, signatures in self.PATTERN_SIGNATURES.items():
|
|
252
|
-
for
|
|
252
|
+
for _signature_type, patterns in signatures.items():
|
|
253
253
|
for pattern_regex in patterns:
|
|
254
254
|
if re.search(pattern_regex, code_line, re.IGNORECASE):
|
|
255
255
|
detected.append(pattern_name)
|
|
@@ -257,7 +257,7 @@ class CodeDiffAnalyzer:
|
|
|
257
257
|
|
|
258
258
|
return detected
|
|
259
259
|
|
|
260
|
-
def _calculate_metrics(self, commit: Commit) ->
|
|
260
|
+
def _calculate_metrics(self, commit: Commit) -> dict[str, int]:
|
|
261
261
|
# Calculate code change metrics.
|
|
262
262
|
lines_added = sum(f.added_lines for f in commit.modified_files)
|
|
263
263
|
lines_removed = sum(f.deleted_lines for f in commit.modified_files)
|
|
@@ -276,7 +276,7 @@ class CodeDiffAnalyzer:
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
def _calculate_diff_confidence(
|
|
279
|
-
self, patterns:
|
|
279
|
+
self, patterns: list[str], evidence: dict[str, list[str]], metrics: dict[str, int]
|
|
280
280
|
) -> str:
|
|
281
281
|
# Calculate confidence level for diff-based detection.
|
|
282
282
|
if not patterns:
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from typing import Any
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
from scipy import stats
|
|
@@ -22,7 +22,7 @@ class CorrelationResult:
|
|
|
22
22
|
significant: bool = False
|
|
23
23
|
strength: str = "none"
|
|
24
24
|
|
|
25
|
-
def to_dict(self) ->
|
|
25
|
+
def to_dict(self) -> dict[str, Any]:
|
|
26
26
|
return {
|
|
27
27
|
"metric_name": self.metric_name,
|
|
28
28
|
"pearson_r": round(self.pearson_r, 4),
|
|
@@ -44,17 +44,17 @@ class MetricsPowerCorrelator:
|
|
|
44
44
|
# Args:
|
|
45
45
|
# significance_level: P-value threshold for significance
|
|
46
46
|
self.significance_level = significance_level
|
|
47
|
-
self._metrics_data:
|
|
48
|
-
self._power_data:
|
|
47
|
+
self._metrics_data: dict[str, list[float]] = {}
|
|
48
|
+
self._power_data: list[float] = []
|
|
49
49
|
self._fitted = False
|
|
50
|
-
self._results:
|
|
51
|
-
self._feature_importance:
|
|
50
|
+
self._results: dict[str, CorrelationResult] = {}
|
|
51
|
+
self._feature_importance: dict[str, float] = {}
|
|
52
52
|
|
|
53
53
|
def fit(
|
|
54
54
|
self,
|
|
55
|
-
metrics:
|
|
56
|
-
metrics_values:
|
|
57
|
-
power_measurements:
|
|
55
|
+
metrics: list[str],
|
|
56
|
+
metrics_values: dict[str, list[float]],
|
|
57
|
+
power_measurements: list[float],
|
|
58
58
|
) -> None:
|
|
59
59
|
# Fit the correlator with metrics and power data.
|
|
60
60
|
# Args:
|
|
@@ -86,7 +86,7 @@ class MetricsPowerCorrelator:
|
|
|
86
86
|
self._fitted = True
|
|
87
87
|
|
|
88
88
|
def _compute_correlation(
|
|
89
|
-
self, metric_name: str, metric_values:
|
|
89
|
+
self, metric_name: str, metric_values: list[float], power_values: list[float]
|
|
90
90
|
) -> CorrelationResult:
|
|
91
91
|
# Compute Pearson and Spearman correlations for a single metric.
|
|
92
92
|
x = np.array(metric_values, dtype=float)
|
|
@@ -127,29 +127,29 @@ class MetricsPowerCorrelator:
|
|
|
127
127
|
)
|
|
128
128
|
|
|
129
129
|
@property
|
|
130
|
-
def pearson(self) ->
|
|
130
|
+
def pearson(self) -> dict[str, float]:
|
|
131
131
|
# Get Pearson correlations for all metrics.
|
|
132
132
|
return {name: r.pearson_r for name, r in self._results.items()}
|
|
133
133
|
|
|
134
134
|
@property
|
|
135
|
-
def spearman(self) ->
|
|
135
|
+
def spearman(self) -> dict[str, float]:
|
|
136
136
|
# Get Spearman correlations for all metrics.
|
|
137
137
|
return {name: r.spearman_r for name, r in self._results.items()}
|
|
138
138
|
|
|
139
139
|
@property
|
|
140
|
-
def feature_importance(self) ->
|
|
140
|
+
def feature_importance(self) -> dict[str, float]:
|
|
141
141
|
# Get normalized feature importance scores.
|
|
142
142
|
return self._feature_importance
|
|
143
143
|
|
|
144
|
-
def get_results(self) ->
|
|
144
|
+
def get_results(self) -> dict[str, CorrelationResult]:
|
|
145
145
|
# Get all correlation results.
|
|
146
146
|
return self._results
|
|
147
147
|
|
|
148
|
-
def get_significant_correlations(self) ->
|
|
148
|
+
def get_significant_correlations(self) -> dict[str, CorrelationResult]:
|
|
149
149
|
# Get only statistically significant correlations.
|
|
150
150
|
return {name: r for name, r in self._results.items() if r.significant}
|
|
151
151
|
|
|
152
|
-
def summary(self) ->
|
|
152
|
+
def summary(self) -> dict[str, Any]:
|
|
153
153
|
# Generate summary of correlation analysis.
|
|
154
154
|
return {
|
|
155
155
|
"total_metrics": len(self._results),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import pandas as pd
|
|
@@ -12,7 +12,7 @@ from scipy import stats
|
|
|
12
12
|
class StatisticalAnalyzer:
|
|
13
13
|
# Advanced statistical analyses for green software patterns.
|
|
14
14
|
|
|
15
|
-
def analyze_pattern_correlations(self, commit_data: pd.DataFrame) ->
|
|
15
|
+
def analyze_pattern_correlations(self, commit_data: pd.DataFrame) -> dict[str, Any]:
|
|
16
16
|
# Analyze correlations between patterns.
|
|
17
17
|
# Create pattern co-occurrence matrix
|
|
18
18
|
pattern_columns = [col for col in commit_data.columns if col.startswith("pattern_")]
|
|
@@ -47,7 +47,7 @@ class StatisticalAnalyzer:
|
|
|
47
47
|
"interpretation": self._interpret_correlations(significant_pairs),
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
def temporal_trend_analysis(self, commits_df: pd.DataFrame) ->
|
|
50
|
+
def temporal_trend_analysis(self, commits_df: pd.DataFrame) -> dict[str, Any]:
|
|
51
51
|
# Analyze temporal trends in green awareness.
|
|
52
52
|
# Prepare time series data
|
|
53
53
|
commits_df["date"] = pd.to_datetime(commits_df["date"], utc=True, errors="coerce")
|
|
@@ -101,7 +101,7 @@ class StatisticalAnalyzer:
|
|
|
101
101
|
"monthly_data": monthly.to_dict(),
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
def effect_size_analysis(self, group1:
|
|
104
|
+
def effect_size_analysis(self, group1: list[float], group2: list[float]) -> dict[str, Any]:
|
|
105
105
|
# Calculate effect size between two groups.
|
|
106
106
|
# Cohen's d (effect size)
|
|
107
107
|
mean1, mean2 = np.mean(group1), np.mean(group2)
|
|
@@ -135,7 +135,7 @@ class StatisticalAnalyzer:
|
|
|
135
135
|
"significant": bool(p_value < 0.05),
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
def _interpret_correlations(self, significant_pairs:
|
|
138
|
+
def _interpret_correlations(self, significant_pairs: list[dict[str, Any]]) -> str:
|
|
139
139
|
# Generate interpretation of correlation results.
|
|
140
140
|
if not significant_pairs:
|
|
141
141
|
return "No significant correlations found between patterns."
|
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from datetime import datetime, timedelta
|
|
6
|
-
from typing import Dict, List, Optional, Tuple
|
|
7
|
-
from dataclasses import dataclass
|
|
8
|
-
from collections import defaultdict
|
|
9
5
|
import statistics
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from datetime import datetime, timedelta
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
@dataclass
|
|
@@ -20,7 +19,7 @@ class TemporalMetrics:
|
|
|
20
19
|
green_commit_count: int
|
|
21
20
|
green_awareness_rate: float
|
|
22
21
|
unique_patterns: int
|
|
23
|
-
dominant_pattern:
|
|
22
|
+
dominant_pattern: str | None
|
|
24
23
|
velocity: float # commits per day
|
|
25
24
|
|
|
26
25
|
|
|
@@ -44,8 +43,8 @@ class TemporalAnalyzer:
|
|
|
44
43
|
self.granularity = granularity
|
|
45
44
|
|
|
46
45
|
def group_commits_by_period(
|
|
47
|
-
self, commits:
|
|
48
|
-
) ->
|
|
46
|
+
self, commits: list[dict], date_field: str = "date"
|
|
47
|
+
) -> dict[str, list[dict]]:
|
|
49
48
|
# Group commits into time periods.
|
|
50
49
|
periods = defaultdict(list)
|
|
51
50
|
|
|
@@ -86,7 +85,7 @@ class TemporalAnalyzer:
|
|
|
86
85
|
else:
|
|
87
86
|
return date.strftime("%Y-%m")
|
|
88
87
|
|
|
89
|
-
def _parse_period_key(self, period_key: str) ->
|
|
88
|
+
def _parse_period_key(self, period_key: str) -> tuple[datetime, datetime]:
|
|
90
89
|
# Parse period key back to start and end dates.
|
|
91
90
|
if "W" in period_key:
|
|
92
91
|
# Week format: 2024-W15
|
|
@@ -138,7 +137,7 @@ class TemporalAnalyzer:
|
|
|
138
137
|
return start, end
|
|
139
138
|
|
|
140
139
|
def calculate_period_metrics(
|
|
141
|
-
self, period_key: str, commits:
|
|
140
|
+
self, period_key: str, commits: list[dict], analysis_results: list[dict]
|
|
142
141
|
) -> TemporalMetrics:
|
|
143
142
|
# Calculate metrics for a time period.
|
|
144
143
|
start_date, end_date = self._parse_period_key(period_key)
|
|
@@ -185,7 +184,7 @@ class TemporalAnalyzer:
|
|
|
185
184
|
velocity=round(velocity, 2),
|
|
186
185
|
)
|
|
187
186
|
|
|
188
|
-
def analyze_trends(self, commits:
|
|
187
|
+
def analyze_trends(self, commits: list[dict], analysis_results: list[dict]) -> dict:
|
|
189
188
|
# Comprehensive temporal trend analysis.
|
|
190
189
|
# Group by periods
|
|
191
190
|
grouped = self.group_commits_by_period(commits)
|
|
@@ -227,7 +226,7 @@ class TemporalAnalyzer:
|
|
|
227
226
|
},
|
|
228
227
|
}
|
|
229
228
|
|
|
230
|
-
def _calculate_trend(self, periods:
|
|
229
|
+
def _calculate_trend(self, periods: list[TemporalMetrics]) -> TrendAnalysis | None:
|
|
231
230
|
# Calculate linear trend using least squares regression.
|
|
232
231
|
if len(periods) < 2:
|
|
233
232
|
return None
|
|
@@ -275,7 +274,7 @@ class TemporalAnalyzer:
|
|
|
275
274
|
change_percentage=round(change, 2),
|
|
276
275
|
)
|
|
277
276
|
|
|
278
|
-
def _calculate_adoption_curve(self, periods:
|
|
277
|
+
def _calculate_adoption_curve(self, periods: list[TemporalMetrics]) -> list[tuple[str, float]]:
|
|
279
278
|
# Calculate cumulative adoption over time.
|
|
280
279
|
cumulative_green = 0
|
|
281
280
|
cumulative_total = 0
|
|
@@ -291,7 +290,7 @@ class TemporalAnalyzer:
|
|
|
291
290
|
|
|
292
291
|
return curve
|
|
293
292
|
|
|
294
|
-
def _calculate_velocity_trend(self, periods:
|
|
293
|
+
def _calculate_velocity_trend(self, periods: list[TemporalMetrics]) -> dict:
|
|
295
294
|
# Analyze velocity changes over time.
|
|
296
295
|
if not periods:
|
|
297
296
|
return {}
|
|
@@ -307,8 +306,8 @@ class TemporalAnalyzer:
|
|
|
307
306
|
}
|
|
308
307
|
|
|
309
308
|
def _analyze_pattern_evolution(
|
|
310
|
-
self, periods:
|
|
311
|
-
) ->
|
|
309
|
+
self, periods: list[TemporalMetrics], analysis_results: list[dict]
|
|
310
|
+
) -> dict:
|
|
312
311
|
# Track when different patterns emerged and dominated.
|
|
313
312
|
pattern_timeline = defaultdict(lambda: {"first_seen": None, "occurrences_by_period": {}})
|
|
314
313
|
|
|
@@ -349,7 +348,7 @@ class TemporalAnalyzer:
|
|
|
349
348
|
for pattern, data in pattern_timeline.items()
|
|
350
349
|
}
|
|
351
350
|
|
|
352
|
-
def _metrics_to_dict(self, metrics: TemporalMetrics) ->
|
|
351
|
+
def _metrics_to_dict(self, metrics: TemporalMetrics) -> dict:
|
|
353
352
|
# Convert TemporalMetrics to dictionary.
|
|
354
353
|
return {
|
|
355
354
|
"period": metrics.period,
|
|
@@ -363,7 +362,7 @@ class TemporalAnalyzer:
|
|
|
363
362
|
"velocity": metrics.velocity,
|
|
364
363
|
}
|
|
365
364
|
|
|
366
|
-
def _trend_to_dict(self, trend:
|
|
365
|
+
def _trend_to_dict(self, trend: TrendAnalysis | None) -> dict:
|
|
367
366
|
# Convert TrendAnalysis to dictionary.
|
|
368
367
|
if not trend:
|
|
369
368
|
return {}
|
|
@@ -41,9 +41,7 @@ class RepositoryController:
|
|
|
41
41
|
f" Created: {created_after or 'any'} to {created_before or 'any'}", "cyan"
|
|
42
42
|
)
|
|
43
43
|
if pushed_after or pushed_before:
|
|
44
|
-
colored_print(
|
|
45
|
-
f" Pushed: {pushed_after or 'any'} to {pushed_before or 'any'}", "cyan"
|
|
46
|
-
)
|
|
44
|
+
colored_print(f" Pushed: {pushed_after or 'any'} to {pushed_before or 'any'}", "cyan")
|
|
47
45
|
|
|
48
46
|
try:
|
|
49
47
|
repositories = self.graphql_fetcher.search_repositories(
|
greenmining/energy/__init__.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Energy measurement module for GreenMining.
|
|
2
2
|
|
|
3
|
-
from .base import
|
|
4
|
-
from .
|
|
3
|
+
from .base import CommitEnergyProfile, EnergyBackend, EnergyMeter, EnergyMetrics, get_energy_meter
|
|
4
|
+
from .carbon_reporter import CarbonReport, CarbonReporter
|
|
5
5
|
from .codecarbon_meter import CodeCarbonMeter
|
|
6
6
|
from .cpu_meter import CPUEnergyMeter
|
|
7
|
-
from .
|
|
7
|
+
from .rapl import RAPLEnergyMeter
|
|
8
8
|
|
|
9
9
|
__all__ = [
|
|
10
10
|
"EnergyMeter",
|
greenmining/energy/base.py
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
|
-
from dataclasses import dataclass
|
|
6
|
+
from dataclasses import dataclass
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
from enum import Enum
|
|
9
|
-
from typing import Any,
|
|
10
|
-
import time
|
|
9
|
+
from typing import Any, Callable
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
class EnergyBackend(Enum):
|
|
@@ -31,16 +30,16 @@ class EnergyMetrics:
|
|
|
31
30
|
# Component-specific energy (if available)
|
|
32
31
|
cpu_energy_joules: float = 0.0 # CPU-specific energy
|
|
33
32
|
dram_energy_joules: float = 0.0 # Memory energy
|
|
34
|
-
gpu_energy_joules:
|
|
33
|
+
gpu_energy_joules: float | None = None # GPU energy if available
|
|
35
34
|
|
|
36
35
|
# Carbon footprint (if carbon tracking enabled)
|
|
37
|
-
carbon_grams:
|
|
38
|
-
carbon_intensity:
|
|
36
|
+
carbon_grams: float | None = None # CO2 equivalent in grams
|
|
37
|
+
carbon_intensity: float | None = None # gCO2/kWh of grid
|
|
39
38
|
|
|
40
39
|
# Metadata
|
|
41
40
|
backend: str = ""
|
|
42
|
-
start_time:
|
|
43
|
-
end_time:
|
|
41
|
+
start_time: datetime | None = None
|
|
42
|
+
end_time: datetime | None = None
|
|
44
43
|
|
|
45
44
|
@property
|
|
46
45
|
def energy_joules(self) -> float:
|
|
@@ -50,7 +49,7 @@ class EnergyMetrics:
|
|
|
50
49
|
def average_power_watts(self) -> float:
|
|
51
50
|
return self.watts_avg
|
|
52
51
|
|
|
53
|
-
def to_dict(self) ->
|
|
52
|
+
def to_dict(self) -> dict[str, Any]:
|
|
54
53
|
# Convert to dictionary.
|
|
55
54
|
return {
|
|
56
55
|
"joules": self.joules,
|
|
@@ -73,13 +72,13 @@ class CommitEnergyProfile:
|
|
|
73
72
|
# Energy profile for a specific commit.
|
|
74
73
|
|
|
75
74
|
commit_hash: str
|
|
76
|
-
energy_before:
|
|
77
|
-
energy_after:
|
|
75
|
+
energy_before: EnergyMetrics | None = None # Parent commit energy
|
|
76
|
+
energy_after: EnergyMetrics | None = None # This commit energy
|
|
78
77
|
energy_delta: float = 0.0 # Change in joules
|
|
79
78
|
energy_regression: bool = False # True if energy increased
|
|
80
79
|
regression_percentage: float = 0.0 # % change
|
|
81
80
|
|
|
82
|
-
def to_dict(self) ->
|
|
81
|
+
def to_dict(self) -> dict[str, Any]:
|
|
83
82
|
# Convert to dictionary.
|
|
84
83
|
return {
|
|
85
84
|
"commit_hash": self.commit_hash,
|
|
@@ -98,8 +97,8 @@ class EnergyMeter(ABC):
|
|
|
98
97
|
# Initialize the energy meter.
|
|
99
98
|
self.backend = backend
|
|
100
99
|
self._is_measuring = False
|
|
101
|
-
self._start_time:
|
|
102
|
-
self._measurements:
|
|
100
|
+
self._start_time: float | None = None
|
|
101
|
+
self._measurements: list[float] = []
|
|
103
102
|
|
|
104
103
|
@abstractmethod
|
|
105
104
|
def is_available(self) -> bool:
|
|
@@ -125,7 +124,7 @@ class EnergyMeter(ABC):
|
|
|
125
124
|
metrics = self.stop()
|
|
126
125
|
return result, metrics
|
|
127
126
|
|
|
128
|
-
def measure_command(self, command: str, timeout:
|
|
127
|
+
def measure_command(self, command: str, timeout: int | None = None) -> EnergyMetrics:
|
|
129
128
|
# Measure energy consumption of a shell command.
|
|
130
129
|
import subprocess
|
|
131
130
|
|
|
@@ -156,9 +155,9 @@ class EnergyMeter(ABC):
|
|
|
156
155
|
def get_energy_meter(backend: str = "rapl") -> EnergyMeter:
|
|
157
156
|
# Factory function to get an energy meter instance.
|
|
158
157
|
# Supported backends: rapl, codecarbon, cpu_meter, auto
|
|
159
|
-
from .rapl import RAPLEnergyMeter
|
|
160
158
|
from .codecarbon_meter import CodeCarbonMeter
|
|
161
159
|
from .cpu_meter import CPUEnergyMeter
|
|
160
|
+
from .rapl import RAPLEnergyMeter
|
|
162
161
|
|
|
163
162
|
backend_lower = backend.lower()
|
|
164
163
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
from .base import EnergyMetrics
|
|
10
10
|
|
|
@@ -88,9 +88,9 @@ class CarbonReport:
|
|
|
88
88
|
tree_months: float = 0.0 # Equivalent tree-months to offset
|
|
89
89
|
smartphone_charges: float = 0.0 # Equivalent smartphone charges
|
|
90
90
|
km_driven: float = 0.0 # Equivalent km driven in average car
|
|
91
|
-
analysis_results:
|
|
91
|
+
analysis_results: list[dict[str, Any]] = field(default_factory=list)
|
|
92
92
|
|
|
93
|
-
def to_dict(self) ->
|
|
93
|
+
def to_dict(self) -> dict[str, Any]:
|
|
94
94
|
return {
|
|
95
95
|
"total_energy_kwh": round(self.total_energy_kwh, 6),
|
|
96
96
|
"total_emissions_kg": round(self.total_emissions_kg, 6),
|
|
@@ -143,8 +143,8 @@ class CarbonReporter:
|
|
|
143
143
|
def __init__(
|
|
144
144
|
self,
|
|
145
145
|
country_iso: str = "USA",
|
|
146
|
-
cloud_provider:
|
|
147
|
-
region:
|
|
146
|
+
cloud_provider: str | None = None,
|
|
147
|
+
region: str | None = None,
|
|
148
148
|
):
|
|
149
149
|
# Initialize carbon reporter.
|
|
150
150
|
# Args:
|
|
@@ -169,9 +169,9 @@ class CarbonReporter:
|
|
|
169
169
|
|
|
170
170
|
def generate_report(
|
|
171
171
|
self,
|
|
172
|
-
energy_metrics:
|
|
173
|
-
analysis_results:
|
|
174
|
-
total_joules:
|
|
172
|
+
energy_metrics: EnergyMetrics | None = None,
|
|
173
|
+
analysis_results: list[dict[str, Any]] | None = None,
|
|
174
|
+
total_joules: float | None = None,
|
|
175
175
|
) -> CarbonReport:
|
|
176
176
|
# Generate a carbon footprint report.
|
|
177
177
|
# Args:
|
|
@@ -232,11 +232,11 @@ class CarbonReporter:
|
|
|
232
232
|
return self.carbon_intensity
|
|
233
233
|
|
|
234
234
|
@staticmethod
|
|
235
|
-
def get_supported_countries() ->
|
|
235
|
+
def get_supported_countries() -> list[str]:
|
|
236
236
|
# Get list of supported country ISO codes.
|
|
237
237
|
return list(CARBON_INTENSITY_BY_COUNTRY.keys())
|
|
238
238
|
|
|
239
239
|
@staticmethod
|
|
240
|
-
def get_supported_cloud_regions(provider: str) ->
|
|
240
|
+
def get_supported_cloud_regions(provider: str) -> list[str]:
|
|
241
241
|
# Get list of supported cloud regions for a provider.
|
|
242
242
|
return list(CLOUD_REGION_INTENSITY.get(provider.lower(), {}).keys())
|
|
@@ -4,9 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import time
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from typing import Optional
|
|
8
7
|
|
|
9
|
-
from .base import EnergyMeter, EnergyMetrics
|
|
8
|
+
from .base import EnergyBackend, EnergyMeter, EnergyMetrics
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
class CodeCarbonMeter(EnergyMeter):
|
|
@@ -15,7 +14,7 @@ class CodeCarbonMeter(EnergyMeter):
|
|
|
15
14
|
def __init__(
|
|
16
15
|
self,
|
|
17
16
|
project_name: str = "greenmining",
|
|
18
|
-
output_dir:
|
|
17
|
+
output_dir: str | None = None,
|
|
19
18
|
save_to_file: bool = False,
|
|
20
19
|
):
|
|
21
20
|
# Initialize CodeCarbon energy meter.
|
|
@@ -24,13 +23,13 @@ class CodeCarbonMeter(EnergyMeter):
|
|
|
24
23
|
self.output_dir = output_dir
|
|
25
24
|
self.save_to_file = save_to_file
|
|
26
25
|
self._tracker = None
|
|
27
|
-
self._start_time:
|
|
26
|
+
self._start_time: float | None = None
|
|
28
27
|
self._codecarbon_available = self._check_codecarbon()
|
|
29
28
|
|
|
30
29
|
def _check_codecarbon(self) -> bool:
|
|
31
30
|
# Check if CodeCarbon is installed.
|
|
32
31
|
try:
|
|
33
|
-
from codecarbon import EmissionsTracker
|
|
32
|
+
from codecarbon import EmissionsTracker # noqa: F401
|
|
34
33
|
|
|
35
34
|
return True
|
|
36
35
|
except ImportError:
|
|
@@ -123,4 +122,3 @@ class CodeCarbonMeter(EnergyMeter):
|
|
|
123
122
|
start_time=datetime.fromtimestamp(self._start_time),
|
|
124
123
|
end_time=datetime.fromtimestamp(end_time),
|
|
125
124
|
)
|
|
126
|
-
|
greenmining/energy/cpu_meter.py
CHANGED
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
import time
|
|
7
6
|
import platform
|
|
7
|
+
import time
|
|
8
8
|
from datetime import datetime
|
|
9
|
-
from typing import List, Optional
|
|
10
9
|
|
|
11
|
-
from .base import EnergyMeter, EnergyMetrics
|
|
10
|
+
from .base import EnergyBackend, EnergyMeter, EnergyMetrics
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class CPUEnergyMeter(EnergyMeter):
|
|
@@ -23,7 +22,7 @@ class CPUEnergyMeter(EnergyMeter):
|
|
|
23
22
|
"Windows": 65.0,
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
def __init__(self, tdp_watts:
|
|
25
|
+
def __init__(self, tdp_watts: float | None = None, sample_interval: float = 0.5):
|
|
27
26
|
# Initialize CPU energy meter.
|
|
28
27
|
# Args:
|
|
29
28
|
# tdp_watts: CPU Thermal Design Power in watts (auto-detected if None)
|
|
@@ -31,15 +30,15 @@ class CPUEnergyMeter(EnergyMeter):
|
|
|
31
30
|
super().__init__(EnergyBackend.CPU_METER)
|
|
32
31
|
self.tdp_watts = tdp_watts or self._detect_tdp()
|
|
33
32
|
self.sample_interval = sample_interval
|
|
34
|
-
self._start_time:
|
|
35
|
-
self._samples:
|
|
33
|
+
self._start_time: float | None = None
|
|
34
|
+
self._samples: list[float] = []
|
|
36
35
|
self._platform = platform.system()
|
|
37
36
|
self._psutil_available = self._check_psutil()
|
|
38
37
|
|
|
39
38
|
def _check_psutil(self) -> bool:
|
|
40
39
|
# Check if psutil is available.
|
|
41
40
|
try:
|
|
42
|
-
import psutil
|
|
41
|
+
import psutil # noqa: F401
|
|
43
42
|
|
|
44
43
|
return True
|
|
45
44
|
except ImportError:
|
greenmining/energy/rapl.py
CHANGED
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import os
|
|
6
5
|
import time
|
|
7
6
|
from datetime import datetime
|
|
8
7
|
from pathlib import Path
|
|
9
|
-
from typing import Dict, List, Optional
|
|
10
8
|
|
|
11
|
-
from .base import EnergyMeter, EnergyMetrics
|
|
9
|
+
from .base import EnergyBackend, EnergyMeter, EnergyMetrics
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
class RAPLEnergyMeter(EnergyMeter):
|
|
@@ -19,10 +17,10 @@ class RAPLEnergyMeter(EnergyMeter):
|
|
|
19
17
|
def __init__(self):
|
|
20
18
|
# Initialize RAPL energy meter.
|
|
21
19
|
super().__init__(EnergyBackend.RAPL)
|
|
22
|
-
self._domains:
|
|
23
|
-
self._start_energy:
|
|
24
|
-
self._start_time:
|
|
25
|
-
self._power_samples:
|
|
20
|
+
self._domains: dict[str, Path] = {}
|
|
21
|
+
self._start_energy: dict[str, int] = {}
|
|
22
|
+
self._start_time: float | None = None
|
|
23
|
+
self._power_samples: list[float] = []
|
|
26
24
|
self._discover_domains()
|
|
27
25
|
|
|
28
26
|
def _discover_domains(self) -> None:
|
|
@@ -146,6 +144,6 @@ class RAPLEnergyMeter(EnergyMeter):
|
|
|
146
144
|
end_time=datetime.fromtimestamp(end_time),
|
|
147
145
|
)
|
|
148
146
|
|
|
149
|
-
def get_available_domains(self) ->
|
|
147
|
+
def get_available_domains(self) -> list[str]:
|
|
150
148
|
# Get list of available RAPL domains.
|
|
151
149
|
return list(self._domains.keys())
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import Optional
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@dataclass
|
|
@@ -14,7 +13,7 @@ class AggregatedStats:
|
|
|
14
13
|
known_patterns: dict = field(default_factory=dict)
|
|
15
14
|
repositories: list[dict] = field(default_factory=list)
|
|
16
15
|
languages: dict = field(default_factory=dict)
|
|
17
|
-
timestamp:
|
|
16
|
+
timestamp: str | None = None
|
|
18
17
|
|
|
19
18
|
def to_dict(self) -> dict:
|
|
20
19
|
# Convert to dictionary.
|
|
@@ -27,6 +26,6 @@ class AggregatedStats:
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
@classmethod
|
|
30
|
-
def from_dict(cls, data: dict) ->
|
|
29
|
+
def from_dict(cls, data: dict) -> AggregatedStats:
|
|
31
30
|
# Create from dictionary.
|
|
32
31
|
return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})
|
greenmining/models/commit.py
CHANGED
|
@@ -44,12 +44,12 @@ class Commit:
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
@classmethod
|
|
47
|
-
def from_dict(cls, data: dict) ->
|
|
47
|
+
def from_dict(cls, data: dict) -> Commit:
|
|
48
48
|
# Create from dictionary.
|
|
49
49
|
return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})
|
|
50
50
|
|
|
51
51
|
@classmethod
|
|
52
|
-
def from_pydriller_commit(cls, commit, repo_name: str) ->
|
|
52
|
+
def from_pydriller_commit(cls, commit, repo_name: str) -> Commit:
|
|
53
53
|
# Create from PyDriller commit object.
|
|
54
54
|
return cls(
|
|
55
55
|
commit_id=commit.hash,
|
greenmining/models/repository.py
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import Optional
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@dataclass
|
|
@@ -16,21 +15,21 @@ class Repository:
|
|
|
16
15
|
full_name: str
|
|
17
16
|
url: str
|
|
18
17
|
clone_url: str
|
|
19
|
-
language:
|
|
18
|
+
language: str | None
|
|
20
19
|
stars: int
|
|
21
20
|
forks: int
|
|
22
21
|
watchers: int
|
|
23
22
|
open_issues: int
|
|
24
23
|
last_updated: str
|
|
25
24
|
created_at: str
|
|
26
|
-
description:
|
|
25
|
+
description: str | None
|
|
27
26
|
main_branch: str
|
|
28
27
|
topics: list[str] = field(default_factory=list)
|
|
29
28
|
size: int = 0
|
|
30
29
|
has_issues: bool = True
|
|
31
30
|
has_wiki: bool = True
|
|
32
31
|
archived: bool = False
|
|
33
|
-
license:
|
|
32
|
+
license: str | None = None
|
|
34
33
|
|
|
35
34
|
def to_dict(self) -> dict:
|
|
36
35
|
# Convert to dictionary.
|
|
@@ -59,12 +58,12 @@ class Repository:
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
@classmethod
|
|
62
|
-
def from_dict(cls, data: dict) ->
|
|
61
|
+
def from_dict(cls, data: dict) -> Repository:
|
|
63
62
|
# Create from dictionary.
|
|
64
63
|
return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})
|
|
65
64
|
|
|
66
65
|
@classmethod
|
|
67
|
-
def from_github_repo(cls, repo, repo_id: int) ->
|
|
66
|
+
def from_github_repo(cls, repo, repo_id: int) -> Repository:
|
|
68
67
|
# Create from PyGithub repository object.
|
|
69
68
|
return cls(
|
|
70
69
|
repo_id=repo_id,
|
greenmining/services/__init__.py
CHANGED
|
@@ -5,10 +5,10 @@ from .data_aggregator import DataAggregator
|
|
|
5
5
|
from .data_analyzer import DataAnalyzer
|
|
6
6
|
from .github_graphql_fetcher import GitHubGraphQLFetcher
|
|
7
7
|
from .local_repo_analyzer import (
|
|
8
|
-
LocalRepoAnalyzer,
|
|
9
8
|
CommitAnalysis,
|
|
10
|
-
|
|
9
|
+
LocalRepoAnalyzer,
|
|
11
10
|
MethodMetrics,
|
|
11
|
+
RepositoryAnalysis,
|
|
12
12
|
SourceCodeChange,
|
|
13
13
|
)
|
|
14
14
|
from .reports import ReportGenerator
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import time
|
|
5
|
-
from typing import Any,
|
|
5
|
+
from typing import Any, Optional
|
|
6
6
|
|
|
7
7
|
import requests
|
|
8
8
|
|
|
@@ -30,12 +30,12 @@ class GitHubGraphQLFetcher:
|
|
|
30
30
|
keywords: str = "microservices",
|
|
31
31
|
max_repos: int = 100,
|
|
32
32
|
min_stars: int = 100,
|
|
33
|
-
languages: Optional[
|
|
33
|
+
languages: Optional[list[str]] = None,
|
|
34
34
|
created_after: Optional[str] = None,
|
|
35
35
|
created_before: Optional[str] = None,
|
|
36
36
|
pushed_after: Optional[str] = None,
|
|
37
37
|
pushed_before: Optional[str] = None,
|
|
38
|
-
) ->
|
|
38
|
+
) -> list[Repository]:
|
|
39
39
|
# Search GitHub repositories using GraphQL.
|
|
40
40
|
#
|
|
41
41
|
# Args:
|
|
@@ -172,7 +172,7 @@ class GitHubGraphQLFetcher:
|
|
|
172
172
|
self,
|
|
173
173
|
keywords: str,
|
|
174
174
|
min_stars: int,
|
|
175
|
-
languages: Optional[
|
|
175
|
+
languages: Optional[list[str]],
|
|
176
176
|
created_after: Optional[str],
|
|
177
177
|
created_before: Optional[str],
|
|
178
178
|
pushed_after: Optional[str],
|
|
@@ -201,7 +201,7 @@ class GitHubGraphQLFetcher:
|
|
|
201
201
|
|
|
202
202
|
return " ".join(query_parts)
|
|
203
203
|
|
|
204
|
-
def _execute_query(self, query: str, variables:
|
|
204
|
+
def _execute_query(self, query: str, variables: dict[str, Any]) -> dict[str, Any]:
|
|
205
205
|
# Execute GraphQL query.
|
|
206
206
|
payload = {"query": query, "variables": variables}
|
|
207
207
|
|
|
@@ -212,7 +212,7 @@ class GitHubGraphQLFetcher:
|
|
|
212
212
|
response.raise_for_status()
|
|
213
213
|
return response.json()
|
|
214
214
|
|
|
215
|
-
def _parse_repository(self, node:
|
|
215
|
+
def _parse_repository(self, node: dict[str, Any], repo_id: int = 0) -> Repository:
|
|
216
216
|
# Parse GraphQL repository node to Repository object.
|
|
217
217
|
full_name = node.get("nameWithOwner", "")
|
|
218
218
|
owner = full_name.split("/")[0] if "/" in full_name else ""
|
|
@@ -252,7 +252,7 @@ class GitHubGraphQLFetcher:
|
|
|
252
252
|
|
|
253
253
|
def get_repository_commits(
|
|
254
254
|
self, owner: str, name: str, max_commits: int = 100
|
|
255
|
-
) ->
|
|
255
|
+
) -> list[dict[str, Any]]:
|
|
256
256
|
# Fetch commits for a specific repository using GraphQL.
|
|
257
257
|
#
|
|
258
258
|
# Args:
|
|
@@ -341,7 +341,7 @@ class GitHubGraphQLFetcher:
|
|
|
341
341
|
|
|
342
342
|
return commits
|
|
343
343
|
|
|
344
|
-
def save_results(self, repositories:
|
|
344
|
+
def save_results(self, repositories: list[Repository], output_file: str):
|
|
345
345
|
# Save repositories to JSON file.
|
|
346
346
|
data = {
|
|
347
347
|
"total_repositories": len(repositories),
|
|
@@ -5,12 +5,11 @@ from __future__ import annotations
|
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
7
7
|
import shutil
|
|
8
|
-
import tempfile
|
|
9
8
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
10
9
|
from dataclasses import dataclass, field
|
|
11
10
|
from datetime import datetime, timedelta
|
|
12
11
|
from pathlib import Path
|
|
13
|
-
from typing import Any
|
|
12
|
+
from typing import Any
|
|
14
13
|
|
|
15
14
|
from pydriller import Repository
|
|
16
15
|
from pydriller.metrics.process.change_set import ChangeSet
|
|
@@ -22,7 +21,7 @@ from pydriller.metrics.process.history_complexity import HistoryComplexity
|
|
|
22
21
|
from pydriller.metrics.process.hunks_count import HunksCount
|
|
23
22
|
from pydriller.metrics.process.lines_count import LinesCount
|
|
24
23
|
|
|
25
|
-
from greenmining.gsf_patterns import get_pattern_by_keywords, is_green_aware
|
|
24
|
+
from greenmining.gsf_patterns import GSF_PATTERNS, get_pattern_by_keywords, is_green_aware
|
|
26
25
|
from greenmining.utils import colored_print
|
|
27
26
|
|
|
28
27
|
|
|
@@ -40,7 +39,7 @@ class MethodMetrics:
|
|
|
40
39
|
start_line: int = 0
|
|
41
40
|
end_line: int = 0
|
|
42
41
|
|
|
43
|
-
def to_dict(self) ->
|
|
42
|
+
def to_dict(self) -> dict[str, Any]:
|
|
44
43
|
return {
|
|
45
44
|
"name": self.name,
|
|
46
45
|
"long_name": self.long_name,
|
|
@@ -59,14 +58,14 @@ class SourceCodeChange:
|
|
|
59
58
|
# Source code before/after a commit for refactoring detection.
|
|
60
59
|
|
|
61
60
|
filename: str
|
|
62
|
-
source_code_before:
|
|
63
|
-
source_code_after:
|
|
64
|
-
diff:
|
|
61
|
+
source_code_before: str | None = None
|
|
62
|
+
source_code_after: str | None = None
|
|
63
|
+
diff: str | None = None
|
|
65
64
|
added_lines: int = 0
|
|
66
65
|
deleted_lines: int = 0
|
|
67
66
|
change_type: str = "" # ADD, DELETE, MODIFY, RENAME
|
|
68
67
|
|
|
69
|
-
def to_dict(self) ->
|
|
68
|
+
def to_dict(self) -> dict[str, Any]:
|
|
70
69
|
return {
|
|
71
70
|
"filename": self.filename,
|
|
72
71
|
"source_code_before": self.source_code_before,
|
|
@@ -88,18 +87,18 @@ class CommitAnalysis:
|
|
|
88
87
|
author_email: str
|
|
89
88
|
date: datetime
|
|
90
89
|
green_aware: bool
|
|
91
|
-
gsf_patterns_matched:
|
|
90
|
+
gsf_patterns_matched: list[str]
|
|
92
91
|
pattern_count: int
|
|
93
|
-
pattern_details:
|
|
92
|
+
pattern_details: list[dict[str, Any]]
|
|
94
93
|
confidence: str
|
|
95
|
-
files_modified:
|
|
94
|
+
files_modified: list[str]
|
|
96
95
|
insertions: int
|
|
97
96
|
deletions: int
|
|
98
97
|
|
|
99
98
|
# PyDriller DMM metrics
|
|
100
|
-
dmm_unit_size:
|
|
101
|
-
dmm_unit_complexity:
|
|
102
|
-
dmm_unit_interfacing:
|
|
99
|
+
dmm_unit_size: float | None = None
|
|
100
|
+
dmm_unit_complexity: float | None = None
|
|
101
|
+
dmm_unit_interfacing: float | None = None
|
|
103
102
|
|
|
104
103
|
# Structural metrics (Lizard)
|
|
105
104
|
total_nloc: int = 0
|
|
@@ -108,16 +107,16 @@ class CommitAnalysis:
|
|
|
108
107
|
methods_count: int = 0
|
|
109
108
|
|
|
110
109
|
# Method-level analysis (Phase 3.2)
|
|
111
|
-
methods:
|
|
110
|
+
methods: list[MethodMetrics] = field(default_factory=list)
|
|
112
111
|
|
|
113
112
|
# Source code access (Phase 3.3)
|
|
114
|
-
source_changes:
|
|
113
|
+
source_changes: list[SourceCodeChange] = field(default_factory=list)
|
|
115
114
|
|
|
116
115
|
# Energy metrics (Phase 2.2 - populated when energy_tracking=True)
|
|
117
|
-
energy_joules:
|
|
118
|
-
energy_watts_avg:
|
|
116
|
+
energy_joules: float | None = None
|
|
117
|
+
energy_watts_avg: float | None = None
|
|
119
118
|
|
|
120
|
-
def to_dict(self) ->
|
|
119
|
+
def to_dict(self) -> dict[str, Any]:
|
|
121
120
|
# Convert to dictionary.
|
|
122
121
|
result = {
|
|
123
122
|
"commit_hash": self.hash,
|
|
@@ -164,11 +163,11 @@ class RepositoryAnalysis:
|
|
|
164
163
|
total_commits: int
|
|
165
164
|
green_commits: int
|
|
166
165
|
green_commit_rate: float
|
|
167
|
-
commits:
|
|
168
|
-
process_metrics:
|
|
169
|
-
energy_metrics:
|
|
166
|
+
commits: list[CommitAnalysis] = field(default_factory=list)
|
|
167
|
+
process_metrics: dict[str, Any] = field(default_factory=dict)
|
|
168
|
+
energy_metrics: dict[str, Any] | None = None
|
|
170
169
|
|
|
171
|
-
def to_dict(self) ->
|
|
170
|
+
def to_dict(self) -> dict[str, Any]:
|
|
172
171
|
# Convert to dictionary.
|
|
173
172
|
result = {
|
|
174
173
|
"url": self.url,
|
|
@@ -190,21 +189,21 @@ class LocalRepoAnalyzer:
|
|
|
190
189
|
|
|
191
190
|
def __init__(
|
|
192
191
|
self,
|
|
193
|
-
clone_path:
|
|
192
|
+
clone_path: Path | None = None,
|
|
194
193
|
max_commits: int = 500,
|
|
195
194
|
days_back: int = 730,
|
|
196
195
|
skip_merges: bool = True,
|
|
197
196
|
compute_process_metrics: bool = True,
|
|
198
197
|
cleanup_after: bool = True,
|
|
199
|
-
ssh_key_path:
|
|
200
|
-
github_token:
|
|
198
|
+
ssh_key_path: str | None = None,
|
|
199
|
+
github_token: str | None = None,
|
|
201
200
|
energy_tracking: bool = False,
|
|
202
201
|
energy_backend: str = "rapl",
|
|
203
202
|
method_level_analysis: bool = False,
|
|
204
203
|
include_source_code: bool = False,
|
|
205
204
|
process_metrics: str = "standard",
|
|
206
|
-
since_date:
|
|
207
|
-
to_date:
|
|
205
|
+
since_date: datetime | None = None,
|
|
206
|
+
to_date: datetime | None = None,
|
|
208
207
|
commit_order: str = "newest_first",
|
|
209
208
|
):
|
|
210
209
|
# Initialize the local repository analyzer.
|
|
@@ -272,7 +271,7 @@ class LocalRepoAnalyzer:
|
|
|
272
271
|
return url.replace("https://", f"https://x-access-token:{self.github_token}@")
|
|
273
272
|
return url
|
|
274
273
|
|
|
275
|
-
def _setup_ssh_env(self) ->
|
|
274
|
+
def _setup_ssh_env(self) -> dict[str, str]:
|
|
276
275
|
# Set up SSH environment for private repository cloning.
|
|
277
276
|
env = os.environ.copy()
|
|
278
277
|
if self.ssh_key_path:
|
|
@@ -297,10 +296,10 @@ class LocalRepoAnalyzer:
|
|
|
297
296
|
|
|
298
297
|
raise ValueError(f"Could not parse GitHub URL: {url}")
|
|
299
298
|
|
|
300
|
-
def _get_pattern_details(self, matched_patterns:
|
|
299
|
+
def _get_pattern_details(self, matched_patterns: list[str]) -> list[dict[str, Any]]:
|
|
301
300
|
# Get detailed pattern information.
|
|
302
301
|
details = []
|
|
303
|
-
for
|
|
302
|
+
for _pattern_id, pattern in self.gsf_patterns.items():
|
|
304
303
|
if pattern["name"] in matched_patterns:
|
|
305
304
|
details.append(
|
|
306
305
|
{
|
|
@@ -312,7 +311,7 @@ class LocalRepoAnalyzer:
|
|
|
312
311
|
)
|
|
313
312
|
return details
|
|
314
313
|
|
|
315
|
-
def _extract_method_metrics(self, commit) ->
|
|
314
|
+
def _extract_method_metrics(self, commit) -> list[MethodMetrics]:
|
|
316
315
|
# Extract per-method metrics from modified files using Lizard (via PyDriller).
|
|
317
316
|
methods = []
|
|
318
317
|
try:
|
|
@@ -336,7 +335,7 @@ class LocalRepoAnalyzer:
|
|
|
336
335
|
pass
|
|
337
336
|
return methods
|
|
338
337
|
|
|
339
|
-
def _extract_source_changes(self, commit) ->
|
|
338
|
+
def _extract_source_changes(self, commit) -> list[SourceCodeChange]:
|
|
340
339
|
# Extract source code before/after for each modified file.
|
|
341
340
|
changes = []
|
|
342
341
|
try:
|
|
@@ -486,6 +485,7 @@ class LocalRepoAnalyzer:
|
|
|
486
485
|
if self.energy_tracking:
|
|
487
486
|
try:
|
|
488
487
|
from greenmining.energy.base import get_energy_meter
|
|
488
|
+
|
|
489
489
|
energy_meter = get_energy_meter(self.energy_backend)
|
|
490
490
|
energy_meter.start()
|
|
491
491
|
except Exception:
|
|
@@ -557,7 +557,7 @@ class LocalRepoAnalyzer:
|
|
|
557
557
|
colored_print(f" Cleaning up: {clone_parent}", "cyan")
|
|
558
558
|
shutil.rmtree(clone_parent, ignore_errors=True)
|
|
559
559
|
|
|
560
|
-
def _compute_process_metrics(self, repo_path: str) ->
|
|
560
|
+
def _compute_process_metrics(self, repo_path: str) -> dict[str, Any]:
|
|
561
561
|
# Compute PyDriller process metrics for the repository.
|
|
562
562
|
metrics = {}
|
|
563
563
|
since_date = datetime.now() - timedelta(days=self.days_back)
|
|
@@ -624,10 +624,10 @@ class LocalRepoAnalyzer:
|
|
|
624
624
|
|
|
625
625
|
def analyze_repositories(
|
|
626
626
|
self,
|
|
627
|
-
urls:
|
|
627
|
+
urls: list[str],
|
|
628
628
|
parallel_workers: int = 1,
|
|
629
629
|
output_format: str = "dict",
|
|
630
|
-
) ->
|
|
630
|
+
) -> list[RepositoryAnalysis]:
|
|
631
631
|
# Analyze multiple repositories from URLs.
|
|
632
632
|
# Args:
|
|
633
633
|
# urls: List of repository URLs to analyze
|
|
@@ -637,7 +637,7 @@ class LocalRepoAnalyzer:
|
|
|
637
637
|
return self._analyze_sequential(urls)
|
|
638
638
|
return self._analyze_parallel(urls, parallel_workers)
|
|
639
639
|
|
|
640
|
-
def _analyze_sequential(self, urls:
|
|
640
|
+
def _analyze_sequential(self, urls: list[str]) -> list[RepositoryAnalysis]:
|
|
641
641
|
# Analyze repositories sequentially.
|
|
642
642
|
results = []
|
|
643
643
|
for i, url in enumerate(urls, 1):
|
|
@@ -653,7 +653,7 @@ class LocalRepoAnalyzer:
|
|
|
653
653
|
continue
|
|
654
654
|
return results
|
|
655
655
|
|
|
656
|
-
def _analyze_parallel(self, urls:
|
|
656
|
+
def _analyze_parallel(self, urls: list[str], max_workers: int) -> list[RepositoryAnalysis]:
|
|
657
657
|
# Analyze repositories in parallel using thread pool.
|
|
658
658
|
results = []
|
|
659
659
|
colored_print(f"\n Analyzing {len(urls)} repositories with {max_workers} workers", "cyan")
|
|
@@ -665,7 +665,9 @@ class LocalRepoAnalyzer:
|
|
|
665
665
|
try:
|
|
666
666
|
result = future.result()
|
|
667
667
|
if result.total_commits == 0:
|
|
668
|
-
colored_print(
|
|
668
|
+
colored_print(
|
|
669
|
+
f" Skipping {result.name}: no commits in date range", "yellow"
|
|
670
|
+
)
|
|
669
671
|
continue
|
|
670
672
|
results.append(result)
|
|
671
673
|
colored_print(f" Completed: {result.name}", "green")
|
greenmining/services/reports.py
CHANGED
|
@@ -366,7 +366,7 @@ No novel microservice-specific green practices were automatically detected. Manu
|
|
|
366
366
|
if green_vs_nongreen:
|
|
367
367
|
cohens_d = green_vs_nongreen.get("cohens_d", 0)
|
|
368
368
|
magnitude = green_vs_nongreen.get("magnitude", "negligible")
|
|
369
|
-
sections.append(
|
|
369
|
+
sections.append("**Green vs Non-Green Pattern Usage:**")
|
|
370
370
|
sections.append(f"- Cohen's d: {cohens_d:.3f}")
|
|
371
371
|
sections.append(f"- Effect magnitude: {magnitude.capitalize()}")
|
|
372
372
|
sections.append("")
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
greenmining/__init__.py,sha256=jKktkjxKacTGmUoXMwfuW7DcyUKepUkTCNLjTmU2Hvc,4496
|
|
2
|
+
greenmining/__main__.py,sha256=NYOVS7D4w2XDLn6SyXHXPKE5GrNGOeoWSTb_KazgK5c,590
|
|
3
|
+
greenmining/gsf_patterns.py,sha256=UvNJPY3HlAx1SicwUqci40TlLg8lCL0tszSOH4haxQs,55921
|
|
4
|
+
greenmining/utils.py,sha256=-dnLUw9taCzvQ2dk6uc66GAohOFiXJFKs9TLSEPk5kM,2893
|
|
5
|
+
greenmining/analyzers/__init__.py,sha256=FExlzEE2c2TZ82wqTh1il5qcZFhUDKBtB_HB-aC4ynA,416
|
|
6
|
+
greenmining/analyzers/code_diff_analyzer.py,sha256=KVvIYMmTvrjrH0n1EjyXuSfqwWPmlU8mAZ0F4Q1nYaQ,10939
|
|
7
|
+
greenmining/analyzers/metrics_power_correlator.py,sha256=4p2E_JRFjDSRLn10V-slZKvgNFR3H-8OXF6waBN-7DU,5928
|
|
8
|
+
greenmining/analyzers/statistical_analyzer.py,sha256=hMUNC8IjvN30f365CBtUqhrExNKGMPMVb1uKVEdBvwU,5930
|
|
9
|
+
greenmining/analyzers/temporal_analyzer.py,sha256=OoT1lUTimocag8TGgpTMHVdfiyEqnM2yiYA7QeHWZ3g,14203
|
|
10
|
+
greenmining/controllers/__init__.py,sha256=UiAT6zBvC1z_9cJWfzq1cLA0I4r9b2vURHipj8oDczI,180
|
|
11
|
+
greenmining/controllers/repository_controller.py,sha256=8XzeFIpaYzPznlQRaftHxkpBdNmyzchxU40yolZcodw,6011
|
|
12
|
+
greenmining/energy/__init__.py,sha256=WR_BvnHrUmEyDWaOPVpYap_kpat13K-mgtmvMAtXPZQ,558
|
|
13
|
+
greenmining/energy/base.py,sha256=G_II_7tgITVJtXZTLFvB7oMmhK5nJDem1hHkOEcifF0,5850
|
|
14
|
+
greenmining/energy/carbon_reporter.py,sha256=k41M6vcDuEYVK4KnsG2DrT9jV5oO82nJzAfIWz1r6Z4,8261
|
|
15
|
+
greenmining/energy/codecarbon_meter.py,sha256=zhMsZXdk2WRLZf3mU6p7FF2uAn5YlNogqhuMOCm6Xbs,4193
|
|
16
|
+
greenmining/energy/cpu_meter.py,sha256=g7oJzcpbEW-qp-9uUGhHduCj2-RBgry9VpsPIiYOKYE,4975
|
|
17
|
+
greenmining/energy/rapl.py,sha256=CpFa_j_g6UKf4f82CH8DIBZRRdRSqg_4Og51D6kMYVU,5239
|
|
18
|
+
greenmining/models/__init__.py,sha256=2hkB0quhMePvvA1AkYfj5uiF_HyGtXVxn0BU-5m_oSg,302
|
|
19
|
+
greenmining/models/aggregated_stats.py,sha256=il5c0pHF2PAYywbVwbod-SNcdz0q80XJ0AFKo2Gmits,971
|
|
20
|
+
greenmining/models/analysis_result.py,sha256=YICTCEcrJxZ1R8Xaio3AZOjCGwMzC_62BMAL0J_XY1w,1509
|
|
21
|
+
greenmining/models/commit.py,sha256=tkjMXXoMEAPxVR7M9Bf95gUSLqQF4GLeCs6_bBlK1go,2411
|
|
22
|
+
greenmining/models/repository.py,sha256=qsFoBmtmDn71g8WjHel5Zu9Ny4ij98E7n-cgcFNkJWI,2809
|
|
23
|
+
greenmining/services/__init__.py,sha256=QBsyLE5vNcQpyVaT1DD_ThS4pJ7pb4Obl-zUpp2GAnM,690
|
|
24
|
+
greenmining/services/commit_extractor.py,sha256=qBM9QpGzPZRmGMFufJ6gP8eWIuufTowLX8mQxqZwyEU,6996
|
|
25
|
+
greenmining/services/data_aggregator.py,sha256=BU_HUb-8c0n0sa_7VZRB8jIVnaVhRLf-E6KA4ASh-08,19427
|
|
26
|
+
greenmining/services/data_analyzer.py,sha256=0XqW-slrnt7RotrHDweOqKtoN8XIA7y6p7s2Jau6cMg,7431
|
|
27
|
+
greenmining/services/github_graphql_fetcher.py,sha256=WhSbQGMdkb0D4uLcMKW6xZK77c5AkW-nZf718issap4,11527
|
|
28
|
+
greenmining/services/local_repo_analyzer.py,sha256=Cq6NixciZmqDuWpU5976TuhGiaGFNnvvz4Rs11bq2Ug,25891
|
|
29
|
+
greenmining/services/reports.py,sha256=HQo52mdhwXGZgRV1BWaIA4WSs6N3Q2_Wgdsbb2RSQZU,23218
|
|
30
|
+
greenmining-1.2.5.dist-info/licenses/LICENSE,sha256=M7ma3JHGeiIZIs3ea0HTcFl_wLFPX2NZElUliYs4bCA,1083
|
|
31
|
+
greenmining-1.2.5.dist-info/METADATA,sha256=WVoukYReWst87-OVgNEsi3vXaI51Dsh4h54jq3gieYI,10522
|
|
32
|
+
greenmining-1.2.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
33
|
+
greenmining-1.2.5.dist-info/top_level.txt,sha256=nreXgXxZIWI-42yQknQ0HXtUrFnzZ8N1ra4Mdy2KcsI,12
|
|
34
|
+
greenmining-1.2.5.dist-info/RECORD,,
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
greenmining/__init__.py,sha256=erfTupwdZ-PlNwj2YygUyEbLT7aNd2zTfTJJ-NMG1wg,4496
|
|
2
|
-
greenmining/__main__.py,sha256=NYOVS7D4w2XDLn6SyXHXPKE5GrNGOeoWSTb_KazgK5c,590
|
|
3
|
-
greenmining/gsf_patterns.py,sha256=UvNJPY3HlAx1SicwUqci40TlLg8lCL0tszSOH4haxQs,55921
|
|
4
|
-
greenmining/utils.py,sha256=-dnLUw9taCzvQ2dk6uc66GAohOFiXJFKs9TLSEPk5kM,2893
|
|
5
|
-
greenmining/analyzers/__init__.py,sha256=wnBrn8EyAHG_qnesOPAYkZyc-XigXWy2pI3bMeIoLH4,416
|
|
6
|
-
greenmining/analyzers/code_diff_analyzer.py,sha256=1dk68R3O0RZG8gx1cm9B_UlZ1Uwyb_Q3oScRbCVx4tM,10950
|
|
7
|
-
greenmining/analyzers/metrics_power_correlator.py,sha256=MgKXAIYjNihzzyilCd88_AMjZP9sdC6NkCAVbrvvOus,5957
|
|
8
|
-
greenmining/analyzers/statistical_analyzer.py,sha256=PA0w0sytRmMO6N1a2iH7VdA6Icg4DcyBLFXOGq7PepY,5942
|
|
9
|
-
greenmining/analyzers/temporal_analyzer.py,sha256=JfTcAoI20oCFMehGrSRnDqhJTXI-RUbdCTMwDOTW9-g,14259
|
|
10
|
-
greenmining/controllers/__init__.py,sha256=UiAT6zBvC1z_9cJWfzq1cLA0I4r9b2vURHipj8oDczI,180
|
|
11
|
-
greenmining/controllers/repository_controller.py,sha256=sjfbDhyRY59MsKLw0dkxzpe1QZKtm9ScO4E8VFYZy9A,6041
|
|
12
|
-
greenmining/energy/__init__.py,sha256=GoCYh7hitWBoPMtan1HF1yezCHi7o4sa_YUJgGkeJc8,558
|
|
13
|
-
greenmining/energy/base.py,sha256=3hIPgc4B0Nz9V7DTh2Xd6trDRtmozUBBpa5UWRuWzcw,5918
|
|
14
|
-
greenmining/energy/carbon_reporter.py,sha256=bKIFlLhHfYzI4DBu_ff4GW1Psz4oSCAF4NmzQb-EShA,8298
|
|
15
|
-
greenmining/energy/codecarbon_meter.py,sha256=8obsfiJi0V3R_2BMHjTQCZSN52YPvFn5d9q_MKOZVb4,4214
|
|
16
|
-
greenmining/energy/cpu_meter.py,sha256=GmUZsOIzWnAWcuSW4RndDdgszDHzqnBjAIeLBgelZ0w,5001
|
|
17
|
-
greenmining/energy/rapl.py,sha256=b63M1mS7uF9Uo0vFi0z7Qwdo56U1TqxIYQXINhYp9Jo,5292
|
|
18
|
-
greenmining/models/__init__.py,sha256=2hkB0quhMePvvA1AkYfj5uiF_HyGtXVxn0BU-5m_oSg,302
|
|
19
|
-
greenmining/models/aggregated_stats.py,sha256=CZxjwXswvtmYPwpcbodLUsZpsbsNKBDIqvU9DpFO_t0,1004
|
|
20
|
-
greenmining/models/analysis_result.py,sha256=YICTCEcrJxZ1R8Xaio3AZOjCGwMzC_62BMAL0J_XY1w,1509
|
|
21
|
-
greenmining/models/commit.py,sha256=LCwDcRu4-BeCJQdk590oQNZZZM9t8W9FlaHlo9DCVmc,2415
|
|
22
|
-
greenmining/models/repository.py,sha256=MUeCOtVMOsU4Oa_BBoB163Ij5BKytTKwbzoGORJx4rU,2850
|
|
23
|
-
greenmining/services/__init__.py,sha256=ZEMOVut0KRdume_vz58beSNps3YgeoGBXmUjEqNgIhc,690
|
|
24
|
-
greenmining/services/commit_extractor.py,sha256=qBM9QpGzPZRmGMFufJ6gP8eWIuufTowLX8mQxqZwyEU,6996
|
|
25
|
-
greenmining/services/data_aggregator.py,sha256=BU_HUb-8c0n0sa_7VZRB8jIVnaVhRLf-E6KA4ASh-08,19427
|
|
26
|
-
greenmining/services/data_analyzer.py,sha256=0XqW-slrnt7RotrHDweOqKtoN8XIA7y6p7s2Jau6cMg,7431
|
|
27
|
-
greenmining/services/github_graphql_fetcher.py,sha256=ZklXdEAc60KeFL83zRYMwW_-2OwMKpfPY7Wrifl0D50,11539
|
|
28
|
-
greenmining/services/local_repo_analyzer.py,sha256=kmNs6KzW8_hgRdzArqBq2TZ-3Rflh-9Ody0lqYa4Vl4,25915
|
|
29
|
-
greenmining/services/reports.py,sha256=nhJuYiA5tPD_9AjtgSLEnrpW3x15sZXrwIxpxQEBbh0,23219
|
|
30
|
-
greenmining-1.2.4.dist-info/licenses/LICENSE,sha256=M7ma3JHGeiIZIs3ea0HTcFl_wLFPX2NZElUliYs4bCA,1083
|
|
31
|
-
greenmining-1.2.4.dist-info/METADATA,sha256=2-7qoQ9C6nbcQxXKYG2Dv0BvEMtbX6GecWlSXGPCdOo,10522
|
|
32
|
-
greenmining-1.2.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
33
|
-
greenmining-1.2.4.dist-info/top_level.txt,sha256=nreXgXxZIWI-42yQknQ0HXtUrFnzZ8N1ra4Mdy2KcsI,12
|
|
34
|
-
greenmining-1.2.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|