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/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) +} 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()...)