Skip to content

Commit ccea33f

Browse files
authored
Gather concurrently from snmp agents (#3365)
1 parent db8b7e2 commit ccea33f

File tree

2 files changed

+64
-41
lines changed

2 files changed

+64
-41
lines changed

plugins/inputs/snmp/snmp.go

+37-28
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ type Snmp struct {
135135
Name string
136136
Fields []Field `toml:"field"`
137137

138-
connectionCache map[string]snmpConnection
138+
connectionCache []snmpConnection
139139
initialized bool
140140
}
141141

@@ -144,6 +144,8 @@ func (s *Snmp) init() error {
144144
return nil
145145
}
146146

147+
s.connectionCache = make([]snmpConnection, len(s.Agents))
148+
147149
for i := range s.Tables {
148150
if err := s.Tables[i].init(); err != nil {
149151
return Errorf(err, "initializing table %s", s.Tables[i].Name)
@@ -342,30 +344,36 @@ func (s *Snmp) Gather(acc telegraf.Accumulator) error {
342344
return err
343345
}
344346

345-
for _, agent := range s.Agents {
346-
gs, err := s.getConnection(agent)
347-
if err != nil {
348-
acc.AddError(Errorf(err, "agent %s", agent))
349-
continue
350-
}
347+
var wg sync.WaitGroup
348+
for i, agent := range s.Agents {
349+
wg.Add(1)
350+
go func(i int, agent string) {
351+
defer wg.Done()
352+
gs, err := s.getConnection(i)
353+
if err != nil {
354+
acc.AddError(Errorf(err, "agent %s", agent))
355+
return
356+
}
351357

352-
// First is the top-level fields. We treat the fields as table prefixes with an empty index.
353-
t := Table{
354-
Name: s.Name,
355-
Fields: s.Fields,
356-
}
357-
topTags := map[string]string{}
358-
if err := s.gatherTable(acc, gs, t, topTags, false); err != nil {
359-
acc.AddError(Errorf(err, "agent %s", agent))
360-
}
358+
// First is the top-level fields. We treat the fields as table prefixes with an empty index.
359+
t := Table{
360+
Name: s.Name,
361+
Fields: s.Fields,
362+
}
363+
topTags := map[string]string{}
364+
if err := s.gatherTable(acc, gs, t, topTags, false); err != nil {
365+
acc.AddError(Errorf(err, "agent %s", agent))
366+
}
361367

362-
// Now is the real tables.
363-
for _, t := range s.Tables {
364-
if err := s.gatherTable(acc, gs, t, topTags, true); err != nil {
365-
acc.AddError(Errorf(err, "agent %s: gathering table %s", agent, t.Name))
368+
// Now is the real tables.
369+
for _, t := range s.Tables {
370+
if err := s.gatherTable(acc, gs, t, topTags, true); err != nil {
371+
acc.AddError(Errorf(err, "agent %s: gathering table %s", agent, t.Name))
372+
}
366373
}
367-
}
374+
}(i, agent)
368375
}
376+
wg.Wait()
369377

370378
return nil
371379
}
@@ -568,16 +576,18 @@ func (gsw gosnmpWrapper) Get(oids []string) (*gosnmp.SnmpPacket, error) {
568576
}
569577

570578
// getConnection creates a snmpConnection (*gosnmp.GoSNMP) object and caches the
571-
// result using `agent` as the cache key.
572-
func (s *Snmp) getConnection(agent string) (snmpConnection, error) {
573-
if s.connectionCache == nil {
574-
s.connectionCache = map[string]snmpConnection{}
575-
}
576-
if gs, ok := s.connectionCache[agent]; ok {
579+
// result using `agentIndex` as the cache key. This is done to allow multiple
580+
// connections to a single address. It is an error to use a connection in
581+
// more than one goroutine.
582+
func (s *Snmp) getConnection(idx int) (snmpConnection, error) {
583+
if gs := s.connectionCache[idx]; gs != nil {
577584
return gs, nil
578585
}
579586

587+
agent := s.Agents[idx]
588+
580589
gs := gosnmpWrapper{&gosnmp.GoSNMP{}}
590+
s.connectionCache[idx] = gs
581591

582592
host, portStr, err := net.SplitHostPort(agent)
583593
if err != nil {
@@ -677,7 +687,6 @@ func (s *Snmp) getConnection(agent string) (snmpConnection, error) {
677687
return nil, Errorf(err, "setting up connection")
678688
}
679689

680-
s.connectionCache[agent] = gs
681690
return gs, nil
682691
}
683692

plugins/inputs/snmp/snmp_test.go

+27-13
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func TestSampleConfig(t *testing.T) {
120120
},
121121
},
122122
}
123-
assert.Equal(t, s, *conf.Inputs.Snmp[0])
123+
assert.Equal(t, &s, conf.Inputs.Snmp[0])
124124
}
125125

126126
func TestFieldInit(t *testing.T) {
@@ -251,21 +251,24 @@ func TestSnmpInit_noTranslate(t *testing.T) {
251251

252252
func TestGetSNMPConnection_v2(t *testing.T) {
253253
s := &Snmp{
254+
Agents: []string{"1.2.3.4:567", "1.2.3.4"},
254255
Timeout: internal.Duration{Duration: 3 * time.Second},
255256
Retries: 4,
256257
Version: 2,
257258
Community: "foo",
258259
}
260+
err := s.init()
261+
require.NoError(t, err)
259262

260-
gsc, err := s.getConnection("1.2.3.4:567")
263+
gsc, err := s.getConnection(0)
261264
require.NoError(t, err)
262265
gs := gsc.(gosnmpWrapper)
263266
assert.Equal(t, "1.2.3.4", gs.Target)
264267
assert.EqualValues(t, 567, gs.Port)
265268
assert.Equal(t, gosnmp.Version2c, gs.Version)
266269
assert.Equal(t, "foo", gs.Community)
267270

268-
gsc, err = s.getConnection("1.2.3.4")
271+
gsc, err = s.getConnection(1)
269272
require.NoError(t, err)
270273
gs = gsc.(gosnmpWrapper)
271274
assert.Equal(t, "1.2.3.4", gs.Target)
@@ -274,6 +277,7 @@ func TestGetSNMPConnection_v2(t *testing.T) {
274277

275278
func TestGetSNMPConnection_v3(t *testing.T) {
276279
s := &Snmp{
280+
Agents: []string{"1.2.3.4"},
277281
Version: 3,
278282
MaxRepetitions: 20,
279283
ContextName: "mycontext",
@@ -287,8 +291,10 @@ func TestGetSNMPConnection_v3(t *testing.T) {
287291
EngineBoots: 1,
288292
EngineTime: 2,
289293
}
294+
err := s.init()
295+
require.NoError(t, err)
290296

291-
gsc, err := s.getConnection("1.2.3.4")
297+
gsc, err := s.getConnection(0)
292298
require.NoError(t, err)
293299
gs := gsc.(gosnmpWrapper)
294300
assert.Equal(t, gs.Version, gosnmp.Version3)
@@ -308,15 +314,22 @@ func TestGetSNMPConnection_v3(t *testing.T) {
308314
}
309315

310316
func TestGetSNMPConnection_caching(t *testing.T) {
311-
s := &Snmp{}
312-
gs1, err := s.getConnection("1.2.3.4")
317+
s := &Snmp{
318+
Agents: []string{"1.2.3.4", "1.2.3.5", "1.2.3.5"},
319+
}
320+
err := s.init()
321+
require.NoError(t, err)
322+
gs1, err := s.getConnection(0)
323+
require.NoError(t, err)
324+
gs2, err := s.getConnection(0)
313325
require.NoError(t, err)
314-
gs2, err := s.getConnection("1.2.3.4")
326+
gs3, err := s.getConnection(1)
315327
require.NoError(t, err)
316-
gs3, err := s.getConnection("1.2.3.5")
328+
gs4, err := s.getConnection(2)
317329
require.NoError(t, err)
318330
assert.True(t, gs1 == gs2)
319331
assert.False(t, gs2 == gs3)
332+
assert.False(t, gs3 == gs4)
320333
}
321334

322335
func TestGosnmpWrapper_walk_retry(t *testing.T) {
@@ -560,11 +573,11 @@ func TestGather(t *testing.T) {
560573
},
561574
},
562575

563-
connectionCache: map[string]snmpConnection{
564-
"TestGather": tsc,
576+
connectionCache: []snmpConnection{
577+
tsc,
565578
},
579+
initialized: true,
566580
}
567-
568581
acc := &testutil.Accumulator{}
569582

570583
tstart := time.Now()
@@ -607,9 +620,10 @@ func TestGather_host(t *testing.T) {
607620
},
608621
},
609622

610-
connectionCache: map[string]snmpConnection{
611-
"TestGather": tsc,
623+
connectionCache: []snmpConnection{
624+
tsc,
612625
},
626+
initialized: true,
613627
}
614628

615629
acc := &testutil.Accumulator{}

0 commit comments

Comments
 (0)