File size: 4,716 Bytes
9aab84d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package internal

import (
	"sync"
	"sync/atomic"
	"time"
)

// ModelStats 模型统计
type ModelStats struct {
	Requests  int64 `json:"requests"`
	InputTok  int64 `json:"input_tokens"`
	OutputTok int64 `json:"output_tokens"`
}

// Telemetry 遥测数据
type Telemetry struct {
	StartTime       time.Time
	TotalRequests   int64
	TotalInputTok   int64
	TotalOutputTok  int64
	minuteRequests  int64
	minuteInputTok  int64
	minuteOutputTok int64
	requestTimes    []time.Time
	modelStats      map[string]*ModelStats
	mu              sync.Mutex
}

var telemetry = &Telemetry{
	StartTime:    time.Now(),
	requestTimes: make([]time.Time, 0),
	modelStats:   make(map[string]*ModelStats),
}

func RecordRequest(inputTokens, outputTokens int64, model string) {
	atomic.AddInt64(&telemetry.TotalRequests, 1)
	atomic.AddInt64(&telemetry.TotalInputTok, inputTokens)
	atomic.AddInt64(&telemetry.TotalOutputTok, outputTokens)
	atomic.AddInt64(&telemetry.minuteRequests, 1)
	atomic.AddInt64(&telemetry.minuteInputTok, inputTokens)
	atomic.AddInt64(&telemetry.minuteOutputTok, outputTokens)
	telemetry.mu.Lock()
	telemetry.requestTimes = append(telemetry.requestTimes, time.Now())
	// 模型维度统计
	if model != "" {
		if _, ok := telemetry.modelStats[model]; !ok {
			telemetry.modelStats[model] = &ModelStats{}
		}
		telemetry.modelStats[model].Requests++
		telemetry.modelStats[model].InputTok += inputTokens
		telemetry.modelStats[model].OutputTok += outputTokens
	}
	telemetry.mu.Unlock()
}

func GetRPM() int {
	telemetry.mu.Lock()
	defer telemetry.mu.Unlock()
	now := time.Now()
	cutoff := now.Add(-time.Minute)
	validTimes := make([]time.Time, 0)
	for _, t := range telemetry.requestTimes {
		if t.After(cutoff) {
			validTimes = append(validTimes, t)
		}
	}
	telemetry.requestTimes = validTimes
	return len(validTimes)
}

type TelemetryData struct {
	Uptime          string                 `json:"uptime"`
	TotalRequests   int64                  `json:"total_requests"`
	RPM             int                    `json:"rpm"`
	TotalInputTok   int64                  `json:"total_input_tokens"`
	TotalOutputTok  int64                  `json:"total_output_tokens"`
	AvgInputTok     int64                  `json:"avg_input_tokens"`
	AvgOutputTok    int64                  `json:"avg_output_tokens"`
	ValidTokens     int                    `json:"valid_tokens"`
	MultimodalCalls int64                  `json:"multimodal_calls"`
	TotalCalls      int64                  `json:"total_calls"`
	SuccessCalls    int64                  `json:"success_calls"`
	SuccessRate     float64                `json:"success_rate"`
	ModelStats      map[string]*ModelStats `json:"model_stats,omitempty"`
}

func GetTelemetryData() TelemetryData {
	totalReqs := atomic.LoadInt64(&telemetry.TotalRequests)
	totalIn := atomic.LoadInt64(&telemetry.TotalInputTok)
	totalOut := atomic.LoadInt64(&telemetry.TotalOutputTok)

	var avgIn, avgOut int64
	if totalReqs > 0 {
		avgIn = totalIn / totalReqs
		avgOut = totalOut / totalReqs
	}

	uptime := time.Since(telemetry.StartTime)
	uptimeStr := formatDuration(uptime)

	// 获取 token 管理器统计
	tmStats := GetTokenManager().GetStats()

	// 复制模型统计
	telemetry.mu.Lock()
	modelStatsCopy := make(map[string]*ModelStats)
	for k, v := range telemetry.modelStats {
		modelStatsCopy[k] = &ModelStats{
			Requests:  v.Requests,
			InputTok:  v.InputTok,
			OutputTok: v.OutputTok,
		}
	}
	telemetry.mu.Unlock()

	return TelemetryData{
		Uptime:          uptimeStr,
		TotalRequests:   totalReqs,
		RPM:             GetRPM(),
		TotalInputTok:   totalIn,
		TotalOutputTok:  totalOut,
		AvgInputTok:     avgIn,
		AvgOutputTok:    avgOut,
		ValidTokens:     tmStats.ValidTokenCount,
		MultimodalCalls: tmStats.MultimodalCount,
		TotalCalls:      tmStats.TotalCalls,
		SuccessCalls:    tmStats.SuccessCalls,
		SuccessRate:     tmStats.SuccessRate,
		ModelStats:      modelStatsCopy,
	}
}

func formatDuration(d time.Duration) string {
	days := int(d.Hours()) / 24
	hours := int(d.Hours()) % 24
	minutes := int(d.Minutes()) % 60
	seconds := int(d.Seconds()) % 60

	if days > 0 {
		return formatTime(days, "d") + formatTime(hours, "h") + formatTime(minutes, "m")
	}
	if hours > 0 {
		return formatTime(hours, "h") + formatTime(minutes, "m") + formatTime(seconds, "s")
	}
	if minutes > 0 {
		return formatTime(minutes, "m") + formatTime(seconds, "s")
	}
	return formatTime(seconds, "s")
}

func formatTime(value int, unit string) string {
	if value == 0 {
		return ""
	}
	return string(rune('0'+value/10)) + string(rune('0'+value%10)) + unit
}