Skip to content

Commit 2c9d2c5

Browse files
Add network device dimension and option to filter by it
1 parent 71392e5 commit 2c9d2c5

File tree

8 files changed

+198
-44
lines changed

8 files changed

+198
-44
lines changed

receiver/hostmetricsreceiver/config_test.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,17 @@ func TestLoadConfig(t *testing.T) {
7171
loadscraper.TypeStr: &loadscraper.Config{},
7272
filesystemscraper.TypeStr: &filesystemscraper.Config{},
7373
memoryscraper.TypeStr: &memoryscraper.Config{},
74-
networkscraper.TypeStr: &networkscraper.Config{},
75-
processesscraper.TypeStr: &processesscraper.Config{},
76-
swapscraper.TypeStr: &swapscraper.Config{},
74+
networkscraper.TypeStr: &networkscraper.Config{
75+
Include: networkscraper.MatchConfig{
76+
Interfaces: []string{"test1"},
77+
Config: filterset.Config{MatchType: "strict"},
78+
},
79+
},
80+
processesscraper.TypeStr: &processesscraper.Config{},
81+
swapscraper.TypeStr: &swapscraper.Config{},
7782
processscraper.TypeStr: &processscraper.Config{
7883
Include: processscraper.MatchConfig{
79-
Names: []string{"test1", "test2"},
84+
Names: []string{"test2", "test3"},
8085
Config: filterset.Config{MatchType: "regexp"},
8186
},
8287
},

receiver/hostmetricsreceiver/internal/scraper/networkscraper/config.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,24 @@
1414

1515
package networkscraper
1616

17-
import "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal"
17+
import (
18+
"go.opentelemetry.io/collector/internal/processor/filterset"
19+
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal"
20+
)
1821

1922
// Config relating to Network Metric Scraper.
2023
type Config struct {
2124
internal.ConfigSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
25+
26+
// Include specifies a filter on the network interfaces that should be included from the generated metrics.
27+
// Exclude specifies a filter on the network interfaces that should be excluded from the generated metrics.
28+
// If neither `include` or `exclude` are set, metrics will be generated for all network interfaces.
29+
Include MatchConfig `mapstructure:"include"`
30+
Exclude MatchConfig `mapstructure:"exclude"`
31+
}
32+
33+
type MatchConfig struct {
34+
filterset.Config `mapstructure:",squash"`
35+
36+
Interfaces []string `mapstructure:"interfaces"`
2237
}

receiver/hostmetricsreceiver/internal/scraper/networkscraper/factory.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ func (f *Factory) CreateMetricsScraper(
4545
logger *zap.Logger,
4646
config internal.Config,
4747
) (internal.Scraper, error) {
48-
cfg := config.(*Config)
49-
return obsreportscraper.WrapScraper(newNetworkScraper(ctx, cfg), TypeStr), nil
48+
scraper, err := newNetworkScraper(ctx, config.(*Config))
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
return obsreportscraper.WrapScraper(scraper, TypeStr), nil
5054
}

receiver/hostmetricsreceiver/internal/scraper/networkscraper/factory_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,12 @@ func TestCreateMetricsScraper(t *testing.T) {
3737
assert.NoError(t, err)
3838
assert.NotNil(t, scraper)
3939
}
40+
41+
func TestCreateMetricsScraper_Error(t *testing.T) {
42+
factory := &Factory{}
43+
cfg := &Config{Include: MatchConfig{Interfaces: []string{""}}}
44+
45+
_, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg)
46+
47+
assert.Error(t, err)
48+
}

receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_metadata.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
// network metric constants
2222

2323
const (
24+
interfaceLabelName = "interface"
2425
directionLabelName = "direction"
2526
stateLabelName = "state"
2627
)

receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_scraper.go

Lines changed: 96 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,23 @@ package networkscraper
1616

1717
import (
1818
"context"
19+
"fmt"
1920
"time"
2021

2122
"github.com/shirou/gopsutil/host"
2223
"github.com/shirou/gopsutil/net"
2324

2425
"go.opentelemetry.io/collector/component/componenterror"
2526
"go.opentelemetry.io/collector/consumer/pdata"
27+
"go.opentelemetry.io/collector/internal/processor/filterset"
2628
)
2729

2830
// scraper for Network Metrics
2931
type scraper struct {
3032
config *Config
3133
startTime pdata.TimestampUnixNano
34+
includeFS filterset.FilterSet
35+
excludeFS filterset.FilterSet
3236

3337
// for mocking
3438
bootTime func() (uint64, error)
@@ -37,8 +41,26 @@ type scraper struct {
3741
}
3842

3943
// newNetworkScraper creates a set of Network related metrics
40-
func newNetworkScraper(_ context.Context, cfg *Config) *scraper {
41-
return &scraper{config: cfg, bootTime: host.BootTime, ioCounters: net.IOCounters, connections: net.Connections}
44+
func newNetworkScraper(_ context.Context, cfg *Config) (*scraper, error) {
45+
scraper := &scraper{config: cfg, bootTime: host.BootTime, ioCounters: net.IOCounters, connections: net.Connections}
46+
47+
var err error
48+
49+
if len(cfg.Include.Interfaces) > 0 {
50+
scraper.includeFS, err = filterset.CreateFilterSet(cfg.Include.Interfaces, &cfg.Include.Config)
51+
if err != nil {
52+
return nil, fmt.Errorf("error creating network interface include filters: %w", err)
53+
}
54+
}
55+
56+
if len(cfg.Exclude.Interfaces) > 0 {
57+
scraper.excludeFS, err = filterset.CreateFilterSet(cfg.Exclude.Interfaces, &cfg.Exclude.Config)
58+
if err != nil {
59+
return nil, fmt.Errorf("error creating network interface exclude filters: %w", err)
60+
}
61+
}
62+
63+
return scraper, nil
4264
}
4365

4466
// Initialize
@@ -82,33 +104,73 @@ func (s *scraper) ScrapeMetrics(_ context.Context) (pdata.MetricSlice, error) {
82104

83105
func (s *scraper) scrapeAndAppendNetworkCounterMetrics(metrics pdata.MetricSlice, startTime pdata.TimestampUnixNano) error {
84106
// get total stats only
85-
networkStatsSlice, err := s.ioCounters( /*perNetworkInterfaceController=*/ false)
107+
ioCounters, err := s.ioCounters( /*perNetworkInterfaceController=*/ true)
86108
if err != nil {
87109
return err
88110
}
89111

90-
networkStats := networkStatsSlice[0]
112+
// filter network interfaces by name
113+
ioCounters = s.filterByInterface(ioCounters)
114+
115+
if len(ioCounters) > 0 {
116+
startIdx := metrics.Len()
117+
metrics.Resize(startIdx + 4)
118+
initializeNetworkPacketsMetric(metrics.At(startIdx+0), networkPacketsDescriptor, startTime, ioCounters)
119+
initializeNetworkDroppedPacketsMetric(metrics.At(startIdx+1), networkDroppedPacketsDescriptor, startTime, ioCounters)
120+
initializeNetworkErrorsMetric(metrics.At(startIdx+2), networkErrorsDescriptor, startTime, ioCounters)
121+
initializeNetworkIOMetric(metrics.At(startIdx+3), networkIODescriptor, startTime, ioCounters)
122+
}
91123

92-
startIdx := metrics.Len()
93-
metrics.Resize(startIdx + 4)
94-
initializeNetworkMetric(metrics.At(startIdx+0), networkPacketsDescriptor, startTime, networkStats.PacketsSent, networkStats.PacketsRecv)
95-
initializeNetworkMetric(metrics.At(startIdx+1), networkDroppedPacketsDescriptor, startTime, networkStats.Dropout, networkStats.Dropin)
96-
initializeNetworkMetric(metrics.At(startIdx+2), networkErrorsDescriptor, startTime, networkStats.Errout, networkStats.Errin)
97-
initializeNetworkMetric(metrics.At(startIdx+3), networkIODescriptor, startTime, networkStats.BytesSent, networkStats.BytesRecv)
98124
return nil
99125
}
100126

101-
func initializeNetworkMetric(metric pdata.Metric, metricDescriptor pdata.MetricDescriptor, startTime pdata.TimestampUnixNano, transmitValue, receiveValue uint64) {
127+
func initializeNetworkPacketsMetric(metric pdata.Metric, metricDescriptor pdata.MetricDescriptor, startTime pdata.TimestampUnixNano, ioCountersSlice []net.IOCountersStat) {
102128
metricDescriptor.CopyTo(metric.MetricDescriptor())
103129

104130
idps := metric.Int64DataPoints()
105-
idps.Resize(2)
106-
initializeNetworkDataPoint(idps.At(0), startTime, transmitDirectionLabelValue, int64(transmitValue))
107-
initializeNetworkDataPoint(idps.At(1), startTime, receiveDirectionLabelValue, int64(receiveValue))
131+
idps.Resize(2 * len(ioCountersSlice))
132+
for idx, ioCounters := range ioCountersSlice {
133+
initializeNetworkDataPoint(idps.At(2*idx+0), startTime, ioCounters.Name, transmitDirectionLabelValue, int64(ioCounters.PacketsSent))
134+
initializeNetworkDataPoint(idps.At(2*idx+1), startTime, ioCounters.Name, receiveDirectionLabelValue, int64(ioCounters.PacketsRecv))
135+
}
108136
}
109137

110-
func initializeNetworkDataPoint(dataPoint pdata.Int64DataPoint, startTime pdata.TimestampUnixNano, directionLabel string, value int64) {
138+
func initializeNetworkDroppedPacketsMetric(metric pdata.Metric, metricDescriptor pdata.MetricDescriptor, startTime pdata.TimestampUnixNano, ioCountersSlice []net.IOCountersStat) {
139+
metricDescriptor.CopyTo(metric.MetricDescriptor())
140+
141+
idps := metric.Int64DataPoints()
142+
idps.Resize(2 * len(ioCountersSlice))
143+
for idx, ioCounters := range ioCountersSlice {
144+
initializeNetworkDataPoint(idps.At(2*idx+0), startTime, ioCounters.Name, transmitDirectionLabelValue, int64(ioCounters.Dropout))
145+
initializeNetworkDataPoint(idps.At(2*idx+1), startTime, ioCounters.Name, receiveDirectionLabelValue, int64(ioCounters.Dropin))
146+
}
147+
}
148+
149+
func initializeNetworkErrorsMetric(metric pdata.Metric, metricDescriptor pdata.MetricDescriptor, startTime pdata.TimestampUnixNano, ioCountersSlice []net.IOCountersStat) {
150+
metricDescriptor.CopyTo(metric.MetricDescriptor())
151+
152+
idps := metric.Int64DataPoints()
153+
idps.Resize(2 * len(ioCountersSlice))
154+
for idx, ioCounters := range ioCountersSlice {
155+
initializeNetworkDataPoint(idps.At(2*idx+0), startTime, ioCounters.Name, transmitDirectionLabelValue, int64(ioCounters.Errout))
156+
initializeNetworkDataPoint(idps.At(2*idx+1), startTime, ioCounters.Name, receiveDirectionLabelValue, int64(ioCounters.Errin))
157+
}
158+
}
159+
160+
func initializeNetworkIOMetric(metric pdata.Metric, metricDescriptor pdata.MetricDescriptor, startTime pdata.TimestampUnixNano, ioCountersSlice []net.IOCountersStat) {
161+
metricDescriptor.CopyTo(metric.MetricDescriptor())
162+
163+
idps := metric.Int64DataPoints()
164+
idps.Resize(2 * len(ioCountersSlice))
165+
for idx, ioCounters := range ioCountersSlice {
166+
initializeNetworkDataPoint(idps.At(2*idx+0), startTime, ioCounters.Name, transmitDirectionLabelValue, int64(ioCounters.BytesSent))
167+
initializeNetworkDataPoint(idps.At(2*idx+1), startTime, ioCounters.Name, receiveDirectionLabelValue, int64(ioCounters.BytesRecv))
168+
}
169+
}
170+
171+
func initializeNetworkDataPoint(dataPoint pdata.Int64DataPoint, startTime pdata.TimestampUnixNano, interfaceLabel, directionLabel string, value int64) {
111172
labelsMap := dataPoint.LabelsMap()
173+
labelsMap.Insert(interfaceLabelName, interfaceLabel)
112174
labelsMap.Insert(directionLabelName, directionLabel)
113175
dataPoint.SetStartTime(startTime)
114176
dataPoint.SetTimestamp(pdata.TimestampUnixNano(uint64(time.Now().UnixNano())))
@@ -170,3 +232,22 @@ func initializeNetworkTCPConnectionsDataPoint(dataPoint pdata.Int64DataPoint, st
170232
dataPoint.SetTimestamp(pdata.TimestampUnixNano(uint64(time.Now().UnixNano())))
171233
dataPoint.SetValue(value)
172234
}
235+
236+
func (s *scraper) filterByInterface(ioCounters []net.IOCountersStat) []net.IOCountersStat {
237+
if s.includeFS == nil && s.excludeFS == nil {
238+
return ioCounters
239+
}
240+
241+
filteredIOCounters := make([]net.IOCountersStat, 0, len(ioCounters))
242+
for _, io := range ioCounters {
243+
if s.includeInterface(io.Name) {
244+
filteredIOCounters = append(filteredIOCounters, io)
245+
}
246+
}
247+
return filteredIOCounters
248+
}
249+
250+
func (s *scraper) includeInterface(interfaceName string) bool {
251+
return (s.includeFS == nil || s.includeFS.Matches(interfaceName)) &&
252+
(s.excludeFS == nil || !s.excludeFS.Matches(interfaceName))
253+
}

receiver/hostmetricsreceiver/internal/scraper/networkscraper/network_scraper_test.go

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,49 @@ import (
2424
"github.com/stretchr/testify/require"
2525

2626
"go.opentelemetry.io/collector/consumer/pdata"
27+
"go.opentelemetry.io/collector/internal/processor/filterset"
2728
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal"
2829
)
2930

3031
func TestScrapeMetrics(t *testing.T) {
3132
type testCase struct {
32-
name string
33-
bootTimeFunc func() (uint64, error)
34-
ioCountersFunc func(bool) ([]net.IOCountersStat, error)
35-
connectionsFunc func(string) ([]net.ConnectionStat, error)
36-
expectedStartTime pdata.TimestampUnixNano
37-
initializationErr string
38-
expectedErr string
33+
name string
34+
config Config
35+
bootTimeFunc func() (uint64, error)
36+
ioCountersFunc func(bool) ([]net.IOCountersStat, error)
37+
connectionsFunc func(string) ([]net.ConnectionStat, error)
38+
expectNetworkMetrics bool
39+
expectedStartTime pdata.TimestampUnixNano
40+
newErrRegex string
41+
initializationErr string
42+
expectedErr string
3943
}
4044

4145
testCases := []testCase{
4246
{
43-
name: "Standard",
47+
name: "Standard",
48+
expectNetworkMetrics: true,
4449
},
4550
{
46-
name: "Validate Start Time",
47-
bootTimeFunc: func() (uint64, error) { return 100, nil },
48-
expectedStartTime: 100 * 1e9,
51+
name: "Validate Start Time",
52+
bootTimeFunc: func() (uint64, error) { return 100, nil },
53+
expectNetworkMetrics: true,
54+
expectedStartTime: 100 * 1e9,
55+
},
56+
{
57+
name: "Include Filter that matches nothing",
58+
config: Config{Include: MatchConfig{filterset.Config{MatchType: "strict"}, []string{"@*^#&*$^#)"}}},
59+
expectNetworkMetrics: false,
60+
},
61+
{
62+
name: "Invalid Include Filter",
63+
config: Config{Include: MatchConfig{Interfaces: []string{"test"}}},
64+
newErrRegex: "^error creating network interface include filters:",
65+
},
66+
{
67+
name: "Invalid Exclude Filter",
68+
config: Config{Exclude: MatchConfig{Interfaces: []string{"test"}}},
69+
newErrRegex: "^error creating network interface exclude filters:",
4970
},
5071
{
5172
name: "Boot Time Error",
@@ -66,7 +87,14 @@ func TestScrapeMetrics(t *testing.T) {
6687

6788
for _, test := range testCases {
6889
t.Run(test.name, func(t *testing.T) {
69-
scraper := newNetworkScraper(context.Background(), &Config{})
90+
scraper, err := newNetworkScraper(context.Background(), &test.config)
91+
if test.newErrRegex != "" {
92+
require.Error(t, err)
93+
require.Regexp(t, test.newErrRegex, err)
94+
return
95+
}
96+
require.NoError(t, err, "Failed to create network scraper: %v", err)
97+
7098
if test.bootTimeFunc != nil {
7199
scraper.bootTime = test.bootTimeFunc
72100
}
@@ -77,7 +105,7 @@ func TestScrapeMetrics(t *testing.T) {
77105
scraper.connections = test.connectionsFunc
78106
}
79107

80-
err := scraper.Initialize(context.Background())
108+
err = scraper.Initialize(context.Background())
81109
if test.initializationErr != "" {
82110
assert.EqualError(t, err, test.initializationErr)
83111
return
@@ -92,14 +120,21 @@ func TestScrapeMetrics(t *testing.T) {
92120
}
93121
require.NoError(t, err, "Failed to scrape metrics: %v", err)
94122

95-
assert.Equal(t, 5, metrics.Len())
96-
97-
assertNetworkIOMetricValid(t, metrics.At(0), networkPacketsDescriptor, test.expectedStartTime)
98-
assertNetworkIOMetricValid(t, metrics.At(1), networkDroppedPacketsDescriptor, test.expectedStartTime)
99-
assertNetworkIOMetricValid(t, metrics.At(2), networkErrorsDescriptor, test.expectedStartTime)
100-
assertNetworkIOMetricValid(t, metrics.At(3), networkIODescriptor, test.expectedStartTime)
123+
expectedMetricCount := 1
124+
if test.expectNetworkMetrics {
125+
expectedMetricCount += 4
126+
}
127+
assert.Equal(t, expectedMetricCount, metrics.Len())
101128

102-
assertNetworkTCPConnectionsMetricValid(t, metrics.At(4))
129+
idx := 0
130+
if test.expectNetworkMetrics {
131+
assertNetworkIOMetricValid(t, metrics.At(idx+0), networkPacketsDescriptor, test.expectedStartTime)
132+
assertNetworkIOMetricValid(t, metrics.At(idx+1), networkDroppedPacketsDescriptor, test.expectedStartTime)
133+
assertNetworkIOMetricValid(t, metrics.At(idx+2), networkErrorsDescriptor, test.expectedStartTime)
134+
assertNetworkIOMetricValid(t, metrics.At(idx+3), networkIODescriptor, test.expectedStartTime)
135+
idx += 4
136+
}
137+
assertNetworkTCPConnectionsMetricValid(t, metrics.At(idx+0))
103138
})
104139
}
105140
}
@@ -109,7 +144,8 @@ func assertNetworkIOMetricValid(t *testing.T, metric pdata.Metric, descriptor pd
109144
if startTime != 0 {
110145
internal.AssertInt64MetricStartTimeEquals(t, metric, startTime)
111146
}
112-
assert.Equal(t, 2, metric.Int64DataPoints().Len())
147+
assert.GreaterOrEqual(t, metric.Int64DataPoints().Len(), 2)
148+
internal.AssertInt64MetricLabelExists(t, metric, 0, interfaceLabelName)
113149
internal.AssertInt64MetricLabelHasValue(t, metric, 0, directionLabelName, transmitDirectionLabelValue)
114150
internal.AssertInt64MetricLabelHasValue(t, metric, 1, directionLabelName, receiveDirectionLabelValue)
115151
}

receiver/hostmetricsreceiver/testdata/config.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ receivers:
1111
filesystem:
1212
memory:
1313
network:
14+
include:
15+
interfaces: ["test1"]
16+
match_type: "strict"
1417
processes:
1518
swap:
1619
process:
1720
include:
18-
names: ["test1", "test2"]
21+
names: ["test2", "test3"]
1922
match_type: "regexp"
2023

2124
processors:

0 commit comments

Comments
 (0)