Skip to content
Open
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
97 changes: 42 additions & 55 deletions src/ecooptimizer/api/routes/refactor_smell.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""API endpoints for code refactoring with energy measurement."""
"""API endpoints for code refactoring with carbon savings."""

# pyright: reportOptionalMemberAccess=false
import shutil
Expand All @@ -11,8 +11,6 @@

from ecooptimizer.api.error_handler import (
AppError,
EnergyMeasurementError,
EnergySavingsError,
RefactoringError,
RessourceNotFoundError,
remove_readonly,
Expand All @@ -21,15 +19,42 @@
from ecooptimizer.config import CONFIG
from ecooptimizer.refactorers.refactorer_controller import RefactorerController
from ecooptimizer.analyzers.analyzer_controller import AnalyzerController
from ecooptimizer.measurements.codecarbon_energy_meter import CodeCarbonEnergyMeter
from ecooptimizer.data_types.smell import Smell

logger = CONFIG["refactorLogger"]

# Static mapping of carbon savings per smell type (in kg CO2).

Check failure on line 26 in src/ecooptimizer/api/routes/refactor_smell.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (W291)

src/ecooptimizer/api/routes/refactor_smell.py:26:63: W291 Trailing whitespace
# TODO: This is a placeholder and should be replaced with actual values once empirical data is available.
CARBON_SAVINGS_MAP = {
# Pylint smells

Check failure on line 29 in src/ecooptimizer/api/routes/refactor_smell.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (W291)

src/ecooptimizer/api/routes/refactor_smell.py:29:20: W291 Trailing whitespace
"use-a-generator": 0.0025,

Check failure on line 30 in src/ecooptimizer/api/routes/refactor_smell.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (W291)

src/ecooptimizer/api/routes/refactor_smell.py:30:31: W291 Trailing whitespace
"too-many-arguments": 0.0015,

Check failure on line 31 in src/ecooptimizer/api/routes/refactor_smell.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (W291)

src/ecooptimizer/api/routes/refactor_smell.py:31:34: W291 Trailing whitespace
"no-self-use": 0.0010,

Check failure on line 33 in src/ecooptimizer/api/routes/refactor_smell.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (W293)

src/ecooptimizer/api/routes/refactor_smell.py:33:1: W293 Blank line contains whitespace
# Custom smells

Check failure on line 34 in src/ecooptimizer/api/routes/refactor_smell.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (W291)

src/ecooptimizer/api/routes/refactor_smell.py:34:20: W291 Trailing whitespace
"long-lambda-expression": 0.0020,

Check failure on line 35 in src/ecooptimizer/api/routes/refactor_smell.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (W291)

src/ecooptimizer/api/routes/refactor_smell.py:35:38: W291 Trailing whitespace
"long-message-chain": 0.0030,
"long-element-chain": 0.0025,
"cached-repeated-calls": 0.0050,
"string-concat-loop": 0.0040,
}


def get_carbon_savings_for_smell(smell_symbol: str) -> float:
"""Get static energy savings for a smell type.

Args:
smell_symbol: The smell symbol from CARBON_SAVINGS_MAP dictionary.

Returns:
Energy savings in kg CO2
"""
savings = CARBON_SAVINGS_MAP.get(smell_symbol, 0.0)
return savings

router = APIRouter()
refactorer_controller = RefactorerController()
analyzer_controller = AnalyzerController()
energy_meter = CodeCarbonEnergyMeter()


class ChangedFile(BaseModel):
Expand Down Expand Up @@ -88,13 +113,13 @@

@router.post("/refactor", response_model=RefactoredData, summary="Refactor a specific code smell")
def refactor(request: RefactorRqModel) -> RefactoredData | None:
"""Refactors a specific code smell and measures energy impact.
"""Refactors a specific code smell and measures carbon savings through empirical data.

Args:
request: Contains source directory and smell to refactor

Returns:
RefactoredData: Results including energy savings and changed files
RefactoredData: Results including carbon savings and changed files
None: If refactoring fails

Raises:
Expand All @@ -114,13 +139,7 @@
raise RessourceNotFoundError(str(source_dir), "folder")

try:
initial_emissions = measure_energy(target_file)
if not initial_emissions:
logger.error("❌ Could not retrieve initial emissions.")
raise EnergyMeasurementError(str(target_file))

logger.info(f"📊 Initial emissions: {initial_emissions} kg CO2")
refactor_data = perform_refactoring(source_dir, request.smell, initial_emissions)
refactor_data = perform_refactoring(source_dir, request.smell)

if refactor_data:
logger.info(f"{'=' * 100}\n")
Expand All @@ -137,7 +156,7 @@
"/refactor-by-type", response_model=RefactoredData, summary="Refactor all smells of a type"
)
def refactorSmell(request: RefactorTypeRqModel) -> RefactoredData:
"""Refactors all instances of a smell type in a file.
"""Refactors all instances of a smell type in a file and measures carbon savings through empirical data..

Args:
request: Contains source directory, smell type and first instance
Expand All @@ -159,19 +178,15 @@

if not source_dir.is_dir():
raise RessourceNotFoundError(str(source_dir), "folder")

try:
initial_emissions = measure_energy(target_file)
if not initial_emissions:
raise EnergyMeasurementError("Could not retrieve initial emissions.")
logger.info(f"📊 Initial emissions: {initial_emissions} kg CO2")

total_energy_saved = 0.0
all_affected_files: list[ChangedFile] = []
temp_dir = None
current_smell = request.firstSmell
current_source_dir = source_dir

refactor_data = perform_refactoring(current_source_dir, current_smell, initial_emissions)
refactor_data = perform_refactoring(current_source_dir, current_smell)
total_energy_saved += refactor_data.energySaved or 0.0
all_affected_files.extend(refactor_data.affectedFiles)

Expand All @@ -190,7 +205,6 @@
step_data = perform_refactoring(
source_copy_dir,
current_smell,
initial_emissions - total_energy_saved,
Path(temp_dir),
)
total_energy_saved += step_data.energySaved or 0.0
Expand All @@ -213,23 +227,19 @@
def perform_refactoring(
source_dir: Path,
smell: Smell,
initial_emissions: float,
existing_temp_dir: Optional[Path] = None,
) -> RefactoredData:
"""Executes the refactoring process and measures energy impact.
"""Executes the refactoring process and calculates carbon savings.

Args:
sourceDir: Source directory to refactor
source_dir: Source directory to refactor
smell: Smell to refactor
initial_emissions: Baseline energy measurement
existing_temp_dir: Optional existing temp directory to use

Returns:
RefactoredData: Results of the refactoring operation

Raises:
RuntimeError: If energy measurement fails
EnergySavingsError: If refactoring doesn't save energy
RefactoringError: If refactoring fails
"""
print()
Expand Down Expand Up @@ -258,19 +268,9 @@
traceback.print_exc()
raise RefactoringError(str(e)) from e

print("energy")
final_emissions = measure_energy(target_file_copy)
if not final_emissions:
if existing_temp_dir is None:
shutil.rmtree(temp_dir, onerror=remove_readonly) # type: ignore
raise EnergyMeasurementError(str(target_file))

if CONFIG["mode"] == "production" and final_emissions >= initial_emissions:
if existing_temp_dir is None:
shutil.rmtree(temp_dir, onerror=remove_readonly) # type: ignore
raise EnergySavingsError()

energy_saved = initial_emissions - final_emissions
# Get static energy savings instead of measuring
energy_saved = get_carbon_savings_for_smell(smell.symbol)

return RefactoredData(
tempDir=str(temp_dir),
targetFile=ChangedFile(
Expand All @@ -285,17 +285,4 @@
)
for file in modified_files
],
)


def measure_energy(file: Path) -> Optional[float]:
"""Measures energy consumption of executing a file.

Args:
file: Python file to measure

Returns:
Optional[float]: Energy consumption in kg CO2, or None if measurement fails
"""
energy_meter.measure_energy(file)
return energy_meter.emissions
)
Loading