初始提交:边缘AI算力机统一AI通讯层
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
package observability
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LogLevel represents logging severity levels.
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
LevelDebug LogLevel = iota
|
||||
LevelInfo
|
||||
LevelWarn
|
||||
LevelError
|
||||
)
|
||||
|
||||
func (l LogLevel) String() string {
|
||||
switch l {
|
||||
case LevelDebug:
|
||||
return "DEBUG"
|
||||
case LevelInfo:
|
||||
return "INFO"
|
||||
case LevelWarn:
|
||||
return "WARN"
|
||||
case LevelError:
|
||||
return "ERROR"
|
||||
default:
|
||||
return "INFO"
|
||||
}
|
||||
}
|
||||
|
||||
// ParseLogLevel parses a string to LogLevel.
|
||||
func ParseLogLevel(s string) LogLevel {
|
||||
switch strings.ToLower(s) {
|
||||
case "debug":
|
||||
return LevelDebug
|
||||
case "info":
|
||||
return LevelInfo
|
||||
case "warn", "warning":
|
||||
return LevelWarn
|
||||
case "error":
|
||||
return LevelError
|
||||
default:
|
||||
return LevelInfo
|
||||
}
|
||||
}
|
||||
|
||||
// LogEntry is a structured JSON log entry.
|
||||
type LogEntry struct {
|
||||
Timestamp string `json:"timestamp"`
|
||||
Level string `json:"level"`
|
||||
Event string `json:"event,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
TaskID string `json:"task_id,omitempty"`
|
||||
SessionID string `json:"session_id,omitempty"`
|
||||
TraceID string `json:"trace_id,omitempty"`
|
||||
Application string `json:"application,omitempty"`
|
||||
TenantID string `json:"tenant_id,omitempty"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
FromState string `json:"from_state,omitempty"`
|
||||
ToState string `json:"to_state,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
LogicalModel string `json:"logical_model,omitempty"`
|
||||
ActualModel string `json:"actual_model,omitempty"`
|
||||
NodeID string `json:"node_id,omitempty"`
|
||||
Degraded bool `json:"degraded,omitempty"`
|
||||
Extra map[string]any `json:"extra,omitempty"`
|
||||
}
|
||||
|
||||
// Logger is a structured JSON logger with sensitive field masking.
|
||||
type Logger struct {
|
||||
mu sync.RWMutex
|
||||
level LogLevel
|
||||
output *os.File
|
||||
maskFields []string
|
||||
promptLogging string
|
||||
}
|
||||
|
||||
var defaultLogger *Logger
|
||||
|
||||
func init() {
|
||||
defaultLogger = NewLogger(LevelInfo, os.Stdout, "metadata_only")
|
||||
}
|
||||
|
||||
// NewLogger creates a new Logger instance.
|
||||
func NewLogger(level LogLevel, out *os.File, promptLogging string) *Logger {
|
||||
return &Logger{
|
||||
level: level,
|
||||
output: out,
|
||||
maskFields: []string{"api_key", "apikey", "authorization", "jwt", "secret", "password", "token"},
|
||||
promptLogging: promptLogging,
|
||||
}
|
||||
}
|
||||
|
||||
// GetLogger returns the default logger.
|
||||
func GetLogger() *Logger {
|
||||
return defaultLogger
|
||||
}
|
||||
|
||||
// SetLevel updates the log level (thread-safe).
|
||||
func (l *Logger) SetLevel(level LogLevel) {
|
||||
l.mu.Lock()
|
||||
l.level = level
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// SetPromptLogging updates the prompt logging policy.
|
||||
func (l *Logger) SetPromptLogging(policy string) {
|
||||
l.mu.Lock()
|
||||
l.promptLogging = policy
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
func (l *Logger) shouldLog(level LogLevel) bool {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
return level >= l.level
|
||||
}
|
||||
|
||||
func (l *Logger) maskSensitive(data map[string]any) map[string]any {
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
masked := make(map[string]any, len(data))
|
||||
for k, v := range data {
|
||||
if l.isSensitive(k) {
|
||||
masked[k] = "***REDACTED***"
|
||||
} else if sub, ok := v.(map[string]any); ok {
|
||||
masked[k] = l.maskSensitive(sub)
|
||||
} else {
|
||||
masked[k] = v
|
||||
}
|
||||
}
|
||||
return masked
|
||||
}
|
||||
|
||||
func (l *Logger) isSensitive(key string) bool {
|
||||
lk := strings.ToLower(key)
|
||||
for _, s := range l.maskFields {
|
||||
if strings.Contains(lk, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *Logger) write(entry LogEntry) {
|
||||
if !l.shouldLog(parseLevelFromString(entry.Level)) {
|
||||
return
|
||||
}
|
||||
if entry.Extra != nil {
|
||||
entry.Extra = l.maskSensitive(entry.Extra)
|
||||
}
|
||||
if entry.Timestamp == "" {
|
||||
entry.Timestamp = time.Now().UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
data, err := json.Marshal(entry)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "log marshal error: %v\n", err)
|
||||
return
|
||||
}
|
||||
l.mu.Lock()
|
||||
fmt.Fprintln(l.output, string(data))
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
func parseLevelFromString(s string) LogLevel {
|
||||
switch strings.ToUpper(s) {
|
||||
case "DEBUG":
|
||||
return LevelDebug
|
||||
case "INFO":
|
||||
return LevelInfo
|
||||
case "WARN", "WARNING":
|
||||
return LevelWarn
|
||||
case "ERROR":
|
||||
return LevelError
|
||||
default:
|
||||
return LevelInfo
|
||||
}
|
||||
}
|
||||
|
||||
// LogFields is a builder for structured log fields.
|
||||
type LogFields struct {
|
||||
fields map[string]any
|
||||
}
|
||||
|
||||
func F() *LogFields {
|
||||
return &LogFields{fields: make(map[string]any)}
|
||||
}
|
||||
|
||||
func (f *LogFields) Set(key string, value any) *LogFields {
|
||||
f.fields[key] = value
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *LogFields) RequestID(id string) *LogFields { f.fields["request_id"] = id; return f }
|
||||
func (f *LogFields) TaskID(id string) *LogFields { f.fields["task_id"] = id; return f }
|
||||
func (f *LogFields) SessionID(id string) *LogFields { f.fields["session_id"] = id; return f }
|
||||
func (f *LogFields) TraceID(id string) *LogFields { f.fields["trace_id"] = id; return f }
|
||||
func (f *LogFields) Application(app string) *LogFields { f.fields["application"] = app; return f }
|
||||
func (f *LogFields) TenantID(id string) *LogFields { f.fields["tenant_id"] = id; return f }
|
||||
func (f *LogFields) UserID(id string) *LogFields { f.fields["user_id"] = id; return f }
|
||||
func (f *LogFields) Event(e string) *LogFields { f.fields["event"] = e; return f }
|
||||
func (f *LogFields) Reason(r string) *LogFields { f.fields["reason"] = r; return f }
|
||||
|
||||
func (l *Logger) Debug(msg string, fields *LogFields) {
|
||||
entry := l.buildEntry("DEBUG", msg, fields)
|
||||
l.write(entry)
|
||||
}
|
||||
|
||||
func (l *Logger) Info(msg string, fields *LogFields) {
|
||||
entry := l.buildEntry("INFO", msg, fields)
|
||||
l.write(entry)
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(msg string, fields *LogFields) {
|
||||
entry := l.buildEntry("WARN", msg, fields)
|
||||
l.write(entry)
|
||||
}
|
||||
|
||||
func (l *Logger) Error(msg string, fields *LogFields) {
|
||||
entry := l.buildEntry("ERROR", msg, fields)
|
||||
l.write(entry)
|
||||
}
|
||||
|
||||
func (l *Logger) buildEntry(level, msg string, fields *LogFields) LogEntry {
|
||||
entry := LogEntry{
|
||||
Level: level,
|
||||
Message: msg,
|
||||
}
|
||||
if fields != nil {
|
||||
for k, v := range fields.fields {
|
||||
switch k {
|
||||
case "event":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.Event = s
|
||||
}
|
||||
case "request_id":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.RequestID = s
|
||||
}
|
||||
case "task_id":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.TaskID = s
|
||||
}
|
||||
case "session_id":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.SessionID = s
|
||||
}
|
||||
case "trace_id":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.TraceID = s
|
||||
}
|
||||
case "application":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.Application = s
|
||||
}
|
||||
case "tenant_id":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.TenantID = s
|
||||
}
|
||||
case "user_id":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.UserID = s
|
||||
}
|
||||
case "reason":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.Reason = s
|
||||
}
|
||||
case "from_state":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.FromState = s
|
||||
}
|
||||
case "to_state":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.ToState = s
|
||||
}
|
||||
case "logical_model":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.LogicalModel = s
|
||||
}
|
||||
case "actual_model":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.ActualModel = s
|
||||
}
|
||||
case "node_id":
|
||||
if s, ok := v.(string); ok {
|
||||
entry.NodeID = s
|
||||
}
|
||||
case "degraded":
|
||||
if b, ok := v.(bool); ok {
|
||||
entry.Degraded = b
|
||||
}
|
||||
default:
|
||||
if entry.Extra == nil {
|
||||
entry.Extra = make(map[string]any)
|
||||
}
|
||||
entry.Extra[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
// SetLogLevel updates the global log level.
|
||||
func SetLogLevel(level string) {
|
||||
defaultLogger.SetLevel(ParseLogLevel(level))
|
||||
}
|
||||
|
||||
// SetPromptLoggingPolicy updates the global prompt logging policy.
|
||||
func SetPromptLoggingPolicy(policy string) {
|
||||
defaultLogger.SetPromptLogging(policy)
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package observability
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseLogLevel(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want LogLevel
|
||||
}{
|
||||
{"debug", LevelDebug}, {"info", LevelInfo},
|
||||
{"warn", LevelWarn}, {"warning", LevelWarn},
|
||||
{"error", LevelError}, {"invalid", LevelInfo},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := ParseLogLevel(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("ParseLogLevel(%q) = %d, want %d", tt.input, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerMaskSensitive(t *testing.T) {
|
||||
logger := &Logger{
|
||||
level: LevelInfo,
|
||||
maskFields: []string{"api_key", "secret", "password", "token"},
|
||||
}
|
||||
|
||||
data := map[string]any{
|
||||
"api_key": "sk-12345",
|
||||
"message": "hello",
|
||||
"nested": map[string]any{
|
||||
"secret": "my-secret",
|
||||
},
|
||||
}
|
||||
masked := logger.maskSensitive(data)
|
||||
if masked["api_key"] != "***REDACTED***" {
|
||||
t.Errorf("expected api_key redacted, got %v", masked["api_key"])
|
||||
}
|
||||
if masked["message"] != "hello" {
|
||||
t.Errorf("expected message preserved, got %v", masked["message"])
|
||||
}
|
||||
nested, ok := masked["nested"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("expected nested map")
|
||||
}
|
||||
if nested["secret"] != "***REDACTED***" {
|
||||
t.Errorf("expected nested secret redacted, got %v", nested["secret"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogFieldsBuilder(t *testing.T) {
|
||||
f := F().RequestID("req-1").TaskID("task-1").Event("test_event").Set("custom", "value")
|
||||
if f.fields["request_id"] != "req-1" {
|
||||
t.Error("request_id not set")
|
||||
}
|
||||
if f.fields["task_id"] != "task-1" {
|
||||
t.Error("task_id not set")
|
||||
}
|
||||
if f.fields["event"] != "test_event" {
|
||||
t.Error("event not set")
|
||||
}
|
||||
if f.fields["custom"] != "value" {
|
||||
t.Error("custom not set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerWrite(t *testing.T) {
|
||||
// Use a temp file to capture output
|
||||
tmpFile, err := os.CreateTemp("", "logtest*.json")
|
||||
if err != nil {
|
||||
t.Fatalf("create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
logger := NewLogger(LevelDebug, tmpFile, "metadata_only")
|
||||
logger.Info("test message", F().RequestID("req-123").Event("unit_test"))
|
||||
|
||||
tmpFile.Close()
|
||||
|
||||
data, err := os.ReadFile(tmpFile.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("read log file: %v", err)
|
||||
}
|
||||
|
||||
var entry map[string]any
|
||||
if err := json.Unmarshal(bytes.TrimSpace(data), &entry); err != nil {
|
||||
t.Fatalf("parse log json: %v\nraw: %s", err, string(data))
|
||||
}
|
||||
|
||||
if entry["level"] != "INFO" {
|
||||
t.Errorf("expected level INFO, got %v", entry["level"])
|
||||
}
|
||||
if entry["message"] != "test message" {
|
||||
t.Errorf("expected message 'test message', got %v", entry["message"])
|
||||
}
|
||||
if entry["request_id"] != "req-123" {
|
||||
t.Errorf("expected request_id req-123, got %v", entry["request_id"])
|
||||
}
|
||||
if entry["event"] != "unit_test" {
|
||||
t.Errorf("expected event unit_test, got %v", entry["event"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerLevelFiltering(t *testing.T) {
|
||||
// This test verifies that debug messages are not logged when level is INFO
|
||||
tmpFile, err := os.CreateTemp("", "logtest*.json")
|
||||
if err != nil {
|
||||
t.Fatalf("create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
logger := NewLogger(LevelWarn, tmpFile, "metadata_only")
|
||||
logger.Info("should not appear", F().Event("info_event"))
|
||||
logger.Warn("should appear", F().Event("warn_event"))
|
||||
|
||||
tmpFile.Close()
|
||||
data, _ := os.ReadFile(tmpFile.Name())
|
||||
if strings.Contains(string(data), "should not appear") {
|
||||
t.Error("INFO message was logged when level is WARN")
|
||||
}
|
||||
if !strings.Contains(string(data), "should appear") {
|
||||
t.Error("WARN message was not logged")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package observability
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Metrics holds all Prometheus-compatible metrics for the gateway.
|
||||
type Metrics struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
// Counters
|
||||
requestsTotal map[string]int64 // by status
|
||||
tasksTotal map[string]int64 // by state
|
||||
tokensInputTotal int64
|
||||
tokensOutputTotal int64
|
||||
cancellationsTotal int64
|
||||
queueTimeoutsTotal int64
|
||||
firstTokenTimeoutsTotal int64
|
||||
inferenceTimeoutsTotal int64
|
||||
degradedRequestsTotal int64
|
||||
|
||||
// Gauges
|
||||
queueLength int64
|
||||
runningTasks int64
|
||||
activeSessions int64
|
||||
backpressureLevel int64
|
||||
|
||||
// Histograms (simplified as buckets)
|
||||
gatewayLatencyBuckets map[string]int64
|
||||
firstTokenLatencyBuckets map[string]int64
|
||||
}
|
||||
|
||||
// NewMetrics creates a new Metrics instance.
|
||||
func NewMetrics() *Metrics {
|
||||
return &Metrics{
|
||||
requestsTotal: make(map[string]int64),
|
||||
tasksTotal: make(map[string]int64),
|
||||
gatewayLatencyBuckets: make(map[string]int64),
|
||||
firstTokenLatencyBuckets: make(map[string]int64),
|
||||
}
|
||||
}
|
||||
|
||||
// IncRequest increments the request counter by status.
|
||||
func (m *Metrics) IncRequest(status string) {
|
||||
key := fmt.Sprintf("status=%s", status)
|
||||
m.mu.Lock()
|
||||
m.requestsTotal[key]++
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// IncTask increments the task counter by final state.
|
||||
func (m *Metrics) IncTask(state string) {
|
||||
key := fmt.Sprintf("state=%s", state)
|
||||
m.mu.Lock()
|
||||
m.tasksTotal[key]++
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// AddTokens adds to the token counters.
|
||||
func (m *Metrics) AddTokens(input, output int) {
|
||||
atomic.AddInt64(&m.tokensInputTotal, int64(input))
|
||||
atomic.AddInt64(&m.tokensOutputTotal, int64(output))
|
||||
}
|
||||
|
||||
// IncCancellation increments the cancellation counter.
|
||||
func (m *Metrics) IncCancellation() {
|
||||
atomic.AddInt64(&m.cancellationsTotal, 1)
|
||||
}
|
||||
|
||||
// IncQueueTimeout increments the queue timeout counter.
|
||||
func (m *Metrics) IncQueueTimeout() {
|
||||
atomic.AddInt64(&m.queueTimeoutsTotal, 1)
|
||||
}
|
||||
|
||||
// IncFirstTokenTimeout increments the first token timeout counter.
|
||||
func (m *Metrics) IncFirstTokenTimeout() {
|
||||
atomic.AddInt64(&m.firstTokenTimeoutsTotal, 1)
|
||||
}
|
||||
|
||||
// IncInferenceTimeout increments the inference timeout counter.
|
||||
func (m *Metrics) IncInferenceTimeout() {
|
||||
atomic.AddInt64(&m.inferenceTimeoutsTotal, 1)
|
||||
}
|
||||
|
||||
// IncDegraded increments the degraded request counter.
|
||||
func (m *Metrics) IncDegraded() {
|
||||
atomic.AddInt64(&m.degradedRequestsTotal, 1)
|
||||
}
|
||||
|
||||
// SetQueueLength sets the current queue length gauge.
|
||||
func (m *Metrics) SetQueueLength(n int) {
|
||||
atomic.StoreInt64(&m.queueLength, int64(n))
|
||||
}
|
||||
|
||||
// SetRunningTasks sets the running tasks gauge.
|
||||
func (m *Metrics) SetRunningTasks(n int) {
|
||||
atomic.StoreInt64(&m.runningTasks, int64(n))
|
||||
}
|
||||
|
||||
// SetActiveSessions sets the active sessions gauge.
|
||||
func (m *Metrics) SetActiveSessions(n int) {
|
||||
atomic.StoreInt64(&m.activeSessions, int64(n))
|
||||
}
|
||||
|
||||
// SetBackpressureLevel sets the backpressure level gauge.
|
||||
func (m *Metrics) SetBackpressureLevel(level int) {
|
||||
atomic.StoreInt64(&m.backpressureLevel, int64(level))
|
||||
}
|
||||
|
||||
// ObserveGatewayLatency records gateway latency in a histogram bucket.
|
||||
func (m *Metrics) ObserveGatewayLatency(ms int64) {
|
||||
bucket := latencyBucket(ms)
|
||||
m.mu.Lock()
|
||||
m.gatewayLatencyBuckets[bucket]++
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// ObserveFirstTokenLatency records first token latency in a histogram bucket.
|
||||
func (m *Metrics) ObserveFirstTokenLatency(ms int64) {
|
||||
bucket := latencyBucket(ms)
|
||||
m.mu.Lock()
|
||||
m.firstTokenLatencyBuckets[bucket]++
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func latencyBucket(ms int64) string {
|
||||
buckets := []int64{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000}
|
||||
for _, b := range buckets {
|
||||
if ms <= b {
|
||||
return fmt.Sprintf("le_%d", b)
|
||||
}
|
||||
}
|
||||
return "le_inf"
|
||||
}
|
||||
|
||||
// Handler returns an http.HandlerFunc that writes Prometheus-format metrics.
|
||||
func (m *Metrics) Handler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
|
||||
|
||||
// Counters
|
||||
m.mu.RLock()
|
||||
for key, val := range m.requestsTotal {
|
||||
fmt.Fprintf(w, "edgeai_requests_total{%s} %d\n", key, val)
|
||||
}
|
||||
for key, val := range m.tasksTotal {
|
||||
fmt.Fprintf(w, "edgeai_tasks_total{%s} %d\n", key, val)
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
|
||||
fmt.Fprintf(w, "edgeai_tokens_input_total %d\n", atomic.LoadInt64(&m.tokensInputTotal))
|
||||
fmt.Fprintf(w, "edgeai_tokens_output_total %d\n", atomic.LoadInt64(&m.tokensOutputTotal))
|
||||
fmt.Fprintf(w, "edgeai_cancellations_total %d\n", atomic.LoadInt64(&m.cancellationsTotal))
|
||||
fmt.Fprintf(w, "edgeai_queue_timeouts_total %d\n", atomic.LoadInt64(&m.queueTimeoutsTotal))
|
||||
fmt.Fprintf(w, "edgeai_first_token_timeouts_total %d\n", atomic.LoadInt64(&m.firstTokenTimeoutsTotal))
|
||||
fmt.Fprintf(w, "edgeai_inference_timeouts_total %d\n", atomic.LoadInt64(&m.inferenceTimeoutsTotal))
|
||||
fmt.Fprintf(w, "edgeai_degraded_requests_total %d\n", atomic.LoadInt64(&m.degradedRequestsTotal))
|
||||
|
||||
// Gauges
|
||||
fmt.Fprintf(w, "edgeai_queue_length %d\n", atomic.LoadInt64(&m.queueLength))
|
||||
fmt.Fprintf(w, "edgeai_running_tasks %d\n", atomic.LoadInt64(&m.runningTasks))
|
||||
fmt.Fprintf(w, "edgeai_active_sessions %d\n", atomic.LoadInt64(&m.activeSessions))
|
||||
fmt.Fprintf(w, "edgeai_backpressure_level %d\n", atomic.LoadInt64(&m.backpressureLevel))
|
||||
|
||||
// Histograms
|
||||
m.mu.RLock()
|
||||
for bucket, count := range m.gatewayLatencyBuckets {
|
||||
fmt.Fprintf(w, "edgeai_gateway_latency_bucket{%s} %d\n", bucket, count)
|
||||
}
|
||||
for bucket, count := range m.firstTokenLatencyBuckets {
|
||||
fmt.Fprintf(w, "edgeai_first_token_latency_bucket{%s} %d\n", bucket, count)
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user