greenmining 1.1.9__py3-none-any.whl → 1.2.0__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.
@@ -1,246 +0,0 @@
1
- # Version-by-version power analysis.
2
- # Measure power consumption across multiple software versions/tags.
3
-
4
- from __future__ import annotations
5
-
6
- import subprocess
7
- from dataclasses import dataclass, field
8
- from typing import Any, Dict, List, Optional
9
-
10
- from greenmining.utils import colored_print
11
-
12
-
13
- @dataclass
14
- class VersionPowerProfile:
15
- # Power profile for a single version.
16
-
17
- version: str
18
- commit_sha: str
19
- energy_joules: float = 0.0
20
- power_watts_avg: float = 0.0
21
- duration_seconds: float = 0.0
22
- iterations: int = 0
23
- energy_std: float = 0.0 # Standard deviation across iterations
24
-
25
- def to_dict(self) -> Dict[str, Any]:
26
- return {
27
- "version": self.version,
28
- "commit_sha": self.commit_sha,
29
- "energy_joules": round(self.energy_joules, 4),
30
- "power_watts_avg": round(self.power_watts_avg, 4),
31
- "duration_seconds": round(self.duration_seconds, 4),
32
- "iterations": self.iterations,
33
- "energy_std": round(self.energy_std, 4),
34
- }
35
-
36
-
37
- @dataclass
38
- class VersionPowerReport:
39
- # Complete power analysis report across versions.
40
-
41
- versions: List[VersionPowerProfile] = field(default_factory=list)
42
- trend: str = "stable" # increasing, decreasing, stable
43
- total_change_percent: float = 0.0
44
- most_efficient: str = ""
45
- least_efficient: str = ""
46
-
47
- def to_dict(self) -> Dict[str, Any]:
48
- return {
49
- "versions": [v.to_dict() for v in self.versions],
50
- "trend": self.trend,
51
- "total_change_percent": round(self.total_change_percent, 2),
52
- "most_efficient": self.most_efficient,
53
- "least_efficient": self.least_efficient,
54
- }
55
-
56
- def summary(self) -> str:
57
- # Generate human-readable summary.
58
- lines = [
59
- "Version Power Analysis Report",
60
- "-" * 40,
61
- f"Versions analyzed: {len(self.versions)}",
62
- f"Trend: {self.trend}",
63
- f"Total change: {self.total_change_percent:+.2f}%",
64
- f"Most efficient: {self.most_efficient}",
65
- f"Least efficient: {self.least_efficient}",
66
- "",
67
- "Per-version breakdown:",
68
- ]
69
- for v in self.versions:
70
- lines.append(
71
- f" {v.version}: {v.energy_joules:.4f}J "
72
- f"({v.power_watts_avg:.2f}W avg, {v.duration_seconds:.2f}s)"
73
- )
74
- return "\n".join(lines)
75
-
76
-
77
- class VersionPowerAnalyzer:
78
- # Measure and compare power consumption across software versions.
79
-
80
- def __init__(
81
- self,
82
- test_command: str = "pytest tests/",
83
- energy_backend: str = "rapl",
84
- iterations: int = 10,
85
- warmup_iterations: int = 2,
86
- ):
87
- # Initialize version power analyzer.
88
- # Args:
89
- # test_command: Shell command to run for measurement
90
- # energy_backend: Energy measurement backend
91
- # iterations: Number of measurement iterations per version
92
- # warmup_iterations: Warmup runs before measuring
93
- self.test_command = test_command
94
- self.energy_backend = energy_backend
95
- self.iterations = iterations
96
- self.warmup_iterations = warmup_iterations
97
- self._meter = None
98
-
99
- def _get_energy_meter(self):
100
- # Get energy meter instance.
101
- if self._meter is None:
102
- from greenmining.energy.base import get_energy_meter
103
-
104
- self._meter = get_energy_meter(self.energy_backend)
105
- return self._meter
106
-
107
- def _measure_version(self, repo_path: str, version: str) -> VersionPowerProfile:
108
- # Measure power consumption for a specific version.
109
- meter = self._get_energy_meter()
110
-
111
- # Checkout version
112
- sha = self._checkout_version(repo_path, version)
113
- colored_print(f" Measuring {version} ({sha[:8]})...", "cyan")
114
-
115
- # Warmup
116
- for i in range(self.warmup_iterations):
117
- subprocess.run(
118
- self.test_command,
119
- shell=True,
120
- cwd=repo_path,
121
- capture_output=True,
122
- text=True,
123
- timeout=600,
124
- )
125
-
126
- # Measure iterations
127
- measurements = []
128
- for i in range(self.iterations):
129
- meter.start()
130
- subprocess.run(
131
- self.test_command,
132
- shell=True,
133
- cwd=repo_path,
134
- capture_output=True,
135
- text=True,
136
- timeout=600,
137
- )
138
- metrics = meter.stop()
139
- measurements.append(metrics)
140
-
141
- # Aggregate
142
- import numpy as np
143
-
144
- energies = [m.joules for m in measurements]
145
- avg_energy = float(np.mean(energies))
146
- std_energy = float(np.std(energies))
147
- avg_power = float(np.mean([m.watts_avg for m in measurements]))
148
- avg_duration = float(np.mean([m.duration_seconds for m in measurements]))
149
-
150
- profile = VersionPowerProfile(
151
- version=version,
152
- commit_sha=sha,
153
- energy_joules=avg_energy,
154
- power_watts_avg=avg_power,
155
- duration_seconds=avg_duration,
156
- iterations=self.iterations,
157
- energy_std=std_energy,
158
- )
159
-
160
- colored_print(
161
- f" {version}: {avg_energy:.4f}J +/-{std_energy:.4f} "
162
- f"({avg_power:.2f}W, {avg_duration:.2f}s)",
163
- "green",
164
- )
165
- return profile
166
-
167
- def analyze_versions(
168
- self,
169
- repo_path: str,
170
- versions: List[str],
171
- ) -> VersionPowerReport:
172
- # Analyze power consumption across multiple versions.
173
- # Args:
174
- # repo_path: Path to local git repository
175
- # versions: List of version tags or commit references
176
- colored_print(f"\nAnalyzing {len(versions)} versions for power consumption", "cyan")
177
- colored_print(f" Test: {self.test_command}", "cyan")
178
- colored_print(f" Iterations: {self.iterations} (+{self.warmup_iterations} warmup)", "cyan")
179
-
180
- profiles = []
181
- for version in versions:
182
- try:
183
- profile = self._measure_version(repo_path, version)
184
- profiles.append(profile)
185
- except Exception as e:
186
- colored_print(f" Error measuring {version}: {e}", "red")
187
-
188
- if not profiles:
189
- return VersionPowerReport()
190
-
191
- # Determine trend
192
- first_energy = profiles[0].energy_joules
193
- last_energy = profiles[-1].energy_joules
194
-
195
- if first_energy > 0:
196
- total_change = ((last_energy - first_energy) / first_energy) * 100
197
- else:
198
- total_change = 0.0
199
-
200
- if total_change > 5:
201
- trend = "increasing"
202
- elif total_change < -5:
203
- trend = "decreasing"
204
- else:
205
- trend = "stable"
206
-
207
- # Find most/least efficient
208
- most_efficient = min(profiles, key=lambda p: p.energy_joules)
209
- least_efficient = max(profiles, key=lambda p: p.energy_joules)
210
-
211
- report = VersionPowerReport(
212
- versions=profiles,
213
- trend=trend,
214
- total_change_percent=total_change,
215
- most_efficient=most_efficient.version,
216
- least_efficient=least_efficient.version,
217
- )
218
-
219
- colored_print(f"\nTrend: {trend} ({total_change:+.2f}%)", "cyan")
220
- colored_print(f"Most efficient: {most_efficient.version}", "green")
221
- colored_print(f"Least efficient: {least_efficient.version}", "red")
222
-
223
- # Restore to latest version
224
- try:
225
- self._checkout_version(repo_path, versions[-1])
226
- except Exception:
227
- pass
228
-
229
- return report
230
-
231
- @staticmethod
232
- def _checkout_version(repo_path: str, version: str) -> str:
233
- # Checkout a version and return its SHA.
234
- subprocess.run(
235
- ["git", "checkout", version, "--quiet"],
236
- cwd=repo_path,
237
- capture_output=True,
238
- text=True,
239
- )
240
- result = subprocess.run(
241
- ["git", "rev-parse", "HEAD"],
242
- cwd=repo_path,
243
- capture_output=True,
244
- text=True,
245
- )
246
- return result.stdout.strip()
greenmining/config.py DELETED
@@ -1,91 +0,0 @@
1
- import os
2
- from pathlib import Path
3
- from typing import Any, Dict, List
4
-
5
- from dotenv import load_dotenv
6
-
7
-
8
- def _load_yaml_config(yaml_path: Path) -> Dict[str, Any]:
9
- # Load configuration from YAML file if it exists.
10
- if not yaml_path.exists():
11
- return {}
12
- try:
13
- import yaml
14
-
15
- with open(yaml_path, "r") as f:
16
- return yaml.safe_load(f) or {}
17
- except ImportError:
18
- return {}
19
- except Exception:
20
- return {}
21
-
22
-
23
- class Config:
24
- # Configuration class for loading from env vars and YAML.
25
-
26
- def __init__(self, env_file: str = ".env", yaml_file: str = "greenmining.yaml"):
27
- # Initialize configuration from environment and YAML file.
28
- env_path = Path(env_file)
29
- if env_path.exists():
30
- load_dotenv(env_path)
31
- else:
32
- load_dotenv()
33
-
34
- # Load YAML config
35
- yaml_path = Path(yaml_file)
36
- self._yaml_config = _load_yaml_config(yaml_path)
37
-
38
- # GitHub API Configuration
39
- self.GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
40
- if not self.GITHUB_TOKEN or self.GITHUB_TOKEN == "your_github_pat_here":
41
- raise ValueError("GITHUB_TOKEN not set. Please set it in .env file or environment.")
42
-
43
- # Search Configuration (YAML: sources.search.*)
44
- yaml_search = self._yaml_config.get("sources", {}).get("search", {})
45
-
46
- self.SUPPORTED_LANGUAGES: List[str] = yaml_search.get(
47
- "languages",
48
- [
49
- "Python",
50
- "JavaScript",
51
- "TypeScript",
52
- "Java",
53
- "C++",
54
- "C#",
55
- "Go",
56
- "Rust",
57
- "PHP",
58
- "Ruby",
59
- "Swift",
60
- "Kotlin",
61
- "Scala",
62
- "R",
63
- "MATLAB",
64
- "Dart",
65
- "Lua",
66
- "Perl",
67
- "Haskell",
68
- "Elixir",
69
- ],
70
- )
71
-
72
- # Repository Limits
73
- self.MIN_STARS = yaml_search.get("min_stars", int(os.getenv("MIN_STARS", "100")))
74
- self.MAX_REPOS = int(os.getenv("MAX_REPOS", "100"))
75
-
76
- # Output Configuration (YAML: output.directory)
77
- yaml_output = self._yaml_config.get("output", {})
78
- self.OUTPUT_DIR = Path(yaml_output.get("directory", os.getenv("OUTPUT_DIR", "./data")))
79
- self.OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
80
-
81
- # File Paths
82
- self.REPOS_FILE = self.OUTPUT_DIR / "repositories.json"
83
-
84
- def __repr__(self) -> str:
85
- # String representation of configuration (hiding sensitive data).
86
- return (
87
- f"Config("
88
- f"MAX_REPOS={self.MAX_REPOS}, "
89
- f"OUTPUT_DIR={self.OUTPUT_DIR}"
90
- f")"
91
- )
@@ -1,7 +0,0 @@
1
- # Presenters Package - UI/CLI presentation layer.
2
-
3
- from .console_presenter import ConsolePresenter
4
-
5
- __all__ = [
6
- "ConsolePresenter",
7
- ]
@@ -1,143 +0,0 @@
1
- # Console Presenter - Handles console output formatting.
2
-
3
- from __future__ import annotations
4
-
5
- from typing import Any, Dict, List
6
-
7
- from tabulate import tabulate
8
-
9
- from greenmining.utils import colored_print
10
-
11
-
12
- class ConsolePresenter:
13
- # Presenter for console/terminal output.
14
-
15
- @staticmethod
16
- def show_banner():
17
- # Display application banner.
18
- banner = """
19
-
20
- Green Microservices Mining
21
-
22
- """
23
- colored_print(banner, "green")
24
-
25
- @staticmethod
26
- def show_repositories(repositories: list[dict], limit: int = 10):
27
- # Display repository table.
28
- if not repositories:
29
- colored_print("No repositories to display", "yellow")
30
- return
31
-
32
- colored_print(f"\n Top {min(limit, len(repositories))} Repositories:\n", "cyan")
33
-
34
- table_data = []
35
- for repo in repositories[:limit]:
36
- table_data.append(
37
- [
38
- repo.get("full_name", "N/A"),
39
- repo.get("language", "N/A"),
40
- f"{repo.get('stars', 0):,}",
41
- (
42
- repo.get("description", "")[:50] + "..."
43
- if len(repo.get("description", "")) > 50
44
- else repo.get("description", "")
45
- ),
46
- ]
47
- )
48
-
49
- headers = ["Repository", "Language", "Stars", "Description"]
50
- print(tabulate(table_data, headers=headers, tablefmt="grid"))
51
-
52
- @staticmethod
53
- def show_commit_stats(stats: dict[str, Any]):
54
- # Display commit statistics.
55
- colored_print("\n Commit Statistics:\n", "cyan")
56
-
57
- table_data = [
58
- ["Total Commits", f"{stats.get('total_commits', 0):,}"],
59
- ["Repositories", stats.get("total_repos", 0)],
60
- ["Avg per Repo", f"{stats.get('avg_per_repo', 0):.1f}"],
61
- ["Date Range", stats.get("date_range", "N/A")],
62
- ]
63
-
64
- print(tabulate(table_data, headers=["Metric", "Value"], tablefmt="grid"))
65
-
66
- @staticmethod
67
- def show_analysis_results(results: dict[str, Any]):
68
- # Display analysis results.
69
- colored_print("\n Analysis Results:\n", "cyan")
70
-
71
- summary = results.get("summary", {})
72
- table_data = [
73
- ["Total Commits Analyzed", f"{summary.get('total_commits', 0):,}"],
74
- ["Green-Aware Commits", f"{summary.get('green_commits', 0):,}"],
75
- ["Green Rate", f"{summary.get('green_commit_rate', 0):.1%}"],
76
- ["Patterns Detected", len(results.get("known_patterns", {}))],
77
- ]
78
-
79
- print(tabulate(table_data, headers=["Metric", "Value"], tablefmt="grid"))
80
-
81
- @staticmethod
82
- def show_pattern_distribution(patterns: dict[str, Any], limit: int = 10):
83
- # Display pattern distribution.
84
- if not patterns:
85
- colored_print("No patterns to display", "yellow")
86
- return
87
-
88
- colored_print(f"\n Top {limit} Green Patterns:\n", "cyan")
89
-
90
- # Sort by count
91
- sorted_patterns = sorted(
92
- patterns.items(), key=lambda x: x[1].get("count", 0), reverse=True
93
- )[:limit]
94
-
95
- table_data = []
96
- for pattern_name, data in sorted_patterns:
97
- table_data.append(
98
- [
99
- pattern_name,
100
- data.get("count", 0),
101
- f"{data.get('percentage', 0):.1f}%",
102
- data.get("confidence_distribution", {}).get("HIGH", 0),
103
- ]
104
- )
105
-
106
- headers = ["Pattern", "Count", "Percentage", "High Confidence"]
107
- print(tabulate(table_data, headers=headers, tablefmt="grid"))
108
-
109
- @staticmethod
110
- def show_pipeline_status(status: dict[str, Any]):
111
- # Display pipeline status.
112
- colored_print("\n Pipeline Status:\n", "cyan")
113
-
114
- table_data = []
115
- for phase, info in status.items():
116
- status_icon = "done" if info.get("completed") else "pending"
117
- table_data.append(
118
- [status_icon, phase, info.get("file", "N/A"), info.get("size", "N/A")]
119
- )
120
-
121
- headers = ["Status", "Phase", "Output File", "Size"]
122
- print(tabulate(table_data, headers=headers, tablefmt="grid"))
123
-
124
- @staticmethod
125
- def show_progress_message(phase: str, current: int, total: int):
126
- # Display progress message.
127
- percentage = (current / total * 100) if total > 0 else 0
128
- colored_print(f"[{phase}] Progress: {current}/{total} ({percentage:.1f}%)", "cyan")
129
-
130
- @staticmethod
131
- def show_error(message: str):
132
- # Display error message.
133
- colored_print(f" Error: {message}", "red")
134
-
135
- @staticmethod
136
- def show_success(message: str):
137
- # Display success message.
138
- colored_print(f" {message}", "green")
139
-
140
- @staticmethod
141
- def show_warning(message: str):
142
- # Display warning message.
143
- colored_print(f" Warning: {message}", "yellow")