Skip to content

Commit f8c6399

Browse files
committed
feat: support export simple metrics
0 parents  commit f8c6399

File tree

12 files changed

+436
-0
lines changed

12 files changed

+436
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pingexporter

.vscode/settings.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"cSpell.words": [
3+
"digineo"
4+
]
5+
}

Makefile

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build:
2+
go build -o pingexporter -v --trimpath -ldflags "-s -w" ./

README

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# pingexporter
2+
3+
Pingexporter is a project that allows you to monitor the availability and latency of network hosts using ICMP ping requests.
4+
5+
It collects ping metrics and generates detailed reports for analysis.
6+
7+
This tool draws inspiration from both the [ping_exporter](https://github.com/czerwonk/ping_exporter) and [SmokePing](https://github.com/oetiker/SmokePing) repositories, combining their strengths and introducing additional enhancements.
8+
9+
Please note that the project is still in its early stages of development, and there may be some issues or incomplete documentation.

config.toml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[dns]
2+
name_server = "223.5.5.5"
3+
4+
[web]
5+
address = "0.0.0.0:8080"
6+
7+
[[targets]]
8+
addr = "223.5.5.5"
9+
10+
[[targets]]
11+
addr = "114.114.114.114"
12+
13+
[global_labels]
14+
"from" = "192.168.2.1"
15+
"from_host" = "m1"

config/config.go

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package config
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net"
7+
"strings"
8+
"time"
9+
10+
"github.com/digineo/go-ping"
11+
mon "github.com/digineo/go-ping/monitor"
12+
"github.com/jimyag/log"
13+
)
14+
15+
// Config represents configuration for the exporter.
16+
type Config struct {
17+
Targets []TargetConfig `toml:"targets"`
18+
Ping struct {
19+
Interval time.Duration ` toml:"interval"` // Interval for ICMP echo requests
20+
Timeout time.Duration `toml:"timeout"` // Timeout for ICMP echo request
21+
History int `toml:"history_size"` // Number of results to remember per target
22+
Size uint16 `toml:"payload_size"` // Payload size for ICMP echo requests
23+
} `toml:"ping"`
24+
25+
DNS struct {
26+
Refresh time.Duration `toml:"refresh"` // Interval for refreshing DNS records and updating targets accordingly (0 if disabled)
27+
NameServer string `toml:"name_server"` // DNS server used to resolve hostname of targets
28+
} `yaml:"dns"`
29+
30+
Web struct {
31+
Address string `toml:"address"` // Address on which to expose metrics and web interface
32+
MetricsPath string `toml:"metrics_path"` // Path under which to expose metrics
33+
} `toml:"web"`
34+
GlobalLabels map[string]string `toml:"global_labels"`
35+
}
36+
37+
func (c *Config) Default() {
38+
if c.Ping.Interval == 0 {
39+
c.Ping.Interval = 5 * time.Second
40+
}
41+
if c.Ping.Timeout == 0 {
42+
c.Ping.Timeout = 4 * time.Second
43+
}
44+
if c.Ping.History == 0 {
45+
c.Ping.History = 10
46+
}
47+
if c.Ping.Size == 0 {
48+
c.Ping.Size = 56
49+
}
50+
51+
if c.DNS.Refresh == 0 {
52+
c.DNS.Refresh = 1 * time.Minute
53+
}
54+
55+
if c.Web.Address == "" {
56+
c.Web.Address = ":9113"
57+
}
58+
if c.Web.MetricsPath == "" {
59+
c.Web.MetricsPath = "/metrics"
60+
}
61+
if c.GlobalLabels == nil {
62+
c.GlobalLabels = make(map[string]string)
63+
}
64+
}
65+
66+
func (c *Config) Verify() {
67+
if c.Ping.History < 1 {
68+
log.Panic().Msg("ping.history-size must be greater than 0")
69+
}
70+
if c.Ping.Size > 65500 {
71+
log.Panic().Msg("ping.size must be between 0 and 65500")
72+
}
73+
74+
if len(c.Targets) == 0 {
75+
log.Panic().Msg("No targets specified")
76+
}
77+
}
78+
79+
func (c *Config) SetupResolver() *net.Resolver {
80+
if c.DNS.NameServer == "" {
81+
return net.DefaultResolver
82+
}
83+
84+
if !strings.HasSuffix(c.DNS.NameServer, ":53") {
85+
c.DNS.NameServer += ":53"
86+
}
87+
dialer := func(ctx context.Context, _, _ string) (net.Conn, error) {
88+
d := net.Dialer{}
89+
return d.DialContext(ctx, "udp", c.DNS.NameServer)
90+
}
91+
92+
return &net.Resolver{PreferGo: true, Dial: dialer}
93+
}
94+
95+
func (cfg *Config) TargetConfigByAddr(addr string) TargetConfig {
96+
for _, t := range cfg.Targets {
97+
if t.Addr == addr {
98+
return t
99+
}
100+
}
101+
102+
return TargetConfig{Addr: addr}
103+
}
104+
105+
func (c *Config) GenMonitor() (*mon.Monitor, error) {
106+
var bind4, bind6 string
107+
if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
108+
// ipv4 enabled
109+
ln.Close()
110+
bind4 = "0.0.0.0"
111+
}
112+
if ln, err := net.Listen("tcp6", "[::1]:0"); err == nil {
113+
// ipv6 enabled
114+
ln.Close()
115+
bind6 = "::"
116+
}
117+
pinger, err := ping.New(bind4, bind6)
118+
if err != nil {
119+
return nil, fmt.Errorf("cannot start monitoring: %w", err)
120+
}
121+
122+
if pinger.PayloadSize() != c.Ping.Size {
123+
pinger.SetPayloadSize(c.Ping.Size)
124+
}
125+
126+
monitor := mon.New(pinger,
127+
c.Ping.Interval,
128+
c.Ping.Timeout)
129+
monitor.HistorySize = c.Ping.History
130+
resolver := c.SetupResolver()
131+
for i, target := range c.Targets {
132+
addrs, err := resolver.LookupIPAddr(context.Background(), target.Addr)
133+
if err != nil {
134+
log.Error(err).Str("target", target.Addr).Msg("cannot resolve target address")
135+
continue
136+
}
137+
for j, addr := range addrs {
138+
key := genPingMonitorKey(target.Addr, addr)
139+
if err := monitor.AddTargetDelayed(key, addr,
140+
time.Duration(10*i+j)*time.Millisecond,
141+
); err != nil {
142+
log.Error(err).Str("target", target.Addr).
143+
Str("key", key).
144+
Msg("cannot add target")
145+
}
146+
}
147+
148+
}
149+
return monitor, nil
150+
}
151+
152+
// genPingMonitorKey returns a unique key for the monitor based on target and addr
153+
// for example: "test.host.com 192.168.2.1 v4"
154+
func genPingMonitorKey(target string, addr net.IPAddr) string {
155+
if addr.IP.To4() == nil {
156+
return fmt.Sprintf("%s %s v6", target, addr.String())
157+
}
158+
return fmt.Sprintf("%s %s v4", target, addr.String())
159+
}
160+
161+
func ParseMonitorKey(key string) (string, net.IPAddr) {
162+
163+
parts := strings.Split(key, " ")
164+
if len(parts) != 3 {
165+
log.Panic().Str("key", key).Msg("cannot parse monitor key")
166+
}
167+
host := parts[0]
168+
ip := net.ParseIP(parts[1])
169+
if ip == nil {
170+
log.Panic().Str("key", key).Msg("unexpected ip in monitor key")
171+
}
172+
173+
if parts[2] == "v4" && ip.To4() == nil {
174+
log.Panic().Str("key", key).Msg("unexpected ip version in monitor key")
175+
}
176+
if parts[2] == "v6" && ip.To4() != nil {
177+
log.Panic().Str("key", key).Msg("unexpected ip version in monitor key")
178+
}
179+
return host, net.IPAddr{IP: ip}
180+
}

config/target.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package config
2+
3+
type TargetConfig struct {
4+
Addr string `yaml:"addr"`
5+
// TODO support labels
6+
// Labels map[string]string `yaml:"labels"`
7+
}

dashboard/TODO

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
grafana dashboard

go.mod

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module github.com/jimyag/pingexporter
2+
3+
go 1.22.2
4+
5+
require (
6+
github.com/BurntSushi/toml v1.3.2
7+
github.com/digineo/go-ping v1.1.0
8+
github.com/jimyag/log v0.1.1
9+
github.com/prometheus/client_golang v1.19.0
10+
)
11+
12+
require (
13+
github.com/beorn7/perks v1.0.1 // indirect
14+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
15+
github.com/digineo/go-logwrap v0.0.0-20181106161722-a178c58ea3f0 // indirect
16+
github.com/mattn/go-colorable v0.1.13 // indirect
17+
github.com/mattn/go-isatty v0.0.20 // indirect
18+
github.com/prometheus/client_model v0.5.0 // indirect
19+
github.com/prometheus/common v0.48.0 // indirect
20+
github.com/prometheus/procfs v0.12.0 // indirect
21+
github.com/rs/zerolog v1.31.0 // indirect
22+
golang.org/x/net v0.24.0 // indirect
23+
golang.org/x/sys v0.19.0 // indirect
24+
google.golang.org/protobuf v1.32.0 // indirect
25+
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
26+
)

go.sum

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
2+
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
3+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
4+
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
5+
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
6+
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
7+
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
8+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
9+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10+
github.com/digineo/go-logwrap v0.0.0-20181106161722-a178c58ea3f0 h1:OT/LKmj81wMymnWXaKaKBR9n1vPlu+GC0VVKaZP6kzs=
11+
github.com/digineo/go-logwrap v0.0.0-20181106161722-a178c58ea3f0/go.mod h1:DmqdumeAKGQNU5E8MN0ruT5ZGx8l/WbAsMbXCXcSEts=
12+
github.com/digineo/go-ping v1.1.0 h1:HXZPBw8/Zk+tFuHrHejBTLopcEkqK4FNn1ocqKo6xhw=
13+
github.com/digineo/go-ping v1.1.0/go.mod h1:rVhwm0cbn6i20vX/MBmo4OoxOvAW/6JiIf+2Oln8n0M=
14+
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
15+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
16+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
17+
github.com/jimyag/log v0.1.1 h1:b3CTE/xGBWAioiqKXICZM8jdsbcrVjOvmR6MyATlTtk=
18+
github.com/jimyag/log v0.1.1/go.mod h1:F12+oHI11ydaa4j0/aYfMPQe6z/igZJraWu9ILCOHUY=
19+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
20+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
21+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
22+
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
23+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
24+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
25+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
26+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
27+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
28+
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
29+
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
30+
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
31+
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
32+
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
33+
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
34+
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
35+
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
36+
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
37+
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
38+
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
39+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
40+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
41+
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
42+
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
43+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
44+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
45+
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
46+
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
47+
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
48+
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
49+
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
50+
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
51+
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
52+
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
53+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
54+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

main.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"os"
7+
8+
"github.com/BurntSushi/toml"
9+
"github.com/jimyag/log"
10+
"github.com/prometheus/client_golang/prometheus"
11+
"github.com/prometheus/client_golang/prometheus/promhttp"
12+
13+
"github.com/jimyag/pingexporter/config"
14+
"github.com/jimyag/pingexporter/metrics"
15+
)
16+
17+
func main() {
18+
19+
if len(os.Args) < 2 {
20+
log.Error().Msg("usage is: ping_exporter <config-path>")
21+
os.Exit(1)
22+
}
23+
configFile := &os.Args[1]
24+
cfg := &config.Config{}
25+
cfg.GlobalLabels = make(map[string]string)
26+
if _, err := toml.DecodeFile(*configFile, &cfg); err != nil {
27+
log.Panic(err).Msg("could not load config")
28+
}
29+
log.Info().Any("config", cfg).Msg("loaded config")
30+
cfg.Default()
31+
cfg.Verify()
32+
ping := metrics.New(cfg)
33+
prometheus.MustRegister(ping)
34+
35+
// 创建HTTP处理程序,用于暴露指标
36+
http.Handle("/metrics", promhttp.Handler())
37+
38+
// 启动HTTP服务
39+
go func() {
40+
log.Info().Str("address", cfg.Web.Address).Msg("starting web server")
41+
if err := http.ListenAndServe(cfg.Web.Address, nil); err != nil {
42+
fmt.Println(err)
43+
}
44+
}()
45+
46+
// 保持程序运行
47+
select {}
48+
}

0 commit comments

Comments
 (0)