mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-11 01:36:37 +08:00
fix(openflare): ClickHouse count scan and dashboard traffic metrics
- Scan ClickHouse count()/countIf() aggregates as uint64 before int64 conversion - Fix access log list count, region stats, and delete pre-count queries - Aggregate dashboard 24h traffic totals from hourly trend buckets - Show current hour value in traffic trend chart summary - Rename docker-compose service to openflare - Remove redundant config preview section from performance page
This commit is contained in:
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
services:
|
services:
|
||||||
wavelet:
|
openflare:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
args:
|
args:
|
||||||
VERSION: v0.9.9
|
VERSION: v0.9.9
|
||||||
# image: ghcr.io/rain-kl/wavelet:latest
|
# image: ghcr.io/rain-kl/openflare-server:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file: .env
|
env_file: .env
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ sidebar: false
|
|||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|
||||||
|
- 修复总览看板「24 小时请求趋势」摘要误展示 24 小时累计值的问题:趋势图摘要改为「当前小时」桶数据,顶部 24h 统计改为按小时趋势聚合。
|
||||||
- 修复访问日志 ClickHouse 聚合查询因 `trim(x) AS x` 别名与表列同名导致总览看板地域分布及 IP 统计失败的问题。
|
- 修复访问日志 ClickHouse 聚合查询因 `trim(x) AS x` 别名与表列同名导致总览看板地域分布及 IP 统计失败的问题。
|
||||||
|
- 修复访问日志页 `count()` 扫描类型不匹配(ClickHouse `UInt64` 写入 `int64`)导致列表计数失败的问题。
|
||||||
|
|
||||||
### 变更
|
### 变更
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export function TrafficTrendChart({
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<TrendChart
|
<TrendChart
|
||||||
labels={points.map((point) => formatTrendHour(point.bucket_started_at))}
|
labels={points.map((point) => formatTrendHour(point.bucket_started_at))}
|
||||||
|
summaryScope="last-point"
|
||||||
|
summaryHint="当前小时"
|
||||||
series={[
|
series={[
|
||||||
{
|
{
|
||||||
label: '请求量',
|
label: '请求量',
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {Input} from "@/components/ui/input"
|
|||||||
import {Label} from "@/components/ui/label"
|
import {Label} from "@/components/ui/label"
|
||||||
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "@/components/ui/select"
|
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "@/components/ui/select"
|
||||||
import {Switch} from "@/components/ui/switch"
|
import {Switch} from "@/components/ui/switch"
|
||||||
import {ConfigVersionService, OptionService} from "@/lib/services/openflare"
|
import {OptionService} from "@/lib/services/openflare"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
defaultPerformanceFields,
|
defaultPerformanceFields,
|
||||||
@@ -44,12 +44,6 @@ export default function PerformancePage() {
|
|||||||
enabled: !!user?.is_admin,
|
enabled: !!user?.is_admin,
|
||||||
})
|
})
|
||||||
|
|
||||||
const previewQuery = useQuery({
|
|
||||||
queryKey: ["openflare", "config-preview"],
|
|
||||||
queryFn: () => ConfigVersionService.preview(),
|
|
||||||
enabled: !!user?.is_admin,
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!optionsQuery.data) return
|
if (!optionsQuery.data) return
|
||||||
setFields(mapOptionsToFields(optionsToMap(optionsQuery.data)))
|
setFields(mapOptionsToFields(optionsToMap(optionsQuery.data)))
|
||||||
@@ -154,25 +148,6 @@ export default function PerformancePage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-3 sm:grid-cols-3">
|
|
||||||
<div className="rounded-lg border border-dashed px-4 py-3">
|
|
||||||
<p className="text-[10px] uppercase tracking-wider text-muted-foreground">发布链路</p>
|
|
||||||
<p className="mt-2 text-sm font-medium">受管模板渲染</p>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-lg border border-dashed px-4 py-3">
|
|
||||||
<p className="text-[10px] uppercase tracking-wider text-muted-foreground">预览规则数</p>
|
|
||||||
<p className="mt-2 text-sm font-semibold">
|
|
||||||
{previewQuery.data?.route_count ?? "—"} 条
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-lg border border-dashed px-4 py-3">
|
|
||||||
<p className="text-[10px] uppercase tracking-wider text-muted-foreground">配置预览</p>
|
|
||||||
<p className="mt-2 text-sm text-muted-foreground">
|
|
||||||
在配置发布页查看完整 nginx 渲染结果
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card className="border-dashed shadow-none">
|
<Card className="border-dashed shadow-none">
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -15,11 +15,16 @@ type TrendChartSeries = {
|
|||||||
valueFormatter?: (value: number) => string;
|
valueFormatter?: (value: number) => string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TrendChartSummaryScope = 'last-point' | 'total';
|
||||||
|
|
||||||
type TrendChartProps = {
|
type TrendChartProps = {
|
||||||
labels: string[];
|
labels: string[];
|
||||||
series: TrendChartSeries[];
|
series: TrendChartSeries[];
|
||||||
height?: number;
|
height?: number;
|
||||||
yAxisValueFormatter?: (value: number) => string;
|
yAxisValueFormatter?: (value: number) => string;
|
||||||
|
showSummary?: boolean;
|
||||||
|
summaryScope?: TrendChartSummaryScope;
|
||||||
|
summaryHint?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TooltipParam = {
|
type TooltipParam = {
|
||||||
@@ -31,11 +36,24 @@ type TooltipParam = {
|
|||||||
|
|
||||||
const defaultFormatter = (value: number) => formatCompactNumber(value);
|
const defaultFormatter = (value: number) => formatCompactNumber(value);
|
||||||
|
|
||||||
|
function resolveSummaryValue(values: number[], scope: TrendChartSummaryScope) {
|
||||||
|
if (values.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (scope === 'total') {
|
||||||
|
return values.reduce((sum, value) => sum + value, 0);
|
||||||
|
}
|
||||||
|
return values[values.length - 1] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
export function TrendChart({
|
export function TrendChart({
|
||||||
labels,
|
labels,
|
||||||
series,
|
series,
|
||||||
height = 220,
|
height = 220,
|
||||||
yAxisValueFormatter,
|
yAxisValueFormatter,
|
||||||
|
showSummary = true,
|
||||||
|
summaryScope = 'last-point',
|
||||||
|
summaryHint,
|
||||||
}: TrendChartProps) {
|
}: TrendChartProps) {
|
||||||
const option = useMemo<EChartsOption>(() => {
|
const option = useMemo<EChartsOption>(() => {
|
||||||
const axisFormatter = yAxisValueFormatter ?? defaultFormatter;
|
const axisFormatter = yAxisValueFormatter ?? defaultFormatter;
|
||||||
@@ -165,31 +183,38 @@ export function TrendChart({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex flex-wrap gap-3">
|
{showSummary ? (
|
||||||
{series.map((item) => {
|
<div className="flex flex-wrap gap-3">
|
||||||
const latestValue = item.values[item.values.length - 1] ?? 0;
|
{series.map((item) => {
|
||||||
const formatter = item.valueFormatter ?? defaultFormatter;
|
const summaryValue = resolveSummaryValue(item.values, summaryScope);
|
||||||
return (
|
const formatter = item.valueFormatter ?? defaultFormatter;
|
||||||
<div
|
return (
|
||||||
key={item.label}
|
<div
|
||||||
className="min-w-[140px] rounded-2xl border bg-card px-4 py-3"
|
key={item.label}
|
||||||
>
|
className="min-w-[140px] rounded-2xl border bg-card px-4 py-3"
|
||||||
<div className="flex items-center gap-2">
|
>
|
||||||
<span
|
<div className="flex items-center gap-2">
|
||||||
className="h-2.5 w-2.5 rounded-full"
|
<span
|
||||||
style={{backgroundColor: item.color}}
|
className="h-2.5 w-2.5 rounded-full"
|
||||||
/>
|
style={{backgroundColor: item.color}}
|
||||||
<p className="text-xs tracking-[0.18em] text-muted-foreground uppercase">
|
/>
|
||||||
{item.label}
|
<p className="text-xs tracking-[0.18em] text-muted-foreground uppercase">
|
||||||
|
{item.label}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p className="mt-2 text-lg font-semibold">
|
||||||
|
{formatter(summaryValue)}
|
||||||
</p>
|
</p>
|
||||||
|
{summaryHint ? (
|
||||||
|
<p className="mt-1 text-[10px] text-muted-foreground">
|
||||||
|
{summaryHint}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-2 text-lg font-semibold">
|
);
|
||||||
{formatter(latestValue)}
|
})}
|
||||||
</p>
|
</div>
|
||||||
</div>
|
) : null}
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="overflow-hidden rounded-[28px] border bg-linear-to-b from-white/3 to-transparent px-4 py-4 dark:from-white/3">
|
<div className="overflow-hidden rounded-[28px] border bg-linear-to-b from-white/3 to-transparent px-4 py-4 dark:from-white/3">
|
||||||
<ReactECharts
|
<ReactECharts
|
||||||
|
|||||||
@@ -184,11 +184,14 @@ func buildOverviewView(ctx context.Context) (*OverviewView, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cpuNodeCount, memoryNodeCount = applyNodeSnapshotMetrics(&nodeHealth, latestSnapshot, view, cpuNodeCount, memoryNodeCount)
|
cpuNodeCount, memoryNodeCount = applyNodeSnapshotMetrics(&nodeHealth, latestSnapshot, view, cpuNodeCount, memoryNodeCount)
|
||||||
applyNodeTrafficMetrics(&nodeHealth, latestTraffic, view)
|
applyNodeTrafficMetrics(&nodeHealth, latestTraffic)
|
||||||
|
|
||||||
view.Nodes = append(view.Nodes, nodeHealth)
|
view.Nodes = append(view.Nodes, nodeHealth)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyTrafficTotalsFromTrend(&view.Traffic, view.Trends.Traffic24h)
|
||||||
|
applyTrafficRuntimeMetrics(&view.Traffic, latestTrafficReports)
|
||||||
|
|
||||||
view.Summary.TotalNodes = len(nodes)
|
view.Summary.TotalNodes = len(nodes)
|
||||||
if cpuNodeCount > 0 {
|
if cpuNodeCount > 0 {
|
||||||
view.Capacity.AverageCPUUsagePercent /= float64(cpuNodeCount)
|
view.Capacity.AverageCPUUsagePercent /= float64(cpuNodeCount)
|
||||||
@@ -234,20 +237,44 @@ func applyNodeSnapshotMetrics(nodeHealth *NodeHealth, snapshot *model.OpenFlareM
|
|||||||
return cpuNodeCount, memoryNodeCount
|
return cpuNodeCount, memoryNodeCount
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyNodeTrafficMetrics(nodeHealth *NodeHealth, traffic *model.OpenFlareRequestReport, view *OverviewView) {
|
func applyNodeTrafficMetrics(nodeHealth *NodeHealth, traffic *model.OpenFlareRequestReport) {
|
||||||
if traffic == nil {
|
if traffic == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
nodeHealth.RequestCount = traffic.RequestCount
|
nodeHealth.RequestCount = traffic.RequestCount
|
||||||
nodeHealth.ErrorCount = traffic.ErrorCount
|
nodeHealth.ErrorCount = traffic.ErrorCount
|
||||||
nodeHealth.UniqueVisitorCount = traffic.UniqueVisitorCount
|
nodeHealth.UniqueVisitorCount = traffic.UniqueVisitorCount
|
||||||
view.Traffic.RequestCount += traffic.RequestCount
|
}
|
||||||
view.Traffic.UniqueVisitors += traffic.UniqueVisitorCount
|
|
||||||
view.Traffic.ErrorCount += traffic.ErrorCount
|
func applyTrafficTotalsFromTrend(traffic *Traffic, points []observability.TrafficTrendPoint) {
|
||||||
if duration := traffic.WindowEndedAt.Sub(traffic.WindowStartedAt).Seconds(); duration > 0 {
|
if traffic == nil {
|
||||||
view.Traffic.EstimatedQPS += float64(traffic.RequestCount) / duration
|
return
|
||||||
|
}
|
||||||
|
traffic.RequestCount = 0
|
||||||
|
traffic.ErrorCount = 0
|
||||||
|
for _, point := range points {
|
||||||
|
traffic.RequestCount += point.RequestCount
|
||||||
|
traffic.ErrorCount += point.ErrorCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyTrafficRuntimeMetrics(traffic *Traffic, latestReports map[string]*model.OpenFlareRequestReport) {
|
||||||
|
if traffic == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
traffic.UniqueVisitors = 0
|
||||||
|
traffic.EstimatedQPS = 0
|
||||||
|
traffic.ReportedNodes = 0
|
||||||
|
for _, report := range latestReports {
|
||||||
|
if report == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
traffic.UniqueVisitors += report.UniqueVisitorCount
|
||||||
|
traffic.ReportedNodes++
|
||||||
|
if duration := report.WindowEndedAt.Sub(report.WindowStartedAt).Seconds(); duration > 0 {
|
||||||
|
traffic.EstimatedQPS += float64(report.RequestCount) / duration
|
||||||
|
}
|
||||||
}
|
}
|
||||||
view.Traffic.ReportedNodes++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func compressOverview(view *OverviewView) *OverviewPayload {
|
func compressOverview(view *OverviewView) *OverviewPayload {
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package observability
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildTrafficTrendPointsBucketsByHour(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
now := time.Date(2026, 6, 19, 17, 30, 0, 0, time.UTC)
|
||||||
|
reports := []*model.OpenFlareRequestReport{
|
||||||
|
{
|
||||||
|
NodeID: "node-a",
|
||||||
|
WindowStartedAt: now.Add(-3 * time.Hour),
|
||||||
|
WindowEndedAt: now.Add(-3*time.Hour + time.Minute),
|
||||||
|
RequestCount: 10,
|
||||||
|
ErrorCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NodeID: "node-a",
|
||||||
|
WindowStartedAt: now.Add(-30 * time.Minute),
|
||||||
|
WindowEndedAt: now.Add(-29 * time.Minute),
|
||||||
|
RequestCount: 6,
|
||||||
|
ErrorCount: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
points := BuildTrafficTrendPoints(now, reports)
|
||||||
|
if len(points) != observabilityTrendBuckets {
|
||||||
|
t.Fatalf("BuildTrafficTrendPoints() len = %d, want %d", len(points), observabilityTrendBuckets)
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalRequests int64
|
||||||
|
for _, point := range points {
|
||||||
|
totalRequests += point.RequestCount
|
||||||
|
}
|
||||||
|
if totalRequests != 16 {
|
||||||
|
t.Fatalf("total request_count = %d, want 16", totalRequests)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentHour := points[len(points)-1]
|
||||||
|
if currentHour.RequestCount != 6 {
|
||||||
|
t.Fatalf("current hour request_count = %d, want 6", currentHour.RequestCount)
|
||||||
|
}
|
||||||
|
if currentHour.ErrorCount != 0 {
|
||||||
|
t.Fatalf("current hour error_count = %d, want 0", currentHour.ErrorCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,13 +69,6 @@ func ListAccessLogs(ctx context.Context, filter AccessLogFilter, page, pageSize
|
|||||||
return logs, safeUint64Count(total), nil
|
return logs, safeUint64Count(total), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func safeUint64Count(count int64) uint64 {
|
|
||||||
if count < 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return uint64(count)
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyFilter(query *gorm.DB, filter AccessLogFilter) *gorm.DB {
|
func applyFilter(query *gorm.DB, filter AccessLogFilter) *gorm.DB {
|
||||||
if filter.UserIDs != nil {
|
if filter.UserIDs != nil {
|
||||||
if len(filter.UserIDs) == 0 {
|
if len(filter.UserIDs) == 0 {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package analytics
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
func safeUint64Count(count int64) uint64 {
|
||||||
|
if count < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return uint64(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeInt64Count(count uint64) int64 {
|
||||||
|
if count > math.MaxInt64 {
|
||||||
|
return math.MaxInt64
|
||||||
|
}
|
||||||
|
return int64(count)
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package analytics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSafeInt64Count(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
count uint64
|
||||||
|
want int64
|
||||||
|
}{
|
||||||
|
{name: "zero", count: 0, want: 0},
|
||||||
|
{name: "small", count: 42, want: 42},
|
||||||
|
{name: "max int64", count: math.MaxInt64, want: math.MaxInt64},
|
||||||
|
{name: "overflow clamps", count: math.MaxUint64, want: math.MaxInt64},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if got := safeInt64Count(tt.count); got != tt.want {
|
||||||
|
t.Fatalf("safeInt64Count(%d) = %d, want %d", tt.count, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSafeUint64Count(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
count int64
|
||||||
|
want uint64
|
||||||
|
}{
|
||||||
|
{name: "zero", count: 0, want: 0},
|
||||||
|
{name: "positive", count: 42, want: 42},
|
||||||
|
{name: "negative clamps", count: -1, want: 0},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if got := safeUint64Count(tt.count); got != tt.want {
|
||||||
|
t.Fatalf("safeUint64Count(%d) = %d, want %d", tt.count, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,7 +87,7 @@ func CountNodeAccessLogs(ctx context.Context, filter NodeAccessLogFilter) (int64
|
|||||||
clause, args := buildNodeAccessLogFilterClause(filter)
|
clause, args := buildNodeAccessLogFilterClause(filter)
|
||||||
tableName := nodeAccessLogTableName()
|
tableName := nodeAccessLogTableName()
|
||||||
|
|
||||||
var totalRecords int64
|
var totalRecords uint64
|
||||||
countSQL := fmt.Sprintf("SELECT count() FROM %s WHERE %s", tableName, clause)
|
countSQL := fmt.Sprintf("SELECT count() FROM %s WHERE %s", tableName, clause)
|
||||||
if err := conn.QueryRow(ctx, countSQL, args...).Scan(&totalRecords); err != nil {
|
if err := conn.QueryRow(ctx, countSQL, args...).Scan(&totalRecords); err != nil {
|
||||||
return 0, 0, fmt.Errorf("count node access logs: %w", err)
|
return 0, 0, fmt.Errorf("count node access logs: %w", err)
|
||||||
@@ -100,11 +100,11 @@ SELECT count() FROM (
|
|||||||
WHERE %s AND trim(remote_addr) != ''
|
WHERE %s AND trim(remote_addr) != ''
|
||||||
GROUP BY trimmed_remote_addr
|
GROUP BY trimmed_remote_addr
|
||||||
)`, tableName, clause)
|
)`, tableName, clause)
|
||||||
var totalIPs int64
|
var totalIPs uint64
|
||||||
if err := conn.QueryRow(ctx, ipSQL, args...).Scan(&totalIPs); err != nil {
|
if err := conn.QueryRow(ctx, ipSQL, args...).Scan(&totalIPs); err != nil {
|
||||||
return 0, 0, fmt.Errorf("count node access log ips: %w", err)
|
return 0, 0, fmt.Errorf("count node access log ips: %w", err)
|
||||||
}
|
}
|
||||||
return totalRecords, totalIPs, nil
|
return safeInt64Count(totalRecords), safeInt64Count(totalIPs), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegionCountsNodeAccessLogs returns region counts for a node since a time.
|
// RegionCountsNodeAccessLogs returns region counts for a node since a time.
|
||||||
@@ -134,11 +134,17 @@ ORDER BY count DESC, trimmed_region ASC`, tableName, clause)
|
|||||||
|
|
||||||
var result []NodeAccessLogRegionCount
|
var result []NodeAccessLogRegionCount
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var item NodeAccessLogRegionCount
|
var (
|
||||||
if err := rows.Scan(&item.Region, &item.Count); err != nil {
|
region string
|
||||||
|
count uint64
|
||||||
|
)
|
||||||
|
if err := rows.Scan(®ion, &count); err != nil {
|
||||||
return nil, fmt.Errorf("scan region count row: %w", err)
|
return nil, fmt.Errorf("scan region count row: %w", err)
|
||||||
}
|
}
|
||||||
result = append(result, item)
|
result = append(result, NodeAccessLogRegionCount{
|
||||||
|
Region: region,
|
||||||
|
Count: safeInt64Count(count),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func deleteNodeAccessLogsWithCount(ctx context.Context, countSQL string, countAr
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
var count int64
|
var count uint64
|
||||||
if err := conn.QueryRow(ctx, countSQL, countArgs...).Scan(&count); err != nil {
|
if err := conn.QueryRow(ctx, countSQL, countArgs...).Scan(&count); err != nil {
|
||||||
return 0, fmt.Errorf("count node access logs for delete: %w", err)
|
return 0, fmt.Errorf("count node access logs for delete: %w", err)
|
||||||
}
|
}
|
||||||
@@ -56,5 +56,5 @@ func deleteNodeAccessLogsWithCount(ctx context.Context, countSQL string, countAr
|
|||||||
if err := conn.Exec(ctx, deleteSQL, deleteArgs...); err != nil {
|
if err := conn.Exec(ctx, deleteSQL, deleteArgs...); err != nil {
|
||||||
return 0, fmt.Errorf("delete node access logs: %w", err)
|
return 0, fmt.Errorf("delete node access logs: %w", err)
|
||||||
}
|
}
|
||||||
return count, nil
|
return safeInt64Count(count), nil
|
||||||
}
|
}
|
||||||
@@ -76,11 +76,20 @@ GROUP BY bucket_epoch`, bucketExpr, tableName, clause)
|
|||||||
|
|
||||||
var result []NodeAccessLogBucketAggregate
|
var result []NodeAccessLogBucketAggregate
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var item NodeAccessLogBucketAggregate
|
var (
|
||||||
if err := rows.Scan(&item.BucketEpoch, &item.RequestCount, &item.SuccessCount, &item.ClientErrorCount, &item.ServerErrorCount); err != nil {
|
bucketEpoch int64
|
||||||
|
requestCount, successCount, clientErrorCount, serverErrorCount uint64
|
||||||
|
)
|
||||||
|
if err := rows.Scan(&bucketEpoch, &requestCount, &successCount, &clientErrorCount, &serverErrorCount); err != nil {
|
||||||
return nil, fmt.Errorf("scan bucket aggregate row: %w", err)
|
return nil, fmt.Errorf("scan bucket aggregate row: %w", err)
|
||||||
}
|
}
|
||||||
result = append(result, item)
|
result = append(result, NodeAccessLogBucketAggregate{
|
||||||
|
BucketEpoch: bucketEpoch,
|
||||||
|
RequestCount: safeInt64Count(requestCount),
|
||||||
|
SuccessCount: safeInt64Count(successCount),
|
||||||
|
ClientErrorCount: safeInt64Count(clientErrorCount),
|
||||||
|
ServerErrorCount: safeInt64Count(serverErrorCount),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
@@ -156,11 +165,22 @@ GROUP BY trimmed_remote_addr`, lastSeenExpr, tableName, queryClause)
|
|||||||
|
|
||||||
var result []NodeAccessLogIPAggregate
|
var result []NodeAccessLogIPAggregate
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var item NodeAccessLogIPAggregate
|
var (
|
||||||
if err := rows.Scan(&item.RemoteAddr, &item.RequestCount, &item.SuccessCount, &item.ClientErrorCount, &item.ServerErrorCount, &item.LastSeenEpoch); err != nil {
|
remoteAddr string
|
||||||
|
lastSeenEpoch int64
|
||||||
|
requestCount, successCount, clientErrorCount, serverErrorCount uint64
|
||||||
|
)
|
||||||
|
if err := rows.Scan(&remoteAddr, &requestCount, &successCount, &clientErrorCount, &serverErrorCount, &lastSeenEpoch); err != nil {
|
||||||
return nil, fmt.Errorf("scan ip aggregate row: %w", err)
|
return nil, fmt.Errorf("scan ip aggregate row: %w", err)
|
||||||
}
|
}
|
||||||
result = append(result, item)
|
result = append(result, NodeAccessLogIPAggregate{
|
||||||
|
RemoteAddr: remoteAddr,
|
||||||
|
RequestCount: safeInt64Count(requestCount),
|
||||||
|
SuccessCount: safeInt64Count(successCount),
|
||||||
|
ClientErrorCount: safeInt64Count(clientErrorCount),
|
||||||
|
ServerErrorCount: safeInt64Count(serverErrorCount),
|
||||||
|
LastSeenEpoch: lastSeenEpoch,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
@@ -198,11 +218,20 @@ GROUP BY trimmed_remote_addr`, recentClause, lastSeenExpr, tableName, clause)
|
|||||||
|
|
||||||
var result []NodeAccessLogIPSummary
|
var result []NodeAccessLogIPSummary
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var item NodeAccessLogIPSummary
|
var (
|
||||||
if err := rows.Scan(&item.RemoteAddr, &item.TotalRequests, &item.RecentRequests, &item.LastSeenEpoch); err != nil {
|
remoteAddr string
|
||||||
|
lastSeenEpoch int64
|
||||||
|
totalRequests, recentRequests uint64
|
||||||
|
)
|
||||||
|
if err := rows.Scan(&remoteAddr, &totalRequests, &recentRequests, &lastSeenEpoch); err != nil {
|
||||||
return nil, fmt.Errorf("scan ip summary row: %w", err)
|
return nil, fmt.Errorf("scan ip summary row: %w", err)
|
||||||
}
|
}
|
||||||
result = append(result, item)
|
result = append(result, NodeAccessLogIPSummary{
|
||||||
|
RemoteAddr: remoteAddr,
|
||||||
|
TotalRequests: safeInt64Count(totalRequests),
|
||||||
|
RecentRequests: safeInt64Count(recentRequests),
|
||||||
|
LastSeenEpoch: lastSeenEpoch,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
@@ -232,11 +261,17 @@ ORDER BY bucket_epoch ASC`, bucketExpr, tableName, clause)
|
|||||||
|
|
||||||
var result []NodeAccessLogIPTrend
|
var result []NodeAccessLogIPTrend
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var item NodeAccessLogIPTrend
|
var (
|
||||||
if err := rows.Scan(&item.BucketEpoch, &item.RequestCount); err != nil {
|
bucketEpoch int64
|
||||||
|
requestCount uint64
|
||||||
|
)
|
||||||
|
if err := rows.Scan(&bucketEpoch, &requestCount); err != nil {
|
||||||
return nil, fmt.Errorf("scan ip trend row: %w", err)
|
return nil, fmt.Errorf("scan ip trend row: %w", err)
|
||||||
}
|
}
|
||||||
result = append(result, item)
|
result = append(result, NodeAccessLogIPTrend{
|
||||||
|
BucketEpoch: bucketEpoch,
|
||||||
|
RequestCount: safeInt64Count(requestCount),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func deleteNodeObservabilityWithCount(ctx context.Context, countSQL string, coun
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
var count int64
|
var count uint64
|
||||||
if err := conn.QueryRow(ctx, countSQL, countArgs...).Scan(&count); err != nil {
|
if err := conn.QueryRow(ctx, countSQL, countArgs...).Scan(&count); err != nil {
|
||||||
return 0, fmt.Errorf("count node observability rows for delete: %w", err)
|
return 0, fmt.Errorf("count node observability rows for delete: %w", err)
|
||||||
}
|
}
|
||||||
@@ -119,5 +119,5 @@ func deleteNodeObservabilityWithCount(ctx context.Context, countSQL string, coun
|
|||||||
if err := conn.Exec(ctx, deleteSQL, deleteArgs...); err != nil {
|
if err := conn.Exec(ctx, deleteSQL, deleteArgs...); err != nil {
|
||||||
return 0, fmt.Errorf("delete node observability rows: %w", err)
|
return 0, fmt.Errorf("delete node observability rows: %w", err)
|
||||||
}
|
}
|
||||||
return count, nil
|
return safeInt64Count(count), nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user