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

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

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