• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stillya / wg-relay / 23806622897

31 Mar 2026 03:52PM UTC coverage: 52.138% (+10.0%) from 42.172%
23806622897

push

github

web-flow
feat: reimplement metrics to avoid high cardinality (#20)

286 of 316 new or added lines in 7 files covered. (90.51%)

5 existing lines in 2 files now uncovered.

634 of 1216 relevant lines covered (52.14%)

0.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.72
/pkg/monitor/tableprinter.go
1
package monitor
2

3
import (
4
        "fmt"
5
        "sort"
6
        "strings"
7
        "time"
8

9
        "github.com/stillya/wg-relay/pkg/maps/metricsmap"
10
)
11

12
// TablePrinter formats and prints traffic statistics tables.
13
type TablePrinter struct {
14
        maxSources int
15
        backends   BackendDiscovery
16
}
17

18
// PrintTrafficTable prints a formatted table of traffic statistics.
19
func (tp *TablePrinter) PrintTrafficTable(mode string, metricsData []metricsmap.MetricData, elapsed time.Duration) {
1✔
20
        tp.clearScreen()
1✔
21

1✔
22
        type directionStats struct {
1✔
23
                downstreamRxBytes uint64
1✔
24
                downstreamTxBytes uint64
1✔
25
                upstreamRxBytes   uint64
1✔
26
                upstreamTxBytes   uint64
1✔
27
        }
1✔
28

1✔
29
        perBackendStats := make(map[string]*directionStats)
1✔
30
        var totalDownstreamRx, totalDownstreamTx, totalUpstreamRx, totalUpstreamTx uint64
1✔
31

1✔
32
        var backendLabels map[uint8]string
1✔
33
        if tp.backends != nil {
2✔
34
                backendLabels = tp.backends.Backends()
1✔
35
        }
1✔
36

37
        for _, metric := range metricsData {
2✔
38
                var backendKey string
1✔
39
                if mode == "forward" {
2✔
40
                        if label, exists := backendLabels[metric.Key.BackendIndex]; exists {
2✔
41
                                backendKey = label
1✔
42
                        } else {
1✔
NEW
43
                                backendKey = fmt.Sprintf("backend_%d", metric.Key.BackendIndex)
×
UNCOV
44
                        }
×
45
                } else {
1✔
46
                        backendKey = metricsmap.DirectionToString(metric.Key.Direction)
1✔
47
                }
1✔
48

49
                if _, exists := perBackendStats[backendKey]; !exists {
2✔
50
                        perBackendStats[backendKey] = &directionStats{}
1✔
51
                }
1✔
52

53
                switch metric.Key.Direction {
1✔
54
                case metricsmap.MetricDownstream:
1✔
55
                        totalDownstreamRx += metric.Value.RxBytes
1✔
56
                        totalDownstreamTx += metric.Value.TxBytes
1✔
57
                        perBackendStats[backendKey].downstreamRxBytes += metric.Value.RxBytes
1✔
58
                        perBackendStats[backendKey].downstreamTxBytes += metric.Value.TxBytes
1✔
59
                case metricsmap.MetricUpstream:
1✔
60
                        totalUpstreamRx += metric.Value.RxBytes
1✔
61
                        totalUpstreamTx += metric.Value.TxBytes
1✔
62
                        perBackendStats[backendKey].upstreamRxBytes += metric.Value.RxBytes
1✔
63
                        perBackendStats[backendKey].upstreamTxBytes += metric.Value.TxBytes
1✔
64
                }
65
        }
66

67
        totalBytes := totalDownstreamRx + totalDownstreamTx + totalUpstreamRx + totalUpstreamTx
1✔
68

1✔
69
        var avgRate float64
1✔
70
        var estimatedDownstreamRxDaily, estimatedDownstreamTxDaily, estimatedUpstreamRxDaily, estimatedUpstreamTxDaily, estimatedTotalDaily uint64
1✔
71

1✔
72
        if elapsed.Seconds() > 0 {
2✔
73
                avgRate = float64(totalBytes) / elapsed.Seconds()
1✔
74

1✔
75
                secondsInDay := float64(24 * 60 * 60)
1✔
76
                estimatedDownstreamRxDaily = uint64(float64(totalDownstreamRx) / elapsed.Seconds() * secondsInDay)
1✔
77
                estimatedDownstreamTxDaily = uint64(float64(totalDownstreamTx) / elapsed.Seconds() * secondsInDay)
1✔
78
                estimatedUpstreamRxDaily = uint64(float64(totalUpstreamRx) / elapsed.Seconds() * secondsInDay)
1✔
79
                estimatedUpstreamTxDaily = uint64(float64(totalUpstreamTx) / elapsed.Seconds() * secondsInDay)
1✔
80
                estimatedTotalDaily = estimatedDownstreamRxDaily + estimatedDownstreamTxDaily + estimatedUpstreamRxDaily + estimatedUpstreamTxDaily
1✔
81
        }
1✔
82

83
        fmt.Printf("\n")
1✔
84
        fmt.Printf("                         wg-relay(%s) traffic statistics\n", mode)
1✔
85
        fmt.Printf("\n")
1✔
86
        fmt.Printf(" %-18s | %12s | %12s | %12s | %12s | %12s | %12s\n", "", "down_rx", "down_tx", "up_rx", "up_tx", "total", "avg. rate")
1✔
87
        fmt.Printf(" %s+%s+%s+%s+%s+%s+%s\n",
1✔
88
                strings.Repeat("-", 18),
1✔
89
                strings.Repeat("-", 14),
1✔
90
                strings.Repeat("-", 14),
1✔
91
                strings.Repeat("-", 14),
1✔
92
                strings.Repeat("-", 14),
1✔
93
                strings.Repeat("-", 14),
1✔
94
                strings.Repeat("-", 14))
1✔
95
        fmt.Printf(" %-18s | %12s | %12s | %12s | %12s | %12s | %9s/s\n", "traffic",
1✔
96
                formatBytes(totalDownstreamRx),
1✔
97
                formatBytes(totalDownstreamTx),
1✔
98
                formatBytes(totalUpstreamRx),
1✔
99
                formatBytes(totalUpstreamTx),
1✔
100
                formatBytes(totalBytes),
1✔
101
                formatBytes(uint64(avgRate)))
1✔
102
        fmt.Printf(" %s+%s+%s+%s+%s+%s+%s\n",
1✔
103
                strings.Repeat("-", 18),
1✔
104
                strings.Repeat("-", 14),
1✔
105
                strings.Repeat("-", 14),
1✔
106
                strings.Repeat("-", 14),
1✔
107
                strings.Repeat("-", 14),
1✔
108
                strings.Repeat("-", 14),
1✔
109
                strings.Repeat("-", 14))
1✔
110
        fmt.Printf(" %-18s | %12s | %12s | %12s | %12s | %12s |\n", "estimated",
1✔
111
                formatBytes(estimatedDownstreamRxDaily),
1✔
112
                formatBytes(estimatedDownstreamTxDaily),
1✔
113
                formatBytes(estimatedUpstreamRxDaily),
1✔
114
                formatBytes(estimatedUpstreamTxDaily),
1✔
115
                formatBytes(estimatedTotalDaily))
1✔
116

1✔
117
        if len(perBackendStats) > 0 {
2✔
118
                fmt.Printf("\n")
1✔
119
                if mode == "forward" {
2✔
120
                        fmt.Printf(" Per-backend statistics:\n")
1✔
121
                        fmt.Printf(" %-18s | %12s | %12s | %12s | %12s | %12s\n", "backend", "down_rx", "down_tx", "up_rx", "up_tx", "total")
1✔
122
                } else {
2✔
123
                        fmt.Printf(" Per-direction statistics:\n")
1✔
124
                        fmt.Printf(" %-18s | %12s | %12s | %12s | %12s | %12s\n", "direction", "down_rx", "down_tx", "up_rx", "up_tx", "total")
1✔
125
                }
1✔
126
                fmt.Printf(" %s+%s+%s+%s+%s+%s\n",
1✔
127
                        strings.Repeat("-", 18),
1✔
128
                        strings.Repeat("-", 14),
1✔
129
                        strings.Repeat("-", 14),
1✔
130
                        strings.Repeat("-", 14),
1✔
131
                        strings.Repeat("-", 14),
1✔
132
                        strings.Repeat("-", 14))
1✔
133

1✔
134
                type backendEntry struct {
1✔
135
                        label string
1✔
136
                        stats *directionStats
1✔
137
                        total uint64
1✔
138
                }
1✔
139
                sortedBackends := make([]backendEntry, 0, len(perBackendStats))
1✔
140
                for label, stats := range perBackendStats {
2✔
141
                        sortedBackends = append(sortedBackends, backendEntry{
1✔
142
                                label: label,
1✔
143
                                stats: stats,
1✔
144
                                total: stats.downstreamRxBytes + stats.downstreamTxBytes + stats.upstreamRxBytes + stats.upstreamTxBytes,
1✔
145
                        })
1✔
146
                }
1✔
147
                sort.Slice(sortedBackends, func(i, j int) bool {
2✔
148
                        return sortedBackends[i].total > sortedBackends[j].total
1✔
149
                })
1✔
150

151
                displayCount := len(sortedBackends)
1✔
152
                if tp.maxSources > 0 && displayCount > tp.maxSources {
2✔
153
                        displayCount = tp.maxSources
1✔
154
                }
1✔
155

156
                for i := 0; i < displayCount; i++ {
2✔
157
                        entry := sortedBackends[i]
1✔
158
                        fmt.Printf(" %-18s | %12s | %12s | %12s | %12s | %12s\n",
1✔
159
                                entry.label,
1✔
160
                                formatBytes(entry.stats.downstreamRxBytes),
1✔
161
                                formatBytes(entry.stats.downstreamTxBytes),
1✔
162
                                formatBytes(entry.stats.upstreamRxBytes),
1✔
163
                                formatBytes(entry.stats.upstreamTxBytes),
1✔
164
                                formatBytes(entry.total))
1✔
165
                }
1✔
166

167
                if tp.maxSources > 0 && len(sortedBackends) > tp.maxSources {
2✔
168
                        fmt.Printf(" ... and %d more backends\n", len(sortedBackends)-tp.maxSources)
1✔
169
                }
1✔
170
        }
171

172
        fmt.Printf("\n")
1✔
173
}
174

175
func (tp *TablePrinter) clearScreen() {
1✔
176
        fmt.Print("\033c")
1✔
177
}
1✔
178

179
func formatBytes(bytes uint64) string {
1✔
180
        const unit = 1024
1✔
181
        if bytes < unit {
2✔
182
                return fmt.Sprintf("%d B", bytes)
1✔
183
        }
1✔
184
        div, exp := uint64(unit), 0
1✔
185
        for n := bytes / unit; n >= unit; n /= unit {
2✔
186
                div *= unit
1✔
187
                exp++
1✔
188
        }
1✔
189
        return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
1✔
190
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc