Metric implementation

This commit is contained in:
2025-01-04 20:54:36 -05:00
parent 23da46fe07
commit 59b598c6b3
13 changed files with 290 additions and 96 deletions

View File

@ -4,18 +4,13 @@ import "math"
// Attempts to complete missing fields that may not
// be set by a specific provider, such as DewPoint and WindChill
//
// If enrich is called repeated with the same station ID, measurements
// will be recorded to produce averages. This will be more stable
// and support scaling if Redis is available
// TODO: Implement average tracker
func (u *WeatherUpdate) Enrich() {
if u.WindChillF == 0 {
u.WindChillF = CalculateWindChill(u.TempF, u.WindSpeedMPH)
u.WindChillF = CalculateWindChill(u.TempOutdoorF, u.WindSpeedMPH)
}
if u.DewPointF == 0 {
u.DewPointF = CalculateDewPoint(u.TempF, float32(u.Humidity))
u.DewPointF = CalculateDewPoint(u.TempOutdoorF, float64(u.HumidityOudoor))
}
if u.BaromAbsoluteIn == 0 {
@ -23,15 +18,15 @@ func (u *WeatherUpdate) Enrich() {
}
}
func CalculateDewPoint(tempF, humidity float32) float32 {
func CalculateDewPoint(tempF, humidity float64) float64 {
// Convert temperature from Fahrenheit to Celsius
tempC := (tempF - 32) * 5 / 9
// Calculate the dew point using the Magnus-Tetens approximation
a := float32(17.27)
b := float32(237.7)
a := float64(17.27)
b := float64(237.7)
alpha := (a*tempC)/(b+tempC) + float32(math.Log(float64(humidity)/100))
alpha := (a*tempC)/(b+tempC) + math.Log(humidity/100)
dewPointC := (b * alpha) / (a - alpha)
// Convert dew point back to Fahrenheit
@ -39,16 +34,14 @@ func CalculateDewPoint(tempF, humidity float32) float32 {
return dewPointF
}
func CalculateWindChill(tempF float32, windSpeedMPH float32) float32 {
func CalculateWindChill(tempF float64, windSpeedMPH float64) float64 {
if windSpeedMPH <= 3 {
// Wind chill calculation doesn't apply for very low wind speeds
return tempF
}
// Formula for calculating wind chill
return float32(
35.74 + 0.6215*float64(tempF) -
35.75*math.Pow(float64(windSpeedMPH), 0.16) +
0.4275*float64(tempF)*
math.Pow(float64(windSpeedMPH), 0.16))
return 35.74 + 0.6215*tempF -
35.75*math.Pow(windSpeedMPH, 0.16) +
0.4275*tempF*math.Pow(windSpeedMPH, 0.16)
}

View File

@ -1,3 +1,148 @@
package weather
// TODO: Add OTEL Metrics
import (
"context"
"gitea.libretechconsulting.com/rmcguire/go-app/pkg/config"
"gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)
type WeatherMetrics struct {
// Weather Metrics
TempOutdoorF metric.Float64Gauge
TempIndoorF metric.Float64Gauge
HumidityOudoor metric.Int64Gauge
HumidityIndoor metric.Int64Gauge
WindSpeedMPH metric.Float64Gauge
WindGustMPH metric.Float64Gauge
MaxDailyGust metric.Float64Gauge
WindDir metric.Int64Gauge
WindDirAvg10m metric.Int64Gauge
UV metric.Int64Gauge
SolarRadiation metric.Float64Gauge
HourlyRainIn metric.Float64Gauge
EventRainIn metric.Float64Gauge
DailyRainIn metric.Float64Gauge
WeeklyRainIn metric.Float64Gauge
MonthlyRainIn metric.Float64Gauge
YearlyRainIn metric.Float64Gauge
TotalRainIn metric.Float64Gauge
BattOutdoorSensor metric.Int64Gauge
BattIndoorSensor metric.Int64Gauge
BattRainSensor metric.Int64Gauge
BaromRelativeIn metric.Float64Gauge
BaromAbsoluteIn metric.Float64Gauge
DewPointF metric.Float64Gauge
WindChillF metric.Float64Gauge
// Internal Telemetry
UpdatesReceived metric.Int64Counter
appCtx context.Context
cfg *config.AppConfig
meter metric.Meter
}
func MustInitMetrics(appCtx context.Context) *WeatherMetrics {
wm := &WeatherMetrics{
appCtx: appCtx,
cfg: config.MustFromCtx(appCtx),
}
wm.meter = otel.GetMeter(appCtx, "weather", "metrics")
// Weather Metrics
wm.TempOutdoorF, _ = wm.meter.Float64Gauge("weather_temp_outdoor_f",
metric.WithDescription("Outdoor Temperature in Faherenheit"))
wm.TempIndoorF, _ = wm.meter.Float64Gauge("weather_temp_indoor_f",
metric.WithDescription("Indoor Temperature in Faherenheit"))
wm.HumidityOudoor, _ = wm.meter.Int64Gauge("weather_humidity_oudoor",
metric.WithDescription("Outdoor Humidity %"))
wm.HumidityIndoor, _ = wm.meter.Int64Gauge("weather_humidity_indoor",
metric.WithDescription("Indoor Humidity %"))
wm.WindSpeedMPH, _ = wm.meter.Float64Gauge("weather_wind_speed_mph",
metric.WithDescription("Wind Speed in MPH"))
wm.WindGustMPH, _ = wm.meter.Float64Gauge("weather_wind_gust_mph",
metric.WithDescription("Wind Gust in MPH"))
wm.MaxDailyGust, _ = wm.meter.Float64Gauge("weather_max_daily_gust",
metric.WithDescription("Max Daily Wind Gust"))
wm.WindDir, _ = wm.meter.Int64Gauge("weather_wind_dir",
metric.WithDescription("Wind Direction in Degrees"))
wm.WindDirAvg10m, _ = wm.meter.Int64Gauge("weather_wind_dir_avg_10m",
metric.WithDescription("Wind Direction 10m Average"))
wm.UV, _ = wm.meter.Int64Gauge("weather_uv",
metric.WithDescription("UV Index"))
wm.SolarRadiation, _ = wm.meter.Float64Gauge("weather_solar_radiation",
metric.WithDescription("Solar Radiation in W/㎡"))
wm.HourlyRainIn, _ = wm.meter.Float64Gauge("weather_hourly_rain_in",
metric.WithDescription("Hourly Rain in Inches"))
wm.EventRainIn, _ = wm.meter.Float64Gauge("weather_event_rain_in",
metric.WithDescription("Event Rain in Inches"))
wm.DailyRainIn, _ = wm.meter.Float64Gauge("weather_daily_rain_in",
metric.WithDescription("Daily Rain in Inches"))
wm.WeeklyRainIn, _ = wm.meter.Float64Gauge("weather_weekly_rain_in",
metric.WithDescription("Weekly Rain in Inches"))
wm.MonthlyRainIn, _ = wm.meter.Float64Gauge("weather_monthly_rain_in",
metric.WithDescription("Monthly Rain in Inches"))
wm.YearlyRainIn, _ = wm.meter.Float64Gauge("weather_yearly_rain_in",
metric.WithDescription("Yearly Rain in Inches"))
wm.TotalRainIn, _ = wm.meter.Float64Gauge("weather_total_rain_in",
metric.WithDescription("Total Rain in Inches"))
wm.BattOutdoorSensor, _ = wm.meter.Int64Gauge("weather_batt_outdoor_sensor",
metric.WithDescription("Outdoor Equipment Battery"))
wm.BattIndoorSensor, _ = wm.meter.Int64Gauge("weather_batt_indoor_sensor",
metric.WithDescription("Indoor Equipmenet Battery"))
wm.BattRainSensor, _ = wm.meter.Int64Gauge("weather_batt_rain_sensor",
metric.WithDescription("Rain Sensor Battery"))
wm.BaromRelativeIn, _ = wm.meter.Float64Gauge("weather_barometric_pressure_relative_in",
metric.WithDescription("Relative Pressure in Inches of Mercury"))
wm.BaromAbsoluteIn, _ = wm.meter.Float64Gauge("weather_barometric_pressure_absolute_in",
metric.WithDescription("Absolute Pressure in Inches of Mercury"))
wm.DewPointF, _ = wm.meter.Float64Gauge("weather_dew_point_f",
metric.WithDescription("Dew Point in Faherenheit"))
wm.WindChillF, _ = wm.meter.Float64Gauge("weather_wind_chill_f",
metric.WithDescription("Wind Chill in Faherenheit"))
// Internal Telemetry
wm.UpdatesReceived, _ = wm.meter.Int64Counter("weather_updates_received",
metric.WithDescription("Metric Updates Processed by Exporter"))
return wm
}
func (wm *WeatherMetrics) Update(u *WeatherUpdate) {
attributes := attribute.NewSet(
semconv.ServiceVersion(wm.cfg.Version),
attribute.String("station_type", u.StationType),
)
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.BattOutdoorSensor.Record(wm.appCtx, int64(u.BattOutdoorSensor), metric.WithAttributeSet(attributes))
wm.BattIndoorSensor.Record(wm.appCtx, int64(u.BattIndoorSensor), metric.WithAttributeSet(attributes))
wm.BattRainSensor.Record(wm.appCtx, int64(u.BattRainSensor), 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.UpdatesReceived.Add(wm.appCtx, 1)
}

View File

@ -9,31 +9,31 @@ import (
type WeatherUpdate struct {
DateUTC *time.Time
StationType string
TempF float32
TempInsideF float32
Humidity int
HumidityInside int
WindSpeedMPH float32
WindGustMPH float32
MaxDailyGust float32
TempOutdoorF float64
TempIndoorF float64
HumidityOudoor int
HumidityIndoor int
WindSpeedMPH float64
WindGustMPH float64
MaxDailyGust float64
WindDir int
WindDirAVG10m int
WindDirAvg10m int
UV int
SolarRadiation float32
HourlyRainIn float32
EventRainIn float32
DailyRainIn float32
WeeklyRainIn float32
MonthlyRainIn float32
YearlyRainIn float32
TotalRainIn float32
SolarRadiation float64
HourlyRainIn float64
EventRainIn float64
DailyRainIn float64
WeeklyRainIn float64
MonthlyRainIn float64
YearlyRainIn float64
TotalRainIn float64
BattOutdoorSensor int
BattIndoorSensor int
BattRainSensor int
BaromRelativeIn float32
BaromAbsoluteIn float32
BaromRelativeIn float64
BaromAbsoluteIn float64
// These fields may be calculated
// if not otherwise set
DewPointF float32
WindChillF float32
DewPointF float64
WindChillF float64
}