Metrics: store entity title on lazy-create (#30196)

This commit is contained in:
andig 2026-06-07 13:11:20 +02:00 • committed by GitHub
parent 0fa8b97daf
commit a8361b575b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 209 additions and 142 deletions

View file

@ -78,19 +78,19 @@ type batteryTotals struct {
discharge float64
}
// metricsBatteryTotals sums charge and discharge energy per battery entity.
// metricsBatteryTotals sums charge and discharge energy per battery title.
func metricsBatteryTotals(series []metrics.Series) map[string]batteryTotals {
res := make(map[string]batteryTotals)
for _, s := range series {
if s.Group != metrics.Battery {
continue
}
t := res[s.Name]
t := res[s.Title]
for _, slot := range s.Data {
t.charge += slot.Energy
t.discharge += slot.ReturnEnergy
}
res[s.Name] = t
res[s.Title] = t
}
return res
}
@ -103,7 +103,7 @@ func metricsWriteBatteryTable(w io.Writer, selected []metrics.EntityInfo, totals
fmt.Fprintln(tw, "name\ttitle\tcharge\tdischarge\tefficiency")
for _, e := range selected {
t := totals[e.Name]
t := totals[title(e.Group, e.Name)]
efficiency := ""
if t.charge > 0 {

View file

@ -11,12 +11,12 @@ import (
func TestMetricsBatteryTotals(t *testing.T) {
series := []metrics.Series{
{Group: metrics.Battery, Name: "bat", Data: []metrics.Slot{
{Group: metrics.Battery, Title: "bat", Data: []metrics.Slot{
{Energy: 1.0, ReturnEnergy: 0.4},
{Energy: 2.0, ReturnEnergy: 1.6},
}},
// non-battery series must be ignored
{Group: metrics.Grid, Name: "grid", Data: []metrics.Slot{
{Group: metrics.Grid, Title: "grid", Data: []metrics.Slot{
{Energy: 5.0, ReturnEnergy: 3.0},
}},
}
@ -33,7 +33,7 @@ func TestMetricsWriteBatteryTable(t *testing.T) {
{Group: metrics.Battery, Name: "bat2"},
}
totals := map[string]batteryTotals{
"bat1": {charge: 10.0, discharge: 9.0},
"Home": {charge: 10.0, discharge: 9.0},
// bat2 deliberately absent: no data in the timeframe
}
title := func(group, name string) string {

View file

@ -72,13 +72,19 @@ func runMetricsData(cmd *cobra.Command, args []string) {
byEntity := make(map[string]metrics.Series, len(series))
for _, s := range series {
byEntity[s.Group+"/"+s.Name] = s
byEntity[s.Group+"/"+s.Title] = s
}
if asCSV, _ := cmd.Flags().GetBool("csv"); asCSV {
var out metrics.SeriesCSV
seen := make(map[string]bool, len(selected))
for _, e := range selected {
if s, ok := byEntity[e.Group+"/"+e.Name]; ok {
key := e.Group + "/" + title(e.Group, e.Name)
if seen[key] {
continue
}
seen[key] = true
if s, ok := byEntity[key]; ok {
out = append(out, s)
}
}
@ -236,7 +242,7 @@ func metricsWriteTable(w io.Writer, selected []metrics.EntityInfo, byEntity map[
}
for _, spec := range specs {
s, ok := byEntity[spec.entity.Group+"/"+spec.entity.Name]
s, ok := byEntity[spec.entity.Group+"/"+title(spec.entity.Group, spec.entity.Name)]
if !ok {
continue
}

View file

@ -93,10 +93,10 @@ func TestMetricsWriteTable(t *testing.T) {
{Group: metrics.Grid, Name: "grid"},
}
byEntity := map[string]metrics.Series{
metrics.Loadpoint + "/lp-1": {Group: metrics.Loadpoint, Name: "lp-1", Data: []metrics.Slot{
metrics.Loadpoint + "/Carport": {Group: metrics.Loadpoint, Title: "Carport", Data: []metrics.Slot{
{Start: h0, Energy: 1.84},
}},
metrics.Grid + "/grid": {Group: metrics.Grid, Name: "grid", Data: []metrics.Slot{
metrics.Grid + "/grid": {Group: metrics.Grid, Title: "grid", Data: []metrics.Slot{
{Start: h0, Energy: 0.412},
{Start: h1, Energy: 0.38, ReturnEnergy: 0.05},
}},
@ -105,6 +105,9 @@ func TestMetricsWriteTable(t *testing.T) {
if group == metrics.Loadpoint {
return "Carport"
}
if group == metrics.Grid {
return "grid"
}
return ""
}

View file

@ -11,18 +11,18 @@ import (
func TestMetricsForecastTotals(t *testing.T) {
series := []metrics.Series{
{Group: metrics.Forecast, Name: "forecast", Data: []metrics.Slot{
{Group: metrics.Forecast, Title: "forecast", Data: []metrics.Slot{
{Energy: 10.0}, {Energy: 5.0},
}},
// actual PV production is summed across all pv entities
{Group: metrics.PV, Name: "pv1", Data: []metrics.Slot{
{Group: metrics.PV, Title: "pv1", Data: []metrics.Slot{
{Energy: 4.0}, {Energy: 3.0},
}},
{Group: metrics.PV, Name: "pv2", Data: []metrics.Slot{
{Group: metrics.PV, Title: "pv2", Data: []metrics.Slot{
{Energy: 2.0},
}},
// other groups are ignored
{Group: metrics.Grid, Name: "grid", Data: []metrics.Slot{
{Group: metrics.Grid, Title: "grid", Data: []metrics.Slot{
{Energy: 99.0},
}},
}

View file

@ -1305,12 +1305,24 @@ func configureSite(conf map[string]any, loadpoints []*core.Loadpoint, tariffs *t
func newLoadpoint(idx int, name string, other map[string]any, settingsFn func(*util.Logger) coresettings.Settings) (*core.Loadpoint, error) {
log := util.NewLoggerWithLoadpoint("lp-"+strconv.Itoa(idx), idx)
collector, err := metrics.NewCollector(metrics.Loadpoint, name)
collector, err := metrics.NewCollector(metrics.Loadpoint, name, "")
if err != nil {
return nil, err
}
return core.NewLoadpointFromConfig(log, settingsFn(log), collector, other)
lp, err := core.NewLoadpointFromConfig(log, settingsFn(log), collector, other)
if err != nil {
return lp, err
}
// lazily update entity title
if title := lp.GetTitle(); title != "" {
if err := collector.UpdateTitle(title); err != nil {
return lp, err
}
}
return lp, nil
}
func configureLoadpoints(conf globalconfig.All) error {