Skip to content
Open
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion webapp/models/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,4 @@ def combined_updated_at(self) -> datetime:
return self.realtime_data_updated_at
if self.static_data_updated_at:
return self.static_data_updated_at
return self.modified_at
return self.modified
15 changes: 14 additions & 1 deletion webapp/prometheus_api/prometheus_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ def __init__(
def get_metrics(self) -> str:
sources = self.source_repository.fetch_sources()

last_modified_metrics = Metrics(
help='Last modification in seconds from now',
type=MetricType.gauge,
identifier='app_ocpdb_source_last_modified',
)
last_static_update_metrics = Metrics(
help='Last static update in seconds from now',
type=MetricType.gauge,
Expand Down Expand Up @@ -77,6 +82,13 @@ def get_metrics(self) -> str:
if source.static_status in [SourceStatus.DISABLED, SourceStatus.PROVISIONED]:
continue

last_modified_metrics.metrics.append(
SourceMetric(
source=source.uid,
value=int((datetime.now(tz=timezone.utc) - source.modified).total_seconds()),
)
)

if source.static_data_updated_at:
last_static_update_metrics.metrics.append(
SourceMetric(
Expand Down Expand Up @@ -121,7 +133,8 @@ def get_metrics(self) -> str:
)

metrics = (
failed_static_sources.to_metrics()
last_modified_metrics.to_metrics()
+ failed_static_sources.to_metrics()
+ last_static_update_metrics.to_metrics()
+ source_static_errors.to_metrics()
+ failed_realtime_sources.to_metrics()
Expand Down
7 changes: 6 additions & 1 deletion webapp/repositories/evse_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,12 @@ def fetch_evses_by_source_and_uids(self, source_uid: str, uids: list[str]) -> li
query = (
self.session
.query(Evse)
.options(selectinload(Evse.connectors))
.options(
selectinload(Evse.connectors),
# save_evse_updates() lifts the EVSE last_updated to its location, so eager-load the whole chain:
# with a lot of realtime updates per second, a lazy load per EVSE would be an N+1 on every heartbeat.
joinedload(Evse.charging_station).joinedload(ChargingStation.location),
)
.filter(Evse.uid.in_(uids))
.join(Evse.charging_station)
.join(ChargingStation.location)
Expand Down
20 changes: 20 additions & 0 deletions webapp/services/import_services/base_import_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ def save_evse_updates(self, evse_updates: list[EvseUpdate | EvseRealtimeUpdate])
continue
setattr(evse, key, value)

# Charging station and location are only as fresh as their freshest EVSE, so lift the new timestamp up
# the chain if it is newer.
charging_station = evse.charging_station
if evse.last_updated > charging_station.last_updated:
charging_station.last_updated = evse.last_updated
if charging_station.last_updated > charging_station.location.last_updated:
charging_station.location.last_updated = charging_station.last_updated

self.evse_repository.save_evse(evse, commit=False)

self.evse_repository.session.commit()
Expand Down Expand Up @@ -255,6 +263,12 @@ def save_location_update(
)
location.charging_pool = new_charging_stations

# get_charging_station() already lifted every charging station to its freshest EVSE, so taking the maximum
# over the charging stations propagates the newest EVSE timestamp up to the location.
for charging_station in new_charging_stations:
if charging_station.last_updated > location.last_updated:
location.last_updated = charging_station.last_updated

# Fetch official regional code if not set, if all required fields are available and if country is supported
if (
not location.official_region_code
Expand Down Expand Up @@ -340,6 +354,12 @@ def get_charging_station(

charging_station.evses = new_evses

# Same rule as in save_evse_updates(): a charging station is only as fresh as its freshest EVSE. Sources
# regularly send EVSE timestamps which are newer than the charging station timestamp.
for evse in new_evses:
if evse.last_updated > charging_station.last_updated:
charging_station.last_updated = evse.last_updated

return charging_station

def get_evse(
Expand Down
Loading