Skip to content

Commit 435ae06

Browse files
authored
Merge pull request #187 from DataCloud-project/carbontracker
Carbontracker * graphql api endpoint * carbontracker service
2 parents 39fa8b1 + 0c6e428 commit 435ae06

21 files changed

+838
-31
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Create and publish the carbontracker docker image
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- carbontracker
8+
tags:
9+
- "*"
10+
pull_request:
11+
paths:
12+
- "carbontracker/**"
13+
14+
env:
15+
REGISTRY: ghcr.io
16+
carbontracker_IMAGE_NAME: datacloud-project/sim-pipe-carbontracker
17+
18+
jobs:
19+
build-and-push-carbontracker-image:
20+
name: Build and push carbontracker image
21+
runs-on: ubuntu-latest
22+
permissions:
23+
contents: read
24+
packages: write
25+
steps:
26+
- name: Checkout repository
27+
uses: actions/checkout@v3
28+
29+
- name: Set up QEMU
30+
uses: docker/setup-qemu-action@v2
31+
32+
- name: Set up Docker Buildx
33+
uses: docker/setup-buildx-action@v2
34+
35+
- name: Log in to the Container registry
36+
uses: docker/login-action@v2
37+
with:
38+
registry: ${{ env.REGISTRY }}
39+
username: ${{ github.actor }}
40+
password: ${{ secrets.GITHUB_TOKEN }}
41+
42+
- name: Extract metadata (tags, labels) for carbontracker main image
43+
id: meta-main
44+
uses: docker/metadata-action@v4
45+
with:
46+
images: ${{ env.REGISTRY }}/${{ env.carbontracker_IMAGE_NAME }}
47+
48+
- name: Build and push Docker carbontracker image
49+
uses: docker/build-push-action@v4
50+
with:
51+
context: ./carbontracker/
52+
push: true
53+
tags: ${{ steps.meta-main.outputs.tags }}
54+
labels: ${{ steps.meta-main.outputs.labels }}
55+
platforms: linux/arm64,linux/amd64

carbontracker/Dockerfile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# File: Dockerfile
2+
FROM python:3.13-slim
3+
4+
WORKDIR /app
5+
6+
# Install system dependencies for reportlab and other packages
7+
RUN apt-get update && \
8+
apt-get install -y \
9+
git \
10+
build-essential \
11+
libfreetype6-dev \
12+
libjpeg-dev \
13+
libpng-dev \
14+
zlib1g-dev \
15+
libffi-dev && \
16+
pip install uv && \
17+
rm -rf /var/lib/apt/lists/*
18+
19+
20+
COPY pyproject.toml ./
21+
COPY src/ ./src/
22+
RUN uv sync
23+
24+
COPY requirements.txt ./
25+
RUN pip install -r requirements.txt
26+
27+
EXPOSE 8000
28+
CMD ["uv", "run", "uvicorn", "fastapi_agent.main:app", "--host", "0.0.0.0", "--port", "8000"]

carbontracker/README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# carbontracker fastapi agent
2+
3+
Simple fastapi server hosting `process` endpoint that takes cpu json data as input
4+
5+
Build image
6+
```bash
7+
docker build -t carbontracker-agent .
8+
```
9+
10+
Run image as server
11+
```bash
12+
docker run -p 8000:8000 carbontracker-agent
13+
```
14+
15+
Call the fastapi endpoint
16+
```bash
17+
jq -Rs '{data: .}' sample_simpipe_metrics.json | curl -X POST http://localhost:8000/process -H 'Content-Type: application/json' -d @-
18+
```
19+
20+
Output
21+
```json
22+
{"stdout":"CarbonTracker: INFO - Detected CPU: -\nCarbonTracker: INFO - Matched CPU - to 6th Gen A10-8700P APU with TDP 15.0W\nCarbonTracker: INFO - Using TDP of 7.50W for -\nCarbonTracker: WARNING - ElectricityMaps API key not set. Will default to average carbon intensity.\nCarbonTracker: WARNING - ElectricityMaps API key not set. Will default to average carbon intensity.\nCarbonTracker: WARNING - Failed to retrieve carbon intensity: Defaulting to average carbon intensity 30.080084 gCO2/kWh.\nCarbonTracker: WARNING - Failed to retrieve carbon intensity: Defaulting to average carbon intensity 30.080084 gCO2/kWh.\n\n=== CarbonTracker Simulation Results ===\nSource: data.json\n========================================\n\nSimulation Details:\n+ Duration: 0:00:11.00\n+ CPU Model: -\n+ CPU Power Data: Available\n+ Average Power Usage: 7.50 W\n+ Energy Usage: 18.37 J\n+ Location: Trondheim, Trøndelag, NO\n+ Carbon Intensity: 30.08 gCO2/kWh\n\nConsumption Summary:\n+ Energy: 0.000005 kWh\n+ CO2eq: 0.000 g\n\nEnvironmental Impact Equivalents:\n+ 0.000001 km travelled by car\n\nSimulation log written to ./logs\n","stderr":" Building fastapi-agent @ file:///app\n Built fastapi-agent @ file:///app\nUninstalled 1 package in 0.24ms\nInstalled 1 package in 1ms\n","parsed":"{\n \"Duration\": \"0:00:11.00\",\n \"CPU Model\": \"-\",\n \"CPU Power Data\": \"Available\",\n \"Average Power Usage\": \"7.50 W\",\n \"Energy Usage\": \"18.37 J\",\n \"Location\": \"Trondheim, Trøndelag, NO\",\n \"Carbon Intensity\": \"30.08 gCO2/kWh\",\n \"Energy\": \"0.000005 kWh\",\n \"CO2eq\": \"0.000 g\"\n}"}
23+
```
24+
25+
Parse json output:
26+
```python
27+
p = json.loads(data["parsed"])
28+
print(p)
29+
```
30+
31+
will print:
32+
```json
33+
{'Duration': '0:00:11.00',
34+
'CPU Model': '-',
35+
'CPU Power Data': 'Available',
36+
'Average Power Usage': '7.50 W',
37+
'Energy Usage': '18.37 J',
38+
'Location': 'Trondheim, Trøndelag, NO',
39+
'Carbon Intensity': '30.08 gCO2/kWh',
40+
'Energy': '0.000005 kWh',
41+
'CO2eq': '0.000 g'}
42+
```

carbontracker/pyproject.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# File: pyproject.toml
2+
[build-system]
3+
requires = ["setuptools>=61.0", "setuptools_scm[toml]>=6.2"]
4+
build-backend = "setuptools.build_meta"
5+
6+
[project]
7+
name = "fastapi-agent"
8+
version = "0.2.0"
9+
requires-python = ">=3.13"
10+
dependencies = [
11+
"fastapi",
12+
"uvicorn[standard]"
13+
]
14+
15+
[tool.setuptools]
16+
package-dir = {"" = "src"}
17+
packages = ["fastapi_agent"]
18+
19+
[project.scripts]
20+
fastapi-agent = "main:app"

carbontracker/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Carbontracker (tags: greenr)
2+
git+https://github.com/lfwa/carbontracker.git@greenr
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# File: main.py
2+
from fastapi import FastAPI, Request
3+
from pydantic import BaseModel
4+
import json
5+
from .utils import store_data_temporarily, delete_temporary_data, delete_logs, parse_carbontracker_stdout
6+
import subprocess
7+
8+
app = FastAPI()
9+
10+
class InputData(BaseModel):
11+
data: str
12+
13+
@app.post("/process")
14+
async def process(
15+
data: InputData,
16+
sim_cpu: str = None, sim_cpu_tdp: str = None, sim_cpu_util: str = None,
17+
sim_gpu: str = None, sim_gpu_watts: str = None, sim_gpu_util: str = None):
18+
store_data_temporarily(data.data) # is stored as data.json
19+
command = ["uv", "run", "carbontracker", "--simpipe", "data.json"]
20+
if sim_cpu:
21+
command.extend(["--sim-cpu", sim_cpu]) # Not working yet (CLI option not implemented?)
22+
completed_process = subprocess.run(
23+
["uv", "run", "carbontracker", "--simpipe", "data.json"],
24+
check=True,
25+
capture_output=True,
26+
text=True
27+
)
28+
delete_temporary_data()
29+
parsed_output = parse_carbontracker_stdout(completed_process.stdout)
30+
parsed_output_str = json.dumps(parsed_output, indent=2, ensure_ascii=False)
31+
return {"stdout": completed_process.stdout, "stderr": completed_process.stderr, "parsed": parsed_output_str}
32+
33+
@app.get("/delete-logs")
34+
async def delete_carbontracker_logs():
35+
try:
36+
delete_logs()
37+
except Exception as e:
38+
print(f"Error deleting temporary data: {e}")
39+
40+
@app.get("/health")
41+
async def health_check():
42+
return {"status": "ok"}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# File: agent.py
2+
import os
3+
import re
4+
5+
6+
def process_input(data: str) -> str:
7+
# Minimal business logic placeholder
8+
return f"Agent processed: {data}"
9+
10+
11+
def store_data_temporarily(data: str) -> str:
12+
# Placeholder for storing data temporarily
13+
with open("data.json", "w") as f:
14+
f.write(data)
15+
return "Data stored temporarily in data.json"
16+
17+
18+
def delete_temporary_data() -> str:
19+
# Placeholder for deleting temporary data
20+
if os.path.exists("data.json"):
21+
os.remove("data.json")
22+
return "Temporary data deleted"
23+
return "No temporary data to delete"
24+
25+
26+
def delete_logs() -> str:
27+
# Placeholder for deleting logs
28+
if os.path.exists("logs"):
29+
os.remove("logs/*.log")
30+
return "Deleted logs"
31+
return "No logs to delete"
32+
33+
34+
def parse_carbontracker_stdout(stdout: str) -> dict:
35+
result = {}
36+
37+
# Patterns for each field
38+
patterns = {
39+
"Duration": r"\+ Duration:\s*([^\n]+)",
40+
"CPU Model": r"\+ CPU Model:\s*([^\n]+)",
41+
"CPU Power Data": r"\+ CPU Power Data:\s*([^\n]+)",
42+
"Average Power Usage": r"\+ Average Power Usage:\s*([^\n]+)",
43+
"Energy Usage": r"\+ Energy Usage:\s*([^\n]+)",
44+
"Location": r"\+ Location:\s*([^\n]+)",
45+
"Carbon Intensity": r"\+ Carbon Intensity:\s*([^\n]+)",
46+
"Energy": r"\+ Energy:\s*([^\n]+)",
47+
"CO2eq": r"\+ CO2eq:\s*([^\n]+)",
48+
}
49+
50+
for key, pattern in patterns.items():
51+
match = re.search(pattern, stdout)
52+
if match:
53+
result[key] = match.group(1).strip()
54+
else:
55+
result[key] = None
56+
57+
return result

charts/simpipe/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apiVersion: v2
22
name: simpipe
33
description: SIMPIPE on Kubernetes
44
type: application
5-
version: 0.4.8
5+
version: 0.4.9
66
appVersion: 0.4.8
77
dependencies:
88
- name: cadvisor
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{{/*
2+
Expand the name of the chart.
3+
*/}}
4+
{{- define "simpipeCarbontracker.name" -}}
5+
{{- default (printf "%s-carbontracker" .Chart.Name) .Values.carbontracker.nameOverride | trunc 63 | trimSuffix "-" }}
6+
{{- end }}
7+
8+
{{/*
9+
Create a default fully qualified app name.
10+
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
11+
If release name contains chart name it will be used as a full name.
12+
*/}}
13+
{{- define "simpipeCarbontracker.fullname" -}}
14+
{{- if .Values.carbontracker.fullnameOverride }}
15+
{{- .Values.carbontracker.fullnameOverride | trunc 63 | trimSuffix "-" }}
16+
{{- else }}
17+
{{- $name := default (printf "%s-carbontracker" .Chart.Name) .Values.carbontracker.nameOverride }}
18+
{{- if contains $name (printf "%s-carbontracker" .Release.Name) }}
19+
{{- printf "%s-carbontracker" .Release.Name | trunc 63 | trimSuffix "-" }}
20+
{{- else }}
21+
{{- printf "%s-%s-carbontracker" .Release.Name $name | trunc 63 | trimSuffix "-" }}
22+
{{- end }}
23+
{{- end }}
24+
{{- end }}
25+
26+
{{/*
27+
Create chart name and version as used by the chart label.
28+
*/}}
29+
{{- define "simpipeCarbontracker.chart" -}}
30+
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
31+
{{- end }}
32+
33+
{{/*
34+
Common labels
35+
*/}}
36+
{{- define "simpipeCarbontracker.labels" -}}
37+
helm.sh/chart: {{ include "simpipeCarbontracker.chart" . }}
38+
{{ include "simpipeCarbontracker.selectorLabels" . }}
39+
{{- if .Chart.AppVersion }}
40+
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
41+
{{- end }}
42+
app.kubernetes.io/managed-by: {{ .Release.Service }}
43+
{{- end }}
44+
45+
{{/*
46+
Selector labels
47+
*/}}
48+
{{- define "simpipeCarbontracker.selectorLabels" -}}
49+
app.kubernetes.io/name: {{ include "simpipeCarbontracker.name" . }}
50+
app.kubernetes.io/instance: {{ .Release.Name }}
51+
{{- end }}
52+
53+
{{/*
54+
Create the name of the service account to use
55+
*/}}
56+
{{- define "simpipeCarbontracker.serviceAccountName" -}}
57+
{{- default (include "simpipeCarbontracker.fullname" .) .Values.carbontracker.serviceAccount.name }}
58+
{{- end }}

0 commit comments

Comments
 (0)