mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 15:06:37 +08:00
observation clickhouse
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
nodeMetricSnapshotTableName = "of_node_metric_snapshots"
|
||||
nodeMetricSnapshotInsertColumns = "id, node_id, captured_at, cpu_usage_percent, memory_used_bytes, memory_total_bytes, storage_used_bytes, storage_total_bytes, disk_read_bytes, disk_write_bytes, network_rx_bytes, network_tx_bytes, created_at"
|
||||
|
||||
nodeRequestReportTableName = "of_node_request_reports"
|
||||
nodeRequestReportInsertColumns = "id, node_id, window_started_at, window_ended_at, request_count, error_count, unique_visitor_count, status_codes_json, top_domains_json, source_countries_json, created_at"
|
||||
|
||||
nodeObsOpenrestyTableName = "of_node_obs_openresty"
|
||||
nodeObsOpenrestyInsertColumns = "id, node_id, captured_at, openresty_rx_bytes, openresty_tx_bytes, openresty_connections, created_at"
|
||||
|
||||
nodeObsFrpsTableName = "of_node_obs_frps"
|
||||
nodeObsFrpsInsertColumns = "id, node_id, captured_at, frps_connections, frps_proxy_count, frps_client_count, frps_proxies, created_at"
|
||||
|
||||
nodeObsFrpcTableName = "of_node_obs_frpc"
|
||||
nodeObsFrpcInsertColumns = "id, node_id, captured_at, tunnel_status, connected_relays_count, created_at"
|
||||
)
|
||||
|
||||
// NodeMetricSnapshot stores periodic node resource utilization metrics in ClickHouse.
|
||||
type NodeMetricSnapshot struct {
|
||||
ID uint64 `gorm:"column:id"`
|
||||
NodeID string `gorm:"column:node_id"`
|
||||
CapturedAt time.Time `gorm:"column:captured_at"`
|
||||
CPUUsagePercent float64 `gorm:"column:cpu_usage_percent"`
|
||||
MemoryUsedBytes int64 `gorm:"column:memory_used_bytes"`
|
||||
MemoryTotalBytes int64 `gorm:"column:memory_total_bytes"`
|
||||
StorageUsedBytes int64 `gorm:"column:storage_used_bytes"`
|
||||
StorageTotalBytes int64 `gorm:"column:storage_total_bytes"`
|
||||
DiskReadBytes int64 `gorm:"column:disk_read_bytes"`
|
||||
DiskWriteBytes int64 `gorm:"column:disk_write_bytes"`
|
||||
NetworkRxBytes int64 `gorm:"column:network_rx_bytes"`
|
||||
NetworkTxBytes int64 `gorm:"column:network_tx_bytes"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
}
|
||||
|
||||
// TableName returns the ClickHouse table name.
|
||||
func (NodeMetricSnapshot) TableName() string {
|
||||
return nodeMetricSnapshotTableName
|
||||
}
|
||||
|
||||
// InsertColumns returns comma-separated column names for batch insert.
|
||||
func (NodeMetricSnapshot) InsertColumns() string {
|
||||
return nodeMetricSnapshotInsertColumns
|
||||
}
|
||||
|
||||
// BatchInsertSQL returns the INSERT prefix used by native batch writers.
|
||||
func (NodeMetricSnapshot) BatchInsertSQL() string {
|
||||
return fmt.Sprintf("INSERT INTO %s (%s)", nodeMetricSnapshotTableName, nodeMetricSnapshotInsertColumns)
|
||||
}
|
||||
|
||||
// NodeRequestReport stores aggregated node request window reports in ClickHouse.
|
||||
type NodeRequestReport struct {
|
||||
ID uint64 `gorm:"column:id"`
|
||||
NodeID string `gorm:"column:node_id"`
|
||||
WindowStartedAt time.Time `gorm:"column:window_started_at"`
|
||||
WindowEndedAt time.Time `gorm:"column:window_ended_at"`
|
||||
RequestCount int64 `gorm:"column:request_count"`
|
||||
ErrorCount int64 `gorm:"column:error_count"`
|
||||
UniqueVisitorCount int64 `gorm:"column:unique_visitor_count"`
|
||||
StatusCodesJSON string `gorm:"column:status_codes_json"`
|
||||
TopDomainsJSON string `gorm:"column:top_domains_json"`
|
||||
SourceCountriesJSON string `gorm:"column:source_countries_json"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
}
|
||||
|
||||
// TableName returns the ClickHouse table name.
|
||||
func (NodeRequestReport) TableName() string {
|
||||
return nodeRequestReportTableName
|
||||
}
|
||||
|
||||
// InsertColumns returns comma-separated column names for batch insert.
|
||||
func (NodeRequestReport) InsertColumns() string {
|
||||
return nodeRequestReportInsertColumns
|
||||
}
|
||||
|
||||
// BatchInsertSQL returns the INSERT prefix used by native batch writers.
|
||||
func (NodeRequestReport) BatchInsertSQL() string {
|
||||
return fmt.Sprintf("INSERT INTO %s (%s)", nodeRequestReportTableName, nodeRequestReportInsertColumns)
|
||||
}
|
||||
|
||||
// NodeObsOpenresty stores OpenResty observability snapshots in ClickHouse.
|
||||
type NodeObsOpenresty struct {
|
||||
ID uint64 `gorm:"column:id"`
|
||||
NodeID string `gorm:"column:node_id"`
|
||||
CapturedAt time.Time `gorm:"column:captured_at"`
|
||||
OpenrestyRxBytes int64 `gorm:"column:openresty_rx_bytes"`
|
||||
OpenrestyTxBytes int64 `gorm:"column:openresty_tx_bytes"`
|
||||
OpenrestyConnections int64 `gorm:"column:openresty_connections"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
}
|
||||
|
||||
// TableName returns the ClickHouse table name.
|
||||
func (NodeObsOpenresty) TableName() string {
|
||||
return nodeObsOpenrestyTableName
|
||||
}
|
||||
|
||||
// InsertColumns returns comma-separated column names for batch insert.
|
||||
func (NodeObsOpenresty) InsertColumns() string {
|
||||
return nodeObsOpenrestyInsertColumns
|
||||
}
|
||||
|
||||
// BatchInsertSQL returns the INSERT prefix used by native batch writers.
|
||||
func (NodeObsOpenresty) BatchInsertSQL() string {
|
||||
return fmt.Sprintf("INSERT INTO %s (%s)", nodeObsOpenrestyTableName, nodeObsOpenrestyInsertColumns)
|
||||
}
|
||||
|
||||
// NodeObsFrps stores FRPS observability snapshots in ClickHouse.
|
||||
type NodeObsFrps struct {
|
||||
ID uint64 `gorm:"column:id"`
|
||||
NodeID string `gorm:"column:node_id"`
|
||||
CapturedAt time.Time `gorm:"column:captured_at"`
|
||||
FrpsConnections int32 `gorm:"column:frps_connections"`
|
||||
FrpsProxyCount int32 `gorm:"column:frps_proxy_count"`
|
||||
FrpsClientCount int32 `gorm:"column:frps_client_count"`
|
||||
FrpsProxies string `gorm:"column:frps_proxies"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
}
|
||||
|
||||
// TableName returns the ClickHouse table name.
|
||||
func (NodeObsFrps) TableName() string {
|
||||
return nodeObsFrpsTableName
|
||||
}
|
||||
|
||||
// InsertColumns returns comma-separated column names for batch insert.
|
||||
func (NodeObsFrps) InsertColumns() string {
|
||||
return nodeObsFrpsInsertColumns
|
||||
}
|
||||
|
||||
// BatchInsertSQL returns the INSERT prefix used by native batch writers.
|
||||
func (NodeObsFrps) BatchInsertSQL() string {
|
||||
return fmt.Sprintf("INSERT INTO %s (%s)", nodeObsFrpsTableName, nodeObsFrpsInsertColumns)
|
||||
}
|
||||
|
||||
// NodeObsFrpc stores FRPC observability snapshots in ClickHouse.
|
||||
type NodeObsFrpc struct {
|
||||
ID uint64 `gorm:"column:id"`
|
||||
NodeID string `gorm:"column:node_id"`
|
||||
CapturedAt time.Time `gorm:"column:captured_at"`
|
||||
TunnelStatus string `gorm:"column:tunnel_status"`
|
||||
ConnectedRelaysCount int32 `gorm:"column:connected_relays_count"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
}
|
||||
|
||||
// TableName returns the ClickHouse table name.
|
||||
func (NodeObsFrpc) TableName() string {
|
||||
return nodeObsFrpcTableName
|
||||
}
|
||||
|
||||
// InsertColumns returns comma-separated column names for batch insert.
|
||||
func (NodeObsFrpc) InsertColumns() string {
|
||||
return nodeObsFrpcInsertColumns
|
||||
}
|
||||
|
||||
// BatchInsertSQL returns the INSERT prefix used by native batch writers.
|
||||
func (NodeObsFrpc) BatchInsertSQL() string {
|
||||
return fmt.Sprintf("INSERT INTO %s (%s)", nodeObsFrpcTableName, nodeObsFrpcInsertColumns)
|
||||
}
|
||||
@@ -13,7 +13,8 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// OpenFlareMetricSnapshot stores a node capacity snapshot (v1 single table, no sharding).
|
||||
// OpenFlareMetricSnapshot stores a node capacity snapshot in ClickHouse (database: openflare, table: of_node_metric_snapshots).
|
||||
// ClickHouse DDL is managed by goose; reads/writes go through internal/repository/analytics.
|
||||
type OpenFlareMetricSnapshot struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
|
||||
@@ -35,7 +36,8 @@ func (OpenFlareMetricSnapshot) TableName() string {
|
||||
return "of_node_metric_snapshots"
|
||||
}
|
||||
|
||||
// OpenFlareRequestReport stores aggregated traffic windows per node.
|
||||
// OpenFlareRequestReport stores aggregated traffic windows per node in ClickHouse (database: openflare, table: of_node_request_reports).
|
||||
// ClickHouse DDL is managed by goose; reads/writes go through internal/repository/analytics.
|
||||
type OpenFlareRequestReport struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
|
||||
@@ -126,7 +128,8 @@ func (OpenFlareNodeSystemProfile) TableName() string {
|
||||
return "of_node_system_profiles"
|
||||
}
|
||||
|
||||
// OpenFlareNodeObservationOpenresty stores openresty network observations.
|
||||
// OpenFlareNodeObservationOpenresty stores openresty network observations in ClickHouse (database: openflare, table: of_node_obs_openresty).
|
||||
// ClickHouse DDL is managed by goose; reads/writes go through internal/repository/analytics.
|
||||
type OpenFlareNodeObservationOpenresty struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
|
||||
@@ -142,7 +145,8 @@ func (OpenFlareNodeObservationOpenresty) TableName() string {
|
||||
return "of_node_obs_openresty"
|
||||
}
|
||||
|
||||
// OpenFlareNodeObservationFrpc stores tunnel client frpc observations.
|
||||
// OpenFlareNodeObservationFrpc stores tunnel client frpc observations in ClickHouse (database: openflare, table: of_node_obs_frpc).
|
||||
// ClickHouse DDL is managed by goose; reads/writes go through internal/repository/analytics.
|
||||
type OpenFlareNodeObservationFrpc struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
|
||||
@@ -157,7 +161,8 @@ func (OpenFlareNodeObservationFrpc) TableName() string {
|
||||
return "of_node_obs_frpc"
|
||||
}
|
||||
|
||||
// OpenFlareNodeObservationFrps stores tunnel relay frps observations.
|
||||
// OpenFlareNodeObservationFrps stores tunnel relay frps observations in ClickHouse (database: openflare, table: of_node_obs_frps).
|
||||
// ClickHouse DDL is managed by goose; reads/writes go through internal/repository/analytics.
|
||||
type OpenFlareNodeObservationFrps struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
|
||||
@@ -285,39 +290,39 @@ func isMissingTableError(err error) bool {
|
||||
strings.Contains(msg, "does not exist")
|
||||
}
|
||||
|
||||
func listOpenFlareSince[T any](ctx context.Context, nodeID string, since time.Time, limit int, orderBy string, sinceColumn string) ([]*T, error) {
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return nil, errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
query := conn.Model(new(T)).Order(orderBy)
|
||||
if nodeID != "" {
|
||||
query = query.Where("node_id = ?", nodeID)
|
||||
}
|
||||
if !since.IsZero() {
|
||||
query = query.Where(sinceColumn+" >= ?", since)
|
||||
}
|
||||
if limit > 0 {
|
||||
query = query.Limit(limit)
|
||||
}
|
||||
var rows []*T
|
||||
if err := query.Find(&rows).Error; err != nil {
|
||||
if isMissingTableError(err) {
|
||||
return []*T{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return rows, nil
|
||||
// InsertOpenFlareMetricSnapshot inserts a metric snapshot into ClickHouse.
|
||||
func InsertOpenFlareMetricSnapshot(ctx context.Context, record *OpenFlareMetricSnapshot) error {
|
||||
return currentObservabilityStore().InsertMetricSnapshot(ctx, record)
|
||||
}
|
||||
|
||||
// InsertOpenFlareRequestReport inserts a request report into ClickHouse.
|
||||
func InsertOpenFlareRequestReport(ctx context.Context, record *OpenFlareRequestReport) error {
|
||||
return currentObservabilityStore().InsertRequestReport(ctx, record)
|
||||
}
|
||||
|
||||
// InsertOpenFlareNodeObservationOpenresty inserts an OpenResty observation into ClickHouse.
|
||||
func InsertOpenFlareNodeObservationOpenresty(ctx context.Context, record *OpenFlareNodeObservationOpenresty) error {
|
||||
return currentObservabilityStore().InsertNodeObservationOpenresty(ctx, record)
|
||||
}
|
||||
|
||||
// InsertOpenFlareNodeObservationFrps inserts an FRPS observation into ClickHouse.
|
||||
func InsertOpenFlareNodeObservationFrps(ctx context.Context, record *OpenFlareNodeObservationFrps) error {
|
||||
return currentObservabilityStore().InsertNodeObservationFrps(ctx, record)
|
||||
}
|
||||
|
||||
// InsertOpenFlareNodeObservationFrpc inserts an FRPC observation into ClickHouse.
|
||||
func InsertOpenFlareNodeObservationFrpc(ctx context.Context, record *OpenFlareNodeObservationFrpc) error {
|
||||
return currentObservabilityStore().InsertNodeObservationFrpc(ctx, record)
|
||||
}
|
||||
|
||||
// ListOpenFlareMetricSnapshotsSince returns metric snapshots since the given time.
|
||||
func ListOpenFlareMetricSnapshotsSince(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareMetricSnapshot, error) {
|
||||
return listOpenFlareSince[OpenFlareMetricSnapshot](ctx, nodeID, since, limit, "captured_at desc, id desc", "captured_at")
|
||||
return currentObservabilityStore().ListMetricSnapshots(ctx, nodeID, since, limit)
|
||||
}
|
||||
|
||||
// ListOpenFlareRequestReportsSince returns request reports since the given time.
|
||||
func ListOpenFlareRequestReportsSince(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareRequestReport, error) {
|
||||
return listOpenFlareSince[OpenFlareRequestReport](ctx, nodeID, since, limit, "window_ended_at desc, id desc", "window_ended_at")
|
||||
return currentObservabilityStore().ListRequestReports(ctx, nodeID, since, limit)
|
||||
}
|
||||
|
||||
// ListOpenFlareActiveHealthEvents returns active health events across all nodes.
|
||||
@@ -361,66 +366,22 @@ func ListOpenFlareHealthEvents(ctx context.Context, nodeID string, activeOnly bo
|
||||
|
||||
// DeleteOpenFlareMetricSnapshotsBefore deletes metric snapshots captured before cutoff.
|
||||
func DeleteOpenFlareMetricSnapshotsBefore(ctx context.Context, cutoff time.Time) (int64, error) {
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return 0, errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
result := conn.Where("captured_at < ?", cutoff).Delete(&OpenFlareMetricSnapshot{})
|
||||
if result.Error != nil {
|
||||
if isMissingTableError(result.Error) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, result.Error
|
||||
}
|
||||
return result.RowsAffected, nil
|
||||
return currentObservabilityStore().DeleteMetricSnapshotsBefore(ctx, cutoff)
|
||||
}
|
||||
|
||||
// DeleteAllOpenFlareMetricSnapshots deletes all metric snapshots.
|
||||
func DeleteAllOpenFlareMetricSnapshots(ctx context.Context) (int64, error) {
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return 0, errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
result := conn.Where("1 = 1").Delete(&OpenFlareMetricSnapshot{})
|
||||
if result.Error != nil {
|
||||
if isMissingTableError(result.Error) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, result.Error
|
||||
}
|
||||
return result.RowsAffected, nil
|
||||
return currentObservabilityStore().DeleteAllMetricSnapshots(ctx)
|
||||
}
|
||||
|
||||
// DeleteOpenFlareRequestReportsBefore deletes request reports ending before cutoff.
|
||||
func DeleteOpenFlareRequestReportsBefore(ctx context.Context, cutoff time.Time) (int64, error) {
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return 0, errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
result := conn.Where("window_ended_at < ?", cutoff).Delete(&OpenFlareRequestReport{})
|
||||
if result.Error != nil {
|
||||
if isMissingTableError(result.Error) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, result.Error
|
||||
}
|
||||
return result.RowsAffected, nil
|
||||
return currentObservabilityStore().DeleteRequestReportsBefore(ctx, cutoff)
|
||||
}
|
||||
|
||||
// DeleteAllOpenFlareRequestReports deletes all request reports.
|
||||
func DeleteAllOpenFlareRequestReports(ctx context.Context) (int64, error) {
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return 0, errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
result := conn.Where("1 = 1").Delete(&OpenFlareRequestReport{})
|
||||
if result.Error != nil {
|
||||
if isMissingTableError(result.Error) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, result.Error
|
||||
}
|
||||
return result.RowsAffected, nil
|
||||
return currentObservabilityStore().DeleteAllRequestReports(ctx)
|
||||
}
|
||||
|
||||
// DeleteOpenFlareHealthEventsByNodeID deletes all health events for a node.
|
||||
@@ -457,15 +418,15 @@ func GetOpenFlareNodeSystemProfile(ctx context.Context, nodeID string) (*OpenFla
|
||||
|
||||
// ListOpenFlareNodeObservationOpenresty returns openresty observations.
|
||||
func ListOpenFlareNodeObservationOpenresty(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationOpenresty, error) {
|
||||
return listOpenFlareSince[OpenFlareNodeObservationOpenresty](ctx, nodeID, since, limit, "captured_at desc, id desc", "captured_at")
|
||||
return currentObservabilityStore().ListNodeObservationOpenresty(ctx, nodeID, since, limit)
|
||||
}
|
||||
|
||||
// ListOpenFlareNodeObservationFrpc returns frpc observations.
|
||||
func ListOpenFlareNodeObservationFrpc(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationFrpc, error) {
|
||||
return listOpenFlareSince[OpenFlareNodeObservationFrpc](ctx, nodeID, since, limit, "captured_at desc, id desc", "captured_at")
|
||||
return currentObservabilityStore().ListNodeObservationFrpc(ctx, nodeID, since, limit)
|
||||
}
|
||||
|
||||
// ListOpenFlareNodeObservationFrps returns frps observations.
|
||||
func ListOpenFlareNodeObservationFrps(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationFrps, error) {
|
||||
return listOpenFlareSince[OpenFlareNodeObservationFrps](ctx, nodeID, since, limit, "captured_at desc, id desc", "captured_at")
|
||||
return currentObservabilityStore().ListNodeObservationFrps(ctx, nodeID, since, limit)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
analyticsmodel "github.com/Rain-kl/Wavelet/internal/model/analytics"
|
||||
analyticsrepo "github.com/Rain-kl/Wavelet/internal/repository/analytics"
|
||||
)
|
||||
|
||||
type observabilityStore interface {
|
||||
InsertMetricSnapshot(ctx context.Context, record *OpenFlareMetricSnapshot) error
|
||||
ListMetricSnapshots(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareMetricSnapshot, error)
|
||||
DeleteAllMetricSnapshots(ctx context.Context) (int64, error)
|
||||
DeleteMetricSnapshotsBefore(ctx context.Context, cutoff time.Time) (int64, error)
|
||||
|
||||
InsertRequestReport(ctx context.Context, record *OpenFlareRequestReport) error
|
||||
ListRequestReports(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareRequestReport, error)
|
||||
DeleteAllRequestReports(ctx context.Context) (int64, error)
|
||||
DeleteRequestReportsBefore(ctx context.Context, cutoff time.Time) (int64, error)
|
||||
|
||||
InsertNodeObservationOpenresty(ctx context.Context, record *OpenFlareNodeObservationOpenresty) error
|
||||
ListNodeObservationOpenresty(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationOpenresty, error)
|
||||
DeleteAllNodeObservationOpenresty(ctx context.Context) (int64, error)
|
||||
DeleteNodeObservationOpenrestyBefore(ctx context.Context, cutoff time.Time) (int64, error)
|
||||
|
||||
InsertNodeObservationFrps(ctx context.Context, record *OpenFlareNodeObservationFrps) error
|
||||
ListNodeObservationFrps(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationFrps, error)
|
||||
DeleteAllNodeObservationFrps(ctx context.Context) (int64, error)
|
||||
DeleteNodeObservationFrpsBefore(ctx context.Context, cutoff time.Time) (int64, error)
|
||||
|
||||
InsertNodeObservationFrpc(ctx context.Context, record *OpenFlareNodeObservationFrpc) error
|
||||
ListNodeObservationFrpc(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationFrpc, error)
|
||||
DeleteAllNodeObservationFrpc(ctx context.Context) (int64, error)
|
||||
DeleteNodeObservationFrpcBefore(ctx context.Context, cutoff time.Time) (int64, error)
|
||||
}
|
||||
|
||||
var (
|
||||
observabilityStoreMu sync.RWMutex
|
||||
observabilityStoreHolder observabilityStore
|
||||
)
|
||||
|
||||
func currentObservabilityStore() observabilityStore {
|
||||
observabilityStoreMu.RLock()
|
||||
defer observabilityStoreMu.RUnlock()
|
||||
if observabilityStoreHolder != nil {
|
||||
return observabilityStoreHolder
|
||||
}
|
||||
return clickhouseObservabilityStore{}
|
||||
}
|
||||
|
||||
// SetObservabilityStoreForTest swaps the observability store implementation for unit tests.
|
||||
func SetObservabilityStoreForTest(store observabilityStore) func() {
|
||||
observabilityStoreMu.Lock()
|
||||
previous := observabilityStoreHolder
|
||||
observabilityStoreHolder = store
|
||||
observabilityStoreMu.Unlock()
|
||||
return func() {
|
||||
observabilityStoreMu.Lock()
|
||||
observabilityStoreHolder = previous
|
||||
observabilityStoreMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// NewMemoryObservabilityStore returns an in-memory observability store for unit tests.
|
||||
func NewMemoryObservabilityStore() observabilityStore {
|
||||
return &memoryObservabilityStore{}
|
||||
}
|
||||
|
||||
type clickhouseObservabilityStore struct{}
|
||||
|
||||
func (clickhouseObservabilityStore) InsertMetricSnapshot(ctx context.Context, record *OpenFlareMetricSnapshot) error {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
return analyticsrepo.InsertNodeMetricSnapshot(ctx, toAnalyticsNodeMetricSnapshot(record))
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) ListMetricSnapshots(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareMetricSnapshot, error) {
|
||||
rows, err := analyticsrepo.ListNodeMetricSnapshots(ctx, toNodeObservabilityFilter(nodeID, since, limit))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromAnalyticsNodeMetricSnapshots(rows), nil
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) DeleteAllMetricSnapshots(ctx context.Context) (int64, error) {
|
||||
return analyticsrepo.DeleteAllNodeMetricSnapshots(ctx)
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) DeleteMetricSnapshotsBefore(ctx context.Context, cutoff time.Time) (int64, error) {
|
||||
return analyticsrepo.DeleteNodeMetricSnapshotsBefore(ctx, cutoff)
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) InsertRequestReport(ctx context.Context, record *OpenFlareRequestReport) error {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
return analyticsrepo.InsertNodeRequestReport(ctx, toAnalyticsNodeRequestReport(record))
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) ListRequestReports(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareRequestReport, error) {
|
||||
rows, err := analyticsrepo.ListNodeRequestReports(ctx, toNodeObservabilityFilter(nodeID, since, limit))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromAnalyticsNodeRequestReports(rows), nil
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) DeleteAllRequestReports(ctx context.Context) (int64, error) {
|
||||
return analyticsrepo.DeleteAllNodeRequestReports(ctx)
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) DeleteRequestReportsBefore(ctx context.Context, cutoff time.Time) (int64, error) {
|
||||
return analyticsrepo.DeleteNodeRequestReportsBefore(ctx, cutoff)
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) InsertNodeObservationOpenresty(ctx context.Context, record *OpenFlareNodeObservationOpenresty) error {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
return analyticsrepo.InsertNodeObsOpenresty(ctx, toAnalyticsNodeObsOpenresty(record))
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) ListNodeObservationOpenresty(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationOpenresty, error) {
|
||||
rows, err := analyticsrepo.ListNodeObsOpenresty(ctx, toNodeObservabilityFilter(nodeID, since, limit))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromAnalyticsNodeObsOpenresty(rows), nil
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) DeleteAllNodeObservationOpenresty(ctx context.Context) (int64, error) {
|
||||
return analyticsrepo.DeleteAllNodeObsOpenresty(ctx)
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) DeleteNodeObservationOpenrestyBefore(ctx context.Context, cutoff time.Time) (int64, error) {
|
||||
return analyticsrepo.DeleteNodeObsOpenrestyBefore(ctx, cutoff)
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) InsertNodeObservationFrps(ctx context.Context, record *OpenFlareNodeObservationFrps) error {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
return analyticsrepo.InsertNodeObsFrps(ctx, toAnalyticsNodeObsFrps(record))
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) ListNodeObservationFrps(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationFrps, error) {
|
||||
rows, err := analyticsrepo.ListNodeObsFrps(ctx, toNodeObservabilityFilter(nodeID, since, limit))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromAnalyticsNodeObsFrps(rows), nil
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) DeleteAllNodeObservationFrps(ctx context.Context) (int64, error) {
|
||||
return analyticsrepo.DeleteAllNodeObsFrps(ctx)
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) DeleteNodeObservationFrpsBefore(ctx context.Context, cutoff time.Time) (int64, error) {
|
||||
return analyticsrepo.DeleteNodeObsFrpsBefore(ctx, cutoff)
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) InsertNodeObservationFrpc(ctx context.Context, record *OpenFlareNodeObservationFrpc) error {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
return analyticsrepo.InsertNodeObsFrpc(ctx, toAnalyticsNodeObsFrpc(record))
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) ListNodeObservationFrpc(ctx context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationFrpc, error) {
|
||||
rows, err := analyticsrepo.ListNodeObsFrpc(ctx, toNodeObservabilityFilter(nodeID, since, limit))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromAnalyticsNodeObsFrpc(rows), nil
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) DeleteAllNodeObservationFrpc(ctx context.Context) (int64, error) {
|
||||
return analyticsrepo.DeleteAllNodeObsFrpc(ctx)
|
||||
}
|
||||
|
||||
func (clickhouseObservabilityStore) DeleteNodeObservationFrpcBefore(ctx context.Context, cutoff time.Time) (int64, error) {
|
||||
return analyticsrepo.DeleteNodeObsFrpcBefore(ctx, cutoff)
|
||||
}
|
||||
|
||||
func toNodeObservabilityFilter(nodeID string, since time.Time, limit int) analyticsrepo.NodeObservabilityFilter {
|
||||
return analyticsrepo.NodeObservabilityFilter{
|
||||
NodeID: nodeID,
|
||||
Since: since,
|
||||
Limit: limit,
|
||||
}
|
||||
}
|
||||
|
||||
func toAnalyticsNodeMetricSnapshot(record *OpenFlareMetricSnapshot) analyticsmodel.NodeMetricSnapshot {
|
||||
return analyticsmodel.NodeMetricSnapshot{
|
||||
ID: uint64(record.ID),
|
||||
NodeID: record.NodeID,
|
||||
CapturedAt: record.CapturedAt,
|
||||
CPUUsagePercent: record.CPUUsagePercent,
|
||||
MemoryUsedBytes: record.MemoryUsedBytes,
|
||||
MemoryTotalBytes: record.MemoryTotalBytes,
|
||||
StorageUsedBytes: record.StorageUsedBytes,
|
||||
StorageTotalBytes: record.StorageTotalBytes,
|
||||
DiskReadBytes: record.DiskReadBytes,
|
||||
DiskWriteBytes: record.DiskWriteBytes,
|
||||
NetworkRxBytes: record.NetworkRxBytes,
|
||||
NetworkTxBytes: record.NetworkTxBytes,
|
||||
CreatedAt: record.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func fromAnalyticsNodeMetricSnapshots(rows []analyticsmodel.NodeMetricSnapshot) []*OpenFlareMetricSnapshot {
|
||||
result := make([]*OpenFlareMetricSnapshot, len(rows))
|
||||
for index, row := range rows {
|
||||
result[index] = &OpenFlareMetricSnapshot{
|
||||
ID: uint(row.ID),
|
||||
NodeID: row.NodeID,
|
||||
CapturedAt: row.CapturedAt,
|
||||
CPUUsagePercent: row.CPUUsagePercent,
|
||||
MemoryUsedBytes: row.MemoryUsedBytes,
|
||||
MemoryTotalBytes: row.MemoryTotalBytes,
|
||||
StorageUsedBytes: row.StorageUsedBytes,
|
||||
StorageTotalBytes: row.StorageTotalBytes,
|
||||
DiskReadBytes: row.DiskReadBytes,
|
||||
DiskWriteBytes: row.DiskWriteBytes,
|
||||
NetworkRxBytes: row.NetworkRxBytes,
|
||||
NetworkTxBytes: row.NetworkTxBytes,
|
||||
CreatedAt: row.CreatedAt,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func toAnalyticsNodeRequestReport(record *OpenFlareRequestReport) analyticsmodel.NodeRequestReport {
|
||||
return analyticsmodel.NodeRequestReport{
|
||||
ID: uint64(record.ID),
|
||||
NodeID: record.NodeID,
|
||||
WindowStartedAt: record.WindowStartedAt,
|
||||
WindowEndedAt: record.WindowEndedAt,
|
||||
RequestCount: record.RequestCount,
|
||||
ErrorCount: record.ErrorCount,
|
||||
UniqueVisitorCount: record.UniqueVisitorCount,
|
||||
StatusCodesJSON: record.StatusCodesJSON,
|
||||
TopDomainsJSON: record.TopDomainsJSON,
|
||||
SourceCountriesJSON: record.SourceCountriesJSON,
|
||||
CreatedAt: record.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func fromAnalyticsNodeRequestReports(rows []analyticsmodel.NodeRequestReport) []*OpenFlareRequestReport {
|
||||
result := make([]*OpenFlareRequestReport, len(rows))
|
||||
for index, row := range rows {
|
||||
result[index] = &OpenFlareRequestReport{
|
||||
ID: uint(row.ID),
|
||||
NodeID: row.NodeID,
|
||||
WindowStartedAt: row.WindowStartedAt,
|
||||
WindowEndedAt: row.WindowEndedAt,
|
||||
RequestCount: row.RequestCount,
|
||||
ErrorCount: row.ErrorCount,
|
||||
UniqueVisitorCount: row.UniqueVisitorCount,
|
||||
StatusCodesJSON: row.StatusCodesJSON,
|
||||
TopDomainsJSON: row.TopDomainsJSON,
|
||||
SourceCountriesJSON: row.SourceCountriesJSON,
|
||||
CreatedAt: row.CreatedAt,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func toAnalyticsNodeObsOpenresty(record *OpenFlareNodeObservationOpenresty) analyticsmodel.NodeObsOpenresty {
|
||||
return analyticsmodel.NodeObsOpenresty{
|
||||
ID: uint64(record.ID),
|
||||
NodeID: record.NodeID,
|
||||
CapturedAt: record.CapturedAt,
|
||||
OpenrestyRxBytes: record.OpenrestyRxBytes,
|
||||
OpenrestyTxBytes: record.OpenrestyTxBytes,
|
||||
OpenrestyConnections: record.OpenrestyConnections,
|
||||
CreatedAt: record.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func fromAnalyticsNodeObsOpenresty(rows []analyticsmodel.NodeObsOpenresty) []*OpenFlareNodeObservationOpenresty {
|
||||
result := make([]*OpenFlareNodeObservationOpenresty, len(rows))
|
||||
for index, row := range rows {
|
||||
result[index] = &OpenFlareNodeObservationOpenresty{
|
||||
ID: uint(row.ID),
|
||||
NodeID: row.NodeID,
|
||||
CapturedAt: row.CapturedAt,
|
||||
OpenrestyRxBytes: row.OpenrestyRxBytes,
|
||||
OpenrestyTxBytes: row.OpenrestyTxBytes,
|
||||
OpenrestyConnections: row.OpenrestyConnections,
|
||||
CreatedAt: row.CreatedAt,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func toAnalyticsNodeObsFrps(record *OpenFlareNodeObservationFrps) analyticsmodel.NodeObsFrps {
|
||||
return analyticsmodel.NodeObsFrps{
|
||||
ID: uint64(record.ID),
|
||||
NodeID: record.NodeID,
|
||||
CapturedAt: record.CapturedAt,
|
||||
FrpsConnections: openFlareObservabilityIntToInt32(record.FrpsConnections),
|
||||
FrpsProxyCount: openFlareObservabilityIntToInt32(record.FrpsProxyCount),
|
||||
FrpsClientCount: openFlareObservabilityIntToInt32(record.FrpsClientCount),
|
||||
FrpsProxies: record.FrpsProxies,
|
||||
CreatedAt: record.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func fromAnalyticsNodeObsFrps(rows []analyticsmodel.NodeObsFrps) []*OpenFlareNodeObservationFrps {
|
||||
result := make([]*OpenFlareNodeObservationFrps, len(rows))
|
||||
for index, row := range rows {
|
||||
result[index] = &OpenFlareNodeObservationFrps{
|
||||
ID: uint(row.ID),
|
||||
NodeID: row.NodeID,
|
||||
CapturedAt: row.CapturedAt,
|
||||
FrpsConnections: int(row.FrpsConnections),
|
||||
FrpsProxyCount: int(row.FrpsProxyCount),
|
||||
FrpsClientCount: int(row.FrpsClientCount),
|
||||
FrpsProxies: row.FrpsProxies,
|
||||
CreatedAt: row.CreatedAt,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func toAnalyticsNodeObsFrpc(record *OpenFlareNodeObservationFrpc) analyticsmodel.NodeObsFrpc {
|
||||
return analyticsmodel.NodeObsFrpc{
|
||||
ID: uint64(record.ID),
|
||||
NodeID: record.NodeID,
|
||||
CapturedAt: record.CapturedAt,
|
||||
TunnelStatus: record.TunnelStatus,
|
||||
ConnectedRelaysCount: openFlareObservabilityIntToInt32(record.ConnectedRelaysCount),
|
||||
CreatedAt: record.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func openFlareObservabilityIntToInt32(value int) int32 {
|
||||
switch {
|
||||
case value > math.MaxInt32:
|
||||
return math.MaxInt32
|
||||
case value < math.MinInt32:
|
||||
return math.MinInt32
|
||||
default:
|
||||
return int32(value)
|
||||
}
|
||||
}
|
||||
|
||||
func fromAnalyticsNodeObsFrpc(rows []analyticsmodel.NodeObsFrpc) []*OpenFlareNodeObservationFrpc {
|
||||
result := make([]*OpenFlareNodeObservationFrpc, len(rows))
|
||||
for index, row := range rows {
|
||||
result[index] = &OpenFlareNodeObservationFrpc{
|
||||
ID: uint(row.ID),
|
||||
NodeID: row.NodeID,
|
||||
CapturedAt: row.CapturedAt,
|
||||
TunnelStatus: row.TunnelStatus,
|
||||
ConnectedRelaysCount: int(row.ConnectedRelaysCount),
|
||||
CreatedAt: row.CreatedAt,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/db/idgen"
|
||||
)
|
||||
|
||||
type memoryObservabilityStore struct {
|
||||
mu sync.RWMutex
|
||||
metricSnapshots []*OpenFlareMetricSnapshot
|
||||
requestReports []*OpenFlareRequestReport
|
||||
openrestyObs []*OpenFlareNodeObservationOpenresty
|
||||
frpsObs []*OpenFlareNodeObservationFrps
|
||||
frpcObs []*OpenFlareNodeObservationFrpc
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) InsertMetricSnapshot(_ context.Context, record *OpenFlareMetricSnapshot) error {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
copyRecord := cloneOpenFlareMetricSnapshot(record)
|
||||
if memoryMetricSnapshotExists(s.metricSnapshots, copyRecord.NodeID, copyRecord.CapturedAt) {
|
||||
return nil
|
||||
}
|
||||
s.metricSnapshots = append(s.metricSnapshots, copyRecord)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) ListMetricSnapshots(_ context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareMetricSnapshot, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
rows := memoryFilterMetricSnapshots(s.metricSnapshots, nodeID, since)
|
||||
sortOpenFlareMetricSnapshots(rows)
|
||||
return memoryLimitObservabilityRows(rows, limit), nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) DeleteAllMetricSnapshots(_ context.Context) (int64, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
count := int64(len(s.metricSnapshots))
|
||||
s.metricSnapshots = nil
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) DeleteMetricSnapshotsBefore(_ context.Context, cutoff time.Time) (int64, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
cutoff = cutoff.UTC()
|
||||
remaining := make([]*OpenFlareMetricSnapshot, 0, len(s.metricSnapshots))
|
||||
var deleted int64
|
||||
for _, row := range s.metricSnapshots {
|
||||
if row.CapturedAt.Before(cutoff) {
|
||||
deleted++
|
||||
continue
|
||||
}
|
||||
remaining = append(remaining, row)
|
||||
}
|
||||
s.metricSnapshots = remaining
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) InsertRequestReport(_ context.Context, record *OpenFlareRequestReport) error {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
copyRecord := cloneOpenFlareRequestReport(record)
|
||||
if memoryRequestReportExists(s.requestReports, copyRecord.NodeID, copyRecord.WindowStartedAt, copyRecord.WindowEndedAt) {
|
||||
return nil
|
||||
}
|
||||
s.requestReports = append(s.requestReports, copyRecord)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) ListRequestReports(_ context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareRequestReport, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
rows := memoryFilterRequestReports(s.requestReports, nodeID, since)
|
||||
sortOpenFlareRequestReports(rows)
|
||||
return memoryLimitObservabilityRows(rows, limit), nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) DeleteAllRequestReports(_ context.Context) (int64, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
count := int64(len(s.requestReports))
|
||||
s.requestReports = nil
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) DeleteRequestReportsBefore(_ context.Context, cutoff time.Time) (int64, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
cutoff = cutoff.UTC()
|
||||
remaining := make([]*OpenFlareRequestReport, 0, len(s.requestReports))
|
||||
var deleted int64
|
||||
for _, row := range s.requestReports {
|
||||
if row.WindowEndedAt.Before(cutoff) {
|
||||
deleted++
|
||||
continue
|
||||
}
|
||||
remaining = append(remaining, row)
|
||||
}
|
||||
s.requestReports = remaining
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) InsertNodeObservationOpenresty(_ context.Context, record *OpenFlareNodeObservationOpenresty) error {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.openrestyObs = append(s.openrestyObs, cloneOpenFlareNodeObservationOpenresty(record))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) ListNodeObservationOpenresty(_ context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationOpenresty, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
rows := memoryFilterOpenrestyObservations(s.openrestyObs, nodeID, since)
|
||||
sortOpenFlareNodeObservationOpenresty(rows)
|
||||
return memoryLimitObservabilityRows(rows, limit), nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) DeleteAllNodeObservationOpenresty(_ context.Context) (int64, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
count := int64(len(s.openrestyObs))
|
||||
s.openrestyObs = nil
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) DeleteNodeObservationOpenrestyBefore(_ context.Context, cutoff time.Time) (int64, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
cutoff = cutoff.UTC()
|
||||
remaining := make([]*OpenFlareNodeObservationOpenresty, 0, len(s.openrestyObs))
|
||||
var deleted int64
|
||||
for _, row := range s.openrestyObs {
|
||||
if row.CapturedAt.Before(cutoff) {
|
||||
deleted++
|
||||
continue
|
||||
}
|
||||
remaining = append(remaining, row)
|
||||
}
|
||||
s.openrestyObs = remaining
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) InsertNodeObservationFrps(_ context.Context, record *OpenFlareNodeObservationFrps) error {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.frpsObs = append(s.frpsObs, cloneOpenFlareNodeObservationFrps(record))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) ListNodeObservationFrps(_ context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationFrps, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
rows := memoryFilterFrpsObservations(s.frpsObs, nodeID, since)
|
||||
sortOpenFlareNodeObservationFrps(rows)
|
||||
return memoryLimitObservabilityRows(rows, limit), nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) DeleteAllNodeObservationFrps(_ context.Context) (int64, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
count := int64(len(s.frpsObs))
|
||||
s.frpsObs = nil
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) DeleteNodeObservationFrpsBefore(_ context.Context, cutoff time.Time) (int64, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
cutoff = cutoff.UTC()
|
||||
remaining := make([]*OpenFlareNodeObservationFrps, 0, len(s.frpsObs))
|
||||
var deleted int64
|
||||
for _, row := range s.frpsObs {
|
||||
if row.CapturedAt.Before(cutoff) {
|
||||
deleted++
|
||||
continue
|
||||
}
|
||||
remaining = append(remaining, row)
|
||||
}
|
||||
s.frpsObs = remaining
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) InsertNodeObservationFrpc(_ context.Context, record *OpenFlareNodeObservationFrpc) error {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.frpcObs = append(s.frpcObs, cloneOpenFlareNodeObservationFrpc(record))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) ListNodeObservationFrpc(_ context.Context, nodeID string, since time.Time, limit int) ([]*OpenFlareNodeObservationFrpc, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
rows := memoryFilterFrpcObservations(s.frpcObs, nodeID, since)
|
||||
sortOpenFlareNodeObservationFrpc(rows)
|
||||
return memoryLimitObservabilityRows(rows, limit), nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) DeleteAllNodeObservationFrpc(_ context.Context) (int64, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
count := int64(len(s.frpcObs))
|
||||
s.frpcObs = nil
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *memoryObservabilityStore) DeleteNodeObservationFrpcBefore(_ context.Context, cutoff time.Time) (int64, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
cutoff = cutoff.UTC()
|
||||
remaining := make([]*OpenFlareNodeObservationFrpc, 0, len(s.frpcObs))
|
||||
var deleted int64
|
||||
for _, row := range s.frpcObs {
|
||||
if row.CapturedAt.Before(cutoff) {
|
||||
deleted++
|
||||
continue
|
||||
}
|
||||
remaining = append(remaining, row)
|
||||
}
|
||||
s.frpcObs = remaining
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
func memoryFilterMetricSnapshots(rows []*OpenFlareMetricSnapshot, nodeID string, since time.Time) []*OpenFlareMetricSnapshot {
|
||||
result := make([]*OpenFlareMetricSnapshot, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
if !memoryObservabilityMatchesNodeID(row.NodeID, nodeID) {
|
||||
continue
|
||||
}
|
||||
if !since.IsZero() && row.CapturedAt.Before(since) {
|
||||
continue
|
||||
}
|
||||
result = append(result, row)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func memoryFilterRequestReports(rows []*OpenFlareRequestReport, nodeID string, since time.Time) []*OpenFlareRequestReport {
|
||||
result := make([]*OpenFlareRequestReport, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
if !memoryObservabilityMatchesNodeID(row.NodeID, nodeID) {
|
||||
continue
|
||||
}
|
||||
if !since.IsZero() && row.WindowEndedAt.Before(since) {
|
||||
continue
|
||||
}
|
||||
result = append(result, row)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func memoryFilterOpenrestyObservations(rows []*OpenFlareNodeObservationOpenresty, nodeID string, since time.Time) []*OpenFlareNodeObservationOpenresty {
|
||||
result := make([]*OpenFlareNodeObservationOpenresty, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
if !memoryObservabilityMatchesNodeID(row.NodeID, nodeID) {
|
||||
continue
|
||||
}
|
||||
if !since.IsZero() && row.CapturedAt.Before(since) {
|
||||
continue
|
||||
}
|
||||
result = append(result, row)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func memoryFilterFrpsObservations(rows []*OpenFlareNodeObservationFrps, nodeID string, since time.Time) []*OpenFlareNodeObservationFrps {
|
||||
result := make([]*OpenFlareNodeObservationFrps, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
if !memoryObservabilityMatchesNodeID(row.NodeID, nodeID) {
|
||||
continue
|
||||
}
|
||||
if !since.IsZero() && row.CapturedAt.Before(since) {
|
||||
continue
|
||||
}
|
||||
result = append(result, row)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func memoryFilterFrpcObservations(rows []*OpenFlareNodeObservationFrpc, nodeID string, since time.Time) []*OpenFlareNodeObservationFrpc {
|
||||
result := make([]*OpenFlareNodeObservationFrpc, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
if !memoryObservabilityMatchesNodeID(row.NodeID, nodeID) {
|
||||
continue
|
||||
}
|
||||
if !since.IsZero() && row.CapturedAt.Before(since) {
|
||||
continue
|
||||
}
|
||||
result = append(result, row)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func memoryObservabilityMatchesNodeID(rowNodeID string, nodeID string) bool {
|
||||
trimmed := strings.TrimSpace(nodeID)
|
||||
if trimmed == "" {
|
||||
return true
|
||||
}
|
||||
return rowNodeID == trimmed
|
||||
}
|
||||
|
||||
func memoryMetricSnapshotExists(rows []*OpenFlareMetricSnapshot, nodeID string, capturedAt time.Time) bool {
|
||||
capturedAt = capturedAt.UTC()
|
||||
for _, row := range rows {
|
||||
if row.NodeID == nodeID && row.CapturedAt.UTC().Equal(capturedAt) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func memoryRequestReportExists(rows []*OpenFlareRequestReport, nodeID string, windowStartedAt, windowEndedAt time.Time) bool {
|
||||
windowStartedAt = windowStartedAt.UTC()
|
||||
windowEndedAt = windowEndedAt.UTC()
|
||||
for _, row := range rows {
|
||||
if row.NodeID == nodeID &&
|
||||
row.WindowStartedAt.UTC().Equal(windowStartedAt) &&
|
||||
row.WindowEndedAt.UTC().Equal(windowEndedAt) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func sortOpenFlareMetricSnapshots(items []*OpenFlareMetricSnapshot) {
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
left := items[i]
|
||||
right := items[j]
|
||||
if left == nil || right == nil {
|
||||
return left != nil
|
||||
}
|
||||
if compare := openFlareAccessLogCompareInt64(left.CapturedAt.Unix(), right.CapturedAt.Unix()); compare != 0 {
|
||||
return compare > 0
|
||||
}
|
||||
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(left.ID), openFlareAccessLogUintToInt64(right.ID)) > 0
|
||||
})
|
||||
}
|
||||
|
||||
func sortOpenFlareRequestReports(items []*OpenFlareRequestReport) {
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
left := items[i]
|
||||
right := items[j]
|
||||
if left == nil || right == nil {
|
||||
return left != nil
|
||||
}
|
||||
if compare := openFlareAccessLogCompareInt64(left.WindowEndedAt.Unix(), right.WindowEndedAt.Unix()); compare != 0 {
|
||||
return compare > 0
|
||||
}
|
||||
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(left.ID), openFlareAccessLogUintToInt64(right.ID)) > 0
|
||||
})
|
||||
}
|
||||
|
||||
func sortOpenFlareNodeObservationOpenresty(items []*OpenFlareNodeObservationOpenresty) {
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
left := items[i]
|
||||
right := items[j]
|
||||
if left == nil || right == nil {
|
||||
return left != nil
|
||||
}
|
||||
if compare := openFlareAccessLogCompareInt64(left.CapturedAt.Unix(), right.CapturedAt.Unix()); compare != 0 {
|
||||
return compare > 0
|
||||
}
|
||||
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(left.ID), openFlareAccessLogUintToInt64(right.ID)) > 0
|
||||
})
|
||||
}
|
||||
|
||||
func sortOpenFlareNodeObservationFrps(items []*OpenFlareNodeObservationFrps) {
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
left := items[i]
|
||||
right := items[j]
|
||||
if left == nil || right == nil {
|
||||
return left != nil
|
||||
}
|
||||
if compare := openFlareAccessLogCompareInt64(left.CapturedAt.Unix(), right.CapturedAt.Unix()); compare != 0 {
|
||||
return compare > 0
|
||||
}
|
||||
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(left.ID), openFlareAccessLogUintToInt64(right.ID)) > 0
|
||||
})
|
||||
}
|
||||
|
||||
func sortOpenFlareNodeObservationFrpc(items []*OpenFlareNodeObservationFrpc) {
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
left := items[i]
|
||||
right := items[j]
|
||||
if left == nil || right == nil {
|
||||
return left != nil
|
||||
}
|
||||
if compare := openFlareAccessLogCompareInt64(left.CapturedAt.Unix(), right.CapturedAt.Unix()); compare != 0 {
|
||||
return compare > 0
|
||||
}
|
||||
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(left.ID), openFlareAccessLogUintToInt64(right.ID)) > 0
|
||||
})
|
||||
}
|
||||
|
||||
func memoryLimitObservabilityRows[T any](rows []T, limit int) []T {
|
||||
if limit <= 0 || len(rows) <= limit {
|
||||
result := make([]T, len(rows))
|
||||
copy(result, rows)
|
||||
return result
|
||||
}
|
||||
result := make([]T, limit)
|
||||
copy(result, rows[:limit])
|
||||
return result
|
||||
}
|
||||
|
||||
func cloneOpenFlareMetricSnapshot(record *OpenFlareMetricSnapshot) *OpenFlareMetricSnapshot {
|
||||
copyRecord := *record
|
||||
if copyRecord.ID == 0 {
|
||||
copyRecord.ID = uint(idgen.NextUint64ID())
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
if copyRecord.CreatedAt.IsZero() {
|
||||
copyRecord.CreatedAt = now
|
||||
}
|
||||
copyRecord.CapturedAt = copyRecord.CapturedAt.UTC()
|
||||
copyRecord.CreatedAt = copyRecord.CreatedAt.UTC()
|
||||
return ©Record
|
||||
}
|
||||
|
||||
func cloneOpenFlareRequestReport(record *OpenFlareRequestReport) *OpenFlareRequestReport {
|
||||
copyRecord := *record
|
||||
if copyRecord.ID == 0 {
|
||||
copyRecord.ID = uint(idgen.NextUint64ID())
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
if copyRecord.CreatedAt.IsZero() {
|
||||
copyRecord.CreatedAt = now
|
||||
}
|
||||
copyRecord.WindowStartedAt = copyRecord.WindowStartedAt.UTC()
|
||||
copyRecord.WindowEndedAt = copyRecord.WindowEndedAt.UTC()
|
||||
copyRecord.CreatedAt = copyRecord.CreatedAt.UTC()
|
||||
return ©Record
|
||||
}
|
||||
|
||||
func cloneOpenFlareNodeObservationOpenresty(record *OpenFlareNodeObservationOpenresty) *OpenFlareNodeObservationOpenresty {
|
||||
copyRecord := *record
|
||||
if copyRecord.ID == 0 {
|
||||
copyRecord.ID = uint(idgen.NextUint64ID())
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
if copyRecord.CreatedAt.IsZero() {
|
||||
copyRecord.CreatedAt = now
|
||||
}
|
||||
if copyRecord.CapturedAt.IsZero() {
|
||||
copyRecord.CapturedAt = now
|
||||
}
|
||||
copyRecord.CapturedAt = copyRecord.CapturedAt.UTC()
|
||||
copyRecord.CreatedAt = copyRecord.CreatedAt.UTC()
|
||||
return ©Record
|
||||
}
|
||||
|
||||
func cloneOpenFlareNodeObservationFrps(record *OpenFlareNodeObservationFrps) *OpenFlareNodeObservationFrps {
|
||||
copyRecord := *record
|
||||
if copyRecord.ID == 0 {
|
||||
copyRecord.ID = uint(idgen.NextUint64ID())
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
if copyRecord.CreatedAt.IsZero() {
|
||||
copyRecord.CreatedAt = now
|
||||
}
|
||||
if copyRecord.CapturedAt.IsZero() {
|
||||
copyRecord.CapturedAt = now
|
||||
}
|
||||
copyRecord.CapturedAt = copyRecord.CapturedAt.UTC()
|
||||
copyRecord.CreatedAt = copyRecord.CreatedAt.UTC()
|
||||
return ©Record
|
||||
}
|
||||
|
||||
func cloneOpenFlareNodeObservationFrpc(record *OpenFlareNodeObservationFrpc) *OpenFlareNodeObservationFrpc {
|
||||
copyRecord := *record
|
||||
if copyRecord.ID == 0 {
|
||||
copyRecord.ID = uint(idgen.NextUint64ID())
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
if copyRecord.CreatedAt.IsZero() {
|
||||
copyRecord.CreatedAt = now
|
||||
}
|
||||
if copyRecord.CapturedAt.IsZero() {
|
||||
copyRecord.CapturedAt = now
|
||||
}
|
||||
copyRecord.CapturedAt = copyRecord.CapturedAt.UTC()
|
||||
copyRecord.CreatedAt = copyRecord.CreatedAt.UTC()
|
||||
return ©Record
|
||||
}
|
||||
Reference in New Issue
Block a user