Improve metrics recording
Some checks failed
Build and Publish / release (push) Failing after 40s

This commit is contained in:
Ryan McGuire 2025-01-08 21:28:38 -05:00
parent c23177b62d
commit dc2470da71
11 changed files with 310 additions and 134 deletions

11
TODO.md
View File

@ -1,15 +1,16 @@
# TODO
- [ ] Configuration for app
- [ ] Configurable metric prefix
- [ ] Helm Chart
- [ ] Update README
- [ ] Add Grafana dashboard
- [ ] Add device name field with ID/Key mappings
- [ ] Add device type field with ID/Key mappings
- [ ] Move EVERYTHING to pointers to support nil
- [ ] Add proxy to upstream support
- [ ] Add new spans
## Done
- [x] Configuration for app
- [x] Configurable metric prefix
- [x] Add device name field with ID/Key mappings
- [x] Add device type field with ID/Key mappings
- [x] Move EVERYTHING to pointers to support nil
- [x] Consolidate battery status into one metric with device label
- [x] Fix shutdown
- [x] Add new fields from WS-2192

View File

@ -34,6 +34,7 @@ type AmbientWeather struct {
func New(appCtx context.Context, awConfig *AmbientLocalExporterConfig) *AmbientWeather {
return &AmbientWeather{
config: awConfig,
appCtx: appCtx,
}
}
@ -43,6 +44,8 @@ func (aw *AmbientWeather) Init() *AmbientWeather {
aw.awnProvider = &awn.AWNProvider{}
aw.wuProvider = &wunderground.WUProvider{}
aw.l = zerolog.Ctx(aw.appCtx)
aw.l.Trace().Any("awConfig", aw.config).Send()
return aw
}
@ -95,13 +98,15 @@ func (aw *AmbientWeather) handleProviderRequest(
// such as dew point and wind chill
update.Enrich()
// Update metrics
// Prepare metrics if this is the first update
if aw.metrics == nil {
if aw.config.MetricPrefix != "" {
weather.MetricPrefix = aw.config.MetricPrefix
}
aw.metrics = weather.MustInitMetrics(aw.appCtx)
aw.InitMetrics()
}
// Enrich station if configured
aw.enrichStation(update)
// Update metrics
aw.metrics.Update(update)
l.Debug().
@ -110,3 +115,26 @@ func (aw *AmbientWeather) handleProviderRequest(
Msg("successfully handled update")
w.Write([]byte("ok"))
}
func (aw *AmbientWeather) InitMetrics() {
if aw.config.MetricPrefix != "" {
weather.MetricPrefix = aw.config.MetricPrefix
}
aw.metrics = weather.MustInitMetrics(aw.appCtx)
}
func (aw *AmbientWeather) enrichStation(update *weather.WeatherUpdate) {
if update != nil && update.StationID != nil && *update.StationID != "" {
for _, station := range aw.config.WeatherStations {
if *update.StationID == station.AWNPassKey || *update.StationID == station.WundergroundID {
update.StationInfo = &weather.StationInfo{
Type: update.StationType,
Equipment: &station.Equipment,
Name: &station.Name,
Keep: station.KeepMetrics,
Drop: station.DropMetrics,
}
}
}
}
}

View File

@ -28,6 +28,10 @@ type WeatherStation struct {
ProxyToWunderground bool `yaml:"proxyToWunderground"`
// Unreliable / unwanted metrics by name of WeatherUpdate Field
// These fields are mapped from the provider fields
DiscardMetrics []weather.WeatherUpdateField `yaml:"discardMetrics"`
// will be excluded if present in discardMetrics
//
// If anything is present in keepMetrics, it is solely applied,
// ignoring discardMetrics
KeepMetrics []weather.WeatherUpdateField `yaml:"keepMetrics"`
DropMetrics []weather.WeatherUpdateField `yaml:"dropMetrics"`
}

View File

@ -33,14 +33,18 @@ func (awn *AWNProvider) ReqToWeather(_ context.Context, r *http.Request) (
}
func MapAwnUpdate(awnUpdate *AmbientWeatherUpdate) *weather.WeatherUpdate {
updateTime, err := time.Parse(time.DateTime, awnUpdate.DateUTC)
if err != nil {
updateTime = time.Now()
updateTime := time.Now()
if awnUpdate.DateUTC != nil {
ut, err := time.Parse(time.DateTime, *awnUpdate.DateUTC)
if err == nil {
updateTime = ut
}
}
return &weather.WeatherUpdate{
StationType: awnUpdate.StationType,
DateUTC: &updateTime,
StationID: awnUpdate.PassKey,
StationType: awnUpdate.StationType,
TempOutdoorF: awnUpdate.TempF,
HumidityOudoor: awnUpdate.Humidity,
WindSpeedMPH: awnUpdate.WindSpeedMPH,

View File

@ -1,31 +1,31 @@
package awn
type AmbientWeatherUpdate struct {
PassKey string `json:"PASSKEY,omitempty" schema:"PASSKEY"`
StationType string `json:"stationtype,omitempty" schema:"stationtype"`
DateUTC string `json:"dateutc,omitempty" schema:"dateutc"`
TempF float64 `json:"tempf,omitempty" schema:"tempf"`
Humidity int `json:"humidity,omitempty" schema:"humidity"`
WindSpeedMPH float64 `json:"windspeedmph,omitempty" schema:"windspeedmph"`
WindGustMPH float64 `json:"windgustmph,omitempty" schema:"windgustmph"`
MaxDailyGust float64 `json:"maxdailygust,omitempty" schema:"maxdailygust"`
WindDir int `json:"winddir,omitempty" schema:"winddir"`
WindDirAVG10m int `json:"winddir_avg10m,omitempty" schema:"winddir_avg10m"`
UV int `json:"uv,omitempty" schema:"uv"`
SolarRadiation float64 `json:"solarradiation,omitempty" schema:"solarradiation"`
HourlyRainIn float64 `json:"hourlyrainin,omitempty" schema:"hourlyrainin"`
EventRainIn float64 `json:"eventrainin,omitempty" schema:"eventrainin"`
DailyRainIn float64 `json:"dailyrainin,omitempty" schema:"dailyrainin"`
WeeklyRainIn float64 `json:"weeklyrainin,omitempty" schema:"weeklyrainin"`
MonthlyRainIn float64 `json:"monthlyrainin,omitempty" schema:"monthlyrainin"`
YearlyRainIn float64 `json:"yearlyrainin,omitempty" schema:"yearlyrainin"`
TotalRainIn float64 `json:"totalrainin,omitempty" schema:"totalrainin"`
BattOut int `json:"battout,omitempty" schema:"battout"`
BattRain int `json:"battrain,omitempty" schema:"battrain"`
TempInF float64 `json:"tempinf,omitempty" schema:"tempinf"`
HumidityIn int `json:"humidityin,omitempty" schema:"humidityin"`
BaromRelIn float64 `json:"baromrelin,omitempty" schema:"baromrelin"`
BaromAbsIn float64 `json:"baromabsin,omitempty" schema:"baromabsin"`
BattIn int `json:"battin,omitempty" schema:"battin"`
BattCO2 int `json:"batt_co2,omitempty" schema:"batt_co2"`
PassKey *string `json:"PASSKEY,omitempty" schema:"PASSKEY"`
StationType *string `json:"stationtype,omitempty" schema:"stationtype"`
DateUTC *string `json:"dateutc,omitempty" schema:"dateutc"`
TempF *float64 `json:"tempf,omitempty" schema:"tempf"`
Humidity *int `json:"humidity,omitempty" schema:"humidity"`
WindSpeedMPH *float64 `json:"windspeedmph,omitempty" schema:"windspeedmph"`
WindGustMPH *float64 `json:"windgustmph,omitempty" schema:"windgustmph"`
MaxDailyGust *float64 `json:"maxdailygust,omitempty" schema:"maxdailygust"`
WindDir *int `json:"winddir,omitempty" schema:"winddir"`
WindDirAVG10m *int `json:"winddir_avg10m,omitempty" schema:"winddir_avg10m"`
UV *int `json:"uv,omitempty" schema:"uv"`
SolarRadiation *float64 `json:"solarradiation,omitempty" schema:"solarradiation"`
HourlyRainIn *float64 `json:"hourlyrainin,omitempty" schema:"hourlyrainin"`
EventRainIn *float64 `json:"eventrainin,omitempty" schema:"eventrainin"`
DailyRainIn *float64 `json:"dailyrainin,omitempty" schema:"dailyrainin"`
WeeklyRainIn *float64 `json:"weeklyrainin,omitempty" schema:"weeklyrainin"`
MonthlyRainIn *float64 `json:"monthlyrainin,omitempty" schema:"monthlyrainin"`
YearlyRainIn *float64 `json:"yearlyrainin,omitempty" schema:"yearlyrainin"`
TotalRainIn *float64 `json:"totalrainin,omitempty" schema:"totalrainin"`
BattOut *int `json:"battout,omitempty" schema:"battout"`
BattRain *int `json:"battrain,omitempty" schema:"battrain"`
TempInF *float64 `json:"tempinf,omitempty" schema:"tempinf"`
HumidityIn *int `json:"humidityin,omitempty" schema:"humidityin"`
BaromRelIn *float64 `json:"baromrelin,omitempty" schema:"baromrelin"`
BaromAbsIn *float64 `json:"baromabsin,omitempty" schema:"baromabsin"`
BattIn *int `json:"battin,omitempty" schema:"battin"`
BattCO2 *int `json:"batt_co2,omitempty" schema:"batt_co2"`
}

View File

@ -33,16 +33,21 @@ func (wu *WUProvider) ReqToWeather(_ context.Context, r *http.Request) (
}
func MapWUUpdate(wuUpdate *WundergroundUpdate) *weather.WeatherUpdate {
updateTime, err := time.Parse(time.DateTime, wuUpdate.DateUTC)
if err != nil {
updateTime = time.Now()
updateTime := time.Now()
if wuUpdate.DateUTC != nil {
ut, err := time.Parse(time.DateTime, *wuUpdate.DateUTC)
if err == nil {
updateTime = ut
}
}
return &weather.WeatherUpdate{
StationType: wuUpdate.SoftwareType,
DateUTC: &updateTime,
TempOutdoorF: wuUpdate.Tempf,
HumidityOudoor: wuUpdate.Humidity,
StationID: wuUpdate.ID,
StationType: wuUpdate.SoftwareType,
TempOutdoorF: wuUpdate.Tempf,
HumidityOudoor: wuUpdate.Humidity,
WindSpeedMPH: wuUpdate.WindGustMPH,
WindGustMPH: wuUpdate.WindGustMPH,
WindDir: wuUpdate.WindDir,

View File

@ -1,29 +1,29 @@
package wunderground
type WundergroundUpdate struct {
ID string `json:"ID,omitempty" schema:"ID"`
Password string `json:"PASSWORD,omitempty" schema:"PASSWORD"`
UV int `json:"UV,omitempty" schema:"UV"`
Action string `json:"action,omitempty" schema:"action"`
BaromIn float64 `json:"baromin,omitempty" schema:"baromin"`
DailyRainIn float64 `json:"dailyrainin,omitempty" schema:"dailyrainin"`
DateUTC string `json:"dateutc,omitempty" schema:"dateutc"`
DewPtF float64 `json:"dewptf,omitempty" schema:"dewptf"`
Humidity int `json:"humidity,omitempty" schema:"humidity"`
IndoorHumidity int `json:"indoorhumidity,omitempty" schema:"indoorhumidity"`
IndoorTempF float64 `json:"indoortempf,omitempty" schema:"indoortempf"`
LowBatt bool `json:"lowbatt,omitempty" schema:"lowbatt"`
MonthlyRainIn float64 `json:"monthlyrainin,omitempty" schema:"monthlyrainin"`
RainIn float64 `json:"rainin,omitempty" schema:"rainin"`
Realtime bool `json:"realtime,omitempty" schema:"realtime"`
Rtfreq int `json:"rtfreq,omitempty" schema:"rtfreq"`
SoftwareType string `json:"softwaretype,omitempty" schema:"softwaretype"`
SolarRadiation float64 `json:"solarradiation,omitempty" schema:"solarradiation"`
Tempf float64 `json:"tempf,omitempty" schema:"tempf"`
WeeklyRainIn float64 `json:"weeklyrainin,omitempty" schema:"weeklyrainin"`
WindChillF float64 `json:"windchillf,omitempty" schema:"windchillf"`
WindDir int `json:"winddir,omitempty" schema:"winddir"`
WindGustMPH float64 `json:"windgustmph,omitempty" schema:"windgustmph"`
WindSpeedMPH float64 `json:"windspeedmph,omitempty" schema:"windspeedmph"`
YearlyRainIn float64 `json:"yearlyrainin,omitempty" schema:"yearlyrainin"`
ID *string `json:"ID,omitempty" schema:"ID"`
Password *string `json:"PASSWORD,omitempty" schema:"PASSWORD"`
UV *int `json:"UV,omitempty" schema:"UV"`
Action *string `json:"action,omitempty" schema:"action"`
BaromIn *float64 `json:"baromin,omitempty" schema:"baromin"`
DailyRainIn *float64 `json:"dailyrainin,omitempty" schema:"dailyrainin"`
DateUTC *string `json:"dateutc,omitempty" schema:"dateutc"`
DewPtF *float64 `json:"dewptf,omitempty" schema:"dewptf"`
Humidity *int `json:"humidity,omitempty" schema:"humidity"`
IndoorHumidity *int `json:"indoorhumidity,omitempty" schema:"indoorhumidity"`
IndoorTempF *float64 `json:"indoortempf,omitempty" schema:"indoortempf"`
LowBatt *bool `json:"lowbatt,omitempty" schema:"lowbatt"`
MonthlyRainIn *float64 `json:"monthlyrainin,omitempty" schema:"monthlyrainin"`
RainIn *float64 `json:"rainin,omitempty" schema:"rainin"`
Realtime *bool `json:"realtime,omitempty" schema:"realtime"`
Rtfreq *int `json:"rtfreq,omitempty" schema:"rtfreq"`
SoftwareType *string `json:"softwaretype,omitempty" schema:"softwaretype"`
SolarRadiation *float64 `json:"solarradiation,omitempty" schema:"solarradiation"`
Tempf *float64 `json:"tempf,omitempty" schema:"tempf"`
WeeklyRainIn *float64 `json:"weeklyrainin,omitempty" schema:"weeklyrainin"`
WindChillF *float64 `json:"windchillf,omitempty" schema:"windchillf"`
WindDir *int `json:"winddir,omitempty" schema:"winddir"`
WindGustMPH *float64 `json:"windgustmph,omitempty" schema:"windgustmph"`
WindSpeedMPH *float64 `json:"windspeedmph,omitempty" schema:"windspeedmph"`
YearlyRainIn *float64 `json:"yearlyrainin,omitempty" schema:"yearlyrainin"`
}

View File

@ -9,15 +9,19 @@ func (u *WeatherUpdate) Enrich() {
return
}
if u.WindChillF == 0 {
u.WindChillF = CalculateWindChill(u.TempOutdoorF, u.WindSpeedMPH)
if u.WindChillF == nil && u.TempOutdoorF != nil && u.WindSpeedMPH != nil {
wc := CalculateWindChill(*u.TempOutdoorF, *u.WindSpeedMPH)
u.WindChillF = &wc
}
if u.DewPointF == 0 && (u.TempOutdoorF != 0 && u.HumidityOudoor != 0) {
u.DewPointF = CalculateDewPoint(u.TempOutdoorF, float64(u.HumidityOudoor))
if u.DewPointF == nil && (u.TempOutdoorF != nil && u.HumidityOudoor != nil) {
if *u.TempOutdoorF != 0 || *u.HumidityOudoor != 0 {
dp := CalculateDewPoint(*u.TempOutdoorF, float64(*u.HumidityOudoor))
u.DewPointF = &dp
}
}
if u.BaromAbsoluteIn == 0 {
if u.BaromAbsoluteIn == nil && u.BaromRelativeIn != nil {
u.BaromAbsoluteIn = u.BaromRelativeIn
}
}

View File

@ -5,6 +5,7 @@ import (
"gitea.libretechconsulting.com/rmcguire/go-app/pkg/config"
"gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel"
"github.com/rs/zerolog"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
@ -41,14 +42,16 @@ type WeatherMetrics struct {
appCtx context.Context
cfg *config.AppConfig
meter metric.Meter
recorder *MetricRecorder
}
var MetricPrefix = "weather"
func MustInitMetrics(appCtx context.Context) *WeatherMetrics {
wm := &WeatherMetrics{
appCtx: appCtx,
cfg: config.MustFromCtx(appCtx),
appCtx: appCtx,
cfg: config.MustFromCtx(appCtx),
recorder: &MetricRecorder{ctx: appCtx, l: zerolog.Ctx(appCtx)},
}
wm.meter = otel.GetMeter(appCtx, "weather", "metrics")
@ -109,40 +112,53 @@ func MustInitMetrics(appCtx context.Context) *WeatherMetrics {
}
func (wm *WeatherMetrics) Update(u *WeatherUpdate) {
attributes := attribute.NewSet(
attributes := []attribute.KeyValue{
semconv.ServiceVersion(wm.cfg.Version),
attribute.String("station_type", u.StationType),
)
}
if u.StationType != nil {
attributes = append(attributes,
attribute.String("station_type", *u.StationType))
}
if u.StationInfo != nil {
if u.StationInfo.Name != nil {
attributes = append(attributes,
attribute.String("station_name", *u.StationInfo.Name))
}
if u.StationInfo.Equipment != nil {
attributes = append(attributes,
attribute.String("station_equipment", *u.StationInfo.Equipment))
}
}
wm.TempOutdoorF.Record(wm.appCtx, u.TempOutdoorF, metric.WithAttributeSet(attributes))
wm.TempIndoorF.Record(wm.appCtx, u.TempIndoorF, metric.WithAttributeSet(attributes))
wm.HumidityOudoor.Record(wm.appCtx, int64(u.HumidityOudoor), metric.WithAttributeSet(attributes))
wm.HumidityIndoor.Record(wm.appCtx, int64(u.HumidityIndoor), metric.WithAttributeSet(attributes))
wm.WindSpeedMPH.Record(wm.appCtx, u.WindSpeedMPH, metric.WithAttributeSet(attributes))
wm.WindGustMPH.Record(wm.appCtx, u.WindGustMPH, metric.WithAttributeSet(attributes))
wm.MaxDailyGust.Record(wm.appCtx, u.MaxDailyGust, metric.WithAttributeSet(attributes))
wm.WindDir.Record(wm.appCtx, int64(u.WindDir), metric.WithAttributeSet(attributes))
wm.WindDirAvg10m.Record(wm.appCtx, int64(u.WindDirAvg10m), metric.WithAttributeSet(attributes))
wm.UV.Record(wm.appCtx, int64(u.UV), metric.WithAttributeSet(attributes))
wm.SolarRadiation.Record(wm.appCtx, u.SolarRadiation, metric.WithAttributeSet(attributes))
wm.HourlyRainIn.Record(wm.appCtx, u.HourlyRainIn, metric.WithAttributeSet(attributes))
wm.EventRainIn.Record(wm.appCtx, u.EventRainIn, metric.WithAttributeSet(attributes))
wm.DailyRainIn.Record(wm.appCtx, u.DailyRainIn, metric.WithAttributeSet(attributes))
wm.WeeklyRainIn.Record(wm.appCtx, u.WeeklyRainIn, metric.WithAttributeSet(attributes))
wm.MonthlyRainIn.Record(wm.appCtx, u.MonthlyRainIn, metric.WithAttributeSet(attributes))
wm.YearlyRainIn.Record(wm.appCtx, u.YearlyRainIn, metric.WithAttributeSet(attributes))
wm.TotalRainIn.Record(wm.appCtx, u.TotalRainIn, metric.WithAttributeSet(attributes))
wm.BaromRelativeIn.Record(wm.appCtx, u.BaromRelativeIn, metric.WithAttributeSet(attributes))
wm.BaromAbsoluteIn.Record(wm.appCtx, u.BaromAbsoluteIn, metric.WithAttributeSet(attributes))
wm.DewPointF.Record(wm.appCtx, u.DewPointF, metric.WithAttributeSet(attributes))
wm.WindChillF.Record(wm.appCtx, u.WindChillF, metric.WithAttributeSet(attributes))
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.TempOutdoorF, FloatVal: u.TempOutdoorF, Field: FieldTempOutdoorF, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.TempIndoorF, FloatVal: u.TempIndoorF, Field: FieldTempIndoorF, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Int64Gauge: wm.HumidityOudoor, IntVal: u.HumidityOudoor, Field: FieldHumidityOudoor, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Int64Gauge: wm.HumidityIndoor, IntVal: u.HumidityIndoor, Field: FieldHumidityIndoor, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.WindSpeedMPH, FloatVal: u.WindSpeedMPH, Field: FieldWindSpeedMPH, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.WindGustMPH, FloatVal: u.WindGustMPH, Field: FieldWindGustMPH, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.MaxDailyGust, FloatVal: u.MaxDailyGust, Field: FieldMaxDailyGust, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Int64Gauge: wm.WindDir, IntVal: u.WindDir, Field: FieldWindDir, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Int64Gauge: wm.WindDirAvg10m, IntVal: u.WindDirAvg10m, Field: FieldWindDirAvg10m, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Int64Gauge: wm.UV, IntVal: u.UV, Field: FieldUV, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.SolarRadiation, FloatVal: u.SolarRadiation, Field: FieldSolarRadiation, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.HourlyRainIn, FloatVal: u.HourlyRainIn, Field: FieldHourlyRainIn, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.EventRainIn, FloatVal: u.EventRainIn, Field: FieldEventRainIn, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.DailyRainIn, FloatVal: u.DailyRainIn, Field: FieldDailyRainIn, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.WeeklyRainIn, FloatVal: u.WeeklyRainIn, Field: FieldWeeklyRainIn, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.MonthlyRainIn, FloatVal: u.MonthlyRainIn, Field: FieldMonthlyRainIn, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.YearlyRainIn, FloatVal: u.YearlyRainIn, Field: FieldYearlyRainIn, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.TotalRainIn, FloatVal: u.TotalRainIn, Field: FieldTotalRainIn, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.BaromRelativeIn, FloatVal: u.BaromRelativeIn, Field: FieldBaromRelativeIn, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.BaromAbsoluteIn, FloatVal: u.BaromAbsoluteIn, Field: FieldBaromAbsoluteIn, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.DewPointF, FloatVal: u.DewPointF, Field: FieldDewPointF, Attributes: attributes, StationInfo: u.StationInfo})
wm.recorder.Record(&RecordOpts{Float64Gauge: wm.WindChillF, FloatVal: u.WindChillF, Field: FieldWindChillF, Attributes: attributes, StationInfo: u.StationInfo})
// Batteries
for _, battery := range u.Batteries {
wm.BatteryStatus.Record(wm.appCtx, int64(battery.Status),
metric.WithAttributeSet(attributes),
metric.WithAttributes(attribute.String("component", battery.Component)),
)
batAttr := attributes
batAttr = append(batAttr, attribute.String("component", battery.Component))
wm.recorder.Record(&RecordOpts{Int64Gauge: wm.BatteryStatus, IntVal: battery.Status, Field: FieldBatteries, Attributes: batAttr, StationInfo: u.StationInfo})
}
wm.UpdatesReceived.Add(wm.appCtx, 1)

View File

@ -0,0 +1,104 @@
package weather
import (
"context"
"errors"
"github.com/rs/zerolog"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
type MetricRecorder struct {
ctx context.Context
l *zerolog.Logger
}
type RecordOpts struct {
Float64Gauge metric.Float64Gauge
Int64Gauge metric.Int64Gauge
IntVal *int
FloatVal *float64
Attributes []attribute.KeyValue
Field WeatherUpdateField
StationInfo *StationInfo
}
func (r *MetricRecorder) Record(opts *RecordOpts) {
if opts.StationInfo != nil && !opts.keep() {
r.l.Trace().
Str("field", string(opts.Field)).
Str("station", *opts.StationInfo.Name).
Msg("Metric dropped by station config")
return
} else if opts.Int64Gauge == nil && opts.Float64Gauge == nil {
r.l.Err(errors.New("neither int nor float gauge provided")).Send()
return
}
if opts.Int64Gauge != nil {
if opts.IntVal == nil {
log := r.l.Trace().Str("field", string(opts.Field))
if opts.StationInfo != nil {
log = log.Str("station", *opts.StationInfo.Name)
}
log.Msg("Dropping nil int metric")
return
}
r.recordInt(opts.Int64Gauge, *opts.IntVal, opts.Attributes...)
} else if opts.Float64Gauge != nil {
if opts.FloatVal == nil {
log := r.l.Trace().Str("field", string(opts.Field))
if opts.StationInfo != nil {
log = log.Str("station", *opts.StationInfo.Name)
}
log.Msg("Dropping nil float metric")
return
}
r.recordFloat(opts.Float64Gauge, *opts.FloatVal, opts.Attributes...)
}
}
func (o *RecordOpts) keep() bool {
// If keep fields are given, only check keep fields
if len(o.StationInfo.Keep) > 0 {
for _, f := range o.StationInfo.Keep {
if f == o.Field {
return true
}
}
return false
}
for _, f := range o.StationInfo.Drop {
if f == o.Field {
return false
}
}
return true
}
func (r *MetricRecorder) recordInt(
m metric.Int64Gauge, value int, attributes ...attribute.KeyValue,
) {
// Prepare metric attributes
options := make([]metric.RecordOption, 0, len(attributes))
if len(attributes) > 0 {
options = append(options, metric.WithAttributes(attributes...))
}
val := int64(value)
m.Record(r.ctx, val, options...)
}
func (r *MetricRecorder) recordFloat(
m metric.Float64Gauge, value float64, attributes ...attribute.KeyValue,
) {
// Prepare metric attributes
options := make([]metric.RecordOption, 0, len(attributes))
if len(attributes) > 0 {
options = append(options, metric.WithAttributes(attributes...))
}
m.Record(r.ctx, value, options...)
}

View File

@ -8,37 +8,47 @@ import (
// between AWN and Wunderground style updates from Ambient devices
type WeatherUpdate struct {
DateUTC *time.Time
StationType string
TempOutdoorF float64
TempIndoorF float64
HumidityOudoor int
HumidityIndoor int
WindSpeedMPH float64
WindGustMPH float64
MaxDailyGust float64
WindDir int
WindDirAvg10m int
UV int
SolarRadiation float64
HourlyRainIn float64
EventRainIn float64
DailyRainIn float64
WeeklyRainIn float64
MonthlyRainIn float64
YearlyRainIn float64
TotalRainIn float64
StationInfo *StationInfo
StationID *string
StationType *string
TempOutdoorF *float64
TempIndoorF *float64
HumidityOudoor *int
HumidityIndoor *int
WindSpeedMPH *float64
WindGustMPH *float64
MaxDailyGust *float64
WindDir *int
WindDirAvg10m *int
UV *int
SolarRadiation *float64
HourlyRainIn *float64
EventRainIn *float64
DailyRainIn *float64
WeeklyRainIn *float64
MonthlyRainIn *float64
YearlyRainIn *float64
TotalRainIn *float64
Batteries []BatteryStatus
BaromRelativeIn float64
BaromAbsoluteIn float64
BaromRelativeIn *float64
BaromAbsoluteIn *float64
// These fields may be calculated
// if not otherwise set
DewPointF float64
WindChillF float64
DewPointF *float64
WindChillF *float64
}
type StationInfo struct {
Type *string
Equipment *string
Name *string
Keep []WeatherUpdateField
Drop []WeatherUpdateField
}
type BatteryStatus struct {
Component string
Status int
Status *int
}
type WeatherUpdateField string