Skip to content

Commit b0545e5

Browse files
committed
Added local refresh and handling of complete offline running for CO2
1 parent ff41785 commit b0545e5

File tree

2 files changed

+41
-5
lines changed

2 files changed

+41
-5
lines changed

simvue/eco/emissions_monitor.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import logging
1616
import humanfriendly
1717
import pathlib
18+
import os.path
1819

1920
from simvue.eco.api_client import APIClient, CO2SignalResponse
2021

@@ -46,6 +47,7 @@ class CO2Monitor(pydantic.BaseModel):
4647
intensity_refresh_interval: int | None | str
4748
co2_intensity: float | None
4849
co2_signal_api_token: str | None
50+
offline: bool = False
4951

5052
def now(self) -> str:
5153
"""Return data file timestamp for the current time"""
@@ -97,7 +99,9 @@ def __init__(self, *args, **kwargs) -> None:
9799
disable using RestAPIs to retrieve CO2 intensity and instead use this value.
98100
Default is None, use remote data. Value is in kgCO2/kWh
99101
co2_signal_api_token: str
100-
RECOMMENDED. The API token for CO2 signal, default is None.
102+
The API token for CO2 signal, default is None.
103+
offline: bool, optional
104+
Run without any server interaction
101105
"""
102106
_logger = logging.getLogger(self.__class__.__name__)
103107

@@ -113,6 +117,7 @@ def __init__(self, *args, **kwargs) -> None:
113117
"⚠️ No TDP value provided for current GPUs, will use arbitrary value of 130W."
114118
)
115119
super().__init__(*args, **kwargs)
120+
self._last_local_write = datetime.datetime.now()
116121

117122
if self.intensity_refresh_interval and isinstance(
118123
self.intensity_refresh_interval, str
@@ -144,7 +149,7 @@ def __init__(self, *args, **kwargs) -> None:
144149
self._logger = _logger
145150
self._client: APIClient | None = (
146151
None
147-
if self.co2_intensity
152+
if self.co2_intensity or self.offline
148153
else APIClient(co2_api_token=self.co2_signal_api_token, timeout=10)
149154
)
150155
self._processes: dict[str, ProcessData] = {}
@@ -158,10 +163,25 @@ def check_refresh(self) -> bool:
158163
whether a refresh of the CO2 intensity was requested
159164
from the CO2 Signal API.
160165
"""
166+
# Need to check if the local cache has been modified
167+
# even if running offline
161168
if (
162-
not self._local_data.setdefault(self._client.country_code, {})
163-
or self.outdated
169+
self._data_file_path.exists()
170+
and (
171+
_check_write := datetime.datetime.fromtimestamp(
172+
os.path.getmtime(f"{self._data_file_path}")
173+
)
174+
)
175+
> self._last_local_write
164176
):
177+
self._last_local_write = _check_write
178+
with self._data_file_path.open("r") as in_f:
179+
self._local_data = json.load(in_f)
180+
181+
if (
182+
self._client
183+
and not self._local_data.setdefault(self._client.country_code, {})
184+
) or self.outdated:
165185
self._logger.info("🌍 CO2 emission outdated, calling API.")
166186
_data: CO2SignalResponse = self._client.get()
167187
self._local_data[self._client.country_code] = _data.model_dump(mode="json")
@@ -200,8 +220,23 @@ def estimate_co2_emissions(
200220
_co2_units = "kgCO2/kWh"
201221
else:
202222
self.check_refresh()
223+
if self._client:
224+
_country_code = self._client.country_code
225+
else:
226+
# If no local data yet then return
227+
if not (_country_codes := list(self._local_data.keys())):
228+
self._logger.warning(
229+
"No CO2 emission data recorded as no CO2 intensity value "
230+
"has been provided and there is no local intensity data available."
231+
)
232+
return
233+
234+
_country_code = _country_codes[0]
235+
self._logger.debug(
236+
f"🗂️ Using data for region '{_country_code}' from local cache for offline estimation."
237+
)
203238
self._current_co2_data = CO2SignalResponse(
204-
**self._local_data[self._client.country_code]
239+
**self._local_data[_country_code]
205240
)
206241
_current_co2_intensity = self._current_co2_data.data.carbon_intensity
207242
_co2_units = self._current_co2_data.carbon_intensity_units

simvue/run.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,7 @@ def config(
11101110
co2_signal_api_token=None,
11111111
thermal_design_power_per_cpu=self._user_config.eco.cpu_thermal_design_power,
11121112
thermal_design_power_per_gpu=self._user_config.eco.gpu_thermal_design_power,
1113+
offline=True,
11131114
)
11141115
else:
11151116
self._emissions_monitor = CO2Monitor(

0 commit comments

Comments
 (0)