mirror of
https://github.com/woodchen-ink/proxy-go.git
synced 2025-07-18 16:41:54 +08:00
feat(metrics): enhance metrics collection and UI with top referers tracking
- Added support for tracking top referers in the metrics collector, allowing for better insights into request sources. - Updated MetricsHandler to include top referers in the metrics response. - Enhanced the metrics UI to display a new section for the top 10 referers, improving visibility into traffic sources. - Refactored request recording to capture referer information in the collector for comprehensive statistics.
This commit is contained in:
parent
9be13ce0ef
commit
d4af4c4d31
@ -32,6 +32,7 @@ type Metrics struct {
|
|||||||
LatencyPercentiles map[string]float64 `json:"latency_percentiles"`
|
LatencyPercentiles map[string]float64 `json:"latency_percentiles"`
|
||||||
TopPaths []metrics.PathMetrics `json:"top_paths"`
|
TopPaths []metrics.PathMetrics `json:"top_paths"`
|
||||||
RecentRequests []metrics.RequestLog `json:"recent_requests"`
|
RecentRequests []metrics.RequestLog `json:"recent_requests"`
|
||||||
|
TopReferers []metrics.PathMetrics `json:"top_referers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ProxyHandler) MetricsHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *ProxyHandler) MetricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -59,6 +60,7 @@ func (h *ProxyHandler) MetricsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
StatusCodeStats: stats["status_code_stats"].(map[string]int64),
|
StatusCodeStats: stats["status_code_stats"].(map[string]int64),
|
||||||
TopPaths: stats["top_paths"].([]metrics.PathMetrics),
|
TopPaths: stats["top_paths"].([]metrics.PathMetrics),
|
||||||
RecentRequests: stats["recent_requests"].([]metrics.RequestLog),
|
RecentRequests: stats["recent_requests"].([]metrics.RequestLog),
|
||||||
|
TopReferers: stats["top_referers"].([]metrics.PathMetrics),
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
@ -259,68 +261,79 @@ var metricsTemplate = `
|
|||||||
.status-3xx { background: #17a2b8; }
|
.status-3xx { background: #17a2b8; }
|
||||||
.status-4xx { background: #ffc107; }
|
.status-4xx { background: #ffc107; }
|
||||||
.status-5xx { background: #dc3545; }
|
.status-5xx { background: #dc3545; }
|
||||||
|
.grid-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.grid-container .card {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Proxy-Go Metrics</h1>
|
<h1>Proxy-Go Metrics</h1>
|
||||||
|
|
||||||
<div class="card">
|
<div class="grid-container">
|
||||||
<h2>基础指标</h2>
|
<div class="card">
|
||||||
<div class="metric">
|
<h2>基础指标</h2>
|
||||||
<span class="metric-label">运行时间</span>
|
<div class="metric">
|
||||||
<span class="metric-value" id="uptime"></span>
|
<span class="metric-label">运行时间</span>
|
||||||
|
<span class="metric-value" id="uptime"></span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">当前活跃请求</span>
|
||||||
|
<span class="metric-value" id="activeRequests"></span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">总请求数</span>
|
||||||
|
<span class="metric-value" id="totalRequests"></span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">错误数</span>
|
||||||
|
<span class="metric-value" id="totalErrors"></span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">错误率</span>
|
||||||
|
<span class="metric-value" id="errorRate"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">当前活跃请求</span>
|
|
||||||
<span class="metric-value" id="activeRequests"></span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">总请求数</span>
|
|
||||||
<span class="metric-value" id="totalRequests"></span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">错误数</span>
|
|
||||||
<span class="metric-value" id="totalErrors"></span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">错误率</span>
|
|
||||||
<span class="metric-value" id="errorRate"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>系统指标</h2>
|
<h2>系统指标</h2>
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric-label">Goroutine数量</span>
|
<span class="metric-label">Goroutine数量</span>
|
||||||
<span class="metric-value" id="numGoroutine"></span>
|
<span class="metric-value" id="numGoroutine"></span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">内存使用</span>
|
||||||
|
<span class="metric-value" id="memoryUsage"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">内存使用</span>
|
|
||||||
<span class="metric-value" id="memoryUsage"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>性能指标</h2>
|
<h2>性能指标</h2>
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric-label">平均响应时间</span>
|
<span class="metric-label">平均响应时间</span>
|
||||||
<span class="metric-value" id="avgResponseTime"></span>
|
<span class="metric-value" id="avgResponseTime"></span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">每秒请求数</span>
|
||||||
|
<span class="metric-value" id="requestsPerSecond"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">每秒请求数</span>
|
|
||||||
<span class="metric-value" id="requestsPerSecond"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>流量统计</h2>
|
<h2>流量统计</h2>
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric-label">总传输字节</span>
|
<span class="metric-label">总传输字节</span>
|
||||||
<span class="metric-value" id="totalBytes"></span>
|
<span class="metric-value" id="totalBytes"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric-label">每秒传输</span>
|
<span class="metric-label">每秒传输</span>
|
||||||
<span class="metric-value" id="bytesPerSecond"></span>
|
<span class="metric-value" id="bytesPerSecond"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -362,6 +375,19 @@ var metricsTemplate = `
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>热门引用来源 (Top 10)</h2>
|
||||||
|
<table id="topReferers">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>来源</th>
|
||||||
|
<th>请求数</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span id="lastUpdate"></span>
|
<span id="lastUpdate"></span>
|
||||||
<button class="refresh" onclick="refreshMetrics()">刷新</button>
|
<button class="refresh" onclick="refreshMetrics()">刷新</button>
|
||||||
|
|
||||||
@ -452,6 +478,15 @@ var metricsTemplate = `
|
|||||||
).join('');
|
).join('');
|
||||||
document.querySelector('#recentRequests tbody').innerHTML = recentRequestsHtml;
|
document.querySelector('#recentRequests tbody').innerHTML = recentRequestsHtml;
|
||||||
|
|
||||||
|
// 更新热门引用来源
|
||||||
|
const topReferersHtml = data.top_referers.map(referer =>
|
||||||
|
'<tr>' +
|
||||||
|
'<td>' + referer.path + '</td>' +
|
||||||
|
'<td>' + referer.request_count + '</td>' +
|
||||||
|
'</tr>'
|
||||||
|
).join('');
|
||||||
|
document.querySelector('#topReferers tbody').innerHTML = topReferersHtml;
|
||||||
|
|
||||||
document.getElementById('lastUpdate').textContent = '最后更新: ' + new Date().toLocaleTimeString();
|
document.getElementById('lastUpdate').textContent = '最后更新: ' + new Date().toLocaleTimeString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,5 +138,5 @@ func (h *MirrorProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.GetRequestSource(r), actualURL)
|
utils.GetRequestSource(r), actualURL)
|
||||||
|
|
||||||
// 记录统计信息
|
// 记录统计信息
|
||||||
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(startTime), bytesCopied, utils.GetClientIP(r))
|
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(startTime), bytesCopied, utils.GetClientIP(r), r)
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确定<EFBFBD><EFBFBD><EFBFBD>标基础URL
|
// 确定标基础URL
|
||||||
targetBase := pathConfig.DefaultTarget
|
targetBase := pathConfig.DefaultTarget
|
||||||
|
|
||||||
// 检查文件扩展名
|
// 检查文件扩展名
|
||||||
@ -228,7 +228,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
written, _ := w.Write(body)
|
written, _ := w.Write(body)
|
||||||
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(start), int64(written), utils.GetClientIP(r))
|
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(start), int64(written), utils.GetClientIP(r), r)
|
||||||
} else {
|
} else {
|
||||||
// 大响应使用流式传输
|
// 大响应使用流式传输
|
||||||
var bytesCopied int64
|
var bytesCopied int64
|
||||||
@ -274,7 +274,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
targetURL, // 目标URL
|
targetURL, // 目标URL
|
||||||
)
|
)
|
||||||
|
|
||||||
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(start), bytesCopied, utils.GetClientIP(r))
|
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(start), bytesCopied, utils.GetClientIP(r), r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package metrics
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
@ -17,6 +18,7 @@ type Collector struct {
|
|||||||
totalBytes atomic.Int64
|
totalBytes atomic.Int64
|
||||||
latencySum atomic.Int64
|
latencySum atomic.Int64
|
||||||
pathStats sync.Map
|
pathStats sync.Map
|
||||||
|
refererStats sync.Map
|
||||||
statusStats [6]atomic.Int64
|
statusStats [6]atomic.Int64
|
||||||
latencyBuckets [10]atomic.Int64
|
latencyBuckets [10]atomic.Int64
|
||||||
recentRequests struct {
|
recentRequests struct {
|
||||||
@ -45,7 +47,7 @@ func (c *Collector) EndRequest() {
|
|||||||
atomic.AddInt64(&c.activeRequests, -1)
|
atomic.AddInt64(&c.activeRequests, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Collector) RecordRequest(path string, status int, latency time.Duration, bytes int64, clientIP string) {
|
func (c *Collector) RecordRequest(path string, status int, latency time.Duration, bytes int64, clientIP string, r *http.Request) {
|
||||||
// 更新总请求数
|
// 更新总请求数
|
||||||
atomic.AddInt64(&c.totalRequests, 1)
|
atomic.AddInt64(&c.totalRequests, 1)
|
||||||
|
|
||||||
@ -88,6 +90,17 @@ func (c *Collector) RecordRequest(path string, status int, latency time.Duration
|
|||||||
c.pathStats.Store(path, newStats)
|
c.pathStats.Store(path, newStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新引用来源统计
|
||||||
|
if referer := r.Header.Get("Referer"); referer != "" {
|
||||||
|
if stats, ok := c.refererStats.Load(referer); ok {
|
||||||
|
stats.(*PathStats).requests.Add(1)
|
||||||
|
} else {
|
||||||
|
newStats := &PathStats{}
|
||||||
|
newStats.requests.Add(1)
|
||||||
|
c.refererStats.Store(referer, newStats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 记录最近的请求
|
// 记录最近的请求
|
||||||
log := &RequestLog{
|
log := &RequestLog{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
@ -151,6 +164,33 @@ func (c *Collector) GetStats() map[string]interface{} {
|
|||||||
pathMetrics = allPaths
|
pathMetrics = allPaths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取Top 10引用来源
|
||||||
|
var refererMetrics []PathMetrics
|
||||||
|
var allReferers []PathMetrics
|
||||||
|
c.refererStats.Range(func(key, value interface{}) bool {
|
||||||
|
stats := value.(*PathStats)
|
||||||
|
if stats.requests.Load() == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
allReferers = append(allReferers, PathMetrics{
|
||||||
|
Path: key.(string),
|
||||||
|
RequestCount: stats.requests.Load(),
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// 按请求数排序
|
||||||
|
sort.Slice(allReferers, func(i, j int) bool {
|
||||||
|
return allReferers[i].RequestCount > allReferers[j].RequestCount
|
||||||
|
})
|
||||||
|
|
||||||
|
// 取前10个
|
||||||
|
if len(allReferers) > 10 {
|
||||||
|
refererMetrics = allReferers[:10]
|
||||||
|
} else {
|
||||||
|
refererMetrics = allReferers
|
||||||
|
}
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"uptime": uptime.String(),
|
"uptime": uptime.String(),
|
||||||
"active_requests": atomic.LoadInt64(&c.activeRequests),
|
"active_requests": atomic.LoadInt64(&c.activeRequests),
|
||||||
@ -170,6 +210,7 @@ func (c *Collector) GetStats() map[string]interface{} {
|
|||||||
"status_code_stats": statusStats,
|
"status_code_stats": statusStats,
|
||||||
"top_paths": pathMetrics,
|
"top_paths": pathMetrics,
|
||||||
"recent_requests": c.getRecentRequests(),
|
"recent_requests": c.getRecentRequests(),
|
||||||
|
"top_referers": refererMetrics,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ func FixedPathProxyMiddleware(configs []config.FixedPathConfig) func(http.Handle
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 记录统计信息
|
// 记录统计信息
|
||||||
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(startTime), bytesCopied, utils.GetClientIP(r))
|
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(startTime), bytesCopied, utils.GetClientIP(r), r)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user