mirror of
https://github.com/woodchen-ink/proxy-go.git
synced 2025-07-18 16:41:54 +08:00
refactor(metrics): remove deprecated metrics history endpoint and clean up related code
- Removed the MetricsHistoryHandler and associated historical data export functionality to streamline the metrics handling process. - Updated the metrics template to improve label translations and ensure default values are used for optional data fields. - Enhanced the JavaScript code for metrics display by adding checks for undefined data arrays, improving robustness. - Cleaned up unused variables and functions to maintain code clarity and reduce complexity. These changes simplify the metrics system by eliminating outdated features and improving the overall reliability of data presentation.
This commit is contained in:
parent
b82096227c
commit
1440eec7ed
@ -2,7 +2,6 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"proxy-go/internal/metrics"
|
"proxy-go/internal/metrics"
|
||||||
@ -512,7 +511,7 @@ var metricsTemplate = `
|
|||||||
<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>
|
||||||
@ -571,7 +570,7 @@ var metricsTemplate = `
|
|||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>历史数据</h2>
|
<h2>历史数据</h2>
|
||||||
<div class="controls">
|
<div class="controls" style="margin-bottom: 20px;">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="autoRefresh" checked>
|
<input type="checkbox" id="autoRefresh" checked>
|
||||||
自动刷新
|
自动刷新
|
||||||
@ -582,12 +581,6 @@ var metricsTemplate = `
|
|||||||
<option value="30000">30秒</option>
|
<option value="30000">30秒</option>
|
||||||
<option value="60000">1分钟</option>
|
<option value="60000">1分钟</option>
|
||||||
</select>
|
</select>
|
||||||
<button onclick="exportData()" class="export-btn">
|
|
||||||
导出数据
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="time-range-info">
|
|
||||||
显示最近30天的数据
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -646,7 +639,7 @@ var metricsTemplate = `
|
|||||||
|
|
||||||
// 更新状态码计
|
// 更新状态码计
|
||||||
const statusCodesHtml = '<div class="status-row">' +
|
const statusCodesHtml = '<div class="status-row">' +
|
||||||
Object.entries(data.status_code_stats)
|
Object.entries(data.status_code_stats || {})
|
||||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||||
.map(([status, count]) => {
|
.map(([status, count]) => {
|
||||||
const firstDigit = status.charAt(0);
|
const firstDigit = status.charAt(0);
|
||||||
@ -664,7 +657,7 @@ var metricsTemplate = `
|
|||||||
statusCodesContainer.innerHTML = statusCodesHtml;
|
statusCodesContainer.innerHTML = statusCodesHtml;
|
||||||
|
|
||||||
// 更新热门路径
|
// 更新热门路径
|
||||||
const topPathsHtml = data.top_paths.map(path =>
|
const topPathsHtml = (data.top_paths || []).map(path =>
|
||||||
'<tr>' +
|
'<tr>' +
|
||||||
'<td>' + path.path + '</td>' +
|
'<td>' + path.path + '</td>' +
|
||||||
'<td>' + path.request_count + '</td>' +
|
'<td>' + path.request_count + '</td>' +
|
||||||
@ -676,7 +669,7 @@ var metricsTemplate = `
|
|||||||
document.querySelector('#topPaths tbody').innerHTML = topPathsHtml;
|
document.querySelector('#topPaths tbody').innerHTML = topPathsHtml;
|
||||||
|
|
||||||
// 更新最近请求
|
// 更新最近请求
|
||||||
const recentRequestsHtml = data.recent_requests.map(req =>
|
const recentRequestsHtml = (data.recent_requests || []).map(req =>
|
||||||
'<tr>' +
|
'<tr>' +
|
||||||
'<td>' + formatDate(req.Time) + '</td>' +
|
'<td>' + formatDate(req.Time) + '</td>' +
|
||||||
'<td>' + req.Path + '</td>' +
|
'<td>' + req.Path + '</td>' +
|
||||||
@ -689,7 +682,7 @@ var metricsTemplate = `
|
|||||||
document.querySelector('#recentRequests tbody').innerHTML = recentRequestsHtml;
|
document.querySelector('#recentRequests tbody').innerHTML = recentRequestsHtml;
|
||||||
|
|
||||||
// 更新热门引用来源
|
// 更新热门引用来源
|
||||||
const topReferersHtml = data.top_referers.map(referer =>
|
const topReferersHtml = (data.top_referers || []).map(referer =>
|
||||||
'<tr>' +
|
'<tr>' +
|
||||||
'<td>' + referer.path + '</td>' +
|
'<td>' + referer.path + '</td>' +
|
||||||
'<td>' + referer.request_count + '</td>' +
|
'<td>' + referer.request_count + '</td>' +
|
||||||
@ -732,187 +725,6 @@ var metricsTemplate = `
|
|||||||
.catch(error => showError(error.message));
|
.catch(error => showError(error.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始加载
|
|
||||||
refreshMetrics();
|
|
||||||
|
|
||||||
// 每5秒自动刷新
|
|
||||||
setInterval(refreshMetrics, 5000);
|
|
||||||
|
|
||||||
// 修改图表相关代码
|
|
||||||
let currentCharts = {
|
|
||||||
requests: null,
|
|
||||||
errorRate: null,
|
|
||||||
bytes: null
|
|
||||||
};
|
|
||||||
|
|
||||||
function loadHistoryData(hours) {
|
|
||||||
const chartContainer = document.getElementById('historyChart');
|
|
||||||
chartContainer.classList.add('loading');
|
|
||||||
|
|
||||||
fetch('/metrics/history?hours=' + hours, {
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + token
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (!Array.isArray(data)) {
|
|
||||||
console.error('Invalid data format');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 反转数据顺序,使时间从左到右
|
|
||||||
data.reverse();
|
|
||||||
|
|
||||||
const labels = data.map(m => {
|
|
||||||
const date = new Date(m.timestamp);
|
|
||||||
if (hours <= 24) {
|
|
||||||
return date.toLocaleTimeString();
|
|
||||||
} else if (hours <= 168) {
|
|
||||||
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
|
|
||||||
} else {
|
|
||||||
return date.toLocaleDateString();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const commonOptions = {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
callbacks: {
|
|
||||||
label: function(context) {
|
|
||||||
let label = context.dataset.label || '';
|
|
||||||
if (label) {
|
|
||||||
label += ': ';
|
|
||||||
}
|
|
||||||
if (context.parsed.y !== null) {
|
|
||||||
if (context.chart.canvas.id === 'bytesChart') {
|
|
||||||
label += formatBytes(context.parsed.y * 1024 * 1024);
|
|
||||||
} else if (context.chart.canvas.id === 'errorRateChart') {
|
|
||||||
label += context.parsed.y.toFixed(2) + '%';
|
|
||||||
} else {
|
|
||||||
label += context.parsed.y.toLocaleString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return label;
|
|
||||||
},
|
|
||||||
title: function(tooltipItems) {
|
|
||||||
const date = new Date(tooltipItems[0].label);
|
|
||||||
return date.toLocaleString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
display: true,
|
|
||||||
grid: {
|
|
||||||
display: false
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
maxRotation: 45,
|
|
||||||
minRotation: 45,
|
|
||||||
autoSkip: true,
|
|
||||||
maxTicksLimit: 20
|
|
||||||
}
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
beginAtZero: true,
|
|
||||||
grid: {
|
|
||||||
drawBorder: false
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
callback: function(value) {
|
|
||||||
if (this.chart.canvas.id === 'bytesChart') {
|
|
||||||
return formatBytes(value * 1024 * 1024);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
elements: {
|
|
||||||
line: {
|
|
||||||
tension: 0.4
|
|
||||||
},
|
|
||||||
point: {
|
|
||||||
radius: 2,
|
|
||||||
hitRadius: 10,
|
|
||||||
hoverRadius: 5
|
|
||||||
}
|
|
||||||
},
|
|
||||||
interaction: {
|
|
||||||
mode: 'nearest',
|
|
||||||
axis: 'x',
|
|
||||||
intersect: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 更新或创建图表
|
|
||||||
updateChart('requestsChart', 'requests', labels, data, '请求数',
|
|
||||||
m => m.total_requests, '#007bff', commonOptions);
|
|
||||||
updateChart('errorRateChart', 'errorRate', labels, data, '错误率 (%)',
|
|
||||||
m => m.error_rate * 100, '#dc3545', commonOptions);
|
|
||||||
updateChart('bytesChart', 'bytes', labels, data, '流量 (MB)',
|
|
||||||
m => m.total_bytes / (1024 * 1024), '#28a745', commonOptions);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
chartContainer.classList.remove('loading');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateChart(canvasId, chartKey, labels, data, label, valueGetter, color, options) {
|
|
||||||
// 保 canvas 元素存在
|
|
||||||
const canvas = document.getElementById(canvasId);
|
|
||||||
if (!canvas) {
|
|
||||||
console.error('Canvas element ' + canvasId + ' not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果存在旧图表,销毁它
|
|
||||||
if (currentCharts[chartKey]) {
|
|
||||||
currentCharts[chartKey].destroy();
|
|
||||||
currentCharts[chartKey] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
const chartData = {
|
|
||||||
labels: labels,
|
|
||||||
datasets: [{
|
|
||||||
label: label,
|
|
||||||
data: data.map(valueGetter),
|
|
||||||
borderColor: color,
|
|
||||||
backgroundColor: color + '20',
|
|
||||||
fill: true
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建新图表
|
|
||||||
currentCharts[chartKey] = new Chart(ctx, {
|
|
||||||
type: 'line',
|
|
||||||
data: chartData,
|
|
||||||
options: options
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 时间范围按钮处理
|
|
||||||
document.querySelectorAll('.time-btn').forEach(btn => {
|
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
document.querySelectorAll('.time-btn').forEach(b => b.classList.remove('active'));
|
|
||||||
this.classList.add('active');
|
|
||||||
loadHistoryData(parseFloat(this.dataset.hours));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始加载历史数据
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
loadHistoryData(24);
|
|
||||||
});
|
|
||||||
|
|
||||||
let refreshTimer;
|
let refreshTimer;
|
||||||
|
|
||||||
function setupAutoRefresh() {
|
function setupAutoRefresh() {
|
||||||
@ -935,51 +747,10 @@ var metricsTemplate = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
loadHistoryData(24);
|
refreshMetrics(); // 立即加载一次数据
|
||||||
setupAutoRefresh();
|
setupAutoRefresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
function exportData() {
|
|
||||||
const activeBtn = document.querySelector('.time-btn.active');
|
|
||||||
const hours = parseInt(activeBtn.dataset.hours);
|
|
||||||
|
|
||||||
fetch('/metrics/history?hours=' + hours, {
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + token
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
const csv = convertToCSV(data);
|
|
||||||
downloadCSV(csv, 'metrics_' + hours + 'h_' + new Date().toISOString() + '.csv');
|
|
||||||
})
|
|
||||||
.catch(error => showError('导出失败: ' + error.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertToCSV(data) {
|
|
||||||
const headers = ['时间', '请求数', '错误数', '流量(MB)', '错误率(%)'];
|
|
||||||
const rows = data.map(row => [
|
|
||||||
row.timestamp,
|
|
||||||
row.total_requests,
|
|
||||||
row.total_errors,
|
|
||||||
(row.total_bytes / (1024 * 1024)).toFixed(2),
|
|
||||||
(row.error_rate * 100).toFixed(2)
|
|
||||||
]);
|
|
||||||
return [headers, ...rows].map(row => row.join(',')).join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadCSV(csv, filename) {
|
|
||||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = URL.createObjectURL(blob);
|
|
||||||
link.download = filename;
|
|
||||||
link.click();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- 添加 Chart.js -->
|
|
||||||
<!-- 在 body 结束标签前添加 -->
|
|
||||||
<script src="https://i-aws.czl.net/jsdelivr/npm/chart.js@3.7.0/dist/chart.min.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
@ -1042,39 +813,6 @@ func (h *ProxyHandler) MetricsAuthHandler(w http.ResponseWriter, r *http.Request
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加历史数据查询接口
|
|
||||||
func (h *ProxyHandler) MetricsHistoryHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
collector := metrics.GetCollector()
|
|
||||||
metrics := collector.GetHistoricalData()
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
json.NewEncoder(w).Encode(metrics)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助函数:解析延迟字符串
|
|
||||||
func parseLatency(latency string) (float64, error) {
|
|
||||||
var value float64
|
|
||||||
var unit string
|
|
||||||
_, err := fmt.Sscanf(latency, "%f %s", &value, &unit)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据单位转换为毫秒
|
|
||||||
switch unit {
|
|
||||||
case "μs":
|
|
||||||
value = value / 1000 // 微秒转毫秒
|
|
||||||
case "ms":
|
|
||||||
// 已经是毫秒
|
|
||||||
case "s":
|
|
||||||
value = value * 1000 // 秒转毫秒
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unknown unit: %s", unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加安全的类型转换辅助函数
|
// 添加安全的类型转换辅助函数
|
||||||
func safeStatusCodeStats(v interface{}) map[string]int64 {
|
func safeStatusCodeStats(v interface{}) map[string]int64 {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
|
3
main.go
3
main.go
@ -89,9 +89,6 @@ func main() {
|
|||||||
case "/metrics/dashboard":
|
case "/metrics/dashboard":
|
||||||
proxyHandler.MetricsDashboardHandler(w, r)
|
proxyHandler.MetricsDashboardHandler(w, r)
|
||||||
return
|
return
|
||||||
case "/metrics/history":
|
|
||||||
proxyHandler.AuthMiddleware(proxyHandler.MetricsHistoryHandler)(w, r)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 遍历所有处理器
|
// 遍历所有处理器
|
||||||
|
Loading…
x
Reference in New Issue
Block a user