Skip to content

Commit d77c262

Browse files
committed
rewrite
1 parent f61d1a1 commit d77c262

File tree

23 files changed

+1079
-597
lines changed

23 files changed

+1079
-597
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

cmd/netexp/main.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// Copyright 2023 the netexp authors.
2+
// SPDX-License-Identifier: MIT
3+
4+
// TODO:
5+
// - Test netdev.*NetDev.Traffic + $HOST_PROC.
6+
// - Add proper logging.
7+
// - Test netdev's logging.
8+
// - Use layer8co/toolbox/container/ringbuf in netdev instead of []int64 once it's ready.
9+
// - Implement a generic bucketed pool in layer8co/toolbox and use that in rcu.*BufferRcu instead of sync.Pool.
10+
// - Move rcu to layer8co/toolbox.
11+
12+
package main
13+
14+
import (
15+
"bytes"
16+
"flag"
17+
"fmt"
18+
"io"
19+
"net/http"
20+
"os"
21+
"regexp"
22+
"strings"
23+
"time"
24+
25+
"github.com/layer8co/netexp/internal/metrics"
26+
"github.com/layer8co/netexp/internal/netdev"
27+
"github.com/layer8co/netexp/internal/rcu"
28+
)
29+
30+
const (
31+
appName = "netexp"
32+
helpText = "netexp is a Prometheus exporter that provides advanced network usage metrics."
33+
)
34+
35+
var (
36+
listen = flag.String(
37+
"listen",
38+
":9298",
39+
"address to listen on",
40+
)
41+
ifaceRegexpFlag = flag.String(
42+
"iface-regexp",
43+
netdev.IfacePattern,
44+
"regexp to match network interface names",
45+
)
46+
interval = flag.Duration(
47+
"interval",
48+
time.Second,
49+
"polling interval (e.g. 500ms, 1s)",
50+
)
51+
burstWindowsFlag = flag.String(
52+
"burst-windows",
53+
"1s,5s",
54+
"comma-separated burst window durations",
55+
)
56+
outputWindowsFlag = flag.String(
57+
"output-windows",
58+
"15s,30s,60s",
59+
"comma-separated output window durations",
60+
)
61+
)
62+
63+
var (
64+
appRcu = rcu.NewBufferRcu()
65+
appNetDev *netdev.NetDev
66+
appMetrics *metrics.Metrics
67+
)
68+
69+
func main() {
70+
71+
flag.Usage = func() {
72+
fmt.Fprintf(flag.CommandLine.Output(), "%s\n\nUsage:\n", helpText)
73+
flag.PrintDefaults()
74+
}
75+
76+
flag.Parse()
77+
78+
ifaceRegexp, err := regexp.Compile(*ifaceRegexpFlag)
79+
if err != nil {
80+
die(fmt.Sprintf("-iface-regexp parse erorr: %s", err))
81+
}
82+
83+
appNetDev = netdev.New(ifaceRegexp.Match, func(fn func(io.Writer)) {
84+
b := new(bytes.Buffer)
85+
fn(b)
86+
fmt.Printf("%s\n", b.Bytes())
87+
})
88+
89+
appMetrics = metrics.New(metrics.Config{
90+
Interval: *interval,
91+
BurstWindows: mustGet(parseDurations(*burstWindowsFlag)),
92+
OutputWindows: mustGet(parseDurations(*outputWindowsFlag)),
93+
})
94+
95+
fmt.Printf("listening on %s\n", *listen)
96+
97+
go func() {
98+
mustDo(gatherMetrics())
99+
}()
100+
101+
serveHttp()
102+
}
103+
104+
func serveHttp() error {
105+
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
106+
fmt.Fprintln(w, appName)
107+
})
108+
http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
109+
appRcu.Read(func(b []byte) {
110+
w.Write(b)
111+
})
112+
})
113+
return http.ListenAndServe(*listen, nil)
114+
}
115+
116+
func gatherMetrics() error {
117+
for ; true; <-time.Tick(*interval) {
118+
recv, trns, err := appNetDev.Traffic()
119+
if err != nil {
120+
return err
121+
}
122+
appRcu.Update(func(b []byte) ([]byte, error) {
123+
b = appMetrics.Step(recv, trns, b)
124+
b = append(b, '\n')
125+
return b, nil
126+
})
127+
}
128+
return nil
129+
}
130+
131+
func parseDurations(s string) (out []time.Duration, err error) {
132+
for field := range strings.SplitSeq(s, ",") {
133+
field = strings.TrimSpace(field)
134+
d, err := time.ParseDuration(field)
135+
if err != nil {
136+
return nil, fmt.Errorf("could not parse duration %q: %w", field, err)
137+
}
138+
out = append(out, d)
139+
}
140+
return out, nil
141+
}
142+
143+
func die(s string) {
144+
fmt.Println(s)
145+
os.Exit(1)
146+
}
147+
148+
func mustDo(err error) {
149+
if err != nil {
150+
die(err.Error())
151+
}
152+
}
153+
154+
func mustGet[T any](v T, err error) T {
155+
mustDo(err)
156+
return v
157+
}

go.mod

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1-
module netexp
1+
module github.com/layer8co/netexp
22

33
go 1.25
4+
5+
require (
6+
github.com/google/go-cmp v0.7.0
7+
github.com/layer8co/toolbox v0.0.0-20251226110524-6a835a85a5f0
8+
github.com/stretchr/testify v1.11.1
9+
)
10+
11+
require (
12+
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/pmezard/go-difflib v1.0.0 // indirect
14+
gopkg.in/yaml.v3 v3.0.1 // indirect
15+
)

go.sum

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
4+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
5+
github.com/layer8co/toolbox v0.0.0-20251226110524-6a835a85a5f0 h1:vUMx/YkYWE90NarteFC+1khg1ccBoxtEv98HfY7bUJI=
6+
github.com/layer8co/toolbox v0.0.0-20251226110524-6a835a85a5f0/go.mod h1:hPcuU4E0sSSIS1Zy/I7GAlAXbM0su5lbiKBJGEpvpCE=
7+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
8+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
10+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
11+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
12+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
13+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
14+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/metrics/metrics.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2023 the netexp authors.
2+
// SPDX-License-Identifier: MIT
3+
4+
// Package metrics produces netexp's Prometheus metrics.
5+
package metrics
6+
7+
import (
8+
"bytes"
9+
"fmt"
10+
"slices"
11+
"time"
12+
13+
"github.com/layer8co/netexp/internal/netdev"
14+
"github.com/layer8co/netexp/internal/series"
15+
)
16+
17+
// e.g. a burst window of 5 seconds and an output window of 60 seconds
18+
// provides the metric of `the maximum 5 second burst over the last 60 seconds`.
19+
type Metrics struct {
20+
Config
21+
recv *series.TimeSeries
22+
trns *series.TimeSeries
23+
recvBurst []*series.TimeSeries
24+
trnsBurst []*series.TimeSeries
25+
netdev *netdev.NetDev
26+
}
27+
28+
type Config struct {
29+
Interval time.Duration
30+
BurstWindows []time.Duration
31+
OutputWindows []time.Duration
32+
}
33+
34+
func New(c Config) *Metrics {
35+
m := &Metrics{
36+
Config: c,
37+
}
38+
window := slices.Max(m.OutputWindows)
39+
m.recv = series.New(m.Interval, window)
40+
m.trns = series.New(m.Interval, window)
41+
for range m.BurstWindows {
42+
m.recvBurst = append(m.recvBurst, series.New(m.Interval, window))
43+
m.trnsBurst = append(m.trnsBurst, series.New(m.Interval, window))
44+
}
45+
return m
46+
}
47+
func (m *Metrics) Step(recv, trns int64, b []byte) []byte {
48+
m.recv.Put(recv)
49+
m.trns.Put(trns)
50+
b = fmt.Appendf(b, "netexp_recv_bytes %d\n", recv)
51+
b = fmt.Appendf(b, "netexp_trns_bytes %d\n", trns)
52+
for i, bw := range m.BurstWindows {
53+
recvBurst, ok := m.recv.Rate(bw)
54+
if ok {
55+
m.recvBurst[i].Put(recvBurst)
56+
}
57+
trnsBurst, ok := m.trns.Rate(bw)
58+
if ok {
59+
m.trnsBurst[i].Put(trnsBurst)
60+
}
61+
for _, ow := range m.OutputWindows {
62+
maxRecvBurst, ok := m.recvBurst[i].Max(ow)
63+
if ok {
64+
b = fmt.Appendf(
65+
b,
66+
"netexp_max_%s_recv_burst_bps_over_%s %d\n",
67+
bw, ow, maxRecvBurst,
68+
)
69+
}
70+
maxTransBurst, ok := m.trnsBurst[i].Max(ow)
71+
if ok {
72+
b = fmt.Appendf(
73+
b,
74+
"netexp_max_%s_trns_burst_bps_over_%s %d\n",
75+
bw, ow, maxTransBurst,
76+
)
77+
}
78+
}
79+
}
80+
b = bytes.TrimRight(b, "\n")
81+
return b
82+
}

0 commit comments

Comments
 (0)