From 759e57908a6af2567c41c033f9cc55af055a29e4 Mon Sep 17 00:00:00 2001 From: sbackend Date: Sun, 30 Aug 2026 23:40:40 +0200 Subject: [PATCH 1/3] feat: add feed metrics --- openapi/Swarm.yaml | 6 ++-- openapi/SwarmCommon.yaml | 4 ++- pkg/feeds/factory/factory.go | 56 +++++++++++++++++++++++++++++++++--- pkg/node/node.go | 3 ++ 4 files changed, 61 insertions(+), 8 deletions(-) diff --git a/openapi/Swarm.yaml b/openapi/Swarm.yaml index c320410b4ee..4ed796371db 100644 --- a/openapi/Swarm.yaml +++ b/openapi/Swarm.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: - version: 8.1.1 + version: 8.1.2 title: Bee API description: "API endpoints for interacting with the Swarm network, supporting file operations, messaging, and node management" @@ -1114,7 +1114,7 @@ paths: schema: $ref: "SwarmCommon.yaml#/components/schemas/FeedType" required: false - description: "Feed indexing scheme (default: sequence)" + description: "Feed indexing scheme. Only sequence is supported; other values are ignored and sequence is always used." - $ref: "SwarmCommon.yaml#/components/parameters/SwarmPinParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmAct" @@ -1179,7 +1179,7 @@ paths: schema: $ref: "SwarmCommon.yaml#/components/schemas/FeedType" required: false - description: "Feed indexing scheme (default: sequence)" + description: "Feed indexing scheme. Only sequence is supported; other values are ignored and sequence is always used." - $ref: "SwarmCommon.yaml#/components/parameters/SwarmOnlyRootChunkParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmCache" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmRedundancyStrategyParameter" diff --git a/openapi/SwarmCommon.yaml b/openapi/SwarmCommon.yaml index ffcbbac3b8e..b7e567b58ae 100644 --- a/openapi/SwarmCommon.yaml +++ b/openapi/SwarmCommon.yaml @@ -866,7 +866,9 @@ components: FeedType: type: string - pattern: "^(sequence|epoch)$" + description: Feed indexing scheme. Only sequence is supported by the HTTP API. + enum: + - sequence IsRetrievableResponse: type: object diff --git a/pkg/feeds/factory/factory.go b/pkg/feeds/factory/factory.go index 1d555416407..5b019a80a70 100644 --- a/pkg/feeds/factory/factory.go +++ b/pkg/feeds/factory/factory.go @@ -5,27 +5,75 @@ package factory import ( + "context" + "errors" + "strings" + "time" + "github.com/ethersphere/bee/v2/pkg/feeds" "github.com/ethersphere/bee/v2/pkg/feeds/epochs" "github.com/ethersphere/bee/v2/pkg/feeds/sequence" storage "github.com/ethersphere/bee/v2/pkg/storage" + "github.com/ethersphere/bee/v2/pkg/swarm" ) type factory struct { storage.Getter + metrics metrics } func New(getter storage.Getter) feeds.Factory { - return &factory{getter} + return &factory{ + Getter: getter, + metrics: newMetrics(), + } } func (f *factory) NewLookup(t feeds.Type, feed *feeds.Feed) (feeds.Lookup, error) { + var lookup feeds.Lookup switch t { case feeds.Sequence: - return sequence.NewAsyncFinder(f.Getter, feed), nil + lookup = sequence.NewAsyncFinder(f.Getter, feed) case feeds.Epoch: - return epochs.NewAsyncFinder(f.Getter, feed), nil + lookup = epochs.NewAsyncFinder(f.Getter, feed) + default: + return nil, feeds.ErrFeedTypeNotFound + } + + return f.wrapLookup(t, lookup), nil +} + +func (f *factory) wrapLookup(t feeds.Type, lookup feeds.Lookup) feeds.Lookup { + return &instrumentedLookup{ + lookup: lookup, + typ: strings.ToLower(t.String()), + m: f.metrics, } +} + +type instrumentedLookup struct { + lookup feeds.Lookup + typ string + m metrics +} - return nil, feeds.ErrFeedTypeNotFound +func (l *instrumentedLookup) At(ctx context.Context, at int64, after uint64) (swarm.Chunk, feeds.Index, feeds.Index, error) { + l.m.LookupStarted.WithLabelValues(l.typ).Inc() + start := time.Now() + ch, cur, next, err := l.lookup.At(ctx, at, after) + l.m.LookupDuration.WithLabelValues(l.typ, lookupResult(ch, err)).Observe(time.Since(start).Seconds()) + return ch, cur, next, err +} + +func lookupResult(ch swarm.Chunk, err error) string { + switch { + case err != nil && errors.Is(err, context.Canceled): + return "canceled" + case err != nil: + return "error" + case ch == nil: + return "not_found" + default: + return "found" + } } diff --git a/pkg/node/node.go b/pkg/node/node.go index c05517ad5be..acef3c9fdca 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -1414,6 +1414,9 @@ func NewBee( apiService.MustRegisterMetrics(retrieval.Metrics()...) apiService.MustRegisterMetrics(lightNodes.Metrics()...) apiService.MustRegisterMetrics(hive.Metrics()...) + if feedFactoryMetrics, ok := feedFactory.(metrics.Collector); ok { + apiService.MustRegisterMetrics(feedFactoryMetrics.Metrics()...) + } if bs, ok := batchStore.(metrics.Collector); ok { apiService.MustRegisterMetrics(bs.Metrics()...) From 6ea61ea8c50fa388cbdb73921164ae79e444069d Mon Sep 17 00:00:00 2001 From: sbackend Date: Sun, 30 Aug 2026 23:46:25 +0200 Subject: [PATCH 2/3] fix: missing metrics file --- pkg/feeds/factory/metrics.go | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 pkg/feeds/factory/metrics.go diff --git a/pkg/feeds/factory/metrics.go b/pkg/feeds/factory/metrics.go new file mode 100644 index 00000000000..04561d90b5e --- /dev/null +++ b/pkg/feeds/factory/metrics.go @@ -0,0 +1,49 @@ +// Copyright 2026 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package factory + +import ( + "github.com/prometheus/client_golang/prometheus" + + m "github.com/ethersphere/bee/v2/pkg/metrics" +) + +type metrics struct { + // all metrics fields must be exported + // to be able to return them by Metrics() + // using reflection + LookupStarted *prometheus.CounterVec + LookupDuration *prometheus.HistogramVec +} + +func newMetrics() metrics { + subsystem := "feeds" + + return metrics{ + LookupStarted: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: m.Namespace, + Subsystem: subsystem, + Name: "lookup_started_total", + Help: "Number of feed lookup attempts started.", + }, + []string{"type"}, + ), + LookupDuration: prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: m.Namespace, + Subsystem: subsystem, + Name: "lookup_duration_seconds", + Help: "Histogram of feed lookup durations.", + Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}, + }, + []string{"type", "result"}, + ), + } +} + +func (f *factory) Metrics() []prometheus.Collector { + return m.PrometheusCollectorsFromFields(f.metrics) +} From 4bc6444ce09db045afa6b552e6c4b6284f9e1ac2 Mon Sep 17 00:00:00 2001 From: sbackend Date: Thu, 10 Sep 2026 16:34:10 +0200 Subject: [PATCH 3/3] fix: clean up PR --- openapi/Swarm.yaml | 6 +++--- openapi/SwarmCommon.yaml | 4 +--- pkg/feeds/factory/factory.go | 17 +++++++++++++++-- pkg/feeds/factory/metrics.go | 2 +- pkg/node/node.go | 4 +--- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/openapi/Swarm.yaml b/openapi/Swarm.yaml index 811bc28543c..9140a8b4783 100644 --- a/openapi/Swarm.yaml +++ b/openapi/Swarm.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: - version: 8.2.1 + version: 8.2.0 title: Bee API description: "API endpoints for interacting with the Swarm network, supporting file operations, messaging, and node management" @@ -1114,7 +1114,7 @@ paths: schema: $ref: "SwarmCommon.yaml#/components/schemas/FeedType" required: false - description: "Feed indexing scheme. Only sequence is supported; other values are ignored and sequence is always used." + description: "Feed indexing scheme (default: sequence)" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmPinParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmAct" @@ -1179,7 +1179,7 @@ paths: schema: $ref: "SwarmCommon.yaml#/components/schemas/FeedType" required: false - description: "Feed indexing scheme. Only sequence is supported; other values are ignored and sequence is always used." + description: "Feed indexing scheme (default: sequence)" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmOnlyRootChunkParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmCache" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmRedundancyStrategyParameter" diff --git a/openapi/SwarmCommon.yaml b/openapi/SwarmCommon.yaml index fb907eb9e66..cdacafb3f7f 100644 --- a/openapi/SwarmCommon.yaml +++ b/openapi/SwarmCommon.yaml @@ -878,9 +878,7 @@ components: FeedType: type: string - description: Feed indexing scheme. Only sequence is supported by the HTTP API. - enum: - - sequence + pattern: "^(sequence|epoch)$" IsRetrievableResponse: type: object diff --git a/pkg/feeds/factory/factory.go b/pkg/feeds/factory/factory.go index 5b019a80a70..44b7e911a2a 100644 --- a/pkg/feeds/factory/factory.go +++ b/pkg/feeds/factory/factory.go @@ -7,16 +7,18 @@ package factory import ( "context" "errors" - "strings" "time" "github.com/ethersphere/bee/v2/pkg/feeds" "github.com/ethersphere/bee/v2/pkg/feeds/epochs" "github.com/ethersphere/bee/v2/pkg/feeds/sequence" + m "github.com/ethersphere/bee/v2/pkg/metrics" storage "github.com/ethersphere/bee/v2/pkg/storage" "github.com/ethersphere/bee/v2/pkg/swarm" ) +var _ m.Collector = (*factory)(nil) + type factory struct { storage.Getter metrics metrics @@ -46,7 +48,7 @@ func (f *factory) NewLookup(t feeds.Type, feed *feeds.Feed) (feeds.Lookup, error func (f *factory) wrapLookup(t feeds.Type, lookup feeds.Lookup) feeds.Lookup { return &instrumentedLookup{ lookup: lookup, - typ: strings.ToLower(t.String()), + typ: lookupType(t), m: f.metrics, } } @@ -65,6 +67,17 @@ func (l *instrumentedLookup) At(ctx context.Context, at int64, after uint64) (sw return ch, cur, next, err } +func lookupType(t feeds.Type) string { + switch t { + case feeds.Sequence: + return "sequence" + case feeds.Epoch: + return "epoch" + default: + return "unknown" + } +} + func lookupResult(ch swarm.Chunk, err error) string { switch { case err != nil && errors.Is(err, context.Canceled): diff --git a/pkg/feeds/factory/metrics.go b/pkg/feeds/factory/metrics.go index 04561d90b5e..95888a1414a 100644 --- a/pkg/feeds/factory/metrics.go +++ b/pkg/feeds/factory/metrics.go @@ -37,7 +37,7 @@ func newMetrics() metrics { Subsystem: subsystem, Name: "lookup_duration_seconds", Help: "Histogram of feed lookup durations.", - Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}, + Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 15, 30, 60}, }, []string{"type", "result"}, ), diff --git a/pkg/node/node.go b/pkg/node/node.go index 04f3c5058c9..3c4a45f6b3a 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -1414,9 +1414,7 @@ func NewBee( apiService.MustRegisterMetrics(retrieval.Metrics()...) apiService.MustRegisterMetrics(lightNodes.Metrics()...) apiService.MustRegisterMetrics(hive.Metrics()...) - if feedFactoryMetrics, ok := feedFactory.(metrics.Collector); ok { - apiService.MustRegisterMetrics(feedFactoryMetrics.Metrics()...) - } + apiService.MustRegisterMetrics(feedFactory.(metrics.Collector).Metrics()...) if bs, ok := batchStore.(metrics.Collector); ok { apiService.MustRegisterMetrics(bs.Metrics()...)