chore: update gateway pressure observability

This commit is contained in:
elky
2026-06-30 17:01:39 +08:00
parent 974def5fef
commit f179ee72f9
69 changed files with 14843 additions and 1609 deletions
@@ -0,0 +1,348 @@
import { describe, expect, it } from 'vitest'
import { buildGatewayMetricsSummary } from '../monitoring'
describe('buildGatewayMetricsSummary', () => {
it('parses gateway process resource metrics with namespace prefixes', () => {
const summary = buildGatewayMetricsSummary(`
aether_gateway_service_up{service="aether-gateway"} 1
aether_gateway_gateway_process_cpu_usage_basis_points 1234
aether_gateway_gateway_process_memory_bytes 268435456
aether_gateway_gateway_process_memory_basis_points 250
aether_gateway_gateway_process_threads 64
aether_gateway_gateway_process_open_fds 2048
aether_gateway_gateway_process_fd_limit 500000
aether_gateway_gateway_process_fd_usage_basis_points 40
aether_gateway_gateway_process_socket_fds 1800
aether_gateway_gateway_network_observability_available 1
aether_gateway_gateway_network_interfaces 4
aether_gateway_gateway_network_received_bytes_total 123456789
aether_gateway_gateway_network_transmitted_bytes_total 987654321
aether_gateway_gateway_network_received_packets_total 12345
aether_gateway_gateway_network_transmitted_packets_total 23456
aether_gateway_gateway_network_receive_errors_total 1
aether_gateway_gateway_network_transmit_errors_total 2
aether_gateway_gateway_network_receive_dropped_total 3
aether_gateway_gateway_network_transmit_dropped_total 4
aether_gateway_gateway_tcp_state_observability_available 1
aether_gateway_gateway_host_tcp_connections 2200
aether_gateway_gateway_host_tcp_established_connections 1801
aether_gateway_gateway_host_tcp_listen_connections 12
aether_gateway_gateway_host_tcp_time_wait_connections 210
aether_gateway_gateway_host_tcp_syn_sent_connections 5
aether_gateway_gateway_host_tcp_syn_recv_connections 6
aether_gateway_gateway_host_tcp_close_wait_connections 7
aether_gateway_gateway_process_tcp_connections 1812
aether_gateway_gateway_process_tcp_established_connections 1800
aether_gateway_gateway_process_tcp_listen_connections 2
aether_gateway_gateway_process_tcp_time_wait_connections 3
aether_gateway_gateway_process_tcp_syn_sent_connections 4
aether_gateway_gateway_process_tcp_syn_recv_connections 5
aether_gateway_gateway_process_tcp_close_wait_connections 0
aether_gateway_gateway_allocator_observability_available 1
aether_gateway_gateway_allocator_allocated_bytes 67108864
aether_gateway_gateway_allocator_active_bytes 83886080
aether_gateway_gateway_allocator_resident_bytes 100663296
aether_gateway_gateway_allocator_mapped_bytes 134217728
aether_gateway_gateway_allocator_retained_bytes 33554432
aether_gateway_gateway_allocator_metadata_bytes 4194304
aether_gateway_gateway_allocator_active_to_allocated_basis_points 12500
aether_gateway_gateway_allocator_resident_to_allocated_basis_points 15000
aether_gateway_gateway_background_tasks_active 18
aether_gateway_gateway_background_tasks_supervised_total 18
aether_gateway_gateway_background_tasks_unexpected_exits_total 1
aether_gateway_gateway_background_tasks_completed_total 1
aether_gateway_gateway_background_tasks_panicked_total 0
aether_gateway_gateway_background_tasks_aborted_total 0
aether_gateway_gateway_background_tasks_cancelled_total 2
aether_gateway_gateway_tokio_runtime_observability_available 1
aether_gateway_gateway_tokio_runtime_workers 16
aether_gateway_gateway_tokio_runtime_alive_tasks 123
aether_gateway_gateway_tokio_runtime_global_queue_depth 4
aether_gateway_postgres_observability_available{driver="postgres"} 1
aether_gateway_postgres_observability_unavailable{driver="postgres"} 0
aether_gateway_postgres_active_connections{driver="postgres"} 8
aether_gateway_postgres_idle_connections{driver="postgres"} 12
aether_gateway_postgres_idle_in_transaction_connections{driver="postgres"} 0
aether_gateway_postgres_waiting_connections{driver="postgres"} 1
aether_gateway_postgres_lock_waiting_connections{driver="postgres"} 0
aether_gateway_postgres_oldest_active_query_age_ms{driver="postgres"} 123
aether_gateway_postgres_oldest_transaction_age_ms{driver="postgres"} 456
aether_gateway_postgres_deadlocks_total{driver="postgres"} 2
aether_gateway_postgres_block_read_total{driver="postgres"} 100
aether_gateway_postgres_block_hit_total{driver="postgres"} 9900
aether_gateway_postgres_block_cache_hit_rate_basis_points{driver="postgres"} 9900
aether_gateway_postgres_temp_files_total{driver="postgres"} 3
aether_gateway_postgres_temp_bytes_total{driver="postgres"} 4096
aether_gateway_postgres_xact_commit_total{driver="postgres"} 1000
aether_gateway_postgres_xact_rollback_total{driver="postgres"} 4
aether_gateway_postgres_wal_observability_available{driver="postgres"} 1
aether_gateway_postgres_wal_observability_unavailable{driver="postgres"} 0
aether_gateway_postgres_wal_records_total{driver="postgres"} 5000
aether_gateway_postgres_wal_fpi_total{driver="postgres"} 6
aether_gateway_postgres_wal_bytes_total{driver="postgres"} 1048576
aether_gateway_postgres_wal_buffers_full_total{driver="postgres"} 0
aether_gateway_postgres_wal_write_total{driver="postgres"} 50
aether_gateway_postgres_wal_sync_total{driver="postgres"} 40
aether_gateway_postgres_wal_write_time_ms_total{driver="postgres"} 700
aether_gateway_postgres_wal_sync_time_ms_total{driver="postgres"} 80
aether_gateway_postgres_checkpoint_observability_available{driver="postgres"} 1
aether_gateway_postgres_checkpoint_observability_unavailable{driver="postgres"} 0
aether_gateway_postgres_checkpoints_timed_total{driver="postgres"} 2
aether_gateway_postgres_checkpoints_requested_total{driver="postgres"} 1
aether_gateway_postgres_checkpoint_write_time_ms_total{driver="postgres"} 900
aether_gateway_postgres_checkpoint_sync_time_ms_total{driver="postgres"} 120
aether_gateway_postgres_buffers_checkpoint_total{driver="postgres"} 123
aether_gateway_postgres_buffers_backend_total{driver="postgres"} 45
aether_gateway_postgres_statement_observability_available{driver="postgres"} 1
aether_gateway_postgres_statement_observability_unavailable{driver="postgres"} 0
aether_gateway_postgres_statement_top_calls_total{driver="postgres"} 42
aether_gateway_postgres_statement_top_exec_time_ms_total{driver="postgres"} 2345
aether_gateway_postgres_statement_top_max_mean_exec_time_ms{driver="postgres"} 12
aether_gateway_postgres_statement_top_max_exec_time_ms{driver="postgres"} 345
aether_gateway_postgres_statement_top_shared_blks_read_total{driver="postgres"} 22
aether_gateway_postgres_statement_top_shared_blks_hit_total{driver="postgres"} 900
aether_gateway_postgres_statement_top_temp_blks_total{driver="postgres"} 7
aether_gateway_redis_runtime_enabled{backend="redis"} 1
aether_gateway_redis_runtime_health_unavailable{backend="redis"} 0
aether_gateway_redis_runtime_connected_clients{backend="redis"} 9
aether_gateway_redis_runtime_blocked_clients{backend="redis"} 2
aether_gateway_redis_runtime_total_connections_received{backend="redis"} 40
aether_gateway_redis_runtime_rejected_connections_total{backend="redis"} 0
aether_gateway_redis_runtime_total_commands_processed{backend="redis"} 100
aether_gateway_redis_runtime_instantaneous_ops_per_sec{backend="redis"} 17
aether_gateway_redis_runtime_total_error_replies{backend="redis"} 1
aether_gateway_redis_runtime_expired_keys_total{backend="redis"} 3
aether_gateway_redis_runtime_evicted_keys_total{backend="redis"} 0
aether_gateway_redis_runtime_keyspace_hits_total{backend="redis"} 20
aether_gateway_redis_runtime_keyspace_misses_total{backend="redis"} 5
aether_gateway_redis_runtime_keyspace_hit_rate_basis_points{backend="redis"} 8000
aether_gateway_redis_runtime_used_memory_bytes{backend="redis"} 1048576
aether_gateway_redis_runtime_maxmemory_bytes{backend="redis"} 8388608
aether_gateway_redis_runtime_memory_usage_basis_points{backend="redis"} 1250
aether_gateway_redis_runtime_memory_fragmentation_ratio_basis_points{backend="redis"} 12500
aether_gateway_redis_runtime_lane_command_errors_total{backend="redis",lane="fast"} 1
aether_gateway_redis_runtime_lane_command_errors_total{backend="redis",lane="stream"} 2
aether_gateway_redis_runtime_lane_command_timeouts_total{backend="redis",lane="fast"} 3
aether_gateway_redis_runtime_lane_command_timeouts_total{backend="redis",lane="stream"} 4
aether_gateway_redis_runtime_lane_command_count_total{backend="redis",lane="fast"} 10
aether_gateway_redis_runtime_lane_command_count_total{backend="redis",lane="stream"} 5
aether_gateway_redis_runtime_lane_command_latency_ms_sum{backend="redis",lane="fast"} 70
aether_gateway_redis_runtime_lane_command_latency_ms_sum{backend="redis",lane="stream"} 55
aether_gateway_redis_runtime_lane_command_latency_ms_count{backend="redis",lane="fast"} 10
aether_gateway_redis_runtime_lane_command_latency_ms_count{backend="redis",lane="stream"} 5
aether_gateway_redis_runtime_lane_command_latency_ms_max{backend="redis",lane="fast"} 12
aether_gateway_redis_runtime_lane_command_latency_ms_max{backend="redis",lane="stream"} 23
aether_gateway_redis_runtime_lane_command_latency_ms_max{backend="redis",lane="blocking_stream"} 1001
aether_gateway_redis_runtime_lane_command_latency_ms_bucket{backend="redis",lane="fast",le="1"} 2
aether_gateway_redis_runtime_lane_command_latency_ms_bucket{backend="redis",lane="fast",le="+Inf"} 10
aether_gateway_usage_runtime_enabled 1
aether_gateway_usage_runtime_queue_terminal_events_enabled 1
aether_gateway_usage_runtime_queue_lifecycle_events_enabled 1
aether_gateway_usage_runtime_queue_worker_count 2
aether_gateway_usage_runtime_queue_worker_autoscale_enabled 1
aether_gateway_usage_runtime_queue_worker_active_count 3
aether_gateway_usage_runtime_queue_worker_desired_count 4
aether_gateway_usage_runtime_queue_worker_max_count 8
aether_gateway_usage_runtime_queue_worker_read_batches_total 10
aether_gateway_usage_runtime_queue_worker_read_entries_total 20
aether_gateway_usage_runtime_queue_worker_reclaimed_entries_total 2
aether_gateway_usage_runtime_queue_worker_acked_entries_total 18
aether_gateway_usage_runtime_queue_worker_dead_lettered_entries_total 1
aether_gateway_usage_runtime_queue_worker_process_failures_total 2
aether_gateway_usage_runtime_queue_worker_read_failures_total 3
aether_gateway_usage_runtime_queue_worker_reclaim_failures_total 4
aether_gateway_usage_runtime_terminal_enqueue_failed_total 5
aether_gateway_usage_runtime_lifecycle_enqueue_failed_total 6
aether_gateway_request_candidate_queue_depth 5
aether_gateway_request_candidate_queue_pending_depth 4
aether_gateway_request_candidate_queue_capacity 1024
aether_gateway_request_candidate_queue_enqueued_total 30
aether_gateway_request_candidate_queue_dropped_total 1
aether_gateway_request_candidate_queue_flushed_total 28
aether_gateway_request_candidate_queue_flush_failed_total 2
aether_gateway_request_candidate_queue_flush_batches_total 7
aether_gateway_request_candidate_queue_flush_sql_ops_total 8
aether_gateway_request_candidate_queue_flush_sql_records_total 24
aether_gateway_request_candidate_queue_compacted_total 4
aether_gateway_request_candidate_queue_sync_fallback_total 3
aether_gateway_usage_queue_health_unavailable 0
aether_gateway_usage_queue_enabled{stream="usage:events",group="usage_consumers"} 1
aether_gateway_usage_queue_configured{stream="usage:events",group="usage_consumers"} 1
aether_gateway_usage_queue_stream_length{stream="usage:events",group="usage_consumers"} 12
aether_gateway_usage_queue_group_pending{stream="usage:events",group="usage_consumers"} 2
aether_gateway_usage_queue_group_lag{stream="usage:events",group="usage_consumers"} 3
aether_gateway_usage_queue_oldest_pending_idle_ms{stream="usage:events",group="usage_consumers"} 4500
aether_gateway_usage_queue_dlq_length{stream="usage:events:dlq"} 1
aether_gateway_usage_counter_health_unavailable 0
aether_gateway_usage_counter_outbox_pending_rows 3
aether_gateway_usage_counter_outbox_processed_rows 42
aether_gateway_usage_counter_outbox_oldest_pending_age_seconds 12
aether_gateway_usage_counter_outbox_oldest_pending_created_at_unix_secs 1800000000
aether_gateway_usage_counter_outbox_latest_processed_at_unix_secs 1800000012
aether_gateway_usage_counter_outbox_pending_rows_by_kind{kind="api_key"} 2
aether_gateway_usage_counter_outbox_pending_rows_by_kind{kind="model"} 1
aether_gateway_usage_counter_outbox_flush_batches_total 7
aether_gateway_usage_counter_outbox_flush_rows_claimed_total 11
aether_gateway_usage_counter_outbox_flush_targets_total{kind="api_key"} 3
aether_gateway_usage_counter_outbox_flush_targets_total{kind="model"} 4
aether_gateway_usage_counter_outbox_flush_failed_batches_total 1
aether_gateway_usage_counter_outbox_cleanup_rows_total 5
aether_gateway_usage_counter_outbox_cleanup_failed_batches_total 2
`)
expect(summary.process.processCpuUsageBasisPoints).toBe(1234)
expect(summary.process.processMemoryBytes).toBe(268435456)
expect(summary.process.processMemoryBasisPoints).toBe(250)
expect(summary.process.processThreads).toBe(64)
expect(summary.process.openFds).toBe(2048)
expect(summary.process.fdLimit).toBe(500000)
expect(summary.process.fdUsageBasisPoints).toBe(40)
expect(summary.process.socketFds).toBe(1800)
expect(summary.process.networkAvailable).toBe(true)
expect(summary.process.networkInterfaces).toBe(4)
expect(summary.process.networkReceivedBytesTotal).toBe(123456789)
expect(summary.process.networkTransmittedBytesTotal).toBe(987654321)
expect(summary.process.networkReceiveErrorsTotal).toBe(1)
expect(summary.process.networkTransmitErrorsTotal).toBe(2)
expect(summary.process.networkReceiveDroppedTotal).toBe(3)
expect(summary.process.networkTransmitDroppedTotal).toBe(4)
expect(summary.process.tcpStateAvailable).toBe(true)
expect(summary.process.hostTcpEstablishedConnections).toBe(1801)
expect(summary.process.hostTcpTimeWaitConnections).toBe(210)
expect(summary.process.hostTcpCloseWaitConnections).toBe(7)
expect(summary.process.processTcpConnections).toBe(1812)
expect(summary.process.processTcpEstablishedConnections).toBe(1800)
expect(summary.process.processTcpCloseWaitConnections).toBe(0)
expect(summary.allocator.available).toBe(true)
expect(summary.allocator.allocatedBytes).toBe(67108864)
expect(summary.allocator.activeBytes).toBe(83886080)
expect(summary.allocator.residentBytes).toBe(100663296)
expect(summary.allocator.retainedBytes).toBe(33554432)
expect(summary.allocator.activeToAllocatedBasisPoints).toBe(12500)
expect(summary.allocator.residentToAllocatedBasisPoints).toBe(15000)
expect(summary.backgroundTasks.active).toBe(18)
expect(summary.backgroundTasks.supervisedTotal).toBe(18)
expect(summary.backgroundTasks.unexpectedExitsTotal).toBe(1)
expect(summary.backgroundTasks.completedTotal).toBe(1)
expect(summary.backgroundTasks.cancelledTotal).toBe(2)
expect(summary.tokioRuntime.available).toBe(true)
expect(summary.tokioRuntime.workers).toBe(16)
expect(summary.tokioRuntime.aliveTasks).toBe(123)
expect(summary.tokioRuntime.globalQueueDepth).toBe(4)
expect(summary.postgres.driver).toBe('postgres')
expect(summary.postgres.available).toBe(true)
expect(summary.postgres.unavailable).toBe(false)
expect(summary.postgres.activeConnections).toBe(8)
expect(summary.postgres.idleConnections).toBe(12)
expect(summary.postgres.idleInTransactionConnections).toBe(0)
expect(summary.postgres.waitingConnections).toBe(1)
expect(summary.postgres.lockWaitingConnections).toBe(0)
expect(summary.postgres.oldestActiveQueryAgeMs).toBe(123)
expect(summary.postgres.oldestTransactionAgeMs).toBe(456)
expect(summary.postgres.deadlocksTotal).toBe(2)
expect(summary.postgres.blockReadTotal).toBe(100)
expect(summary.postgres.blockHitTotal).toBe(9900)
expect(summary.postgres.blockCacheHitRateBasisPoints).toBe(9900)
expect(summary.postgres.tempBytesTotal).toBe(4096)
expect(summary.postgres.xactRollbackTotal).toBe(4)
expect(summary.postgres.walAvailable).toBe(true)
expect(summary.postgres.walUnavailable).toBe(false)
expect(summary.postgres.walBytesTotal).toBe(1048576)
expect(summary.postgres.walWriteTimeMsTotal).toBe(700)
expect(summary.postgres.walSyncTimeMsTotal).toBe(80)
expect(summary.postgres.checkpointAvailable).toBe(true)
expect(summary.postgres.checkpointUnavailable).toBe(false)
expect(summary.postgres.checkpointWriteTimeMsTotal).toBe(900)
expect(summary.postgres.checkpointSyncTimeMsTotal).toBe(120)
expect(summary.postgres.buffersCheckpointTotal).toBe(123)
expect(summary.postgres.buffersBackendTotal).toBe(45)
expect(summary.postgres.statementAvailable).toBe(true)
expect(summary.postgres.statementUnavailable).toBe(false)
expect(summary.postgres.statementTopExecTimeMsTotal).toBe(2345)
expect(summary.postgres.statementTopMaxMeanExecTimeMs).toBe(12)
expect(summary.postgres.statementTopMaxExecTimeMs).toBe(345)
expect(summary.postgres.statementTopTempBlksTotal).toBe(7)
expect(summary.redisRuntime.enabled).toBe(true)
expect(summary.redisRuntime.unavailable).toBe(false)
expect(summary.redisRuntime.connectedClients).toBe(9)
expect(summary.redisRuntime.blockedClients).toBe(2)
expect(summary.redisRuntime.totalConnectionsReceived).toBe(40)
expect(summary.redisRuntime.rejectedConnectionsTotal).toBe(0)
expect(summary.redisRuntime.totalCommandsProcessed).toBe(100)
expect(summary.redisRuntime.instantaneousOpsPerSec).toBe(17)
expect(summary.redisRuntime.totalErrorReplies).toBe(1)
expect(summary.redisRuntime.expiredKeysTotal).toBe(3)
expect(summary.redisRuntime.evictedKeysTotal).toBe(0)
expect(summary.redisRuntime.keyspaceHitsTotal).toBe(20)
expect(summary.redisRuntime.keyspaceMissesTotal).toBe(5)
expect(summary.redisRuntime.keyspaceHitRateBasisPoints).toBe(8000)
expect(summary.redisRuntime.usedMemoryBytes).toBe(1048576)
expect(summary.redisRuntime.maxmemoryBytes).toBe(8388608)
expect(summary.redisRuntime.memoryUsageBasisPoints).toBe(1250)
expect(summary.redisRuntime.memoryFragmentationRatioBasisPoints).toBe(12500)
expect(summary.redisRuntime.laneCommandErrorsTotal).toBe(3)
expect(summary.redisRuntime.laneCommandTimeoutsTotal).toBe(7)
expect(summary.redisRuntime.laneCommandCountTotal).toBe(15)
expect(summary.redisRuntime.commandLatencyTotalMs).toBe(125)
expect(summary.redisRuntime.commandLatencyObservationCount).toBe(15)
expect(summary.redisRuntime.commandLatencyMaxMs).toBe(1001)
expect(summary.redisRuntime.nonblockingCommandLatencyMaxMs).toBe(23)
expect(summary.usageRuntime.enabled).toBe(true)
expect(summary.usageRuntime.terminalQueueEnabled).toBe(true)
expect(summary.usageRuntime.lifecycleQueueEnabled).toBe(true)
expect(summary.usageRuntime.workerCount).toBe(2)
expect(summary.usageRuntime.workerAutoscaleEnabled).toBe(true)
expect(summary.usageRuntime.workerActiveCount).toBe(3)
expect(summary.usageRuntime.workerDesiredCount).toBe(4)
expect(summary.usageRuntime.workerMaxCount).toBe(8)
expect(summary.usageRuntime.workerReadBatchesTotal).toBe(10)
expect(summary.usageRuntime.workerReadEntriesTotal).toBe(20)
expect(summary.usageRuntime.workerReclaimedEntriesTotal).toBe(2)
expect(summary.usageRuntime.workerAckedEntriesTotal).toBe(18)
expect(summary.usageRuntime.workerDeadLetteredEntriesTotal).toBe(1)
expect(summary.usageRuntime.workerProcessFailuresTotal).toBe(2)
expect(summary.usageRuntime.workerReadFailuresTotal).toBe(3)
expect(summary.usageRuntime.workerReclaimFailuresTotal).toBe(4)
expect(summary.usageRuntime.terminalEnqueueFailedTotal).toBe(5)
expect(summary.usageRuntime.lifecycleEnqueueFailedTotal).toBe(6)
expect(summary.requestCandidateQueue.depth).toBe(5)
expect(summary.requestCandidateQueue.pendingDepth).toBe(4)
expect(summary.requestCandidateQueue.capacity).toBe(1024)
expect(summary.requestCandidateQueue.enqueuedTotal).toBe(30)
expect(summary.requestCandidateQueue.droppedTotal).toBe(1)
expect(summary.requestCandidateQueue.flushedTotal).toBe(28)
expect(summary.requestCandidateQueue.flushFailedTotal).toBe(2)
expect(summary.requestCandidateQueue.flushBatchesTotal).toBe(7)
expect(summary.requestCandidateQueue.flushSqlOpsTotal).toBe(8)
expect(summary.requestCandidateQueue.flushSqlRecordsTotal).toBe(24)
expect(summary.requestCandidateQueue.compactedTotal).toBe(4)
expect(summary.requestCandidateQueue.syncFallbackTotal).toBe(3)
expect(summary.usageQueue.unavailable).toBe(false)
expect(summary.usageQueue.enabled).toBe(true)
expect(summary.usageQueue.configured).toBe(true)
expect(summary.usageQueue.stream).toBe('usage:events')
expect(summary.usageQueue.group).toBe('usage_consumers')
expect(summary.usageQueue.streamLength).toBe(12)
expect(summary.usageQueue.groupPending).toBe(2)
expect(summary.usageQueue.groupLag).toBe(3)
expect(summary.usageQueue.oldestPendingIdleMs).toBe(4500)
expect(summary.usageQueue.dlqStream).toBe('usage:events:dlq')
expect(summary.usageQueue.dlqLength).toBe(1)
expect(summary.usageCounter.unavailable).toBe(false)
expect(summary.usageCounter.pendingRows).toBe(3)
expect(summary.usageCounter.processedRows).toBe(42)
expect(summary.usageCounter.oldestPendingAgeSeconds).toBe(12)
expect(summary.usageCounter.oldestPendingCreatedAtUnixSecs).toBe(1800000000)
expect(summary.usageCounter.latestProcessedAtUnixSecs).toBe(1800000012)
expect(summary.usageCounter.flushBatchesTotal).toBe(7)
expect(summary.usageCounter.flushRowsClaimedTotal).toBe(11)
expect(summary.usageCounter.flushTargetsTotal).toBe(7)
expect(summary.usageCounter.flushFailedBatchesTotal).toBe(1)
expect(summary.usageCounter.cleanupRowsTotal).toBe(5)
expect(summary.usageCounter.cleanupFailedBatchesTotal).toBe(2)
expect(summary.usageCounter.pendingByKind).toEqual([
{ kind: 'api_key', pendingRows: 2 },
{ kind: 'model', pendingRows: 1 },
])
})
})
+667 -1
View File
@@ -1,8 +1,10 @@
import apiClient from './client'
import {
findMetricSamples,
findMetricValueNumber,
parsePrometheusSamples,
sumMetricValues,
type PrometheusSample,
} from '@/utils/prometheus'
export interface AdminMonitoringSystemStatus {
@@ -116,6 +118,21 @@ export interface GatewayMetricsSummary {
serviceUp: number | null
local: GatewayGateMetrics
distributed: GatewayGateMetrics
candidatePlanning: GatewayGateMetrics
upstreamExecution: GatewayGateMetrics
databasePool: GatewayDatabasePoolMetrics
postgres: GatewayPostgresObservabilityMetrics
redisRuntime: GatewayRedisRuntimeMetrics
process: GatewayProcessResourceMetrics
allocator: GatewayAllocatorMetrics
backgroundTasks: GatewayBackgroundTaskMetrics
tokioRuntime: GatewayTokioRuntimeMetrics
usageRuntime: GatewayUsageRuntimeMetrics
usageQueue: GatewayUsageQueueMetrics
usageCounter: GatewayUsageCounterMetrics
requestCandidateQueue: GatewayRequestCandidateQueueMetrics
upstreamTargets: GatewayUpstreamTargetMetrics
stageLatency: GatewayStageLatencyMetrics
tunnel: {
proxyConnections: number | null
availableProxyConnections: number | null
@@ -138,6 +155,276 @@ export interface GatewayMetricsSummary {
fallbacks: GatewayFallbackMetricSummary[]
}
export interface GatewayDatabasePoolMetrics {
driver: string | null
checkedOut: number | null
idle: number | null
size: number | null
max: number | null
usageBasisPoints: number | null
idleReserve: number | null
underMaintenancePressure: boolean | null
}
export interface GatewayPostgresObservabilityMetrics {
driver: string | null
available: boolean | null
unavailable: boolean | null
activeConnections: number | null
idleConnections: number | null
idleInTransactionConnections: number | null
waitingConnections: number | null
lockWaitingConnections: number | null
oldestActiveQueryAgeMs: number | null
oldestTransactionAgeMs: number | null
deadlocksTotal: number | null
blockReadTotal: number | null
blockHitTotal: number | null
blockCacheHitRateBasisPoints: number | null
tempFilesTotal: number | null
tempBytesTotal: number | null
xactCommitTotal: number | null
xactRollbackTotal: number | null
walAvailable: boolean | null
walUnavailable: boolean | null
walRecordsTotal: number | null
walFpiTotal: number | null
walBytesTotal: number | null
walBuffersFullTotal: number | null
walWriteTotal: number | null
walSyncTotal: number | null
walWriteTimeMsTotal: number | null
walSyncTimeMsTotal: number | null
checkpointAvailable: boolean | null
checkpointUnavailable: boolean | null
checkpointsTimedTotal: number | null
checkpointsRequestedTotal: number | null
checkpointWriteTimeMsTotal: number | null
checkpointSyncTimeMsTotal: number | null
buffersCheckpointTotal: number | null
buffersBackendTotal: number | null
statementAvailable: boolean | null
statementUnavailable: boolean | null
statementTopCallsTotal: number | null
statementTopExecTimeMsTotal: number | null
statementTopMaxMeanExecTimeMs: number | null
statementTopMaxExecTimeMs: number | null
statementTopSharedBlksReadTotal: number | null
statementTopSharedBlksHitTotal: number | null
statementTopTempBlksTotal: number | null
}
export interface GatewayRedisRuntimeMetrics {
enabled: boolean | null
unavailable: boolean | null
connectedClients: number | null
blockedClients: number | null
totalConnectionsReceived: number | null
rejectedConnectionsTotal: number | null
totalCommandsProcessed: number | null
instantaneousOpsPerSec: number | null
totalErrorReplies: number | null
expiredKeysTotal: number | null
evictedKeysTotal: number | null
keyspaceHitsTotal: number | null
keyspaceMissesTotal: number | null
keyspaceHitRateBasisPoints: number | null
usedMemoryBytes: number | null
maxmemoryBytes: number | null
memoryUsageBasisPoints: number | null
memoryFragmentationRatioBasisPoints: number | null
laneCommandErrorsTotal: number
laneCommandTimeoutsTotal: number
laneCommandCountTotal: number
commandLatencyTotalMs: number
commandLatencyObservationCount: number
commandLatencyMaxMs: number | null
nonblockingCommandLatencyMaxMs: number | null
}
export interface GatewayProcessResourceMetrics {
sampledAtUnixSecs: number | null
systemCpuUsageBasisPoints: number | null
processCpuUsageBasisPoints: number | null
systemMemoryTotalBytes: number | null
systemMemoryUsedBytes: number | null
systemMemoryAvailableBytes: number | null
systemMemoryUsageBasisPoints: number | null
processMemoryBytes: number | null
processVirtualMemoryBytes: number | null
processMemoryBasisPoints: number | null
processUptimeSeconds: number | null
processThreads: number | null
openFds: number | null
fdLimit: number | null
fdUsageBasisPoints: number | null
socketFds: number | null
networkAvailable: boolean | null
networkInterfaces: number | null
networkReceivedBytesTotal: number | null
networkTransmittedBytesTotal: number | null
networkReceivedPacketsTotal: number | null
networkTransmittedPacketsTotal: number | null
networkReceiveErrorsTotal: number | null
networkTransmitErrorsTotal: number | null
networkReceiveDroppedTotal: number | null
networkTransmitDroppedTotal: number | null
tcpStateAvailable: boolean | null
hostTcpConnections: number | null
hostTcpEstablishedConnections: number | null
hostTcpListenConnections: number | null
hostTcpTimeWaitConnections: number | null
hostTcpSynSentConnections: number | null
hostTcpSynRecvConnections: number | null
hostTcpCloseWaitConnections: number | null
processTcpConnections: number | null
processTcpEstablishedConnections: number | null
processTcpListenConnections: number | null
processTcpTimeWaitConnections: number | null
processTcpSynSentConnections: number | null
processTcpSynRecvConnections: number | null
processTcpCloseWaitConnections: number | null
}
export interface GatewayAllocatorMetrics {
available: boolean | null
allocatedBytes: number | null
activeBytes: number | null
residentBytes: number | null
mappedBytes: number | null
retainedBytes: number | null
metadataBytes: number | null
activeToAllocatedBasisPoints: number | null
residentToAllocatedBasisPoints: number | null
}
export interface GatewayBackgroundTaskMetrics {
active: number | null
supervisedTotal: number | null
unexpectedExitsTotal: number | null
completedTotal: number | null
panickedTotal: number | null
abortedTotal: number | null
cancelledTotal: number | null
}
export interface GatewayTokioRuntimeMetrics {
available: boolean | null
workers: number | null
aliveTasks: number | null
globalQueueDepth: number | null
}
export interface GatewayUsageRuntimeMetrics {
enabled: boolean | null
terminalQueueEnabled: boolean | null
lifecycleQueueEnabled: boolean | null
workerCount: number | null
workerAutoscaleEnabled: boolean | null
workerActiveCount: number | null
workerDesiredCount: number | null
workerMaxCount: number | null
workerReadBatchesTotal: number | null
workerReadEntriesTotal: number | null
workerReclaimedEntriesTotal: number | null
workerAckedEntriesTotal: number | null
workerDeadLetteredEntriesTotal: number | null
workerProcessFailuresTotal: number | null
workerReadFailuresTotal: number | null
workerReclaimFailuresTotal: number | null
terminalEnqueueInFlight: number | null
terminalEnqueueDeferredTotal: number | null
terminalEnqueueDeferredRetryTotal: number | null
terminalEnqueueFailedTotal: number | null
lifecycleEnqueueInFlight: number | null
lifecycleEnqueueDeferredTotal: number | null
lifecycleEnqueueDeferredDroppedTotal: number | null
lifecycleEnqueueDeferredRetryTotal: number | null
lifecycleEnqueueFailedTotal: number | null
enqueueRetryScheduledTotal: number | null
}
export interface GatewayUsageQueueMetrics {
unavailable: boolean | null
enabled: boolean | null
configured: boolean | null
stream: string | null
group: string | null
streamLength: number | null
groupPending: number | null
groupLag: number | null
oldestPendingIdleMs: number | null
dlqStream: string | null
dlqLength: number | null
}
export interface GatewayUsageCounterKindMetrics {
kind: string
pendingRows: number | null
}
export interface GatewayUsageCounterMetrics {
unavailable: boolean | null
pendingRows: number | null
processedRows: number | null
oldestPendingAgeSeconds: number | null
oldestPendingCreatedAtUnixSecs: number | null
latestProcessedAtUnixSecs: number | null
flushBatchesTotal: number | null
flushRowsClaimedTotal: number | null
flushTargetsTotal: number | null
flushFailedBatchesTotal: number | null
cleanupRowsTotal: number | null
cleanupFailedBatchesTotal: number | null
pendingByKind: GatewayUsageCounterKindMetrics[]
}
export interface GatewayRequestCandidateQueueMetrics {
depth: number | null
pendingDepth: number | null
capacity: number | null
enqueuedTotal: number | null
droppedTotal: number | null
flushedTotal: number | null
flushFailedTotal: number | null
flushBatchesTotal: number | null
flushSqlOpsTotal: number | null
flushSqlRecordsTotal: number | null
compactedTotal: number | null
syncFallbackTotal: number | null
}
export interface GatewayUpstreamTargetRow {
target: string
inFlight: number | null
availablePermits: number | null
highWatermark: number | null
rejectedTotal: number | null
selectedTotal: number | null
saturatedTotal: number | null
}
export interface GatewayUpstreamTargetMetrics {
activeTargets: number | null
limit: number | null
selectedTotal: number
saturatedTotal: number
rejectedTotal: number
rows: GatewayUpstreamTargetRow[]
}
export interface GatewayStageLatencyRow {
stage: string
label: string
count: number | null
avgMs: number | null
maxMs: number | null
}
export interface GatewayStageLatencyMetrics {
rows: GatewayStageLatencyRow[]
}
const FALLBACK_METRICS: Array<{ name: string; label: string }> = [
{ name: 'decision_remote_total', label: '远端决策回退' },
{ name: 'plan_fallback_total', label: 'Plan 回退' },
@@ -146,8 +433,24 @@ const FALLBACK_METRICS: Array<{ name: string; label: string }> = [
{ name: 'local_execution_runtime_miss_total', label: '本地运行时缺失' },
]
const CAPACITY_STAGES: Array<{ stage: string; label: string }> = [
{ stage: 'frontdoor_handler_queue', label: '入口排队' },
{ stage: 'frontdoor_admission', label: '入口准入' },
{ stage: 'candidate_planning_gate_wait', label: '候选规划 Gate' },
{ stage: 'candidate_page_load', label: '候选分页读取' },
{ stage: 'candidate_page_resolve', label: '候选分页解析' },
{ stage: 'upstream_execution_gate_wait', label: '上游执行 Gate' },
{ stage: 'stream_upstream_target_admission', label: 'Target 准入' },
{ stage: 'stream_total', label: '流式总耗时' },
]
function metricBoolean(value: number | null): boolean | null {
if (value == null) return null
return value === 1
}
function buildGateMetrics(
samples: ReturnType<typeof parsePrometheusSamples>,
samples: PrometheusSample[],
gate: string
): GatewayGateMetrics {
return {
@@ -159,6 +462,354 @@ function buildGateMetrics(
}
}
function buildDatabasePoolMetrics(samples: PrometheusSample[]): GatewayDatabasePoolMetrics {
const maxSample = findMetricSamples(samples, 'database_pool_max_connections')[0]
return {
driver: maxSample?.labels.driver ?? null,
checkedOut: findMetricValueNumber(samples, 'database_pool_checked_out_connections'),
idle: findMetricValueNumber(samples, 'database_pool_idle_connections'),
size: findMetricValueNumber(samples, 'database_pool_size_connections'),
max: findMetricValueNumber(samples, 'database_pool_max_connections'),
usageBasisPoints: findMetricValueNumber(samples, 'database_pool_usage_basis_points'),
idleReserve: findMetricValueNumber(samples, 'database_pool_idle_reserve_connections'),
underMaintenancePressure: metricBoolean(findMetricValueNumber(samples, 'database_pool_under_maintenance_pressure')),
}
}
function buildPostgresObservabilityMetrics(samples: PrometheusSample[]): GatewayPostgresObservabilityMetrics {
const availabilitySample = findMetricSamples(samples, 'postgres_observability_available')[0]
return {
driver: availabilitySample?.labels.driver ?? null,
available: metricBoolean(findMetricValueNumber(samples, 'postgres_observability_available')),
unavailable: metricBoolean(findMetricValueNumber(samples, 'postgres_observability_unavailable')),
activeConnections: findMetricValueNumber(samples, 'postgres_active_connections'),
idleConnections: findMetricValueNumber(samples, 'postgres_idle_connections'),
idleInTransactionConnections: findMetricValueNumber(samples, 'postgres_idle_in_transaction_connections'),
waitingConnections: findMetricValueNumber(samples, 'postgres_waiting_connections'),
lockWaitingConnections: findMetricValueNumber(samples, 'postgres_lock_waiting_connections'),
oldestActiveQueryAgeMs: findMetricValueNumber(samples, 'postgres_oldest_active_query_age_ms'),
oldestTransactionAgeMs: findMetricValueNumber(samples, 'postgres_oldest_transaction_age_ms'),
deadlocksTotal: findMetricValueNumber(samples, 'postgres_deadlocks_total'),
blockReadTotal: findMetricValueNumber(samples, 'postgres_block_read_total'),
blockHitTotal: findMetricValueNumber(samples, 'postgres_block_hit_total'),
blockCacheHitRateBasisPoints: findMetricValueNumber(samples, 'postgres_block_cache_hit_rate_basis_points'),
tempFilesTotal: findMetricValueNumber(samples, 'postgres_temp_files_total'),
tempBytesTotal: findMetricValueNumber(samples, 'postgres_temp_bytes_total'),
xactCommitTotal: findMetricValueNumber(samples, 'postgres_xact_commit_total'),
xactRollbackTotal: findMetricValueNumber(samples, 'postgres_xact_rollback_total'),
walAvailable: metricBoolean(findMetricValueNumber(samples, 'postgres_wal_observability_available')),
walUnavailable: metricBoolean(findMetricValueNumber(samples, 'postgres_wal_observability_unavailable')),
walRecordsTotal: findMetricValueNumber(samples, 'postgres_wal_records_total'),
walFpiTotal: findMetricValueNumber(samples, 'postgres_wal_fpi_total'),
walBytesTotal: findMetricValueNumber(samples, 'postgres_wal_bytes_total'),
walBuffersFullTotal: findMetricValueNumber(samples, 'postgres_wal_buffers_full_total'),
walWriteTotal: findMetricValueNumber(samples, 'postgres_wal_write_total'),
walSyncTotal: findMetricValueNumber(samples, 'postgres_wal_sync_total'),
walWriteTimeMsTotal: findMetricValueNumber(samples, 'postgres_wal_write_time_ms_total'),
walSyncTimeMsTotal: findMetricValueNumber(samples, 'postgres_wal_sync_time_ms_total'),
checkpointAvailable: metricBoolean(findMetricValueNumber(samples, 'postgres_checkpoint_observability_available')),
checkpointUnavailable: metricBoolean(findMetricValueNumber(samples, 'postgres_checkpoint_observability_unavailable')),
checkpointsTimedTotal: findMetricValueNumber(samples, 'postgres_checkpoints_timed_total'),
checkpointsRequestedTotal: findMetricValueNumber(samples, 'postgres_checkpoints_requested_total'),
checkpointWriteTimeMsTotal: findMetricValueNumber(samples, 'postgres_checkpoint_write_time_ms_total'),
checkpointSyncTimeMsTotal: findMetricValueNumber(samples, 'postgres_checkpoint_sync_time_ms_total'),
buffersCheckpointTotal: findMetricValueNumber(samples, 'postgres_buffers_checkpoint_total'),
buffersBackendTotal: findMetricValueNumber(samples, 'postgres_buffers_backend_total'),
statementAvailable: metricBoolean(findMetricValueNumber(samples, 'postgres_statement_observability_available')),
statementUnavailable: metricBoolean(findMetricValueNumber(samples, 'postgres_statement_observability_unavailable')),
statementTopCallsTotal: findMetricValueNumber(samples, 'postgres_statement_top_calls_total'),
statementTopExecTimeMsTotal: findMetricValueNumber(samples, 'postgres_statement_top_exec_time_ms_total'),
statementTopMaxMeanExecTimeMs: findMetricValueNumber(samples, 'postgres_statement_top_max_mean_exec_time_ms'),
statementTopMaxExecTimeMs: findMetricValueNumber(samples, 'postgres_statement_top_max_exec_time_ms'),
statementTopSharedBlksReadTotal: findMetricValueNumber(samples, 'postgres_statement_top_shared_blks_read_total'),
statementTopSharedBlksHitTotal: findMetricValueNumber(samples, 'postgres_statement_top_shared_blks_hit_total'),
statementTopTempBlksTotal: findMetricValueNumber(samples, 'postgres_statement_top_temp_blks_total'),
}
}
function buildRedisRuntimeMetrics(samples: PrometheusSample[]): GatewayRedisRuntimeMetrics {
const commandLatencyMaxMs = maxMetricValue(samples, 'redis_runtime_lane_command_latency_ms_max')
const nonblockingLatencyValues = [
findMetricValueNumber(samples, 'redis_runtime_lane_command_latency_ms_max', { lane: 'fast' }),
findMetricValueNumber(samples, 'redis_runtime_lane_command_latency_ms_max', { lane: 'stream' }),
findMetricValueNumber(samples, 'redis_runtime_lane_command_latency_ms_max', { lane: 'admin' }),
].filter((value): value is number => value != null && Number.isFinite(value))
const nonblockingCommandLatencyMaxMs = nonblockingLatencyValues.length > 0
? Math.max(...nonblockingLatencyValues)
: null
return {
enabled: metricBoolean(findMetricValueNumber(samples, 'redis_runtime_enabled')),
unavailable: metricBoolean(findMetricValueNumber(samples, 'redis_runtime_health_unavailable')),
connectedClients: findMetricValueNumber(samples, 'redis_runtime_connected_clients'),
blockedClients: findMetricValueNumber(samples, 'redis_runtime_blocked_clients'),
totalConnectionsReceived: findMetricValueNumber(samples, 'redis_runtime_total_connections_received'),
rejectedConnectionsTotal: findMetricValueNumber(samples, 'redis_runtime_rejected_connections_total'),
totalCommandsProcessed: findMetricValueNumber(samples, 'redis_runtime_total_commands_processed'),
instantaneousOpsPerSec: findMetricValueNumber(samples, 'redis_runtime_instantaneous_ops_per_sec'),
totalErrorReplies: findMetricValueNumber(samples, 'redis_runtime_total_error_replies'),
expiredKeysTotal: findMetricValueNumber(samples, 'redis_runtime_expired_keys_total'),
evictedKeysTotal: findMetricValueNumber(samples, 'redis_runtime_evicted_keys_total'),
keyspaceHitsTotal: findMetricValueNumber(samples, 'redis_runtime_keyspace_hits_total'),
keyspaceMissesTotal: findMetricValueNumber(samples, 'redis_runtime_keyspace_misses_total'),
keyspaceHitRateBasisPoints: findMetricValueNumber(samples, 'redis_runtime_keyspace_hit_rate_basis_points'),
usedMemoryBytes: findMetricValueNumber(samples, 'redis_runtime_used_memory_bytes'),
maxmemoryBytes: findMetricValueNumber(samples, 'redis_runtime_maxmemory_bytes'),
memoryUsageBasisPoints: findMetricValueNumber(samples, 'redis_runtime_memory_usage_basis_points'),
memoryFragmentationRatioBasisPoints: findMetricValueNumber(samples, 'redis_runtime_memory_fragmentation_ratio_basis_points'),
laneCommandErrorsTotal: sumMetricValues(samples, 'redis_runtime_lane_command_errors_total'),
laneCommandTimeoutsTotal: sumMetricValues(samples, 'redis_runtime_lane_command_timeouts_total'),
laneCommandCountTotal: sumMetricValues(samples, 'redis_runtime_lane_command_count_total'),
commandLatencyTotalMs: sumMetricValues(samples, 'redis_runtime_lane_command_latency_ms_sum'),
commandLatencyObservationCount: sumMetricValues(samples, 'redis_runtime_lane_command_latency_ms_count'),
commandLatencyMaxMs,
nonblockingCommandLatencyMaxMs,
}
}
function maxMetricValue(samples: PrometheusSample[], metricName: string): number | null {
const values = findMetricSamples(samples, metricName)
.map((sample) => Number(sample.value))
.filter((value) => Number.isFinite(value))
if (values.length === 0) return null
return Math.max(...values)
}
function buildProcessResourceMetrics(samples: PrometheusSample[]): GatewayProcessResourceMetrics {
return {
sampledAtUnixSecs: findMetricValueNumber(samples, 'gateway_process_sampled_at_unix_secs'),
systemCpuUsageBasisPoints: findMetricValueNumber(samples, 'gateway_system_cpu_usage_basis_points'),
processCpuUsageBasisPoints: findMetricValueNumber(samples, 'gateway_process_cpu_usage_basis_points'),
systemMemoryTotalBytes: findMetricValueNumber(samples, 'gateway_system_memory_total_bytes'),
systemMemoryUsedBytes: findMetricValueNumber(samples, 'gateway_system_memory_used_bytes'),
systemMemoryAvailableBytes: findMetricValueNumber(samples, 'gateway_system_memory_available_bytes'),
systemMemoryUsageBasisPoints: findMetricValueNumber(samples, 'gateway_system_memory_usage_basis_points'),
processMemoryBytes: findMetricValueNumber(samples, 'gateway_process_memory_bytes'),
processVirtualMemoryBytes: findMetricValueNumber(samples, 'gateway_process_virtual_memory_bytes'),
processMemoryBasisPoints: findMetricValueNumber(samples, 'gateway_process_memory_basis_points'),
processUptimeSeconds: findMetricValueNumber(samples, 'gateway_process_uptime_seconds'),
processThreads: findMetricValueNumber(samples, 'gateway_process_threads'),
openFds: findMetricValueNumber(samples, 'gateway_process_open_fds'),
fdLimit: findMetricValueNumber(samples, 'gateway_process_fd_limit'),
fdUsageBasisPoints: findMetricValueNumber(samples, 'gateway_process_fd_usage_basis_points'),
socketFds: findMetricValueNumber(samples, 'gateway_process_socket_fds'),
networkAvailable: metricBoolean(findMetricValueNumber(samples, 'gateway_network_observability_available')),
networkInterfaces: findMetricValueNumber(samples, 'gateway_network_interfaces'),
networkReceivedBytesTotal: findMetricValueNumber(samples, 'gateway_network_received_bytes_total'),
networkTransmittedBytesTotal: findMetricValueNumber(samples, 'gateway_network_transmitted_bytes_total'),
networkReceivedPacketsTotal: findMetricValueNumber(samples, 'gateway_network_received_packets_total'),
networkTransmittedPacketsTotal: findMetricValueNumber(samples, 'gateway_network_transmitted_packets_total'),
networkReceiveErrorsTotal: findMetricValueNumber(samples, 'gateway_network_receive_errors_total'),
networkTransmitErrorsTotal: findMetricValueNumber(samples, 'gateway_network_transmit_errors_total'),
networkReceiveDroppedTotal: findMetricValueNumber(samples, 'gateway_network_receive_dropped_total'),
networkTransmitDroppedTotal: findMetricValueNumber(samples, 'gateway_network_transmit_dropped_total'),
tcpStateAvailable: metricBoolean(findMetricValueNumber(samples, 'gateway_tcp_state_observability_available')),
hostTcpConnections: findMetricValueNumber(samples, 'gateway_host_tcp_connections'),
hostTcpEstablishedConnections: findMetricValueNumber(samples, 'gateway_host_tcp_established_connections'),
hostTcpListenConnections: findMetricValueNumber(samples, 'gateway_host_tcp_listen_connections'),
hostTcpTimeWaitConnections: findMetricValueNumber(samples, 'gateway_host_tcp_time_wait_connections'),
hostTcpSynSentConnections: findMetricValueNumber(samples, 'gateway_host_tcp_syn_sent_connections'),
hostTcpSynRecvConnections: findMetricValueNumber(samples, 'gateway_host_tcp_syn_recv_connections'),
hostTcpCloseWaitConnections: findMetricValueNumber(samples, 'gateway_host_tcp_close_wait_connections'),
processTcpConnections: findMetricValueNumber(samples, 'gateway_process_tcp_connections'),
processTcpEstablishedConnections: findMetricValueNumber(samples, 'gateway_process_tcp_established_connections'),
processTcpListenConnections: findMetricValueNumber(samples, 'gateway_process_tcp_listen_connections'),
processTcpTimeWaitConnections: findMetricValueNumber(samples, 'gateway_process_tcp_time_wait_connections'),
processTcpSynSentConnections: findMetricValueNumber(samples, 'gateway_process_tcp_syn_sent_connections'),
processTcpSynRecvConnections: findMetricValueNumber(samples, 'gateway_process_tcp_syn_recv_connections'),
processTcpCloseWaitConnections: findMetricValueNumber(samples, 'gateway_process_tcp_close_wait_connections'),
}
}
function buildAllocatorMetrics(samples: PrometheusSample[]): GatewayAllocatorMetrics {
return {
available: metricBoolean(findMetricValueNumber(samples, 'gateway_allocator_observability_available')),
allocatedBytes: findMetricValueNumber(samples, 'gateway_allocator_allocated_bytes'),
activeBytes: findMetricValueNumber(samples, 'gateway_allocator_active_bytes'),
residentBytes: findMetricValueNumber(samples, 'gateway_allocator_resident_bytes'),
mappedBytes: findMetricValueNumber(samples, 'gateway_allocator_mapped_bytes'),
retainedBytes: findMetricValueNumber(samples, 'gateway_allocator_retained_bytes'),
metadataBytes: findMetricValueNumber(samples, 'gateway_allocator_metadata_bytes'),
activeToAllocatedBasisPoints: findMetricValueNumber(samples, 'gateway_allocator_active_to_allocated_basis_points'),
residentToAllocatedBasisPoints: findMetricValueNumber(samples, 'gateway_allocator_resident_to_allocated_basis_points'),
}
}
function buildBackgroundTaskMetrics(samples: PrometheusSample[]): GatewayBackgroundTaskMetrics {
return {
active: findMetricValueNumber(samples, 'gateway_background_tasks_active'),
supervisedTotal: findMetricValueNumber(samples, 'gateway_background_tasks_supervised_total'),
unexpectedExitsTotal: findMetricValueNumber(samples, 'gateway_background_tasks_unexpected_exits_total'),
completedTotal: findMetricValueNumber(samples, 'gateway_background_tasks_completed_total'),
panickedTotal: findMetricValueNumber(samples, 'gateway_background_tasks_panicked_total'),
abortedTotal: findMetricValueNumber(samples, 'gateway_background_tasks_aborted_total'),
cancelledTotal: findMetricValueNumber(samples, 'gateway_background_tasks_cancelled_total'),
}
}
function buildTokioRuntimeMetrics(samples: PrometheusSample[]): GatewayTokioRuntimeMetrics {
return {
available: metricBoolean(findMetricValueNumber(samples, 'gateway_tokio_runtime_observability_available')),
workers: findMetricValueNumber(samples, 'gateway_tokio_runtime_workers'),
aliveTasks: findMetricValueNumber(samples, 'gateway_tokio_runtime_alive_tasks'),
globalQueueDepth: findMetricValueNumber(samples, 'gateway_tokio_runtime_global_queue_depth'),
}
}
function buildUsageRuntimeMetrics(samples: PrometheusSample[]): GatewayUsageRuntimeMetrics {
return {
enabled: metricBoolean(findMetricValueNumber(samples, 'usage_runtime_enabled')),
terminalQueueEnabled: metricBoolean(findMetricValueNumber(samples, 'usage_runtime_queue_terminal_events_enabled')),
lifecycleQueueEnabled: metricBoolean(findMetricValueNumber(samples, 'usage_runtime_queue_lifecycle_events_enabled')),
workerCount: findMetricValueNumber(samples, 'usage_runtime_queue_worker_count'),
workerAutoscaleEnabled: metricBoolean(findMetricValueNumber(samples, 'usage_runtime_queue_worker_autoscale_enabled')),
workerActiveCount: findMetricValueNumber(samples, 'usage_runtime_queue_worker_active_count'),
workerDesiredCount: findMetricValueNumber(samples, 'usage_runtime_queue_worker_desired_count'),
workerMaxCount: findMetricValueNumber(samples, 'usage_runtime_queue_worker_max_count'),
workerReadBatchesTotal: findMetricValueNumber(samples, 'usage_runtime_queue_worker_read_batches_total'),
workerReadEntriesTotal: findMetricValueNumber(samples, 'usage_runtime_queue_worker_read_entries_total'),
workerReclaimedEntriesTotal: findMetricValueNumber(samples, 'usage_runtime_queue_worker_reclaimed_entries_total'),
workerAckedEntriesTotal: findMetricValueNumber(samples, 'usage_runtime_queue_worker_acked_entries_total'),
workerDeadLetteredEntriesTotal: findMetricValueNumber(samples, 'usage_runtime_queue_worker_dead_lettered_entries_total'),
workerProcessFailuresTotal: findMetricValueNumber(samples, 'usage_runtime_queue_worker_process_failures_total'),
workerReadFailuresTotal: findMetricValueNumber(samples, 'usage_runtime_queue_worker_read_failures_total'),
workerReclaimFailuresTotal: findMetricValueNumber(samples, 'usage_runtime_queue_worker_reclaim_failures_total'),
terminalEnqueueInFlight: findMetricValueNumber(samples, 'usage_runtime_terminal_enqueue_in_flight'),
terminalEnqueueDeferredTotal: findMetricValueNumber(samples, 'usage_runtime_terminal_enqueue_deferred_total'),
terminalEnqueueDeferredRetryTotal: findMetricValueNumber(samples, 'usage_runtime_terminal_enqueue_deferred_retry_total'),
terminalEnqueueFailedTotal: findMetricValueNumber(samples, 'usage_runtime_terminal_enqueue_failed_total'),
lifecycleEnqueueInFlight: findMetricValueNumber(samples, 'usage_runtime_lifecycle_enqueue_in_flight'),
lifecycleEnqueueDeferredTotal: findMetricValueNumber(samples, 'usage_runtime_lifecycle_enqueue_deferred_total'),
lifecycleEnqueueDeferredDroppedTotal: findMetricValueNumber(samples, 'usage_runtime_lifecycle_enqueue_deferred_dropped_total'),
lifecycleEnqueueDeferredRetryTotal: findMetricValueNumber(samples, 'usage_runtime_lifecycle_enqueue_deferred_retry_total'),
lifecycleEnqueueFailedTotal: findMetricValueNumber(samples, 'usage_runtime_lifecycle_enqueue_failed_total'),
enqueueRetryScheduledTotal: findMetricValueNumber(samples, 'usage_runtime_enqueue_retry_scheduled_total'),
}
}
function buildUsageQueueMetrics(samples: PrometheusSample[]): GatewayUsageQueueMetrics {
const streamSample = findMetricSamples(samples, 'usage_queue_stream_length')[0]
const dlqSample = findMetricSamples(samples, 'usage_queue_dlq_length')[0]
return {
unavailable: metricBoolean(findMetricValueNumber(samples, 'usage_queue_health_unavailable')),
enabled: metricBoolean(findMetricValueNumber(samples, 'usage_queue_enabled')),
configured: metricBoolean(findMetricValueNumber(samples, 'usage_queue_configured')),
stream: streamSample?.labels.stream ?? null,
group: streamSample?.labels.group ?? null,
streamLength: findMetricValueNumber(samples, 'usage_queue_stream_length'),
groupPending: findMetricValueNumber(samples, 'usage_queue_group_pending'),
groupLag: findMetricValueNumber(samples, 'usage_queue_group_lag'),
oldestPendingIdleMs: findMetricValueNumber(samples, 'usage_queue_oldest_pending_idle_ms'),
dlqStream: dlqSample?.labels.stream ?? null,
dlqLength: findMetricValueNumber(samples, 'usage_queue_dlq_length'),
}
}
function buildUsageCounterMetrics(samples: PrometheusSample[]): GatewayUsageCounterMetrics {
const pendingByKind = findMetricSamples(samples, 'usage_counter_outbox_pending_rows_by_kind')
.map(sample => ({
kind: sample.labels.kind || 'unknown',
pendingRows: Number.isFinite(Number(sample.value)) ? Number(sample.value) : null,
}))
.sort((left, right) => (right.pendingRows ?? 0) - (left.pendingRows ?? 0))
return {
unavailable: metricBoolean(findMetricValueNumber(samples, 'usage_counter_health_unavailable')),
pendingRows: findMetricValueNumber(samples, 'usage_counter_outbox_pending_rows'),
processedRows: findMetricValueNumber(samples, 'usage_counter_outbox_processed_rows'),
oldestPendingAgeSeconds: findMetricValueNumber(samples, 'usage_counter_outbox_oldest_pending_age_seconds'),
oldestPendingCreatedAtUnixSecs: findMetricValueNumber(samples, 'usage_counter_outbox_oldest_pending_created_at_unix_secs'),
latestProcessedAtUnixSecs: findMetricValueNumber(samples, 'usage_counter_outbox_latest_processed_at_unix_secs'),
flushBatchesTotal: findMetricValueNumber(samples, 'usage_counter_outbox_flush_batches_total'),
flushRowsClaimedTotal: findMetricValueNumber(samples, 'usage_counter_outbox_flush_rows_claimed_total'),
flushTargetsTotal: sumMetricValues(samples, 'usage_counter_outbox_flush_targets_total'),
flushFailedBatchesTotal: findMetricValueNumber(samples, 'usage_counter_outbox_flush_failed_batches_total'),
cleanupRowsTotal: findMetricValueNumber(samples, 'usage_counter_outbox_cleanup_rows_total'),
cleanupFailedBatchesTotal: findMetricValueNumber(samples, 'usage_counter_outbox_cleanup_failed_batches_total'),
pendingByKind,
}
}
function buildRequestCandidateQueueMetrics(samples: PrometheusSample[]): GatewayRequestCandidateQueueMetrics {
return {
depth: findMetricValueNumber(samples, 'request_candidate_queue_depth'),
pendingDepth: findMetricValueNumber(samples, 'request_candidate_queue_pending_depth'),
capacity: findMetricValueNumber(samples, 'request_candidate_queue_capacity'),
enqueuedTotal: findMetricValueNumber(samples, 'request_candidate_queue_enqueued_total'),
droppedTotal: findMetricValueNumber(samples, 'request_candidate_queue_dropped_total'),
flushedTotal: findMetricValueNumber(samples, 'request_candidate_queue_flushed_total'),
flushFailedTotal: findMetricValueNumber(samples, 'request_candidate_queue_flush_failed_total'),
flushBatchesTotal: findMetricValueNumber(samples, 'request_candidate_queue_flush_batches_total'),
flushSqlOpsTotal: findMetricValueNumber(samples, 'request_candidate_queue_flush_sql_ops_total'),
flushSqlRecordsTotal: findMetricValueNumber(samples, 'request_candidate_queue_flush_sql_records_total'),
compactedTotal: findMetricValueNumber(samples, 'request_candidate_queue_compacted_total'),
syncFallbackTotal: findMetricValueNumber(samples, 'request_candidate_queue_sync_fallback_total'),
}
}
function collectUpstreamTargets(samples: PrometheusSample[]): string[] {
const targets = new Set<string>()
const metricNames = [
'upstream_target_gate_in_flight',
'upstream_target_gate_available_permits',
'upstream_target_gate_high_watermark',
'upstream_target_gate_rejected_total',
'upstream_target_selected_total',
'upstream_target_saturated_total',
]
for (const metricName of metricNames) {
for (const sample of findMetricSamples(samples, metricName)) {
if (sample.labels.target) {
targets.add(sample.labels.target)
}
}
}
return Array.from(targets)
}
function buildUpstreamTargetMetrics(samples: PrometheusSample[]): GatewayUpstreamTargetMetrics {
const rows = collectUpstreamTargets(samples)
.map(target => ({
target,
inFlight: findMetricValueNumber(samples, 'upstream_target_gate_in_flight', { target }),
availablePermits: findMetricValueNumber(samples, 'upstream_target_gate_available_permits', { target }),
highWatermark: findMetricValueNumber(samples, 'upstream_target_gate_high_watermark', { target }),
rejectedTotal: findMetricValueNumber(samples, 'upstream_target_gate_rejected_total', { target }),
selectedTotal: findMetricValueNumber(samples, 'upstream_target_selected_total', { target }),
saturatedTotal: findMetricValueNumber(samples, 'upstream_target_saturated_total', { target }),
}))
.sort((left, right) => (
(right.inFlight ?? 0) - (left.inFlight ?? 0)
|| (right.saturatedTotal ?? 0) - (left.saturatedTotal ?? 0)
|| (right.selectedTotal ?? 0) - (left.selectedTotal ?? 0)
))
.slice(0, 8)
return {
activeTargets: findMetricValueNumber(samples, 'upstream_target_gate_active_targets'),
limit: findMetricValueNumber(samples, 'upstream_target_gate_limit'),
selectedTotal: sumMetricValues(samples, 'upstream_target_selected_total'),
saturatedTotal: sumMetricValues(samples, 'upstream_target_saturated_total'),
rejectedTotal: sumMetricValues(samples, 'upstream_target_gate_rejected_total'),
rows,
}
}
function buildStageLatencyMetrics(samples: PrometheusSample[]): GatewayStageLatencyMetrics {
return {
rows: CAPACITY_STAGES.map(({ stage, label }) => {
const count = findMetricValueNumber(samples, 'gateway_stage_latency_count', { stage })
const sumMs = findMetricValueNumber(samples, 'gateway_stage_latency_sum_ms', { stage })
return {
stage,
label,
count,
avgMs: count != null && count > 0 && sumMs != null ? sumMs / count : null,
maxMs: findMetricValueNumber(samples, 'gateway_stage_latency_max_ms', { stage }),
}
}),
}
}
export function buildGatewayMetricsSummary(text: string): GatewayMetricsSummary {
const samples = parsePrometheusSamples(text)
const fallbacks = FALLBACK_METRICS.map(item => ({
@@ -170,6 +821,21 @@ export function buildGatewayMetricsSummary(text: string): GatewayMetricsSummary
serviceUp: findMetricValueNumber(samples, 'service_up', { service: 'aether-gateway' }),
local: buildGateMetrics(samples, 'gateway_requests'),
distributed: buildGateMetrics(samples, 'gateway_requests_distributed'),
candidatePlanning: buildGateMetrics(samples, 'gateway_candidate_planning'),
upstreamExecution: buildGateMetrics(samples, 'gateway_upstream_execution'),
databasePool: buildDatabasePoolMetrics(samples),
postgres: buildPostgresObservabilityMetrics(samples),
redisRuntime: buildRedisRuntimeMetrics(samples),
process: buildProcessResourceMetrics(samples),
allocator: buildAllocatorMetrics(samples),
backgroundTasks: buildBackgroundTaskMetrics(samples),
tokioRuntime: buildTokioRuntimeMetrics(samples),
usageRuntime: buildUsageRuntimeMetrics(samples),
usageQueue: buildUsageQueueMetrics(samples),
usageCounter: buildUsageCounterMetrics(samples),
requestCandidateQueue: buildRequestCandidateQueueMetrics(samples),
upstreamTargets: buildUpstreamTargetMetrics(samples),
stageLatency: buildStageLatencyMetrics(samples),
tunnel: {
proxyConnections: findMetricValueNumber(samples, 'tunnel_proxy_connections'),
availableProxyConnections: findMetricValueNumber(samples, 'tunnel_proxy_connections_available'),
@@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest'
import {
findMetricSamples,
findMetricValueNumber,
parsePrometheusSamples,
sumMetricValues,
@@ -34,4 +35,15 @@ decision_remote_total{route_kind="responses",reason="remote_decision_miss"} 3
expect(sumMetricValues(samples, 'decision_remote_total')).toBe(5)
})
it('finds all matching samples by full or suffix metric name', () => {
const samples = parsePrometheusSamples(`
aether_gateway_upstream_target_selected_total{target="openai"} 4
aether_gateway_upstream_target_selected_total{target="azure"} 6
aether_gateway_upstream_target_saturated_total{target="openai"} 1
`)
expect(findMetricSamples(samples, 'upstream_target_selected_total')).toHaveLength(2)
expect(sumMetricValues(samples, 'upstream_target_selected_total')).toBe(10)
})
})
+7
View File
@@ -44,6 +44,13 @@ export function sumMetricValues(
}, 0)
}
export function findMetricSamples(
samples: PrometheusSample[],
metricName: string
): PrometheusSample[] {
return samples.filter(sample => metricNameMatches(sample.name, metricName))
}
function metricNameMatches(actual: string, expected: string): boolean {
return actual === expected || actual.split('_').pop() === expected || actual.endsWith(`_${expected}`)
}
File diff suppressed because it is too large Load Diff