2021-08-03 15:56:28 +01:00
|
|
|
"""
|
|
|
|
Present observational weather data to Prometheus
|
|
|
|
"""
|
|
|
|
|
|
|
|
import json
|
|
|
|
import os
|
2021-12-12 09:55:26 +00:00
|
|
|
from typing import TypedDict
|
2021-08-03 15:56:28 +01:00
|
|
|
|
2021-12-12 09:55:26 +00:00
|
|
|
import requests
|
|
|
|
from flask import Flask, Response
|
2021-08-03 15:56:28 +01:00
|
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
|
|
|
2021-12-12 09:55:26 +00:00
|
|
|
class PromData(TypedDict):
|
|
|
|
"""
|
|
|
|
Describes the data being returned to Prometheus
|
|
|
|
|
|
|
|
Args:
|
|
|
|
TypedDict ([type]): data to return to Prometheus
|
|
|
|
"""
|
|
|
|
|
|
|
|
key: str
|
|
|
|
labels: dict[str, str]
|
|
|
|
type: str
|
|
|
|
value: float
|
|
|
|
|
|
|
|
|
|
|
|
class FuelMix(TypedDict):
|
|
|
|
"""
|
|
|
|
Generational Fuel Mix
|
|
|
|
|
|
|
|
Args:
|
|
|
|
TypedDict ([type]): the particular fuel's usage
|
|
|
|
"""
|
|
|
|
|
|
|
|
fuel: str
|
|
|
|
perc: float
|
|
|
|
|
|
|
|
|
|
|
|
class Intensity(TypedDict):
|
|
|
|
"""
|
|
|
|
Intensity forecast
|
|
|
|
|
|
|
|
Args:
|
|
|
|
TypedDict ([type]): the intensity forecast
|
|
|
|
"""
|
|
|
|
|
|
|
|
forecast: int
|
|
|
|
index: str
|
|
|
|
|
|
|
|
|
|
|
|
class PostcodeData(TypedDict):
|
|
|
|
"""
|
|
|
|
Data for a postcode
|
|
|
|
|
|
|
|
Args:
|
|
|
|
TypedDict ([type]): Describes the date range, intensity and fuel mix
|
|
|
|
"""
|
|
|
|
|
|
|
|
_from: str
|
|
|
|
to: str
|
|
|
|
intensity: Intensity
|
|
|
|
generationmix: list[FuelMix]
|
|
|
|
|
|
|
|
|
|
|
|
class RegionData(TypedDict):
|
|
|
|
"""
|
|
|
|
Data for a region
|
|
|
|
|
|
|
|
Args:
|
|
|
|
TypedDict ([type]): describes the region and its data
|
|
|
|
"""
|
|
|
|
|
|
|
|
regionid: int
|
|
|
|
dnoregion: str
|
|
|
|
shortname: str
|
|
|
|
postcode: str
|
|
|
|
data: list[PostcodeData]
|
|
|
|
|
|
|
|
|
|
|
|
class CarbonData(TypedDict):
|
|
|
|
"""
|
|
|
|
Overall data for a request
|
|
|
|
|
|
|
|
Args:
|
|
|
|
TypedDict ([type]): describes the returned data
|
|
|
|
"""
|
|
|
|
|
|
|
|
data: list[RegionData]
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_carbon_data(postcode: str) -> CarbonData:
|
2021-08-03 15:56:28 +01:00
|
|
|
"""
|
|
|
|
Fetch current data for the carbon intensity of the provided region
|
|
|
|
"""
|
|
|
|
|
|
|
|
obs_data = requests.get(
|
|
|
|
f'https://api.carbonintensity.org.uk/regional/postcode/{postcode}'
|
|
|
|
)
|
|
|
|
|
2021-12-12 09:55:26 +00:00
|
|
|
carbon_data: CarbonData = json.loads(obs_data.content)
|
|
|
|
return carbon_data
|
2021-08-03 15:56:28 +01:00
|
|
|
|
|
|
|
|
|
|
|
@app.route('/metrics')
|
2021-12-12 09:55:26 +00:00
|
|
|
def metrics() -> Response:
|
2021-08-03 15:56:28 +01:00
|
|
|
"""
|
|
|
|
Output Prometheus-style metrics
|
|
|
|
"""
|
2021-12-12 09:55:26 +00:00
|
|
|
postcode = os.environ.get('CARBON_POSTCODE') or ''
|
2021-08-03 15:56:28 +01:00
|
|
|
|
|
|
|
latest_data = fetch_carbon_data(postcode)['data'][0]
|
|
|
|
|
2021-12-12 09:55:26 +00:00
|
|
|
ret_data: list[PromData] = []
|
2021-08-03 15:56:28 +01:00
|
|
|
for generation in latest_data['data'][0]['generationmix']:
|
|
|
|
ret_data.append(
|
|
|
|
{
|
2021-12-12 09:55:26 +00:00
|
|
|
'key': 'sensor_carbon_generation_perc',
|
2021-08-03 15:56:28 +01:00
|
|
|
'labels': {
|
2021-08-03 16:19:41 +01:00
|
|
|
'fuel': generation['fuel'],
|
2021-08-03 15:56:28 +01:00
|
|
|
'region': latest_data['dnoregion'],
|
|
|
|
},
|
|
|
|
'type': 'gauge',
|
|
|
|
'value': float(generation['perc'])
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
ret_data.append(
|
|
|
|
{
|
|
|
|
'key': 'sensor_carbon_intensity_forecast',
|
|
|
|
'labels': {
|
|
|
|
'region': latest_data['dnoregion'],
|
|
|
|
},
|
|
|
|
'type': 'gauge',
|
|
|
|
'value': float(latest_data['data'][0]['intensity']['forecast']),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2021-12-12 09:55:26 +00:00
|
|
|
ret_strs = []
|
2021-08-03 15:56:28 +01:00
|
|
|
for item in ret_data:
|
|
|
|
ret_strs.append(f'# HELP {item["key"]} Carbon metric')
|
|
|
|
ret_strs.append(f'# TYPE {item["key"]} {item["type"]}')
|
|
|
|
ret_strs.append(
|
|
|
|
item["key"]
|
|
|
|
+ '{'
|
|
|
|
+ " ".join([f'{key}="{val}"' for key, val in item["labels"].items()])
|
|
|
|
+ '} '
|
|
|
|
+ str(item["value"])
|
|
|
|
)
|
|
|
|
|
|
|
|
resp = Response('\n'.join(ret_strs))
|
|
|
|
resp.headers['Content-type'] = 'text/plain'
|
|
|
|
|
|
|
|
return resp
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
app.run(host='0.0.0.0')
|