stats

package
v0.20.0-pr2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 16, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildTrackedEndpoints

func BuildTrackedEndpoints(paths []string) map[string]bool

BuildTrackedEndpoints converts a list of endpoint path patterns to a map of endpoint names.

func Middleware

func Middleware(cfg MiddlewareConfig) fiber.Handler

Middleware creates a Fiber middleware that captures request statistics. The middleware runs after the handler to capture response status and timing.

Types

type Aggregator

type Aggregator struct {
	// contains filtered or unexported fields
}

Aggregator handles daily aggregation and data retention.

func NewAggregator

func NewAggregator(storage AggregatorStorage, detailedRetention, aggregatedRetention time.Duration) *Aggregator

NewAggregator creates a new aggregator instance.

func (*Aggregator) LastAggregation

func (a *Aggregator) LastAggregation() time.Time

LastAggregation returns the date of the last successful aggregation.

func (*Aggregator) Purge

func (a *Aggregator) Purge() (detailed int64, aggregated int64, err error)

Purge manually purges data older than the retention periods.

func (*Aggregator) Run

func (a *Aggregator) Run(ctx context.Context) error

Run starts the aggregation loop. It runs once per day at 2 AM UTC. This method blocks until the context is cancelled.

func (*Aggregator) RunOnce

func (a *Aggregator) RunOnce(date time.Time) error

RunOnce performs a single aggregation for the specified date. This is useful for CLI commands or manual aggregation.

type AggregatorStorage

type AggregatorStorage interface {
	AggregateDailyStats(date time.Time) error
	PurgeDetailedLogs(before time.Time) (int64, error)
	PurgeAggregatedStats(before time.Time) (int64, error)
}

AggregatorStorage is the interface for aggregation storage operations.

type BufferStats

type BufferStats struct {
	Size           int     `json:"size"`
	Capacity       int     `json:"capacity"`
	FillPercentage float64 `json:"fill_percentage"`
}

BufferStats holds buffer operational statistics.

type Collector

type Collector struct {
	// contains filtered or unexported fields
}

Collector manages statistics collection for federation endpoints.

func NewCollector

func NewCollector(cfg Config, storage StorageBackend) (*Collector, error)

NewCollector creates a new statistics collector.

func (*Collector) BufferStats

func (c *Collector) BufferStats() BufferStats

BufferStats returns current buffer statistics.

func (*Collector) FlusherStats

func (c *Collector) FlusherStats() FlusherStats

FlusherStats returns current flusher statistics.

func (*Collector) Middleware

func (c *Collector) Middleware() fiber.Handler

Middleware returns a Fiber middleware handler for collecting statistics.

func (*Collector) Record

func (c *Collector) Record(entry *RequestLog)

Record manually records a request log entry. This is useful for testing or custom integrations.

func (*Collector) Start

func (c *Collector) Start()

Start begins the background flushing goroutine. This method is non-blocking and returns immediately.

func (*Collector) Stop

func (c *Collector) Stop() error

Stop gracefully shuts down the collector. It signals the flusher to stop and waits for the final flush to complete.

type Config

type Config struct {
	// Enabled controls whether statistics collection is active.
	Enabled bool

	// Buffer configuration
	BufferSize     int
	FlushInterval  time.Duration
	FlushThreshold float64

	// Capture options
	CaptureClientIP    bool
	CaptureUserAgent   bool
	CaptureQueryParams bool

	// GeoIP configuration
	GeoIPEnabled bool
	GeoIPDBPath  string

	// Retention
	DetailedRetention   time.Duration
	AggregatedRetention time.Duration

	// Endpoints to track (empty = all federation endpoints)
	Endpoints []string
}

Config holds configuration for the stats collector.

type DailyStats

type DailyStats struct {
	ID        uint      `gorm:"primaryKey;autoIncrement" json:"id"`
	CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
	UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`

	// Primary key fields for grouping
	Date       time.Time `gorm:"type:date;uniqueIndex:idx_ds_date_endpoint_status;not null" json:"date"`
	Endpoint   string    `gorm:"size:100;uniqueIndex:idx_ds_date_endpoint_status;not null" json:"endpoint"`
	StatusCode int       `gorm:"type:smallint;uniqueIndex:idx_ds_date_endpoint_status;not null" json:"status_code"`

	// Counts
	RequestCount int64 `gorm:"not null;default:0" json:"request_count"`
	ErrorCount   int64 `gorm:"not null;default:0" json:"error_count"`

	// Duration statistics (in milliseconds)
	DurationP50Ms int `gorm:"default:0" json:"duration_p50_ms"`
	DurationP95Ms int `gorm:"default:0" json:"duration_p95_ms"`
	DurationP99Ms int `gorm:"default:0" json:"duration_p99_ms"`
	DurationAvgMs int `gorm:"default:0" json:"duration_avg_ms"`
	DurationMinMs int `gorm:"default:0" json:"duration_min_ms"`
	DurationMaxMs int `gorm:"default:0" json:"duration_max_ms"`

	// Top entries as JSON arrays
	TopUserAgents json.RawMessage `gorm:"type:json" json:"top_user_agents,omitempty"`
	TopCountries  json.RawMessage `gorm:"type:json" json:"top_countries,omitempty"`
	TopClientIPs  json.RawMessage `gorm:"type:json" json:"top_client_ips,omitempty"`
	TopParams     json.RawMessage `gorm:"type:json" json:"top_params,omitempty"`
}

DailyStats represents aggregated statistics for a single day. This is used for long-term retention with smaller storage footprint.

func (*DailyStats) BeforeCreate

func (d *DailyStats) BeforeCreate(_ *gorm.DB) error

BeforeCreate sets the date to midnight UTC.

func (DailyStats) TableName

func (DailyStats) TableName() string

TableName returns the table name for DailyStats.

type Flusher

type Flusher struct {
	// contains filtered or unexported fields
}

Flusher handles periodic flushing of the ring buffer to the database.

func NewFlusher

func NewFlusher(buffer *RingBuffer, storage StorageBackend, interval time.Duration) *Flusher

NewFlusher creates a new flusher instance.

func (*Flusher) Run

func (f *Flusher) Run(ctx context.Context) error

Run starts the flusher loop. It flushes on: - Regular interval (FlushInterval) - Buffer threshold reached (via NotifyThreshold channel) - Context cancellation (final flush)

This method blocks until the context is cancelled.

func (*Flusher) Stats

func (f *Flusher) Stats() FlusherStats

Stats returns flusher statistics.

type FlusherStats

type FlusherStats struct {
	TotalFlushed  int64     `json:"total_flushed"`
	TotalDropped  int64     `json:"total_dropped"`
	LastFlushTime time.Time `json:"last_flush_time"`
	LastFlushSize int       `json:"last_flush_size"`
	BufferSize    int       `json:"buffer_size"`
	BufferFill    float64   `json:"buffer_fill"`
}

FlusherStats holds flusher operational statistics.

type GeoIPLookup

type GeoIPLookup struct {
	// contains filtered or unexported fields
}

GeoIPLookup provides country code lookup from IP addresses using MaxMind GeoLite2 database.

func NewGeoIPLookup

func NewGeoIPLookup(dbPath string) (*GeoIPLookup, error)

NewGeoIPLookup creates a new GeoIP lookup instance from a MaxMind database file. The database file should be a GeoLite2-Country.mmdb or GeoIP2-Country.mmdb file.

func (*GeoIPLookup) Close

func (g *GeoIPLookup) Close() error

Close closes the MaxMind database reader.

func (*GeoIPLookup) LookupCountry

func (g *GeoIPLookup) LookupCountry(ipStr string) string

LookupCountry returns the ISO 3166-1 alpha-2 country code for the given IP address. Returns an empty string if the lookup fails or the IP is not found.

type GeoIPProvider

type GeoIPProvider interface {
	LookupCountry(ip string) string
	Close() error
}

GeoIPProvider is the interface for GeoIP lookups.

type Interval

type Interval string

Interval represents a time interval for aggregation.

const (
	IntervalMinute Interval = "minute"
	IntervalHour   Interval = "hour"
	IntervalDay    Interval = "day"
	IntervalWeek   Interval = "week"
	IntervalMonth  Interval = "month"
)

func ParseInterval

func ParseInterval(s string) Interval

ParseInterval parses an interval string.

type LatencyStats

type LatencyStats struct {
	P50Ms int     `json:"p50_ms"`
	P75Ms int     `json:"p75_ms"`
	P90Ms int     `json:"p90_ms"`
	P95Ms int     `json:"p95_ms"`
	P99Ms int     `json:"p99_ms"`
	AvgMs float64 `json:"avg_ms"`
	MinMs int     `json:"min_ms"`
	MaxMs int     `json:"max_ms"`
}

LatencyStats holds latency percentile statistics.

type MiddlewareConfig

type MiddlewareConfig struct {
	// CaptureClientIP enables capturing the client IP address.
	CaptureClientIP bool

	// CaptureUserAgent enables capturing the User-Agent header.
	CaptureUserAgent bool

	// CaptureQueryParams enables capturing URL query parameters.
	CaptureQueryParams bool

	// GeoIP provides country lookup from IP addresses. Can be nil.
	GeoIP GeoIPProvider

	// TrackedEndpoints is a map of endpoints to track. If empty, all requests are tracked.
	// Keys should be normalized endpoint names (e.g., "well-known", "fetch", "resolve").
	TrackedEndpoints map[string]bool

	// Buffer is the ring buffer to write entries to.
	Buffer *RingBuffer
}

MiddlewareConfig contains configuration for the stats middleware.

type NoOpGeoIPLookup

type NoOpGeoIPLookup struct{}

NoOpGeoIPLookup is a no-op implementation that always returns empty strings. Used when GeoIP is disabled.

func (NoOpGeoIPLookup) Close

func (NoOpGeoIPLookup) Close() error

Close is a no-op.

func (NoOpGeoIPLookup) LookupCountry

func (NoOpGeoIPLookup) LookupCountry(string) string

LookupCountry always returns an empty string.

type RequestLog

type RequestLog struct {
	ID        uint64    `gorm:"primaryKey;autoIncrement" json:"id"`
	Timestamp time.Time `gorm:"index:idx_rl_ts;not null" json:"timestamp"`

	// Request info
	Endpoint   string `gorm:"size:100;index:idx_rl_endpoint;not null" json:"endpoint"`
	Method     string `gorm:"size:10;not null" json:"method"`
	StatusCode int    `gorm:"type:smallint;not null" json:"status_code"`
	DurationMs int    `gorm:"not null" json:"duration_ms"`

	// Client info
	ClientIP      string `gorm:"size:45;index:idx_rl_ip" json:"client_ip,omitempty"`
	CountryCode   string `gorm:"size:2" json:"country_code,omitempty"`
	UserAgent     string `gorm:"type:text" json:"user_agent,omitempty"`
	UserAgentHash uint32 `gorm:"index:idx_rl_ua_hash" json:"user_agent_hash,omitempty"`

	// Request details
	QueryParams  json.RawMessage `gorm:"type:json" json:"query_params,omitempty"`
	RequestSize  int             `gorm:"default:0" json:"request_size"`
	ResponseSize int             `gorm:"default:0" json:"response_size"`

	// Error info
	ErrorType string `gorm:"size:100" json:"error_type,omitempty"`
}

RequestLog represents a single request to a federation endpoint. This is stored in the database for detailed analytics.

func (RequestLog) TableName

func (RequestLog) TableName() string

TableName returns the table name for RequestLog.

type RingBuffer

type RingBuffer struct {

	// NotifyThreshold is signaled when the buffer reaches the threshold percentage.
	// It's a buffered channel (size 1) so sends never block.
	NotifyThreshold chan struct{}
	// contains filtered or unexported fields
}

RingBuffer is a thread-safe circular buffer for storing RequestLog entries. It provides non-blocking writes that never fail - if the buffer is full, the oldest entries are overwritten.

func NewRingBuffer

func NewRingBuffer(capacity int, threshold float64) *RingBuffer

NewRingBuffer creates a new ring buffer with the given capacity and flush threshold. The threshold (0.0 to 1.0) determines when NotifyThreshold is signaled.

func (*RingBuffer) Capacity

func (b *RingBuffer) Capacity() int

Capacity returns the maximum capacity of the buffer.

func (*RingBuffer) Drain

func (b *RingBuffer) Drain() []*RequestLog

Drain removes and returns all entries from the buffer. The buffer is empty after this call.

func (*RingBuffer) FillPercentage

func (b *RingBuffer) FillPercentage() float64

FillPercentage returns the current fill level as a percentage (0.0 to 1.0).

func (*RingBuffer) IsFull

func (b *RingBuffer) IsFull() bool

IsFull returns true if the buffer is at capacity.

func (*RingBuffer) Size

func (b *RingBuffer) Size() int

Size returns the current number of entries in the buffer.

func (*RingBuffer) Write

func (b *RingBuffer) Write(entry *RequestLog) bool

Write adds an entry to the buffer. This operation is non-blocking and always succeeds. If the buffer is full, the oldest entry is overwritten. Returns true if the threshold was reached (caller may want to trigger a flush).

type StorageBackend

type StorageBackend interface {
	InsertBatch(entries []*RequestLog) error
}

StorageBackend is the interface that storage must implement for the flusher.

type Summary

type Summary struct {
	TotalRequests      int64            `json:"total_requests"`
	TotalErrors        int64            `json:"total_errors"`
	ErrorRate          float64          `json:"error_rate"`
	AvgLatencyMs       float64          `json:"avg_latency_ms"`
	P50LatencyMs       int              `json:"p50_latency_ms"`
	P95LatencyMs       int              `json:"p95_latency_ms"`
	P99LatencyMs       int              `json:"p99_latency_ms"`
	UniqueClients      int64            `json:"unique_clients"`
	UniqueUserAgents   int64            `json:"unique_user_agents"`
	RequestsByStatus   map[int]int64    `json:"requests_by_status"`
	RequestsByEndpoint map[string]int64 `json:"requests_by_endpoint"`
}

Summary represents an overall statistics summary.

type TimeSeriesPoint

type TimeSeriesPoint struct {
	Timestamp    time.Time `json:"timestamp"`
	RequestCount int64     `json:"request_count"`
	ErrorCount   int64     `json:"error_count"`
	AvgLatencyMs float64   `json:"avg_latency_ms"`
}

TimeSeriesPoint represents a single data point in a time series.

type TopEntry

type TopEntry struct {
	Value string `json:"value"`
	Count int64  `json:"count"`
}

TopEntry represents a single entry in a top-N list.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL