use anyhow::Result;
use crate::codegen::parser::ParsedFunctionBudget;
use crate::manifest::Manifest;
pub fn generate_instrumentation(
manifest: &Manifest,
functions: &[ParsedFunctionBudget],
) -> Result<String> {
let mut code = String::with_capacity(4096);
code.push_str(&format!(
"// Auto-generated by eclexiaiser for project '{}'\n",
manifest.project.name
));
code.push_str("// SPDX-License-Identifier: PMPL-1.0-or-later\n");
code.push_str("//\n");
code.push_str("// Energy/carbon instrumentation wrappers.\n");
code.push_str("// DO NOT EDIT — regenerate with `eclexiaiser generate`.\n\n");
code.push_str("use std::time::Instant;\n\n");
code.push_str("/// Estimated average power draw in milliwatts.\n");
code.push_str("/// Replace with actual measurement in production.\n");
code.push_str("const ESTIMATED_POWER_MW: f64 = 15000.0; // 15W typical\n\n");
code.push_str(&format!(
"/// Carbon intensity: {} mg CO2/kWh (provider: {}, region: {})\n",
manifest.carbon.static_intensity,
manifest.carbon.provider.display_name(),
manifest.carbon.region
));
code.push_str(&format!(
"const CARBON_INTENSITY_MG_PER_KWH: f64 = {:.1};\n\n",
manifest.carbon.static_intensity
));
code.push_str("/// Measure energy consumption of a closure in millijoules.\n");
code.push_str("///\n");
code.push_str("/// Uses wall-clock time * estimated power draw as a proxy.\n");
code.push_str("/// Returns (result, energy_mj, duration_ms).\n");
code.push_str("pub fn measure_energy<F, R>(f: F) -> (R, f64, f64)\n");
code.push_str("where\n");
code.push_str(" F: FnOnce() -> R,\n");
code.push_str("{\n");
code.push_str(" let start = Instant::now();\n");
code.push_str(" let result = f();\n");
code.push_str(" let elapsed_ms = start.elapsed().as_secs_f64() * 1000.0;\n");
code.push_str(" // energy (mJ) = power (mW) * time (s) = power (mW) * time_ms / 1000\n");
code.push_str(" let energy_mj = ESTIMATED_POWER_MW * elapsed_ms / 1000.0;\n");
code.push_str(" (result, energy_mj, elapsed_ms)\n");
code.push_str("}\n\n");
code.push_str("/// Estimate carbon emissions from energy consumption.\n");
code.push_str("///\n");
code.push_str("/// Returns mg CO2 from millijoules of energy.\n");
code.push_str("pub fn estimate_carbon_mg(energy_mj: f64) -> f64 {\n");
code.push_str(" energy_mj * CARBON_INTENSITY_MG_PER_KWH / 3_600_000_000.0\n");
code.push_str("}\n\n");
code.push_str("/// Measurement result for a single function call.\n");
code.push_str("#[derive(Debug)]\n");
code.push_str("pub struct Measurement {\n");
code.push_str(" pub function_name: &'static str,\n");
code.push_str(" pub energy_mj: f64,\n");
code.push_str(" pub carbon_mg: f64,\n");
code.push_str(" pub duration_ms: f64,\n");
code.push_str(" pub energy_budget_mj: Option<f64>,\n");
code.push_str(" pub carbon_budget_mg: Option<f64>,\n");
code.push_str(" pub energy_exceeded: bool,\n");
code.push_str(" pub carbon_exceeded: bool,\n");
code.push_str("}\n\n");
for func in functions {
generate_function_wrapper(&mut code, func);
}
Ok(code)
}
fn generate_function_wrapper(code: &mut String, func: &ParsedFunctionBudget) {
let safe_name = func.name.replace("::", "_");
if let Some(ref budget) = func.energy_budget {
code.push_str(&format!(
"const {}_ENERGY_BUDGET_MJ: f64 = {:.1};\n",
safe_name.to_uppercase(),
budget.max_millijoules
));
}
if let Some(ref budget) = func.carbon_budget {
code.push_str(&format!(
"const {}_CARBON_BUDGET_MG: f64 = {:.1};\n",
safe_name.to_uppercase(),
budget.max_mg_co2
));
}
code.push_str(&format!(
"\n/// Energy-instrumented wrapper for `{}`.\n",
func.name
));
code.push_str(&format!(
"/// Source: {}\n",
func.source
));
code.push_str(&format!(
"pub fn measure_{safe_name}<F, R>(f: F) -> (R, Measurement)\n"
));
code.push_str("where\n");
code.push_str(" F: FnOnce() -> R,\n");
code.push_str("{\n");
code.push_str(" let (result, energy_mj, duration_ms) = measure_energy(f);\n");
code.push_str(" let carbon_mg = estimate_carbon_mg(energy_mj);\n");
let energy_budget_expr = if func.energy_budget.is_some() {
format!("Some({}_ENERGY_BUDGET_MJ)", safe_name.to_uppercase())
} else {
"None".to_string()
};
let carbon_budget_expr = if func.carbon_budget.is_some() {
format!("Some({}_CARBON_BUDGET_MG)", safe_name.to_uppercase())
} else {
"None".to_string()
};
let energy_exceeded = if func.energy_budget.is_some() {
format!(
"energy_mj > {}_ENERGY_BUDGET_MJ",
safe_name.to_uppercase()
)
} else {
"false".to_string()
};
let carbon_exceeded = if func.carbon_budget.is_some() {
format!(
"carbon_mg > {}_CARBON_BUDGET_MG",
safe_name.to_uppercase()
)
} else {
"false".to_string()
};
code.push_str(&format!(
" let measurement = Measurement {{\n\
\x20 function_name: \"{}\",\n\
\x20 energy_mj,\n\
\x20 carbon_mg,\n\
\x20 duration_ms,\n\
\x20 energy_budget_mj: {energy_budget_expr},\n\
\x20 carbon_budget_mg: {carbon_budget_expr},\n\
\x20 energy_exceeded: {energy_exceeded},\n\
\x20 carbon_exceeded: {carbon_exceeded},\n\
\x20 }};\n",
func.name
));
code.push_str(" (result, measurement)\n");
code.push_str("}\n\n");
}
pub fn generate_constraints(
manifest: &Manifest,
functions: &[ParsedFunctionBudget],
) -> Result<String> {
let mut ecl = String::with_capacity(2048);
ecl.push_str(&format!(
";; Eclexia energy constraints for '{}'\n",
manifest.project.name
));
ecl.push_str(";; Auto-generated by eclexiaiser — DO NOT EDIT\n");
ecl.push_str(";; SPDX-License-Identifier: PMPL-1.0-or-later\n\n");
ecl.push_str("(carbon-config\n");
ecl.push_str(&format!(
" (provider \"{}\")\n",
manifest.carbon.provider.display_name().to_lowercase()
));
ecl.push_str(&format!(" (region \"{}\")\n", manifest.carbon.region));
ecl.push_str(&format!(
" (intensity-mg-per-kwh {:.1}))\n\n",
manifest.carbon.static_intensity
));
for func in functions {
ecl.push_str(&format!(";; Constraints for {}\n", func.name));
ecl.push_str(&format!("(define-constraint {}\n", func.name));
ecl.push_str(&format!(" (source \"{}\")\n", func.source));
if let Some(ref budget) = func.energy_budget {
ecl.push_str(&format!(
" (energy-bound-mj {:.1})\n",
budget.max_millijoules
));
}
if let Some(ref budget) = func.carbon_budget {
ecl.push_str(&format!(
" (carbon-bound-mg {:.1})\n",
budget.max_mg_co2
));
}
ecl.push_str(" (enforcement strict))\n\n");
}
Ok(ecl)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::abi::{
CarbonConfig, CarbonProvider, FunctionBudget, ReportConfig, ReportFormat,
};
use crate::codegen::parser::parse_function_budgets;
use crate::manifest::{Manifest, ProjectConfig};
fn test_manifest() -> Manifest {
Manifest {
project: ProjectConfig {
name: "test-project".to_string(),
},
functions: vec![
FunctionBudget {
name: "process_batch".to_string(),
source: "src/batch.rs".to_string(),
energy_budget_mj: Some(50.0),
carbon_budget_mg: Some(10.0),
},
FunctionBudget {
name: "render_page".to_string(),
source: "src/web.rs".to_string(),
energy_budget_mj: Some(5.0),
carbon_budget_mg: None,
},
],
carbon: CarbonConfig {
provider: CarbonProvider::Static,
region: "GB".to_string(),
static_intensity: 200.0,
},
report: ReportConfig {
format: ReportFormat::Text,
include_recommendations: true,
},
}
}
#[test]
fn test_generate_instrumentation_contains_wrappers() {
let m = test_manifest();
let parsed = parse_function_budgets(&m.functions).unwrap();
let code = generate_instrumentation(&m, &parsed).unwrap();
assert!(code.contains("measure_process_batch"));
assert!(code.contains("measure_render_page"));
assert!(code.contains("PROCESS_BATCH_ENERGY_BUDGET_MJ"));
assert!(code.contains("RENDER_PAGE_ENERGY_BUDGET_MJ"));
assert!(code.contains("PROCESS_BATCH_CARBON_BUDGET_MG"));
assert!(!code.contains("RENDER_PAGE_CARBON_BUDGET_MG"));
}
#[test]
fn test_generate_constraints_format() {
let m = test_manifest();
let parsed = parse_function_budgets(&m.functions).unwrap();
let ecl = generate_constraints(&m, &parsed).unwrap();
assert!(ecl.contains("(carbon-config"));
assert!(ecl.contains("(region \"GB\")"));
assert!(ecl.contains("(define-constraint process_batch"));
assert!(ecl.contains("(energy-bound-mj 50.0)"));
assert!(ecl.contains("(carbon-bound-mg 10.0)"));
assert!(ecl.contains("(define-constraint render_page"));
}
}