diff options
| author | Fuwn <[email protected]> | 2026-01-19 19:41:22 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-01-19 19:41:22 -0800 |
| commit | 5cb6f53da0f5927edad01e854432eb0b70371b89 (patch) | |
| tree | 133824d444dc38134996eadb30a34ed63b44cfd1 /internal/monitor/icmp.go | |
| parent | feat: Add disable_ping_tooltips option to hide ping hover details (diff) | |
| download | kaze-5cb6f53da0f5927edad01e854432eb0b70371b89.tar.xz kaze-5cb6f53da0f5927edad01e854432eb0b70371b89.zip | |
feat: Add ICMP, DNS, and GraphQL monitor types
Add three new monitor types with full support:
- ICMP: Ping monitoring with configurable packet count, tracks packet
loss and average RTT. Marks degraded on partial packet loss.
- DNS: DNS resolution monitoring supporting A, AAAA, CNAME, MX, and TXT
records. Optional custom DNS server and validation of expected IPs/CNAME.
- GraphQL: GraphQL endpoint monitoring with query execution, variable
support, error detection, and content validation.
All new monitors include retry support, response time tracking, and
integrate with existing display options (round_response_time, etc).
GraphQL monitors also support SSL certificate tracking.
Diffstat (limited to 'internal/monitor/icmp.go')
| -rw-r--r-- | internal/monitor/icmp.go | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/internal/monitor/icmp.go b/internal/monitor/icmp.go new file mode 100644 index 0000000..8b1385c --- /dev/null +++ b/internal/monitor/icmp.go @@ -0,0 +1,150 @@ +package monitor + +import ( + "context" + "fmt" + "time" + + "github.com/Fuwn/kaze/internal/config" + "github.com/go-ping/ping" +) + +// ICMPMonitor monitors hosts using ICMP ping +type ICMPMonitor struct { + name string + target string + interval time.Duration + timeout time.Duration + retries int + roundResponseTime bool + roundUptime bool + count int // Number of ping packets to send +} + +// NewICMPMonitor creates a new ICMP monitor +func NewICMPMonitor(cfg config.MonitorConfig) (*ICMPMonitor, error) { + // Default to 4 pings if not specified + count := 4 + if cfg.PingCount > 0 { + count = cfg.PingCount + } + + return &ICMPMonitor{ + name: cfg.Name, + target: cfg.Target, + interval: cfg.Interval.Duration, + timeout: cfg.Timeout.Duration, + retries: cfg.Retries, + roundResponseTime: cfg.RoundResponseTime, + roundUptime: cfg.RoundUptime, + count: count, + }, nil +} + +// Name returns the monitor's name +func (m *ICMPMonitor) Name() string { + return m.name +} + +// Type returns the monitor type +func (m *ICMPMonitor) Type() string { + return "icmp" +} + +// Target returns the monitor target +func (m *ICMPMonitor) Target() string { + return m.target +} + +// Interval returns the check interval +func (m *ICMPMonitor) Interval() time.Duration { + return m.interval +} + +// Retries returns the number of retry attempts +func (m *ICMPMonitor) Retries() int { + return m.retries +} + +// HideSSLDays returns whether to hide SSL days from display +func (m *ICMPMonitor) HideSSLDays() bool { + return false // ICMP doesn't use SSL +} + +// RoundResponseTime returns whether to round response time +func (m *ICMPMonitor) RoundResponseTime() bool { + return m.roundResponseTime +} + +// RoundUptime returns whether to round uptime percentage +func (m *ICMPMonitor) RoundUptime() bool { + return m.roundUptime +} + +// Check performs the ICMP ping check +func (m *ICMPMonitor) Check(ctx context.Context) *Result { + result := &Result{ + MonitorName: m.name, + Timestamp: time.Now(), + } + + pinger, err := ping.NewPinger(m.target) + if err != nil { + result.Status = StatusDown + result.Error = fmt.Errorf("failed to create pinger: %w", err) + return result + } + + // Configure pinger + pinger.Count = m.count + pinger.Timeout = m.timeout + pinger.SetPrivileged(false) // Use unprivileged mode (UDP) by default + + // Run with context cancellation support + done := make(chan error, 1) + go func() { + done <- pinger.Run() + }() + + select { + case <-ctx.Done(): + pinger.Stop() + result.Status = StatusDown + result.Error = fmt.Errorf("ping cancelled: %w", ctx.Err()) + return result + case err := <-done: + if err != nil { + result.Status = StatusDown + result.Error = fmt.Errorf("ping failed: %w", err) + return result + } + } + + stats := pinger.Statistics() + + // If no packets were received, mark as down + if stats.PacketsRecv == 0 { + result.Status = StatusDown + result.Error = fmt.Errorf("no packets received (100%% packet loss)") + result.ResponseTime = m.timeout + return result + } + + // Use average RTT as response time + result.ResponseTime = stats.AvgRtt + + // Determine status based on packet loss + packetLoss := float64(stats.PacketsSent-stats.PacketsRecv) / float64(stats.PacketsSent) * 100 + + if packetLoss == 0 { + result.Status = StatusUp + } else if packetLoss < 50 { + result.Status = StatusDegraded + result.Error = fmt.Errorf("%.0f%% packet loss", packetLoss) + } else { + result.Status = StatusDown + result.Error = fmt.Errorf("%.0f%% packet loss", packetLoss) + } + + return result +} |