forked from influxdata/telegraf
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ipvs.go
152 lines (136 loc) · 4.03 KB
/
ipvs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// +build linux
package ipvs
import (
"errors"
"fmt"
"log"
"math/bits"
"strconv"
"syscall"
"github.com/docker/libnetwork/ipvs"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)
// IPVS holds the state for this input plugin
type IPVS struct {
handle *ipvs.Handle
}
// Description returns a description string
func (i *IPVS) Description() string {
return "Collect virtual and real server stats from Linux IPVS"
}
// SampleConfig returns a sample configuration for this input plugin
func (i *IPVS) SampleConfig() string {
return ``
}
// Gather gathers the stats
func (i *IPVS) Gather(acc telegraf.Accumulator) error {
if i.handle == nil {
h, err := ipvs.New("") // TODO: make the namespace configurable
if err != nil {
return errors.New("Unable to open IPVS handle")
}
i.handle = h
}
services, err := i.handle.GetServices()
if err != nil {
i.handle.Close()
i.handle = nil // trigger a reopen on next call to gather
return errors.New("Failed to list IPVS services")
}
for _, s := range services {
fields := map[string]interface{}{
"connections": s.Stats.Connections,
"pkts_in": s.Stats.PacketsIn,
"pkts_out": s.Stats.PacketsOut,
"bytes_in": s.Stats.BytesIn,
"bytes_out": s.Stats.BytesOut,
"pps_in": s.Stats.PPSIn,
"pps_out": s.Stats.PPSOut,
"cps": s.Stats.CPS,
}
acc.AddGauge("ipvs_virtual_server", fields, serviceTags(s))
destinations, err := i.handle.GetDestinations(s)
if err != nil {
log.Println("E! Failed to list destinations for a virtual server")
continue // move on to the next virtual server
}
for _, d := range destinations {
fields := map[string]interface{}{
"active_connections": d.ActiveConnections,
"inactive_connections": d.InactiveConnections,
"connections": d.Stats.Connections,
"pkts_in": d.Stats.PacketsIn,
"pkts_out": d.Stats.PacketsOut,
"bytes_in": d.Stats.BytesIn,
"bytes_out": d.Stats.BytesOut,
"pps_in": d.Stats.PPSIn,
"pps_out": d.Stats.PPSOut,
"cps": d.Stats.CPS,
}
destTags := destinationTags(d)
if s.FWMark > 0 {
destTags["virtual_fwmark"] = strconv.Itoa(int(s.FWMark))
} else {
destTags["virtual_protocol"] = protocolToString(s.Protocol)
destTags["virtual_address"] = s.Address.String()
destTags["virtual_port"] = strconv.Itoa(int(s.Port))
}
acc.AddGauge("ipvs_real_server", fields, destTags)
}
}
return nil
}
// helper: given a Service, return tags that identify it
func serviceTags(s *ipvs.Service) map[string]string {
ret := map[string]string{
"sched": s.SchedName,
"netmask": strconv.Itoa(bits.OnesCount32(s.Netmask)),
"address_family": addressFamilyToString(s.AddressFamily),
}
// Per the ipvsadm man page, a virtual service is defined "based on
// protocol/addr/port or firewall mark"
if s.FWMark > 0 {
ret["fwmark"] = strconv.Itoa(int(s.FWMark))
} else {
ret["protocol"] = protocolToString(s.Protocol)
ret["address"] = s.Address.String()
ret["port"] = strconv.Itoa(int(s.Port))
}
return ret
}
// helper: given a Destination, return tags that identify it
func destinationTags(d *ipvs.Destination) map[string]string {
return map[string]string{
"address": d.Address.String(),
"port": strconv.Itoa(int(d.Port)),
"address_family": addressFamilyToString(d.AddressFamily),
}
}
// helper: convert protocol uint16 to human readable string (if possible)
func protocolToString(p uint16) string {
switch p {
case syscall.IPPROTO_TCP:
return "tcp"
case syscall.IPPROTO_UDP:
return "udp"
case syscall.IPPROTO_SCTP:
return "sctp"
default:
return fmt.Sprintf("%d", p)
}
}
// helper: convert addressFamily to a human readable string
func addressFamilyToString(af uint16) string {
switch af {
case syscall.AF_INET:
return "inet"
case syscall.AF_INET6:
return "inet6"
default:
return fmt.Sprintf("%d", af)
}
}
func init() {
inputs.Add("ipvs", func() telegraf.Input { return &IPVS{} })
}