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:
wood chen 2024-11-30 22:55:31 +08:00
parent 9be13ce0ef
commit d4af4c4d31
5 changed files with 133 additions and 57 deletions

View File

@ -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,11 +261,21 @@ 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="grid-container">
<div class="card"> <div class="card">
<h2>基础指标</h2> <h2>基础指标</h2>
<div class="metric"> <div class="metric">
@ -323,6 +335,7 @@ var metricsTemplate = `
<span class="metric-value" id="bytesPerSecond"></span> <span class="metric-value" id="bytesPerSecond"></span>
</div> </div>
</div> </div>
</div>
<div class="card"> <div class="card">
<h2>状态码统计</h2> <h2>状态码统计</h2>
@ -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();
} }

View File

@ -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)
} }

View File

@ -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)
} }
} }

View File

@ -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,
} }
} }

View File

@ -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
} }