/
netio.go
181 lines (148 loc) · 6.07 KB
/
netio.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package netio
import (
"context"
"runtime"
"strings"
"time"
"github.com/shirou/gopsutil/net"
"github.com/signalfx/golib/datapoint"
"github.com/signalfx/signalfx-agent/internal/core/config"
"github.com/signalfx/signalfx-agent/internal/monitors"
"github.com/signalfx/signalfx-agent/internal/monitors/types"
"github.com/signalfx/signalfx-agent/internal/utils"
"github.com/signalfx/signalfx-agent/internal/utils/filter"
log "github.com/sirupsen/logrus"
)
const monitorType = "net-io"
// setting net.IOCounters to a package variable for testing purposes
var iOCounters = net.IOCounters
// MONITOR(net-io):
// This monitor reports I/O metrics about network interfaces.
//
// On Linux hosts, this monitor relies on the `/proc` filesystem.
// If the underlying host's `/proc` file system is mounted somewhere other than
// /proc please specify the path using the top level configuration `procPath`.
//
// ```yaml
// procPath: /proc
// monitors:
// - type: net-io
// ```
var logger = log.WithFields(log.Fields{"monitorType": monitorType})
func init() {
monitors.Register(monitorType, func() interface{} { return &Monitor{} }, &Config{})
}
// Config for this monitor
type Config struct {
config.MonitorConfig `singleInstance:"false" acceptsEndpoints:"false"`
// The network interfaces to send metrics about. This is a
// [filter set](https://github.com/signalfx/signalfx-agent/blob/master/docs/filtering.md#generic-filters).
Interfaces []string `yaml:"interfaces" default:"[\"*\", \"!/^lo\\\\d*$/\", \"!/^docker.*/\", \"!/^t(un|ap)\\\\d*$/\", \"!/^veth.*$/\", \"!/^Loopback*/\"]"`
}
// structure for storing sent and recieved values
type netio struct {
sent uint64
recv uint64
}
// Monitor for Utilization
type Monitor struct {
Output types.Output
cancel func()
conf *Config
filter *filter.ExhaustiveStringFilter
networkTotal uint64
previousInterfaceStats map[string]*netio
}
func (m *Monitor) updateTotals(pluginInstance string, intf *net.IOCountersStat) {
prev, ok := m.previousInterfaceStats[pluginInstance]
// update total received
// if there's a previous value and the counter didn't reset
if ok && intf.BytesRecv >= prev.recv { // previous value exists and counter incremented
m.networkTotal += (intf.BytesRecv - prev.recv)
} else {
// counter instance is either uninitialized or reset so add current value
m.networkTotal += intf.BytesRecv
}
// update total sent
// if there's a previous value and the counter didn't reset
if ok && intf.BytesSent >= prev.sent {
m.networkTotal += intf.BytesSent - prev.sent
} else {
// counter instance is either uninitialized or reset so add current value
m.networkTotal += intf.BytesSent
}
// store values for reference next interval
m.previousInterfaceStats[pluginInstance] = &netio{sent: intf.BytesSent, recv: intf.BytesRecv}
}
// EmitDatapoints emits a set of memory datapoints
func (m *Monitor) EmitDatapoints() {
info, err := iOCounters(true)
if err != nil {
if err == context.DeadlineExceeded {
logger.WithField("debug", err).Debugf("failed to load net io counters. if this message repeats frequently there may be a problem")
} else {
logger.WithError(err).Errorf("failed to load net io counters. if this message repeats frequently there may be a problem")
}
}
for _, intf := range info {
// skip it if the interface doesn't match
if !m.filter.Matches(intf.Name) {
logger.Debugf("skipping interface '%s'", intf.Name)
continue
}
pluginInstance := strings.Replace(intf.Name, " ", "_", -1)
m.updateTotals(pluginInstance, &intf)
dimensions := map[string]string{"plugin": monitorType, "plugin_instance": pluginInstance, "interface": pluginInstance}
// if_errors.rx
m.Output.SendDatapoint(datapoint.New("if_errors.rx", dimensions, datapoint.NewIntValue(int64(intf.Errin)), datapoint.Counter, time.Time{}))
// if_errors.tx
m.Output.SendDatapoint(datapoint.New("if_errors.tx", dimensions, datapoint.NewIntValue(int64(intf.Errout)), datapoint.Counter, time.Time{}))
// if_octets.rx
m.Output.SendDatapoint(datapoint.New("if_octets.rx", dimensions, datapoint.NewIntValue(int64(intf.BytesRecv)), datapoint.Counter, time.Time{}))
// if_octets.tx
m.Output.SendDatapoint(datapoint.New("if_octets.tx", dimensions, datapoint.NewIntValue(int64(intf.BytesSent)), datapoint.Counter, time.Time{}))
// if_packets.rx
m.Output.SendDatapoint(datapoint.New("if_packets.rx", dimensions, datapoint.NewIntValue(int64(intf.PacketsRecv)), datapoint.Counter, time.Time{}))
// if_packets.tx
m.Output.SendDatapoint(datapoint.New("if_packets.tx", dimensions, datapoint.NewIntValue(int64(intf.PacketsSent)), datapoint.Counter, time.Time{}))
}
// network.total
m.Output.SendDatapoint(datapoint.New("network.total", map[string]string{"plugin": types.UtilizationMetricPluginName}, datapoint.NewIntValue(int64(m.networkTotal)), datapoint.Counter, time.Time{}))
}
// Configure is the main function of the monitor, it will report host metadata
// on a varied interval
func (m *Monitor) Configure(conf *Config) error {
if runtime.GOOS != "windows" {
logger.Warningf("'%s' monitor is in beta on this platform. For production environments please use 'collectd/%s'.", monitorType, monitorType)
}
// create contexts for managing the the plugin loop
var ctx context.Context
ctx, m.cancel = context.WithCancel(context.Background())
m.conf = conf
// initialize previous stats map and network total
m.previousInterfaceStats = map[string]*netio{}
m.networkTotal = 0
// configure filters
var err error
if len(conf.Interfaces) == 0 {
m.filter, err = filter.NewExhaustiveStringFilter([]string{"*"})
logger.Debugf("empty interface list, defaulting to '*'")
} else {
m.filter, err = filter.NewExhaustiveStringFilter(conf.Interfaces)
}
// return an error if we can't set the filter
if err != nil {
return err
}
// gather metrics on the specified interval
utils.RunOnInterval(ctx, func() {
m.EmitDatapoints()
}, time.Duration(conf.IntervalSeconds)*time.Second)
return nil
}
// Shutdown stops the metric sync
func (m *Monitor) Shutdown() {
if m.cancel != nil {
m.cancel()
}
}