diff --git a/plugins/inputs/s7comm/s7comm.go b/plugins/inputs/s7comm/s7comm.go
index ed99f2d7c..c7bfd2f6b 100644
--- a/plugins/inputs/s7comm/s7comm.go
+++ b/plugins/inputs/s7comm/s7comm.go
@@ -25,8 +25,6 @@ import (
//go:embed sample.conf
var sampleConfig string
-const addressRegexp = `^(?P[A-Z]+)(?P[0-9]+)\.(?P[A-Z]+)(?P[0-9]+)(?:\.(?P.*))?$`
-
var (
regexAddr = regexp.MustCompile(addressRegexp)
// Area mapping taken from https://github.com/robinson/gos7/blob/master/client.go
@@ -60,32 +58,8 @@ var (
}
)
-type metricFieldDefinition struct {
- Name string `toml:"name"`
- Address string `toml:"address"`
-}
+const addressRegexp = `^(?P[A-Z]+)(?P[0-9]+)\.(?P[A-Z]+)(?P[0-9]+)(?:\.(?P.*))?$`
-type metricDefinition struct {
- Name string `toml:"name"`
- Fields []metricFieldDefinition `toml:"fields"`
- Tags map[string]string `toml:"tags"`
-}
-
-type converterFunc func([]byte) interface{}
-
-type batch struct {
- items []gos7.S7DataItem
- mappings []fieldMapping
-}
-
-type fieldMapping struct {
- measurement string
- field string
- tags map[string]string
- convert converterFunc
-}
-
-// S7comm represents the plugin
type S7comm struct {
Server string `toml:"server"`
Rack int `toml:"rack"`
@@ -102,13 +76,35 @@ type S7comm struct {
batches []batch
}
-// SampleConfig returns a basic configuration for the plugin
+type metricDefinition struct {
+ Name string `toml:"name"`
+ Fields []metricFieldDefinition `toml:"fields"`
+ Tags map[string]string `toml:"tags"`
+}
+
+type metricFieldDefinition struct {
+ Name string `toml:"name"`
+ Address string `toml:"address"`
+}
+
+type batch struct {
+ items []gos7.S7DataItem
+ mappings []fieldMapping
+}
+
+type fieldMapping struct {
+ measurement string
+ field string
+ tags map[string]string
+ convert converterFunc
+}
+
+type converterFunc func([]byte) interface{}
+
func (*S7comm) SampleConfig() string {
return sampleConfig
}
-// Init checks the config settings and prepares the plugin. It's called
-// once by the Telegraf agent after parsing the config settings.
func (s *S7comm) Init() error {
// Check settings
if s.Server == "" {
@@ -150,8 +146,7 @@ func (s *S7comm) Init() error {
return s.createRequests()
}
-// Start initializes the connection to the remote endpoint
-func (s *S7comm) Start(_ telegraf.Accumulator) error {
+func (s *S7comm) Start(telegraf.Accumulator) error {
s.Log.Debugf("Connecting to %q...", s.Server)
if err := s.handler.Connect(); err != nil {
return &internal.StartupError{
@@ -164,15 +159,6 @@ func (s *S7comm) Start(_ telegraf.Accumulator) error {
return nil
}
-// Stop disconnects from the remote endpoint and cleans up
-func (s *S7comm) Stop() {
- if s.handler != nil {
- s.Log.Debugf("Disconnecting from %q...", s.handler.Address)
- s.handler.Close()
- }
-}
-
-// Gather collects the data from the device
func (s *S7comm) Gather(acc telegraf.Accumulator) error {
timestamp := time.Now()
grouper := metric.NewSeriesGrouper()
@@ -208,7 +194,13 @@ func (s *S7comm) Gather(acc telegraf.Accumulator) error {
return nil
}
-// Internal functions
+func (s *S7comm) Stop() {
+ if s.handler != nil {
+ s.Log.Debugf("Disconnecting from %q...", s.handler.Address)
+ s.handler.Close()
+ }
+}
+
func (s *S7comm) createRequests() error {
seed := maphash.MakeSeed()
seenFields := make(map[uint64]bool)
diff --git a/plugins/inputs/s7comm/s7comm_test.go b/plugins/inputs/s7comm/s7comm_test.go
index 32109602d..89a21b505 100644
--- a/plugins/inputs/s7comm/s7comm_test.go
+++ b/plugins/inputs/s7comm/s7comm_test.go
@@ -711,14 +711,14 @@ func TestMetricCollisions(t *testing.T) {
func TestConnectionLoss(t *testing.T) {
// Create fake S7 comm server that can accept connects
- server, err := NewMockServer("127.0.0.1:0")
+ server, err := newMockServer()
require.NoError(t, err)
- defer server.Close()
- require.NoError(t, server.Start())
+ defer server.close()
+ server.start()
// Create the plugin and attempt a connection
plugin := &S7comm{
- Server: server.Addr(),
+ Server: server.addr(),
Rack: 0,
Slot: 2,
DebugConnection: true,
@@ -742,20 +742,20 @@ func TestConnectionLoss(t *testing.T) {
require.NoError(t, plugin.Gather(&acc))
require.NoError(t, plugin.Gather(&acc))
plugin.Stop()
- server.Close()
+ server.close()
- require.Equal(t, uint32(3), server.ConnectionAttempts.Load())
+ require.Equal(t, uint32(3), server.connectionAttempts.Load())
}
func TestStartupErrorBehaviorError(t *testing.T) {
// Create fake S7 comm server that can accept connects
- server, err := NewMockServer("127.0.0.1:0")
+ server, err := newMockServer()
require.NoError(t, err)
- defer server.Close()
+ defer server.close()
// Setup the plugin and the model to be able to use the startup retry strategy
plugin := &S7comm{
- Server: server.Addr(),
+ Server: server.addr(),
Rack: 0,
Slot: 2,
DebugConnection: true,
@@ -784,18 +784,18 @@ func TestStartupErrorBehaviorError(t *testing.T) {
// Starting the plugin will fail with an error because the server does not listen
var acc testutil.Accumulator
- require.ErrorContains(t, model.Start(&acc), "connecting to \""+server.Addr()+"\" failed")
+ require.ErrorContains(t, model.Start(&acc), "connecting to \""+server.addr()+"\" failed")
}
func TestStartupErrorBehaviorIgnore(t *testing.T) {
// Create fake S7 comm server that can accept connects
- server, err := NewMockServer("127.0.0.1:0")
+ server, err := newMockServer()
require.NoError(t, err)
- defer server.Close()
+ defer server.close()
// Setup the plugin and the model to be able to use the startup retry strategy
plugin := &S7comm{
- Server: server.Addr(),
+ Server: server.addr(),
Rack: 0,
Slot: 2,
DebugConnection: true,
@@ -828,20 +828,20 @@ func TestStartupErrorBehaviorIgnore(t *testing.T) {
// the plugin.
var acc testutil.Accumulator
err = model.Start(&acc)
- require.ErrorContains(t, err, "connecting to \""+server.Addr()+"\" failed")
+ require.ErrorContains(t, err, "connecting to \""+server.addr()+"\" failed")
var fatalErr *internal.FatalError
require.ErrorAs(t, err, &fatalErr)
}
func TestStartupErrorBehaviorRetry(t *testing.T) {
// Create fake S7 comm server that can accept connects
- server, err := NewMockServer("127.0.0.1:0")
+ server, err := newMockServer()
require.NoError(t, err)
- defer server.Close()
+ defer server.close()
// Setup the plugin and the model to be able to use the startup retry strategy
plugin := &S7comm{
- Server: server.Addr(),
+ Server: server.addr(),
Rack: 0,
Slot: 2,
DebugConnection: true,
@@ -880,37 +880,36 @@ func TestStartupErrorBehaviorRetry(t *testing.T) {
require.Equal(t, int64(2), model.StartupErrors.Get())
// Allow connection in the server, now the connection should succeed
- require.NoError(t, server.Start())
+ server.start()
defer model.Stop()
require.NoError(t, model.Gather(&acc))
}
-type MockServer struct {
- ConnectionAttempts atomic.Uint32
-
- listener net.Listener
+type mockServer struct {
+ connectionAttempts atomic.Uint32
+ listener net.Listener
}
-func NewMockServer(addr string) (*MockServer, error) {
- l, err := net.Listen("tcp", addr)
+func newMockServer() (*mockServer, error) {
+ l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, err
}
- return &MockServer{listener: l}, nil
+ return &mockServer{listener: l}, nil
}
-func (s *MockServer) Addr() string {
+func (s *mockServer) addr() string {
return s.listener.Addr().String()
}
-func (s *MockServer) Close() error {
+func (s *mockServer) close() error {
if s.listener != nil {
return s.listener.Close()
}
return nil
}
-func (s *MockServer) Start() error {
+func (s *mockServer) start() {
go func() {
defer s.listener.Close()
for {
@@ -924,7 +923,7 @@ func (s *MockServer) Start() error {
}
// Count the number of connection attempts
- s.ConnectionAttempts.Add(1)
+ s.connectionAttempts.Add(1)
buf := make([]byte, 4096)
@@ -961,6 +960,4 @@ func (s *MockServer) Start() error {
conn.Close()
}
}()
-
- return nil
}
diff --git a/plugins/inputs/salesforce/salesforce.go b/plugins/inputs/salesforce/salesforce.go
index e060d7401..dd4ccd9e1 100644
--- a/plugins/inputs/salesforce/salesforce.go
+++ b/plugins/inputs/salesforce/salesforce.go
@@ -21,6 +21,25 @@ import (
//go:embed sample.conf
var sampleConfig string
+const (
+ defaultVersion = "39.0"
+ defaultEnvironment = "production"
+)
+
+type Salesforce struct {
+ Username string `toml:"username"`
+ Password string `toml:"password"`
+ SecurityToken string `toml:"security_token"`
+ Environment string `toml:"environment"`
+ Version string `toml:"version"`
+
+ sessionID string
+ serverURL *url.URL
+ organizationID string
+
+ client *http.Client
+}
+
type limit struct {
Max int
Remaining int
@@ -28,42 +47,10 @@ type limit struct {
type limits map[string]limit
-type Salesforce struct {
- Username string
- Password string
- SecurityToken string
- Environment string
- SessionID string
- ServerURL *url.URL
- OrganizationID string
- Version string
-
- client *http.Client
-}
-
-const defaultVersion = "39.0"
-const defaultEnvironment = "production"
-
-// returns a new Salesforce plugin instance
-func NewSalesforce() *Salesforce {
- tr := &http.Transport{
- ResponseHeaderTimeout: 5 * time.Second,
- }
- client := &http.Client{
- Transport: tr,
- Timeout: 10 * time.Second,
- }
- return &Salesforce{
- client: client,
- Version: defaultVersion,
- Environment: defaultEnvironment}
-}
-
func (*Salesforce) SampleConfig() string {
return sampleConfig
}
-// Reads limits values from Salesforce API
func (s *Salesforce) Gather(acc telegraf.Accumulator) error {
limits, err := s.fetchLimits()
if err != nil {
@@ -71,8 +58,8 @@ func (s *Salesforce) Gather(acc telegraf.Accumulator) error {
}
tags := map[string]string{
- "organization_id": s.OrganizationID,
- "host": s.ServerURL.Host,
+ "organization_id": s.organizationID,
+ "host": s.serverURL.Host,
}
fields := make(map[string]interface{})
@@ -88,18 +75,18 @@ func (s *Salesforce) Gather(acc telegraf.Accumulator) error {
// query the limits endpoint
func (s *Salesforce) queryLimits() (*http.Response, error) {
- endpoint := fmt.Sprintf("%s://%s/services/data/v%s/limits", s.ServerURL.Scheme, s.ServerURL.Host, s.Version)
+ endpoint := fmt.Sprintf("%s://%s/services/data/v%s/limits", s.serverURL.Scheme, s.serverURL.Host, s.Version)
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
return nil, err
}
req.Header.Add("Accept", "encoding/json")
- req.Header.Add("Authorization", "Bearer "+s.SessionID)
+ req.Header.Add("Authorization", "Bearer "+s.sessionID)
return s.client.Do(req)
}
func (s *Salesforce) isAuthenticated() bool {
- return s.SessionID != ""
+ return s.sessionID != ""
}
func (s *Salesforce) fetchLimits() (limits, error) {
@@ -218,15 +205,29 @@ func (s *Salesforce) login() error {
return err
}
- s.SessionID = loginResult.SessionID
- s.OrganizationID = loginResult.OrganizationID
- s.ServerURL, err = url.Parse(loginResult.ServerURL)
+ s.sessionID = loginResult.SessionID
+ s.organizationID = loginResult.OrganizationID
+ s.serverURL, err = url.Parse(loginResult.ServerURL)
return err
}
+func newSalesforce() *Salesforce {
+ tr := &http.Transport{
+ ResponseHeaderTimeout: 5 * time.Second,
+ }
+ client := &http.Client{
+ Transport: tr,
+ Timeout: 10 * time.Second,
+ }
+ return &Salesforce{
+ client: client,
+ Version: defaultVersion,
+ Environment: defaultEnvironment}
+}
+
func init() {
inputs.Add("salesforce", func() telegraf.Input {
- return NewSalesforce()
+ return newSalesforce()
})
}
diff --git a/plugins/inputs/salesforce/salesforce_test.go b/plugins/inputs/salesforce/salesforce_test.go
index 2ec82ddb3..21f54d096 100644
--- a/plugins/inputs/salesforce/salesforce_test.go
+++ b/plugins/inputs/salesforce/salesforce_test.go
@@ -1,4 +1,4 @@
-package salesforce_test
+package salesforce
import (
"net/http"
@@ -8,7 +8,6 @@ import (
"github.com/stretchr/testify/require"
- "github.com/influxdata/telegraf/plugins/inputs/salesforce"
"github.com/influxdata/telegraf/testutil"
)
@@ -23,13 +22,13 @@ func Test_Gather(t *testing.T) {
}))
defer fakeServer.Close()
- plugin := salesforce.NewSalesforce()
- plugin.SessionID = "test_session"
+ plugin := newSalesforce()
+ plugin.sessionID = "test_session"
u, err := url.Parse(fakeServer.URL)
if err != nil {
t.Error(err)
}
- plugin.ServerURL = u
+ plugin.serverURL = u
var acc testutil.Accumulator
require.NoError(t, acc.GatherError(plugin.Gather))
diff --git a/plugins/inputs/sensors/sensors.go b/plugins/inputs/sensors/sensors.go
index 443874667..a3244f7d8 100644
--- a/plugins/inputs/sensors/sensors.go
+++ b/plugins/inputs/sensors/sensors.go
@@ -28,14 +28,14 @@ var (
defaultTimeout = config.Duration(5 * time.Second)
)
+const cmd = "sensors"
+
type Sensors struct {
RemoveNumbers bool `toml:"remove_numbers"`
Timeout config.Duration `toml:"timeout"`
path string
}
-const cmd = "sensors"
-
func (*Sensors) SampleConfig() string {
return sampleConfig
}
diff --git a/plugins/inputs/sensors/sensors_notlinux.go b/plugins/inputs/sensors/sensors_notlinux.go
index 5973451c7..1b3504c8b 100644
--- a/plugins/inputs/sensors/sensors_notlinux.go
+++ b/plugins/inputs/sensors/sensors_notlinux.go
@@ -17,12 +17,14 @@ type Sensors struct {
Log telegraf.Logger `toml:"-"`
}
+func (*Sensors) SampleConfig() string { return sampleConfig }
+
func (s *Sensors) Init() error {
- s.Log.Warn("current platform is not supported")
+ s.Log.Warn("Current platform is not supported")
return nil
}
-func (*Sensors) SampleConfig() string { return sampleConfig }
-func (*Sensors) Gather(_ telegraf.Accumulator) error { return nil }
+
+func (*Sensors) Gather(telegraf.Accumulator) error { return nil }
func init() {
inputs.Add("sensors", func() telegraf.Input {
diff --git a/plugins/inputs/sensors/sensors_test.go b/plugins/inputs/sensors/sensors_test.go
index 2a94cea3a..69c4468cd 100644
--- a/plugins/inputs/sensors/sensors_test.go
+++ b/plugins/inputs/sensors/sensors_test.go
@@ -304,7 +304,7 @@ func fakeExecCommand(command string, args ...string) *exec.Cmd {
// For example, if you run:
// GO_WANT_HELPER_PROCESS=1 go test -test.run=TestHelperProcess -- chrony tracking
// it returns below mockData.
-func TestHelperProcess(_ *testing.T) {
+func TestHelperProcess(*testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return
}
diff --git a/plugins/inputs/sflow/binaryio/minreader.go b/plugins/inputs/sflow/binaryio/minreader.go
index 35ccdbcf2..fdf6da439 100644
--- a/plugins/inputs/sflow/binaryio/minreader.go
+++ b/plugins/inputs/sflow/binaryio/minreader.go
@@ -4,33 +4,33 @@ import "io"
// MinimumReader is the implementation for MinReader.
type MinimumReader struct {
- R io.Reader
- MinNumberOfBytesToRead int64 // Min number of bytes we need to read from the reader
+ reader io.Reader
+ minNumberOfBytesToRead int64 // Min number of bytes we need to read from the reader
}
-// MinReader reads from R but ensures there is at least N bytes read from the reader.
+// MinReader reads from the reader but ensures there is at least N bytes read from the reader.
// The reader should call Close() when they are done reading.
-// Closing the MinReader will read and discard any unread bytes up to MinNumberOfBytesToRead.
+// Closing the MinReader will read and discard any unread bytes up to minNumberOfBytesToRead.
// CLosing the MinReader does NOT close the underlying reader.
// The underlying implementation is a MinimumReader, which implements ReaderCloser.
func MinReader(r io.Reader, minNumberOfBytesToRead int64) *MinimumReader {
return &MinimumReader{
- R: r,
- MinNumberOfBytesToRead: minNumberOfBytesToRead,
+ reader: r,
+ minNumberOfBytesToRead: minNumberOfBytesToRead,
}
}
func (r *MinimumReader) Read(p []byte) (n int, err error) {
- n, err = r.R.Read(p)
- r.MinNumberOfBytesToRead -= int64(n)
+ n, err = r.reader.Read(p)
+ r.minNumberOfBytesToRead -= int64(n)
return n, err
}
// Close does not close the underlying reader, only the MinimumReader
func (r *MinimumReader) Close() error {
- if r.MinNumberOfBytesToRead > 0 {
- b := make([]byte, r.MinNumberOfBytesToRead)
- _, err := r.R.Read(b)
+ if r.minNumberOfBytesToRead > 0 {
+ b := make([]byte, r.minNumberOfBytesToRead)
+ _, err := r.reader.Read(b)
return err
}
return nil
diff --git a/plugins/inputs/sflow/decoder_test.go b/plugins/inputs/sflow/decoder_test.go
index 9253e7a2e..fd8c1bc05 100644
--- a/plugins/inputs/sflow/decoder_test.go
+++ b/plugins/inputs/sflow/decoder_test.go
@@ -66,12 +66,12 @@ func TestIPv4SW(t *testing.T) {
actual := make([]telegraf.Metric, 0)
dc := newDecoder()
- dc.OnPacket(func(p *v5Format) {
+ dc.onPacket(func(p *v5Format) {
metrics := makeMetrics(p)
actual = append(actual, metrics...)
})
buf := bytes.NewReader(packet)
- err = dc.Decode(buf)
+ err = dc.decode(buf)
require.NoError(t, err)
expected := []telegraf.Metric{
@@ -165,7 +165,7 @@ func BenchmarkDecodeIPv4SW(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
- _, err = dc.DecodeOnePacket(bytes.NewBuffer(packet))
+ _, err = dc.decodeOnePacket(bytes.NewBuffer(packet))
if err != nil {
panic(err)
}
@@ -189,7 +189,7 @@ func TestExpandFlow(t *testing.T) {
require.NoError(t, err)
dc := newDecoder()
- p, err := dc.DecodeOnePacket(bytes.NewBuffer(packet))
+ p, err := dc.decodeOnePacket(bytes.NewBuffer(packet))
require.NoError(t, err)
actual := makeMetrics(p)
@@ -330,7 +330,7 @@ func TestIPv4SWRT(t *testing.T) {
require.NoError(t, err)
dc := newDecoder()
- p, err := dc.DecodeOnePacket(bytes.NewBuffer(packet))
+ p, err := dc.decodeOnePacket(bytes.NewBuffer(packet))
require.NoError(t, err)
actual := makeMetrics(p)
@@ -557,7 +557,7 @@ func TestIPv6SW(t *testing.T) {
require.NoError(t, err)
dc := newDecoder()
- p, err := dc.DecodeOnePacket(bytes.NewBuffer(packet))
+ p, err := dc.decodeOnePacket(bytes.NewBuffer(packet))
require.NoError(t, err)
actual := makeMetrics(p)
@@ -628,7 +628,7 @@ func TestExpandFlowCounter(t *testing.T) {
require.NoError(t, err)
dc := newDecoder()
- p, err := dc.DecodeOnePacket(bytes.NewBuffer(packet))
+ p, err := dc.decodeOnePacket(bytes.NewBuffer(packet))
require.NoError(t, err)
actual := makeMetrics(p)
@@ -830,7 +830,7 @@ func TestFlowExpandCounter(t *testing.T) {
require.NoError(t, err)
dc := newDecoder()
- p, err := dc.DecodeOnePacket(bytes.NewBuffer(packet))
+ p, err := dc.decodeOnePacket(bytes.NewBuffer(packet))
require.NoError(t, err)
actual := makeMetrics(p)
diff --git a/plugins/inputs/sflow/metricencoder.go b/plugins/inputs/sflow/metricencoder.go
index b06c239e2..0a85418b2 100644
--- a/plugins/inputs/sflow/metricencoder.go
+++ b/plugins/inputs/sflow/metricencoder.go
@@ -12,22 +12,22 @@ func makeMetrics(p *v5Format) []telegraf.Metric {
now := time.Now()
metrics := make([]telegraf.Metric, 0)
tags := map[string]string{
- "agent_address": p.AgentAddress.String(),
+ "agent_address": p.agentAddress.String(),
}
fields := make(map[string]interface{}, 2)
- for _, sample := range p.Samples {
- tags["input_ifindex"] = strconv.FormatUint(uint64(sample.SampleData.InputIfIndex), 10)
- tags["output_ifindex"] = strconv.FormatUint(uint64(sample.SampleData.OutputIfIndex), 10)
- tags["sample_direction"] = sample.SampleData.SampleDirection
- tags["source_id_index"] = strconv.FormatUint(uint64(sample.SampleData.SourceIDIndex), 10)
- tags["source_id_type"] = strconv.FormatUint(uint64(sample.SampleData.SourceIDType), 10)
- fields["drops"] = sample.SampleData.Drops
- fields["sampling_rate"] = sample.SampleData.SamplingRate
+ for _, sample := range p.samples {
+ tags["input_ifindex"] = strconv.FormatUint(uint64(sample.smplData.inputIfIndex), 10)
+ tags["output_ifindex"] = strconv.FormatUint(uint64(sample.smplData.outputIfIndex), 10)
+ tags["sample_direction"] = sample.smplData.sampleDirection
+ tags["source_id_index"] = strconv.FormatUint(uint64(sample.smplData.sourceIDIndex), 10)
+ tags["source_id_type"] = strconv.FormatUint(uint64(sample.smplData.sourceIDType), 10)
+ fields["drops"] = sample.smplData.drops
+ fields["sampling_rate"] = sample.smplData.samplingRate
- for _, flowRecord := range sample.SampleData.FlowRecords {
- if flowRecord.FlowData != nil {
- tags2 := flowRecord.FlowData.getTags()
- fields2 := flowRecord.FlowData.getFields()
+ for _, flowRecord := range sample.smplData.flowRecords {
+ if flowRecord.flowData != nil {
+ tags2 := flowRecord.flowData.getTags()
+ fields2 := flowRecord.flowData.getFields()
for k, v := range tags {
tags2[k] = v
}
diff --git a/plugins/inputs/sflow/packetdecoder.go b/plugins/inputs/sflow/packetdecoder.go
index a14c1e5b7..cf6cfe923 100644
--- a/plugins/inputs/sflow/packetdecoder.go
+++ b/plugins/inputs/sflow/packetdecoder.go
@@ -11,8 +11,8 @@ import (
)
type packetDecoder struct {
- onPacket func(p *v5Format)
- Log telegraf.Logger
+ onPacketF func(p *v5Format)
+ Log telegraf.Logger
}
func newDecoder() *packetDecoder {
@@ -25,19 +25,19 @@ func (d *packetDecoder) debug(args ...interface{}) {
}
}
-func (d *packetDecoder) OnPacket(f func(p *v5Format)) {
- d.onPacket = f
+func (d *packetDecoder) onPacket(f func(p *v5Format)) {
+ d.onPacketF = f
}
-func (d *packetDecoder) Decode(r io.Reader) error {
+func (d *packetDecoder) decode(r io.Reader) error {
var err error
var packet *v5Format
for err == nil {
- packet, err = d.DecodeOnePacket(r)
+ packet, err = d.decodeOnePacket(r)
if err != nil {
break
}
- d.onPacket(packet)
+ d.onPacketF(packet)
}
if err != nil && errors.Is(err, io.EOF) {
return nil
@@ -45,51 +45,51 @@ func (d *packetDecoder) Decode(r io.Reader) error {
return err
}
-type AddressType uint32 // must be uint32
+type addressType uint32 // must be uint32
const (
- AddressTypeUnknown AddressType = 0
- AddressTypeIPV4 AddressType = 1
- AddressTypeIPV6 AddressType = 2
+ addressTypeUnknown addressType = 0
+ addressTypeIPV4 addressType = 1
+ addressTypeIPV6 addressType = 2
)
-func (d *packetDecoder) DecodeOnePacket(r io.Reader) (*v5Format, error) {
+func (d *packetDecoder) decodeOnePacket(r io.Reader) (*v5Format, error) {
p := &v5Format{}
- err := read(r, &p.Version, "version")
+ err := read(r, &p.version, "version")
if err != nil {
return nil, err
}
- if p.Version != 5 {
- return nil, fmt.Errorf("version %d not supported, only version 5", p.Version)
+ if p.version != 5 {
+ return nil, fmt.Errorf("version %d not supported, only version 5", p.version)
}
- var addressIPType AddressType
+ var addressIPType addressType
if err := read(r, &addressIPType, "address ip type"); err != nil {
return nil, err
}
switch addressIPType {
- case AddressTypeUnknown:
- p.AgentAddress.IP = make([]byte, 0)
- case AddressTypeIPV4:
- p.AgentAddress.IP = make([]byte, 4)
- case AddressTypeIPV6:
- p.AgentAddress.IP = make([]byte, 16)
+ case addressTypeUnknown:
+ p.agentAddress.IP = make([]byte, 0)
+ case addressTypeIPV4:
+ p.agentAddress.IP = make([]byte, 4)
+ case addressTypeIPV6:
+ p.agentAddress.IP = make([]byte, 16)
default:
return nil, fmt.Errorf("unknown address IP type %d", addressIPType)
}
- if err := read(r, &p.AgentAddress.IP, "Agent Address IP"); err != nil {
+ if err := read(r, &p.agentAddress.IP, "Agent Address IP"); err != nil {
return nil, err
}
- if err := read(r, &p.SubAgentID, "SubAgentID"); err != nil {
+ if err := read(r, &p.subAgentID, "SubAgentID"); err != nil {
return nil, err
}
- if err := read(r, &p.SequenceNumber, "SequenceNumber"); err != nil {
+ if err := read(r, &p.sequenceNumber, "SequenceNumber"); err != nil {
return nil, err
}
- if err := read(r, &p.Uptime, "Uptime"); err != nil {
+ if err := read(r, &p.uptime, "Uptime"); err != nil {
return nil, err
}
- p.Samples, err = d.decodeSamples(r)
+ p.samples, err = d.decodeSamples(r)
return p, err
}
@@ -115,7 +115,7 @@ func (d *packetDecoder) decodeSamples(r io.Reader) ([]sample, error) {
func (d *packetDecoder) decodeSample(r io.Reader) (sample, error) {
var err error
sam := sample{}
- if err := read(r, &sam.SampleType, "sampleType"); err != nil {
+ if err := read(r, &sam.smplType, "sampleType"); err != nil {
return sam, err
}
sampleDataLen := uint32(0)
@@ -125,25 +125,19 @@ func (d *packetDecoder) decodeSample(r io.Reader) (sample, error) {
mr := binaryio.MinReader(r, int64(sampleDataLen))
defer mr.Close()
- switch sam.SampleType {
+ switch sam.smplType {
case sampleTypeFlowSample:
- sam.SampleData, err = d.decodeFlowSample(mr)
+ sam.smplData, err = d.decodeFlowSample(mr)
case sampleTypeFlowSampleExpanded:
- sam.SampleData, err = d.decodeFlowSampleExpanded(mr)
+ sam.smplData, err = d.decodeFlowSampleExpanded(mr)
default:
- d.debug("Unknown sample type: ", sam.SampleType)
+ d.debug("Unknown sample type: ", sam.smplType)
}
return sam, err
}
-type InterfaceFormatType uint8 // sflow_version_5.txt line 1497
-const (
- InterfaceFormatTypeSingleInterface InterfaceFormatType = 0
- InterfaceFormatTypePacketDiscarded InterfaceFormatType = 1
-)
-
func (d *packetDecoder) decodeFlowSample(r io.Reader) (t sampleDataFlowSampleExpanded, err error) {
- if err := read(r, &t.SequenceNumber, "SequenceNumber"); err != nil {
+ if err := read(r, &t.sequenceNumber, "SequenceNumber"); err != nil {
return t, err
}
var sourceID uint32
@@ -151,80 +145,80 @@ func (d *packetDecoder) decodeFlowSample(r io.Reader) (t sampleDataFlowSampleExp
return t, err
}
// split source id to source id type and source id index
- t.SourceIDIndex = sourceID & 0x00ffffff // sflow_version_5.txt line: 1468
- t.SourceIDType = sourceID >> 24 // source_id_type sflow_version_5.txt Line 1465
- if err := read(r, &t.SamplingRate, "SamplingRate"); err != nil {
+ t.sourceIDIndex = sourceID & 0x00ffffff // sflow_version_5.txt line: 1468
+ t.sourceIDType = sourceID >> 24 // source_id_type sflow_version_5.txt Line 1465
+ if err := read(r, &t.samplingRate, "SamplingRate"); err != nil {
return t, err
}
- if err := read(r, &t.SamplePool, "SamplePool"); err != nil {
+ if err := read(r, &t.samplePool, "SamplePool"); err != nil {
return t, err
}
- if err := read(r, &t.Drops, "Drops"); err != nil { // sflow_version_5.txt line 1636
+ if err := read(r, &t.drops, "Drops"); err != nil { // sflow_version_5.txt line 1636
return t, err
}
- if err := read(r, &t.InputIfIndex, "InputIfIndex"); err != nil {
+ if err := read(r, &t.inputIfIndex, "InputIfIndex"); err != nil {
return t, err
}
- t.InputIfFormat = t.InputIfIndex >> 30
- t.InputIfIndex = t.InputIfIndex & 0x3FFFFFFF
+ t.inputIfFormat = t.inputIfIndex >> 30
+ t.inputIfIndex = t.inputIfIndex & 0x3FFFFFFF
- if err := read(r, &t.OutputIfIndex, "OutputIfIndex"); err != nil {
+ if err := read(r, &t.outputIfIndex, "OutputIfIndex"); err != nil {
return t, err
}
- t.OutputIfFormat = t.OutputIfIndex >> 30
- t.OutputIfIndex = t.OutputIfIndex & 0x3FFFFFFF
+ t.outputIfFormat = t.outputIfIndex >> 30
+ t.outputIfIndex = t.outputIfIndex & 0x3FFFFFFF
- switch t.SourceIDIndex {
- case t.OutputIfIndex:
- t.SampleDirection = "egress"
- case t.InputIfIndex:
- t.SampleDirection = "ingress"
+ switch t.sourceIDIndex {
+ case t.outputIfIndex:
+ t.sampleDirection = "egress"
+ case t.inputIfIndex:
+ t.sampleDirection = "ingress"
}
- t.FlowRecords, err = d.decodeFlowRecords(r, t.SamplingRate)
+ t.flowRecords, err = d.decodeFlowRecords(r, t.samplingRate)
return t, err
}
func (d *packetDecoder) decodeFlowSampleExpanded(r io.Reader) (t sampleDataFlowSampleExpanded, err error) {
- if err := read(r, &t.SequenceNumber, "SequenceNumber"); err != nil { // sflow_version_5.txt line 1701
+ if err := read(r, &t.sequenceNumber, "SequenceNumber"); err != nil { // sflow_version_5.txt line 1701
return t, err
}
- if err := read(r, &t.SourceIDType, "SourceIDType"); err != nil { // sflow_version_5.txt line: 1706 + 16878
+ if err := read(r, &t.sourceIDType, "SourceIDType"); err != nil { // sflow_version_5.txt line: 1706 + 16878
return t, err
}
- if err := read(r, &t.SourceIDIndex, "SourceIDIndex"); err != nil { // sflow_version_5.txt line: 1689
+ if err := read(r, &t.sourceIDIndex, "SourceIDIndex"); err != nil { // sflow_version_5.txt line: 1689
return t, err
}
- if err := read(r, &t.SamplingRate, "SamplingRate"); err != nil { // sflow_version_5.txt line: 1707
+ if err := read(r, &t.samplingRate, "SamplingRate"); err != nil { // sflow_version_5.txt line: 1707
return t, err
}
- if err := read(r, &t.SamplePool, "SamplePool"); err != nil { // sflow_version_5.txt line: 1708
+ if err := read(r, &t.samplePool, "SamplePool"); err != nil { // sflow_version_5.txt line: 1708
return t, err
}
- if err := read(r, &t.Drops, "Drops"); err != nil { // sflow_version_5.txt line: 1712
+ if err := read(r, &t.drops, "Drops"); err != nil { // sflow_version_5.txt line: 1712
return t, err
}
- if err := read(r, &t.InputIfFormat, "InputIfFormat"); err != nil { // sflow_version_5.txt line: 1727
+ if err := read(r, &t.inputIfFormat, "InputIfFormat"); err != nil { // sflow_version_5.txt line: 1727
return t, err
}
- if err := read(r, &t.InputIfIndex, "InputIfIndex"); err != nil {
+ if err := read(r, &t.inputIfIndex, "InputIfIndex"); err != nil {
return t, err
}
- if err := read(r, &t.OutputIfFormat, "OutputIfFormat"); err != nil { // sflow_version_5.txt line: 1728
+ if err := read(r, &t.outputIfFormat, "OutputIfFormat"); err != nil { // sflow_version_5.txt line: 1728
return t, err
}
- if err := read(r, &t.OutputIfIndex, "OutputIfIndex"); err != nil {
+ if err := read(r, &t.outputIfIndex, "OutputIfIndex"); err != nil {
return t, err
}
- switch t.SourceIDIndex {
- case t.OutputIfIndex:
- t.SampleDirection = "egress"
- case t.InputIfIndex:
- t.SampleDirection = "ingress"
+ switch t.sourceIDIndex {
+ case t.outputIfIndex:
+ t.sampleDirection = "egress"
+ case t.inputIfIndex:
+ t.sampleDirection = "ingress"
}
- t.FlowRecords, err = d.decodeFlowRecords(r, t.SamplingRate)
+ t.flowRecords, err = d.decodeFlowRecords(r, t.samplingRate)
return t, err
}
@@ -236,7 +230,7 @@ func (d *packetDecoder) decodeFlowRecords(r io.Reader, samplingRate uint32) (rec
}
for i := uint32(0); i < count; i++ {
fr := flowRecord{}
- if err := read(r, &fr.FlowFormat, "FlowFormat"); err != nil { // sflow_version_5.txt line 1597
+ if err := read(r, &fr.flowFormat, "FlowFormat"); err != nil { // sflow_version_5.txt line 1597
return recs, err
}
if err := read(r, &flowDataLen, "Flow data length"); err != nil {
@@ -245,11 +239,11 @@ func (d *packetDecoder) decodeFlowRecords(r io.Reader, samplingRate uint32) (rec
mr := binaryio.MinReader(r, int64(flowDataLen))
- switch fr.FlowFormat {
+ switch fr.flowFormat {
case flowFormatTypeRawPacketHeader: // sflow_version_5.txt line 1938
- fr.FlowData, err = d.decodeRawPacketHeaderFlowData(mr, samplingRate)
+ fr.flowData, err = d.decodeRawPacketHeaderFlowData(mr, samplingRate)
default:
- d.debug("Unknown flow format: ", fr.FlowFormat)
+ d.debug("Unknown flow format: ", fr.flowFormat)
}
if err != nil {
mr.Close()
@@ -264,29 +258,29 @@ func (d *packetDecoder) decodeFlowRecords(r io.Reader, samplingRate uint32) (rec
}
func (d *packetDecoder) decodeRawPacketHeaderFlowData(r io.Reader, samplingRate uint32) (h rawPacketHeaderFlowData, err error) {
- if err := read(r, &h.HeaderProtocol, "HeaderProtocol"); err != nil { // sflow_version_5.txt line 1940
+ if err := read(r, &h.headerProtocol, "HeaderProtocol"); err != nil { // sflow_version_5.txt line 1940
return h, err
}
- if err := read(r, &h.FrameLength, "FrameLength"); err != nil { // sflow_version_5.txt line 1942
+ if err := read(r, &h.frameLength, "FrameLength"); err != nil { // sflow_version_5.txt line 1942
return h, err
}
- h.Bytes = h.FrameLength * samplingRate
+ h.bytes = h.frameLength * samplingRate
- if err := read(r, &h.StrippedOctets, "StrippedOctets"); err != nil { // sflow_version_5.txt line 1967
+ if err := read(r, &h.strippedOctets, "StrippedOctets"); err != nil { // sflow_version_5.txt line 1967
return h, err
}
- if err := read(r, &h.HeaderLength, "HeaderLength"); err != nil {
+ if err := read(r, &h.headerLength, "HeaderLength"); err != nil {
return h, err
}
- mr := binaryio.MinReader(r, int64(h.HeaderLength))
+ mr := binaryio.MinReader(r, int64(h.headerLength))
defer mr.Close()
- switch h.HeaderProtocol {
+ switch h.headerProtocol {
case headerProtocolTypeEthernetISO88023:
- h.Header, err = d.decodeEthHeader(mr)
+ h.header, err = d.decodeEthHeader(mr)
default:
- d.debug("Unknown header protocol type: ", h.HeaderProtocol)
+ d.debug("Unknown header protocol type: ", h.headerProtocol)
}
return h, err
@@ -296,10 +290,10 @@ func (d *packetDecoder) decodeRawPacketHeaderFlowData(r io.Reader, samplingRate
// according to https://en.wikipedia.org/wiki/Ethernet_frame
func (d *packetDecoder) decodeEthHeader(r io.Reader) (h ethHeader, err error) {
// we may have to read out StrippedOctets bytes and throw them away first?
- if err := read(r, &h.DestinationMAC, "DestinationMAC"); err != nil {
+ if err := read(r, &h.destinationMAC, "DestinationMAC"); err != nil {
return h, err
}
- if err := read(r, &h.SourceMAC, "SourceMAC"); err != nil {
+ if err := read(r, &h.sourceMAC, "SourceMAC"); err != nil {
return h, err
}
var tagOrEType uint16
@@ -312,18 +306,18 @@ func (d *packetDecoder) decodeEthHeader(r io.Reader) (h ethHeader, err error) {
if err := read(r, &discard, "unknown"); err != nil {
return h, err
}
- if err := read(r, &h.EtherTypeCode, "EtherTypeCode"); err != nil {
+ if err := read(r, &h.etherTypeCode, "EtherTypeCode"); err != nil {
return h, err
}
default:
- h.EtherTypeCode = tagOrEType
+ h.etherTypeCode = tagOrEType
}
- h.EtherType = eTypeMap[h.EtherTypeCode]
- switch h.EtherType {
+ h.etherType = eTypeMap[h.etherTypeCode]
+ switch h.etherType {
case "IPv4":
- h.IPHeader, err = d.decodeIPv4Header(r)
+ h.ipHeader, err = d.decodeIPv4Header(r)
case "IPv6":
- h.IPHeader, err = d.decodeIPv6Header(r)
+ h.ipHeader, err = d.decodeIPv6Header(r)
default:
}
if err != nil {
@@ -334,49 +328,49 @@ func (d *packetDecoder) decodeEthHeader(r io.Reader) (h ethHeader, err error) {
// https://en.wikipedia.org/wiki/IPv4#Header
func (d *packetDecoder) decodeIPv4Header(r io.Reader) (h ipV4Header, err error) {
- if err := read(r, &h.Version, "Version"); err != nil {
+ if err := read(r, &h.version, "Version"); err != nil {
return h, err
}
- h.InternetHeaderLength = h.Version & 0x0F
- h.Version = h.Version & 0xF0
- if err := read(r, &h.DSCP, "DSCP"); err != nil {
+ h.internetHeaderLength = h.version & 0x0F
+ h.version = h.version & 0xF0
+ if err := read(r, &h.dscp, "DSCP"); err != nil {
return h, err
}
- h.ECN = h.DSCP & 0x03
- h.DSCP = h.DSCP >> 2
- if err := read(r, &h.TotalLength, "TotalLength"); err != nil {
+ h.ecn = h.dscp & 0x03
+ h.dscp = h.dscp >> 2
+ if err := read(r, &h.totalLength, "TotalLength"); err != nil {
return h, err
}
- if err := read(r, &h.Identification, "Identification"); err != nil {
+ if err := read(r, &h.identification, "Identification"); err != nil {
return h, err
}
- if err := read(r, &h.FragmentOffset, "FragmentOffset"); err != nil {
+ if err := read(r, &h.fragmentOffset, "FragmentOffset"); err != nil {
return h, err
}
- h.Flags = uint8(h.FragmentOffset >> 13)
- h.FragmentOffset = h.FragmentOffset & 0x1FFF
- if err := read(r, &h.TTL, "TTL"); err != nil {
+ h.flags = uint8(h.fragmentOffset >> 13)
+ h.fragmentOffset = h.fragmentOffset & 0x1FFF
+ if err := read(r, &h.ttl, "TTL"); err != nil {
return h, err
}
- if err := read(r, &h.Protocol, "Protocol"); err != nil {
+ if err := read(r, &h.protocol, "Protocol"); err != nil {
return h, err
}
- if err := read(r, &h.HeaderChecksum, "HeaderChecksum"); err != nil {
+ if err := read(r, &h.headerChecksum, "HeaderChecksum"); err != nil {
return h, err
}
- if err := read(r, &h.SourceIP, "SourceIP"); err != nil {
+ if err := read(r, &h.sourceIP, "SourceIP"); err != nil {
return h, err
}
- if err := read(r, &h.DestIP, "DestIP"); err != nil {
+ if err := read(r, &h.destIP, "DestIP"); err != nil {
return h, err
}
- switch h.Protocol {
+ switch h.protocol {
case ipProtocolTCP:
- h.ProtocolHeader, err = decodeTCPHeader(r)
+ h.protocolHeader, err = decodeTCPHeader(r)
case ipProtocolUDP:
- h.ProtocolHeader, err = decodeUDPHeader(r)
+ h.protocolHeader, err = decodeUDPHeader(r)
default:
- d.debug("Unknown IP protocol: ", h.Protocol)
+ d.debug("Unknown IP protocol: ", h.protocol)
}
return h, err
}
@@ -391,49 +385,49 @@ func (d *packetDecoder) decodeIPv6Header(r io.Reader) (h ipV6Header, err error)
if version != 0x6 {
return h, fmt.Errorf("unexpected IPv6 header version 0x%x", version)
}
- h.DSCP = uint8((fourByteBlock & 0xFC00000) >> 22)
- h.ECN = uint8((fourByteBlock & 0x300000) >> 20)
+ h.dscp = uint8((fourByteBlock & 0xFC00000) >> 22)
+ h.ecn = uint8((fourByteBlock & 0x300000) >> 20)
// The flowLabel is available via fourByteBlock & 0xFFFFF
- if err := read(r, &h.PayloadLength, "PayloadLength"); err != nil {
+ if err := read(r, &h.payloadLength, "PayloadLength"); err != nil {
return h, err
}
- if err := read(r, &h.NextHeaderProto, "NextHeaderProto"); err != nil {
+ if err := read(r, &h.nextHeaderProto, "NextHeaderProto"); err != nil {
return h, err
}
- if err := read(r, &h.HopLimit, "HopLimit"); err != nil {
+ if err := read(r, &h.hopLimit, "HopLimit"); err != nil {
return h, err
}
- if err := read(r, &h.SourceIP, "SourceIP"); err != nil {
+ if err := read(r, &h.sourceIP, "SourceIP"); err != nil {
return h, err
}
- if err := read(r, &h.DestIP, "DestIP"); err != nil {
+ if err := read(r, &h.destIP, "DestIP"); err != nil {
return h, err
}
- switch h.NextHeaderProto {
+ switch h.nextHeaderProto {
case ipProtocolTCP:
- h.ProtocolHeader, err = decodeTCPHeader(r)
+ h.protocolHeader, err = decodeTCPHeader(r)
case ipProtocolUDP:
- h.ProtocolHeader, err = decodeUDPHeader(r)
+ h.protocolHeader, err = decodeUDPHeader(r)
default:
// not handled
- d.debug("Unknown IP protocol: ", h.NextHeaderProto)
+ d.debug("Unknown IP protocol: ", h.nextHeaderProto)
}
return h, err
}
// https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure
func decodeTCPHeader(r io.Reader) (h tcpHeader, err error) {
- if err := read(r, &h.SourcePort, "SourcePort"); err != nil {
+ if err := read(r, &h.sourcePort, "SourcePort"); err != nil {
return h, err
}
- if err := read(r, &h.DestinationPort, "DestinationPort"); err != nil {
+ if err := read(r, &h.destinationPort, "DestinationPort"); err != nil {
return h, err
}
- if err := read(r, &h.Sequence, "Sequence"); err != nil {
+ if err := read(r, &h.sequence, "Sequence"); err != nil {
return h, err
}
- if err := read(r, &h.AckNumber, "AckNumber"); err != nil {
+ if err := read(r, &h.ackNumber, "AckNumber"); err != nil {
return h, err
}
// Next up: bit reading!
@@ -444,17 +438,17 @@ func decodeTCPHeader(r io.Reader) (h tcpHeader, err error) {
if err := read(r, &dataOffsetAndReservedAndFlags, "TCP Header Octet offset 12"); err != nil {
return h, err
}
- h.TCPHeaderLength = uint8((dataOffsetAndReservedAndFlags >> 12) * 4)
- h.Flags = dataOffsetAndReservedAndFlags & 0x1FF
+ h.tcpHeaderLength = uint8((dataOffsetAndReservedAndFlags >> 12) * 4)
+ h.flags = dataOffsetAndReservedAndFlags & 0x1FF
// done bit reading
- if err := read(r, &h.TCPWindowSize, "TCPWindowSize"); err != nil {
+ if err := read(r, &h.tcpWindowSize, "TCPWindowSize"); err != nil {
return h, err
}
- if err := read(r, &h.Checksum, "Checksum"); err != nil {
+ if err := read(r, &h.checksum, "Checksum"); err != nil {
return h, err
}
- if err := read(r, &h.TCPUrgentPointer, "TCPUrgentPointer"); err != nil {
+ if err := read(r, &h.tcpUrgentPointer, "TCPUrgentPointer"); err != nil {
return h, err
}
@@ -462,16 +456,16 @@ func decodeTCPHeader(r io.Reader) (h tcpHeader, err error) {
}
func decodeUDPHeader(r io.Reader) (h udpHeader, err error) {
- if err := read(r, &h.SourcePort, "SourcePort"); err != nil {
+ if err := read(r, &h.sourcePort, "SourcePort"); err != nil {
return h, err
}
- if err := read(r, &h.DestinationPort, "DestinationPort"); err != nil {
+ if err := read(r, &h.destinationPort, "DestinationPort"); err != nil {
return h, err
}
- if err := read(r, &h.UDPLength, "UDPLength"); err != nil {
+ if err := read(r, &h.udpLength, "UDPLength"); err != nil {
return h, err
}
- if err := read(r, &h.Checksum, "Checksum"); err != nil {
+ if err := read(r, &h.checksum, "Checksum"); err != nil {
return h, err
}
return h, err
diff --git a/plugins/inputs/sflow/packetdecoder_test.go b/plugins/inputs/sflow/packetdecoder_test.go
index 319c6de7f..41e9f439b 100644
--- a/plugins/inputs/sflow/packetdecoder_test.go
+++ b/plugins/inputs/sflow/packetdecoder_test.go
@@ -19,9 +19,9 @@ func TestUDPHeader(t *testing.T) {
require.NoError(t, err)
expected := udpHeader{
- SourcePort: 1,
- DestinationPort: 2,
- UDPLength: 3,
+ sourcePort: 1,
+ destinationPort: 2,
+ udpLength: 3,
}
require.Equal(t, expected, actual)
@@ -66,24 +66,24 @@ func TestIPv4Header(t *testing.T) {
require.NoError(t, err)
expected := ipV4Header{
- Version: 0x40,
- InternetHeaderLength: 0x05,
- DSCP: 0,
- ECN: 0,
- TotalLength: 0,
- Identification: 0,
- Flags: 0,
- FragmentOffset: 0,
- TTL: 0,
- Protocol: 0x11,
- HeaderChecksum: 0,
- SourceIP: [4]byte{127, 0, 0, 1},
- DestIP: [4]byte{127, 0, 0, 2},
- ProtocolHeader: udpHeader{
- SourcePort: 1,
- DestinationPort: 2,
- UDPLength: 3,
- Checksum: 0,
+ version: 0x40,
+ internetHeaderLength: 0x05,
+ dscp: 0,
+ ecn: 0,
+ totalLength: 0,
+ identification: 0,
+ flags: 0,
+ fragmentOffset: 0,
+ ttl: 0,
+ protocol: 0x11,
+ headerChecksum: 0,
+ sourceIP: [4]byte{127, 0, 0, 1},
+ destIP: [4]byte{127, 0, 0, 2},
+ protocolHeader: udpHeader{
+ sourcePort: 1,
+ destinationPort: 2,
+ udpLength: 3,
+ checksum: 0,
},
}
@@ -142,14 +142,14 @@ func TestIPv4HeaderSwitch(t *testing.T) {
require.NoError(t, err)
expected := ipV4Header{
- Version: 64,
- InternetHeaderLength: 5,
- Protocol: 6,
- SourceIP: [4]byte{127, 0, 0, 1},
- DestIP: [4]byte{127, 0, 0, 2},
- ProtocolHeader: tcpHeader{
- SourcePort: 1,
- DestinationPort: 2,
+ version: 64,
+ internetHeaderLength: 5,
+ protocol: 6,
+ sourceIP: [4]byte{127, 0, 0, 1},
+ destIP: [4]byte{127, 0, 0, 2},
+ protocolHeader: tcpHeader{
+ sourcePort: 1,
+ destinationPort: 2,
},
}
@@ -194,11 +194,11 @@ func TestUnknownProtocol(t *testing.T) {
require.NoError(t, err)
expected := ipV4Header{
- Version: 64,
- InternetHeaderLength: 5,
- Protocol: 153,
- SourceIP: [4]byte{127, 0, 0, 1},
- DestIP: [4]byte{127, 0, 0, 2},
+ version: 64,
+ internetHeaderLength: 5,
+ protocol: 153,
+ sourceIP: [4]byte{127, 0, 0, 1},
+ destIP: [4]byte{127, 0, 0, 2},
}
require.Equal(t, expected, actual)
diff --git a/plugins/inputs/sflow/sflow.go b/plugins/inputs/sflow/sflow.go
index 3b370bc5f..b703d78c8 100644
--- a/plugins/inputs/sflow/sflow.go
+++ b/plugins/inputs/sflow/sflow.go
@@ -47,7 +47,7 @@ func (s *SFlow) Init() error {
// Start starts this sFlow listener listening on the configured network for sFlow packets
func (s *SFlow) Start(acc telegraf.Accumulator) error {
- s.decoder.OnPacket(func(p *v5Format) {
+ s.decoder.onPacket(func(p *v5Format) {
metrics := makeMetrics(p)
for _, m := range metrics {
acc.AddMetric(m)
@@ -95,7 +95,7 @@ func (s *SFlow) Stop() {
s.wg.Wait()
}
-func (s *SFlow) Address() net.Addr {
+func (s *SFlow) address() net.Addr {
return s.addr
}
@@ -114,7 +114,7 @@ func (s *SFlow) read(acc telegraf.Accumulator, conn net.PacketConn) {
}
func (s *SFlow) process(acc telegraf.Accumulator, buf []byte) {
- if err := s.decoder.Decode(bytes.NewBuffer(buf)); err != nil {
+ if err := s.decoder.decode(bytes.NewBuffer(buf)); err != nil {
acc.AddError(fmt.Errorf("unable to parse incoming packet: %w", err))
}
}
@@ -132,7 +132,6 @@ func listenUDP(network, address string) (*net.UDPConn, error) {
}
}
-// init registers this SFlow input plug in with the Telegraf framework
func init() {
inputs.Add("sflow", func() telegraf.Input {
return &SFlow{}
diff --git a/plugins/inputs/sflow/sflow_test.go b/plugins/inputs/sflow/sflow_test.go
index dbab8b84c..7858398b2 100644
--- a/plugins/inputs/sflow/sflow_test.go
+++ b/plugins/inputs/sflow/sflow_test.go
@@ -25,7 +25,7 @@ func TestSFlow(t *testing.T) {
require.NoError(t, err)
defer sflow.Stop()
- client, err := net.Dial(sflow.Address().Network(), sflow.Address().String())
+ client, err := net.Dial(sflow.address().Network(), sflow.address().String())
require.NoError(t, err)
packetBytes, err := hex.DecodeString(
@@ -132,7 +132,7 @@ func BenchmarkSFlow(b *testing.B) {
require.NoError(b, err)
defer sflow.Stop()
- client, err := net.Dial(sflow.Address().Network(), sflow.Address().String())
+ client, err := net.Dial(sflow.address().Network(), sflow.address().String())
require.NoError(b, err)
packetBytes, err := hex.DecodeString(
diff --git a/plugins/inputs/sflow/types.go b/plugins/inputs/sflow/types.go
index 6a0e2ff6f..ce4374ef9 100644
--- a/plugins/inputs/sflow/types.go
+++ b/plugins/inputs/sflow/types.go
@@ -23,12 +23,12 @@ type containsMetricData interface {
// v5Format answers and decoder.Directive capable of decoding sFlow v5 packets in accordance
// with SFlow v5 specification at https://sflow.org/sflow_version_5.txt
type v5Format struct {
- Version uint32
- AgentAddress net.IPAddr
- SubAgentID uint32
- SequenceNumber uint32
- Uptime uint32
- Samples []sample
+ version uint32
+ agentAddress net.IPAddr
+ subAgentID uint32
+ sequenceNumber uint32
+ uptime uint32
+ samples []sample
}
type sampleType uint32
@@ -39,23 +39,23 @@ const (
)
type sample struct {
- SampleType sampleType
- SampleData sampleDataFlowSampleExpanded
+ smplType sampleType
+ smplData sampleDataFlowSampleExpanded
}
type sampleDataFlowSampleExpanded struct {
- SequenceNumber uint32
- SourceIDType uint32
- SourceIDIndex uint32
- SamplingRate uint32
- SamplePool uint32
- Drops uint32
- SampleDirection string // ingress/egress
- InputIfFormat uint32
- InputIfIndex uint32
- OutputIfFormat uint32
- OutputIfIndex uint32
- FlowRecords []flowRecord
+ sequenceNumber uint32
+ sourceIDType uint32
+ sourceIDIndex uint32
+ samplingRate uint32
+ samplePool uint32
+ drops uint32
+ sampleDirection string // ingress/egress
+ inputIfFormat uint32
+ inputIfIndex uint32
+ outputIfFormat uint32
+ outputIfIndex uint32
+ flowRecords []flowRecord
}
type flowFormatType uint32
@@ -67,8 +67,8 @@ const (
type flowData containsMetricData
type flowRecord struct {
- FlowFormat flowFormatType
- FlowData flowData
+ flowFormat flowFormatType
+ flowData flowData
}
type headerProtocolType uint32
@@ -97,64 +97,66 @@ var headerProtocolMap = map[headerProtocolType]string{
type header containsMetricData
type rawPacketHeaderFlowData struct {
- HeaderProtocol headerProtocolType
- FrameLength uint32
- Bytes uint32
- StrippedOctets uint32
- HeaderLength uint32
- Header header
+ headerProtocol headerProtocolType
+ frameLength uint32
+ bytes uint32
+ strippedOctets uint32
+ headerLength uint32
+ header header
}
func (h rawPacketHeaderFlowData) getTags() map[string]string {
var t map[string]string
- if h.Header != nil {
- t = h.Header.getTags()
+ if h.header != nil {
+ t = h.header.getTags()
} else {
t = make(map[string]string, 1)
}
- t["header_protocol"] = headerProtocolMap[h.HeaderProtocol]
+ t["header_protocol"] = headerProtocolMap[h.headerProtocol]
return t
}
+
func (h rawPacketHeaderFlowData) getFields() map[string]interface{} {
var f map[string]interface{}
- if h.Header != nil {
- f = h.Header.getFields()
+ if h.header != nil {
+ f = h.header.getFields()
} else {
f = make(map[string]interface{}, 3)
}
- f["bytes"] = h.Bytes
- f["frame_length"] = h.FrameLength
- f["header_length"] = h.HeaderLength
+ f["bytes"] = h.bytes
+ f["frame_length"] = h.frameLength
+ f["header_length"] = h.headerLength
return f
}
type ipHeader containsMetricData
type ethHeader struct {
- DestinationMAC [6]byte
- SourceMAC [6]byte
- TagProtocolIdentifier uint16
- TagControlInformation uint16
- EtherTypeCode uint16
- EtherType string
- IPHeader ipHeader
+ destinationMAC [6]byte
+ sourceMAC [6]byte
+ tagProtocolIdentifier uint16
+ tagControlInformation uint16
+ etherTypeCode uint16
+ etherType string
+ ipHeader ipHeader
}
func (h ethHeader) getTags() map[string]string {
var t map[string]string
- if h.IPHeader != nil {
- t = h.IPHeader.getTags()
+ if h.ipHeader != nil {
+ t = h.ipHeader.getTags()
} else {
t = make(map[string]string, 3)
}
- t["src_mac"] = net.HardwareAddr(h.SourceMAC[:]).String()
- t["dst_mac"] = net.HardwareAddr(h.DestinationMAC[:]).String()
- t["ether_type"] = h.EtherType
+ t["src_mac"] = net.HardwareAddr(h.sourceMAC[:]).String()
+ t["dst_mac"] = net.HardwareAddr(h.destinationMAC[:]).String()
+ t["ether_type"] = h.etherType
return t
}
+
func (h ethHeader) getFields() map[string]interface{} {
- if h.IPHeader != nil {
- return h.IPHeader.getFields()
+ if h.ipHeader != nil {
+ return h.ipHeader.getFields()
}
return make(map[string]interface{})
}
@@ -163,129 +165,133 @@ type protocolHeader containsMetricData
// https://en.wikipedia.org/wiki/IPv4#Header
type ipV4Header struct {
- Version uint8 // 4 bit
- InternetHeaderLength uint8 // 4 bit
- DSCP uint8
- ECN uint8
- TotalLength uint16
- Identification uint16
- Flags uint8
- FragmentOffset uint16
- TTL uint8
- Protocol uint8 // https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers
- HeaderChecksum uint16
- SourceIP [4]byte
- DestIP [4]byte
- ProtocolHeader protocolHeader
+ version uint8 // 4 bit
+ internetHeaderLength uint8 // 4 bit
+ dscp uint8
+ ecn uint8
+ totalLength uint16
+ identification uint16
+ flags uint8
+ fragmentOffset uint16
+ ttl uint8
+ protocol uint8 // https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers
+ headerChecksum uint16
+ sourceIP [4]byte
+ destIP [4]byte
+ protocolHeader protocolHeader
}
func (h ipV4Header) getTags() map[string]string {
var t map[string]string
- if h.ProtocolHeader != nil {
- t = h.ProtocolHeader.getTags()
+ if h.protocolHeader != nil {
+ t = h.protocolHeader.getTags()
} else {
t = make(map[string]string, 2)
}
- t["src_ip"] = net.IP(h.SourceIP[:]).String()
- t["dst_ip"] = net.IP(h.DestIP[:]).String()
+ t["src_ip"] = net.IP(h.sourceIP[:]).String()
+ t["dst_ip"] = net.IP(h.destIP[:]).String()
return t
}
+
func (h ipV4Header) getFields() map[string]interface{} {
var f map[string]interface{}
- if h.ProtocolHeader != nil {
- f = h.ProtocolHeader.getFields()
+ if h.protocolHeader != nil {
+ f = h.protocolHeader.getFields()
} else {
f = make(map[string]interface{}, 6)
}
- f["ip_dscp"] = strconv.FormatUint(uint64(h.DSCP), 10)
- f["ip_ecn"] = strconv.FormatUint(uint64(h.ECN), 10)
- f["ip_flags"] = h.Flags
- f["ip_fragment_offset"] = h.FragmentOffset
- f["ip_total_length"] = h.TotalLength
- f["ip_ttl"] = h.TTL
+ f["ip_dscp"] = strconv.FormatUint(uint64(h.dscp), 10)
+ f["ip_ecn"] = strconv.FormatUint(uint64(h.ecn), 10)
+ f["ip_flags"] = h.flags
+ f["ip_fragment_offset"] = h.fragmentOffset
+ f["ip_total_length"] = h.totalLength
+ f["ip_ttl"] = h.ttl
return f
}
// https://en.wikipedia.org/wiki/IPv6_packet
type ipV6Header struct {
- DSCP uint8
- ECN uint8
- PayloadLength uint16
- NextHeaderProto uint8 // tcp/udp?
- HopLimit uint8
- SourceIP [16]byte
- DestIP [16]byte
- ProtocolHeader protocolHeader
+ dscp uint8
+ ecn uint8
+ payloadLength uint16
+ nextHeaderProto uint8 // tcp/udp?
+ hopLimit uint8
+ sourceIP [16]byte
+ destIP [16]byte
+ protocolHeader protocolHeader
}
func (h ipV6Header) getTags() map[string]string {
var t map[string]string
- if h.ProtocolHeader != nil {
- t = h.ProtocolHeader.getTags()
+ if h.protocolHeader != nil {
+ t = h.protocolHeader.getTags()
} else {
t = make(map[string]string, 2)
}
- t["src_ip"] = net.IP(h.SourceIP[:]).String()
- t["dst_ip"] = net.IP(h.DestIP[:]).String()
+ t["src_ip"] = net.IP(h.sourceIP[:]).String()
+ t["dst_ip"] = net.IP(h.destIP[:]).String()
return t
}
+
func (h ipV6Header) getFields() map[string]interface{} {
var f map[string]interface{}
- if h.ProtocolHeader != nil {
- f = h.ProtocolHeader.getFields()
+ if h.protocolHeader != nil {
+ f = h.protocolHeader.getFields()
} else {
f = make(map[string]interface{}, 3)
}
- f["ip_dscp"] = strconv.FormatUint(uint64(h.DSCP), 10)
- f["ip_ecn"] = strconv.FormatUint(uint64(h.ECN), 10)
- f["payload_length"] = h.PayloadLength
+ f["ip_dscp"] = strconv.FormatUint(uint64(h.dscp), 10)
+ f["ip_ecn"] = strconv.FormatUint(uint64(h.ecn), 10)
+ f["payload_length"] = h.payloadLength
return f
}
// https://en.wikipedia.org/wiki/Transmission_Control_Protocol
type tcpHeader struct {
- SourcePort uint16
- DestinationPort uint16
- Sequence uint32
- AckNumber uint32
- TCPHeaderLength uint8
- Flags uint16
- TCPWindowSize uint16
- Checksum uint16
- TCPUrgentPointer uint16
+ sourcePort uint16
+ destinationPort uint16
+ sequence uint32
+ ackNumber uint32
+ tcpHeaderLength uint8
+ flags uint16
+ tcpWindowSize uint16
+ checksum uint16
+ tcpUrgentPointer uint16
}
func (h tcpHeader) getTags() map[string]string {
t := map[string]string{
- "dst_port": strconv.FormatUint(uint64(h.DestinationPort), 10),
- "src_port": strconv.FormatUint(uint64(h.SourcePort), 10),
+ "dst_port": strconv.FormatUint(uint64(h.destinationPort), 10),
+ "src_port": strconv.FormatUint(uint64(h.sourcePort), 10),
}
return t
}
+
func (h tcpHeader) getFields() map[string]interface{} {
return map[string]interface{}{
- "tcp_header_length": h.TCPHeaderLength,
- "tcp_urgent_pointer": h.TCPUrgentPointer,
- "tcp_window_size": h.TCPWindowSize,
+ "tcp_header_length": h.tcpHeaderLength,
+ "tcp_urgent_pointer": h.tcpUrgentPointer,
+ "tcp_window_size": h.tcpWindowSize,
}
}
type udpHeader struct {
- SourcePort uint16
- DestinationPort uint16
- UDPLength uint16
- Checksum uint16
+ sourcePort uint16
+ destinationPort uint16
+ udpLength uint16
+ checksum uint16
}
func (h udpHeader) getTags() map[string]string {
t := map[string]string{
- "dst_port": strconv.FormatUint(uint64(h.DestinationPort), 10),
- "src_port": strconv.FormatUint(uint64(h.SourcePort), 10),
+ "dst_port": strconv.FormatUint(uint64(h.destinationPort), 10),
+ "src_port": strconv.FormatUint(uint64(h.sourcePort), 10),
}
return t
}
+
func (h udpHeader) getFields() map[string]interface{} {
return map[string]interface{}{
- "udp_length": h.UDPLength,
+ "udp_length": h.udpLength,
}
}
diff --git a/plugins/inputs/sflow/types_test.go b/plugins/inputs/sflow/types_test.go
index 1082ae4cb..614a2e092 100644
--- a/plugins/inputs/sflow/types_test.go
+++ b/plugins/inputs/sflow/types_test.go
@@ -8,12 +8,12 @@ import (
func TestRawPacketHeaderFlowData(t *testing.T) {
h := rawPacketHeaderFlowData{
- HeaderProtocol: headerProtocolTypeEthernetISO88023,
- FrameLength: 64,
- Bytes: 64,
- StrippedOctets: 0,
- HeaderLength: 0,
- Header: nil,
+ headerProtocol: headerProtocolTypeEthernetISO88023,
+ frameLength: 64,
+ bytes: 64,
+ strippedOctets: 0,
+ headerLength: 0,
+ header: nil,
}
tags := h.getTags()
fields := h.getFields()
@@ -27,13 +27,13 @@ func TestRawPacketHeaderFlowData(t *testing.T) {
// process a raw ethernet packet without any encapsulated protocol
func TestEthHeader(t *testing.T) {
h := ethHeader{
- DestinationMAC: [6]byte{0xca, 0xff, 0xee, 0xff, 0xe, 0x0},
- SourceMAC: [6]byte{0xde, 0xad, 0xbe, 0xef, 0x0, 0x0},
- TagProtocolIdentifier: 0x88B5, // IEEE Std 802 - Local Experimental Ethertype
- TagControlInformation: 0,
- EtherTypeCode: 0,
- EtherType: "",
- IPHeader: nil,
+ destinationMAC: [6]byte{0xca, 0xff, 0xee, 0xff, 0xe, 0x0},
+ sourceMAC: [6]byte{0xde, 0xad, 0xbe, 0xef, 0x0, 0x0},
+ tagProtocolIdentifier: 0x88B5, // IEEE Std 802 - Local Experimental Ethertype
+ tagControlInformation: 0,
+ etherTypeCode: 0,
+ etherType: "",
+ ipHeader: nil,
}
tags := h.getTags()
fields := h.getFields()
diff --git a/plugins/inputs/slab/slab.go b/plugins/inputs/slab/slab.go
index 09000574c..fb23ce5ea 100644
--- a/plugins/inputs/slab/slab.go
+++ b/plugins/inputs/slab/slab.go
@@ -24,18 +24,18 @@ import (
//go:embed sample.conf
var sampleConfig string
-type SlabStats struct {
+type Slab struct {
Log telegraf.Logger `toml:"-"`
statFile string
useSudo bool
}
-func (*SlabStats) SampleConfig() string {
+func (*Slab) SampleConfig() string {
return sampleConfig
}
-func (ss *SlabStats) Gather(acc telegraf.Accumulator) error {
+func (ss *Slab) Gather(acc telegraf.Accumulator) error {
fields, err := ss.getSlabStats()
if err != nil {
return err
@@ -45,7 +45,7 @@ func (ss *SlabStats) Gather(acc telegraf.Accumulator) error {
return nil
}
-func (ss *SlabStats) getSlabStats() (map[string]interface{}, error) {
+func (ss *Slab) getSlabStats() (map[string]interface{}, error) {
out, err := ss.runCmd("/bin/cat", []string{ss.statFile})
if err != nil {
return nil, err
@@ -85,7 +85,7 @@ func (ss *SlabStats) getSlabStats() (map[string]interface{}, error) {
return fields, nil
}
-func (ss *SlabStats) runCmd(cmd string, args []string) ([]byte, error) {
+func (ss *Slab) runCmd(cmd string, args []string) ([]byte, error) {
execCmd := exec.Command(cmd, args...)
if os.Geteuid() != 0 && ss.useSudo {
execCmd = exec.Command("sudo", append([]string{"-n", cmd}, args...)...)
@@ -105,7 +105,7 @@ func normalizeName(name string) string {
func init() {
inputs.Add("slab", func() telegraf.Input {
- return &SlabStats{
+ return &Slab{
statFile: path.Join(internal.GetProcPath(), "slabinfo"),
useSudo: true,
}
diff --git a/plugins/inputs/slab/slab_notlinux.go b/plugins/inputs/slab/slab_notlinux.go
index fda946ca7..f21e5b4e7 100644
--- a/plugins/inputs/slab/slab_notlinux.go
+++ b/plugins/inputs/slab/slab_notlinux.go
@@ -17,12 +17,14 @@ type Slab struct {
Log telegraf.Logger `toml:"-"`
}
+func (*Slab) SampleConfig() string { return sampleConfig }
+
func (s *Slab) Init() error {
- s.Log.Warn("current platform is not supported")
+ s.Log.Warn("Current platform is not supported")
return nil
}
-func (*Slab) SampleConfig() string { return sampleConfig }
-func (*Slab) Gather(_ telegraf.Accumulator) error { return nil }
+
+func (*Slab) Gather(telegraf.Accumulator) error { return nil }
func init() {
inputs.Add("slab", func() telegraf.Input {
diff --git a/plugins/inputs/slab/slab_test.go b/plugins/inputs/slab/slab_test.go
index f51558e81..3fa9b4569 100644
--- a/plugins/inputs/slab/slab_test.go
+++ b/plugins/inputs/slab/slab_test.go
@@ -12,7 +12,7 @@ import (
)
func TestSlab(t *testing.T) {
- slabStats := SlabStats{
+ slabStats := Slab{
statFile: path.Join("testdata", "slabinfo"),
useSudo: false,
}
diff --git a/plugins/inputs/slurm/slurm.go b/plugins/inputs/slurm/slurm.go
index 5e2d5fde7..4de4b1cc4 100644
--- a/plugins/inputs/slurm/slurm.go
+++ b/plugins/inputs/slurm/slurm.go
@@ -103,6 +103,74 @@ func (s *Slurm) Init() error {
return nil
}
+func (s *Slurm) Gather(acc telegraf.Accumulator) (err error) {
+ auth := context.WithValue(
+ context.Background(),
+ goslurm.ContextAPIKeys,
+ map[string]goslurm.APIKey{
+ "user": {Key: s.Username},
+ "token": {Key: s.Token},
+ },
+ )
+
+ if s.endpointMap["diag"] {
+ diagResp, respRaw, err := s.client.SlurmAPI.SlurmV0038Diag(auth).Execute()
+ if err != nil {
+ return fmt.Errorf("error getting diag: %w", err)
+ }
+ if diag, ok := diagResp.GetStatisticsOk(); ok {
+ s.gatherDiagMetrics(acc, diag)
+ }
+ respRaw.Body.Close()
+ }
+
+ if s.endpointMap["jobs"] {
+ jobsResp, respRaw, err := s.client.SlurmAPI.SlurmV0038GetJobs(auth).Execute()
+ if err != nil {
+ return fmt.Errorf("error getting jobs: %w", err)
+ }
+ if jobs, ok := jobsResp.GetJobsOk(); ok {
+ s.gatherJobsMetrics(acc, jobs)
+ }
+ respRaw.Body.Close()
+ }
+
+ if s.endpointMap["nodes"] {
+ nodesResp, respRaw, err := s.client.SlurmAPI.SlurmV0038GetNodes(auth).Execute()
+ if err != nil {
+ return fmt.Errorf("error getting nodes: %w", err)
+ }
+ if nodes, ok := nodesResp.GetNodesOk(); ok {
+ s.gatherNodesMetrics(acc, nodes)
+ }
+ respRaw.Body.Close()
+ }
+
+ if s.endpointMap["partitions"] {
+ partitionsResp, respRaw, err := s.client.SlurmAPI.SlurmV0038GetPartitions(auth).Execute()
+ if err != nil {
+ return fmt.Errorf("error getting partitions: %w", err)
+ }
+ if partitions, ok := partitionsResp.GetPartitionsOk(); ok {
+ s.gatherPartitionsMetrics(acc, partitions)
+ }
+ respRaw.Body.Close()
+ }
+
+ if s.endpointMap["reservations"] {
+ reservationsResp, respRaw, err := s.client.SlurmAPI.SlurmV0038GetReservations(auth).Execute()
+ if err != nil {
+ return fmt.Errorf("error getting reservations: %w", err)
+ }
+ if reservations, ok := reservationsResp.GetReservationsOk(); ok {
+ s.gatherReservationsMetrics(acc, reservations)
+ }
+ respRaw.Body.Close()
+ }
+
+ return nil
+}
+
func parseTres(tres string) map[string]interface{} {
tresKVs := strings.Split(tres, ",")
parsedValues := make(map[string]interface{}, len(tresKVs))
@@ -399,74 +467,6 @@ func (s *Slurm) gatherReservationsMetrics(acc telegraf.Accumulator, reservations
}
}
-func (s *Slurm) Gather(acc telegraf.Accumulator) (err error) {
- auth := context.WithValue(
- context.Background(),
- goslurm.ContextAPIKeys,
- map[string]goslurm.APIKey{
- "user": {Key: s.Username},
- "token": {Key: s.Token},
- },
- )
-
- if s.endpointMap["diag"] {
- diagResp, respRaw, err := s.client.SlurmAPI.SlurmV0038Diag(auth).Execute()
- if err != nil {
- return fmt.Errorf("error getting diag: %w", err)
- }
- if diag, ok := diagResp.GetStatisticsOk(); ok {
- s.gatherDiagMetrics(acc, diag)
- }
- respRaw.Body.Close()
- }
-
- if s.endpointMap["jobs"] {
- jobsResp, respRaw, err := s.client.SlurmAPI.SlurmV0038GetJobs(auth).Execute()
- if err != nil {
- return fmt.Errorf("error getting jobs: %w", err)
- }
- if jobs, ok := jobsResp.GetJobsOk(); ok {
- s.gatherJobsMetrics(acc, jobs)
- }
- respRaw.Body.Close()
- }
-
- if s.endpointMap["nodes"] {
- nodesResp, respRaw, err := s.client.SlurmAPI.SlurmV0038GetNodes(auth).Execute()
- if err != nil {
- return fmt.Errorf("error getting nodes: %w", err)
- }
- if nodes, ok := nodesResp.GetNodesOk(); ok {
- s.gatherNodesMetrics(acc, nodes)
- }
- respRaw.Body.Close()
- }
-
- if s.endpointMap["partitions"] {
- partitionsResp, respRaw, err := s.client.SlurmAPI.SlurmV0038GetPartitions(auth).Execute()
- if err != nil {
- return fmt.Errorf("error getting partitions: %w", err)
- }
- if partitions, ok := partitionsResp.GetPartitionsOk(); ok {
- s.gatherPartitionsMetrics(acc, partitions)
- }
- respRaw.Body.Close()
- }
-
- if s.endpointMap["reservations"] {
- reservationsResp, respRaw, err := s.client.SlurmAPI.SlurmV0038GetReservations(auth).Execute()
- if err != nil {
- return fmt.Errorf("error getting reservations: %w", err)
- }
- if reservations, ok := reservationsResp.GetReservationsOk(); ok {
- s.gatherReservationsMetrics(acc, reservations)
- }
- respRaw.Body.Close()
- }
-
- return nil
-}
-
func init() {
inputs.Add("slurm", func() telegraf.Input {
return &Slurm{
diff --git a/plugins/inputs/smart/smart.go b/plugins/inputs/smart/smart.go
index e704971d4..58452f828 100644
--- a/plugins/inputs/smart/smart.go
+++ b/plugins/inputs/smart/smart.go
@@ -25,8 +25,6 @@ import (
//go:embed sample.conf
var sampleConfig string
-const intelVID = "0x8086"
-
var (
// Device Model: APPLE SSD SM256E
// Product: HUH721212AL5204
@@ -356,8 +354,19 @@ var (
}
knownReadMethods = []string{"concurrent", "sequential"}
+
+ // Wrap with sudo
+ runCmd = func(timeout config.Duration, sudo bool, command string, args ...string) ([]byte, error) {
+ cmd := exec.Command(command, args...)
+ if sudo {
+ cmd = exec.Command("sudo", append([]string{"-n", command}, args...)...)
+ }
+ return internal.CombinedOutputTimeout(cmd, time.Duration(timeout))
+ }
)
+const intelVID = "0x8086"
+
// Smart plugin reads metrics from storage devices supporting S.M.A.R.T.
type Smart struct {
Path string `toml:"path" deprecated:"1.16.0;1.35.0;use 'path_smartctl' instead"`
@@ -382,18 +391,10 @@ type nvmeDevice struct {
serialNumber string
}
-func newSmart() *Smart {
- return &Smart{
- Timeout: config.Duration(time.Second * 30),
- ReadMethod: "concurrent",
- }
-}
-
func (*Smart) SampleConfig() string {
return sampleConfig
}
-// Init performs one time setup of the plugin and returns an error if the configuration is invalid.
func (m *Smart) Init() error {
// if deprecated `path` (to smartctl binary) is provided in config and `path_smartctl` override does not exist
if len(m.Path) > 0 && len(m.PathSmartctl) == 0 {
@@ -436,7 +437,6 @@ func (m *Smart) Init() error {
return nil
}
-// Gather takes in an accumulator and adds the metrics that the SMART tools gather.
func (m *Smart) Gather(acc telegraf.Accumulator) error {
var err error
var scannedNVMeDevices []string
@@ -532,15 +532,6 @@ func (m *Smart) scanDevices(ignoreExcludes bool, scanArgs ...string) ([]string,
return devices, nil
}
-// Wrap with sudo
-var runCmd = func(timeout config.Duration, sudo bool, command string, args ...string) ([]byte, error) {
- cmd := exec.Command(command, args...)
- if sudo {
- cmd = exec.Command("sudo", append([]string{"-n", command}, args...)...)
- }
- return internal.CombinedOutputTimeout(cmd, time.Duration(timeout))
-}
-
func excludedDev(excludes []string, deviceLine string) bool {
device := strings.Split(deviceLine, " ")
if len(device) != 0 {
@@ -1109,6 +1100,13 @@ func validatePath(filePath string) error {
return nil
}
+func newSmart() *Smart {
+ return &Smart{
+ Timeout: config.Duration(time.Second * 30),
+ ReadMethod: "concurrent",
+ }
+}
+
func init() {
// Set LC_NUMERIC to uniform numeric output from cli tools
_ = os.Setenv("LC_NUMERIC", "en_US.UTF-8")
diff --git a/plugins/inputs/smartctl/smartctl_test.go b/plugins/inputs/smartctl/smartctl_test.go
index a5042e02c..6545158f8 100644
--- a/plugins/inputs/smartctl/smartctl_test.go
+++ b/plugins/inputs/smartctl/smartctl_test.go
@@ -78,7 +78,7 @@ func fakeScanExecCommand(command string, args ...string) *exec.Cmd {
return cmd
}
-func TestScanHelperProcess(_ *testing.T) {
+func TestScanHelperProcess(*testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return
}
diff --git a/plugins/inputs/snmp/snmp.go b/plugins/inputs/snmp/snmp.go
index f74e03725..83d5e6f5d 100644
--- a/plugins/inputs/snmp/snmp.go
+++ b/plugins/inputs/snmp/snmp.go
@@ -17,7 +17,6 @@ import (
//go:embed sample.conf
var sampleConfig string
-// Snmp holds the configuration for the plugin.
type Snmp struct {
// The SNMP agent to query. Format is [SCHEME://]ADDR[:PORT] (e.g.
// udp://1.2.3.4:161). If the scheme is not specified then "udp" is used.
@@ -36,21 +35,21 @@ type Snmp struct {
Name string `toml:"name"`
Fields []snmp.Field `toml:"field"`
- connectionCache []snmp.Connection
-
Log telegraf.Logger `toml:"-"`
+ connectionCache []snmp.Connection
+
translator snmp.Translator
}
-func (s *Snmp) SetTranslator(name string) {
- s.Translator = name
-}
-
func (*Snmp) SampleConfig() string {
return sampleConfig
}
+func (s *Snmp) SetTranslator(name string) {
+ s.Translator = name
+}
+
func (s *Snmp) Init() error {
var err error
switch s.Translator {
@@ -92,9 +91,6 @@ func (s *Snmp) Init() error {
return nil
}
-// Gather retrieves all the configured fields and tables.
-// Any error encountered does not halt the process. The errors are accumulated
-// and returned at the end.
func (s *Snmp) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
for i, agent := range s.Agents {
diff --git a/plugins/inputs/snmp/snmp_test.go b/plugins/inputs/snmp/snmp_test.go
index 360c2f2cc..a7034aff3 100644
--- a/plugins/inputs/snmp/snmp_test.go
+++ b/plugins/inputs/snmp/snmp_test.go
@@ -43,6 +43,7 @@ func (tsc *testSNMPConnection) Get(oids []string) (*gosnmp.SnmpPacket, error) {
}
return sp, nil
}
+
func (tsc *testSNMPConnection) Walk(oid string, wf gosnmp.WalkFunc) error {
for void, v := range tsc.values {
if void == oid || (len(void) > len(oid) && void[:len(oid)+1] == oid+".") {
@@ -56,6 +57,7 @@ func (tsc *testSNMPConnection) Walk(oid string, wf gosnmp.WalkFunc) error {
}
return nil
}
+
func (*testSNMPConnection) Reconnect() error {
return nil
}
@@ -466,7 +468,7 @@ func TestGosnmpWrapper_walk_retry(t *testing.T) {
gsw := snmp.GosnmpWrapper{
GoSNMP: gs,
}
- err = gsw.Walk(".1.0.0", func(_ gosnmp.SnmpPDU) error { return nil })
+ err = gsw.Walk(".1.0.0", func(gosnmp.SnmpPDU) error { return nil })
require.NoError(t, srvr.Close())
wg.Wait()
require.Error(t, err)
diff --git a/plugins/inputs/snmp_trap/netsnmp.go b/plugins/inputs/snmp_trap/netsnmp.go
index beab7ac67..9095c5f75 100644
--- a/plugins/inputs/snmp_trap/netsnmp.go
+++ b/plugins/inputs/snmp_trap/netsnmp.go
@@ -39,7 +39,7 @@ type netsnmpTranslator struct {
cacheLock sync.Mutex
cache map[string]snmp.MibEntry
execCmd execer
- Timeout config.Duration
+ timeout config.Duration
}
func (s *netsnmpTranslator) lookup(oid string) (e snmp.MibEntry, err error) {
@@ -59,7 +59,7 @@ func (s *netsnmpTranslator) lookup(oid string) (e snmp.MibEntry, err error) {
func (s *netsnmpTranslator) snmptranslate(oid string) (e snmp.MibEntry, err error) {
var out []byte
- out, err = s.execCmd(s.Timeout, "snmptranslate", "-Td", "-Ob", "-m", "all", oid)
+ out, err = s.execCmd(s.timeout, "snmptranslate", "-Td", "-Ob", "-m", "all", oid)
if err != nil {
return e, err
@@ -86,6 +86,6 @@ func newNetsnmpTranslator(timeout config.Duration) *netsnmpTranslator {
return &netsnmpTranslator{
execCmd: realExecCmd,
cache: make(map[string]snmp.MibEntry),
- Timeout: timeout,
+ timeout: timeout,
}
}
diff --git a/plugins/inputs/snmp_trap/snmp_trap.go b/plugins/inputs/snmp_trap/snmp_trap.go
index d3d7a5cb2..d6e09f8b0 100644
--- a/plugins/inputs/snmp_trap/snmp_trap.go
+++ b/plugins/inputs/snmp_trap/snmp_trap.go
@@ -25,33 +25,16 @@ var defaultTimeout = config.Duration(time.Second * 5)
//go:embed sample.conf
var sampleConfig string
-type translator interface {
- lookup(oid string) (snmp.MibEntry, error)
-}
-
-type wrapLog struct {
- telegraf.Logger
-}
-
-func (l wrapLog) Printf(format string, args ...interface{}) {
- l.Debugf(format, args...)
-}
-
-func (l wrapLog) Print(args ...interface{}) {
- l.Debug(args...)
-}
-
type SnmpTrap struct {
ServiceAddress string `toml:"service_address"`
Timeout config.Duration `toml:"timeout"`
Version string `toml:"version"`
- Translator string `toml:"-"`
Path []string `toml:"path"`
-
// Settings for version 3
// Values: "noAuthNoPriv", "authNoPriv", "authPriv"
- SecLevel string `toml:"sec_level"`
- SecName config.Secret `toml:"sec_name"`
+ SecLevel string `toml:"sec_level"`
+
+ SecName config.Secret `toml:"sec_name"`
// Values: "MD5", "SHA", "". Default: ""
AuthProtocol string `toml:"auth_protocol"`
AuthPassword config.Secret `toml:"auth_password"`
@@ -59,38 +42,30 @@ type SnmpTrap struct {
PrivProtocol string `toml:"priv_protocol"`
PrivPassword config.Secret `toml:"priv_password"`
+ Translator string `toml:"-"`
+ Log telegraf.Logger `toml:"-"`
+
acc telegraf.Accumulator
listener *gosnmp.TrapListener
timeFunc func() time.Time
errCh chan error
makeHandlerWrapper func(gosnmp.TrapHandlerFunc) gosnmp.TrapHandlerFunc
+ transl translator
+}
- Log telegraf.Logger `toml:"-"`
+type wrapLog struct {
+ telegraf.Logger
+}
- transl translator
+type translator interface {
+ lookup(oid string) (snmp.MibEntry, error)
}
func (*SnmpTrap) SampleConfig() string {
return sampleConfig
}
-func (*SnmpTrap) Gather(telegraf.Accumulator) error {
- return nil
-}
-
-func init() {
- inputs.Add("snmp_trap", func() telegraf.Input {
- return &SnmpTrap{
- timeFunc: time.Now,
- ServiceAddress: "udp://:162",
- Timeout: defaultTimeout,
- Path: []string{"/usr/share/snmp/mibs"},
- Version: "2c",
- }
- })
-}
-
func (s *SnmpTrap) SetTranslator(name string) {
s.Translator = name
}
@@ -259,6 +234,10 @@ func (s *SnmpTrap) Start(acc telegraf.Accumulator) error {
return nil
}
+func (*SnmpTrap) Gather(telegraf.Accumulator) error {
+ return nil
+}
+
func (s *SnmpTrap) Stop() {
s.listener.Close()
err := <-s.errCh
@@ -385,3 +364,23 @@ func makeTrapHandler(s *SnmpTrap) gosnmp.TrapHandlerFunc {
s.acc.AddFields("snmp_trap", fields, tags, tm)
}
}
+
+func (l wrapLog) Printf(format string, args ...interface{}) {
+ l.Debugf(format, args...)
+}
+
+func (l wrapLog) Print(args ...interface{}) {
+ l.Debug(args...)
+}
+
+func init() {
+ inputs.Add("snmp_trap", func() telegraf.Input {
+ return &SnmpTrap{
+ timeFunc: time.Now,
+ ServiceAddress: "udp://:162",
+ Timeout: defaultTimeout,
+ Path: []string{"/usr/share/snmp/mibs"},
+ Version: "2c",
+ }
+ })
+}
diff --git a/plugins/inputs/socket_listener/socket_listener.go b/plugins/inputs/socket_listener/socket_listener.go
index b8eadbba7..17d753147 100644
--- a/plugins/inputs/socket_listener/socket_listener.go
+++ b/plugins/inputs/socket_listener/socket_listener.go
@@ -34,6 +34,10 @@ func (*SocketListener) SampleConfig() string {
return sampleConfig
}
+func (sl *SocketListener) SetParser(parser telegraf.Parser) {
+ sl.parser = parser
+}
+
func (sl *SocketListener) Init() error {
sock, err := sl.Config.NewSocket(sl.ServiceAddress, &sl.SplitConfig, sl.Log)
if err != nil {
@@ -44,14 +48,6 @@ func (sl *SocketListener) Init() error {
return nil
}
-func (*SocketListener) Gather(telegraf.Accumulator) error {
- return nil
-}
-
-func (sl *SocketListener) SetParser(parser telegraf.Parser) {
- sl.parser = parser
-}
-
func (sl *SocketListener) Start(acc telegraf.Accumulator) error {
// Create the callbacks for parsing the data and recording issues
onData := func(_ net.Addr, data []byte, receiveTime time.Time) {
@@ -93,6 +89,10 @@ func (sl *SocketListener) Start(acc telegraf.Accumulator) error {
return nil
}
+func (*SocketListener) Gather(telegraf.Accumulator) error {
+ return nil
+}
+
func (sl *SocketListener) Stop() {
if sl.socket != nil {
sl.socket.Close()
diff --git a/plugins/inputs/socketstat/socketstat.go b/plugins/inputs/socketstat/socketstat.go
index 7bac4ab22..6f5dae660 100644
--- a/plugins/inputs/socketstat/socketstat.go
+++ b/plugins/inputs/socketstat/socketstat.go
@@ -27,7 +27,6 @@ var sampleConfig string
const measurement = "socketstat"
-// Socketstat is a telegraf plugin to gather indicators from established connections, using iproute2's `ss` command.
type Socketstat struct {
SocketProto []string `toml:"protocols"`
Timeout config.Duration `toml:"timeout"`
@@ -45,7 +44,30 @@ func (*Socketstat) SampleConfig() string {
return sampleConfig
}
-// Gather gathers indicators from established connections
+func (ss *Socketstat) Init() error {
+ if len(ss.SocketProto) == 0 {
+ ss.SocketProto = []string{"tcp", "udp"}
+ }
+
+ // Initialize regexps to validate input data
+ validFields := "(bytes_acked|bytes_received|segs_out|segs_in|data_segs_in|data_segs_out)"
+ ss.validValues = regexp.MustCompile("^" + validFields + ":[0-9]+$")
+ ss.isNewConnection = regexp.MustCompile(`^\s+.*$`)
+
+ ss.lister = socketList
+
+ // Check that ss is installed, get its path.
+ // Do it last, because in test environments where `ss` might not be available,
+ // we still want the other Init() actions to be performed.
+ ssPath, err := exec.LookPath("ss")
+ if err != nil {
+ return err
+ }
+ ss.cmdName = ssPath
+
+ return nil
+}
+
func (ss *Socketstat) Gather(acc telegraf.Accumulator) error {
// best effort : we continue through the protocols even if an error is encountered,
// but we keep track of the last error.
@@ -183,30 +205,6 @@ func getTagsAndState(proto string, words []string, log telegraf.Logger) (map[str
return tags, fields
}
-func (ss *Socketstat) Init() error {
- if len(ss.SocketProto) == 0 {
- ss.SocketProto = []string{"tcp", "udp"}
- }
-
- // Initialize regexps to validate input data
- validFields := "(bytes_acked|bytes_received|segs_out|segs_in|data_segs_in|data_segs_out)"
- ss.validValues = regexp.MustCompile("^" + validFields + ":[0-9]+$")
- ss.isNewConnection = regexp.MustCompile(`^\s+.*$`)
-
- ss.lister = socketList
-
- // Check that ss is installed, get its path.
- // Do it last, because in test environments where `ss` might not be available,
- // we still want the other Init() actions to be performed.
- ssPath, err := exec.LookPath("ss")
- if err != nil {
- return err
- }
- ss.cmdName = ssPath
-
- return nil
-}
-
func init() {
inputs.Add("socketstat", func() telegraf.Input {
return &Socketstat{Timeout: config.Duration(time.Second)}
diff --git a/plugins/inputs/socketstat/socketstat_windows.go b/plugins/inputs/socketstat/socketstat_windows.go
index 3a5ef98f5..9b81a6fbd 100644
--- a/plugins/inputs/socketstat/socketstat_windows.go
+++ b/plugins/inputs/socketstat/socketstat_windows.go
@@ -16,12 +16,14 @@ type Socketstat struct {
Log telegraf.Logger `toml:"-"`
}
+func (*Socketstat) SampleConfig() string { return sampleConfig }
+
func (s *Socketstat) Init() error {
- s.Log.Warn("current platform is not supported")
+ s.Log.Warn("Current platform is not supported")
return nil
}
-func (*Socketstat) SampleConfig() string { return sampleConfig }
-func (*Socketstat) Gather(_ telegraf.Accumulator) error { return nil }
+
+func (*Socketstat) Gather(telegraf.Accumulator) error { return nil }
func init() {
inputs.Add("socketstat", func() telegraf.Input {
diff --git a/plugins/inputs/solr/solr.go b/plugins/inputs/solr/solr.go
index c1b51023a..5accd0d33 100644
--- a/plugins/inputs/solr/solr.go
+++ b/plugins/inputs/solr/solr.go
@@ -20,7 +20,6 @@ import (
//go:embed sample.conf
var sampleConfig string
-// Solr is a plugin to read stats from one or many Solr servers
type Solr struct {
Servers []string `toml:"servers"`
Username string `toml:"username"`
@@ -60,7 +59,7 @@ func (s *Solr) Init() error {
return nil
}
-func (s *Solr) Start(_ telegraf.Accumulator) error {
+func (s *Solr) Start(telegraf.Accumulator) error {
for _, server := range s.Servers {
// Simply fill the cache for all available servers
_ = s.getAPIConfig(server)
@@ -68,8 +67,6 @@ func (s *Solr) Start(_ telegraf.Accumulator) error {
return nil
}
-func (*Solr) Stop() {}
-
func (s *Solr) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
for _, srv := range s.Servers {
@@ -87,6 +84,8 @@ func (s *Solr) Gather(acc telegraf.Accumulator) error {
return nil
}
+func (*Solr) Stop() {}
+
func (s *Solr) getAPIConfig(server string) *apiConfig {
if cfg, found := s.configs[server]; found {
return cfg
diff --git a/plugins/inputs/sql/sql.go b/plugins/inputs/sql/sql.go
index 93307fa0d..5c34ac7a2 100644
--- a/plugins/inputs/sql/sql.go
+++ b/plugins/inputs/sql/sql.go
@@ -24,11 +24,28 @@ import (
//go:embed sample.conf
var sampleConfig string
-const magicIdleCount = -int(^uint(0) >> 1)
-
var disconnectedServersBehavior = []string{"error", "ignore"}
-type Query struct {
+const magicIdleCount = -int(^uint(0) >> 1)
+
+type SQL struct {
+ Driver string `toml:"driver"`
+ Dsn config.Secret `toml:"dsn"`
+ Timeout config.Duration `toml:"timeout"`
+ MaxIdleTime config.Duration `toml:"connection_max_idle_time"`
+ MaxLifetime config.Duration `toml:"connection_max_life_time"`
+ MaxOpenConnections int `toml:"connection_max_open"`
+ MaxIdleConnections int `toml:"connection_max_idle"`
+ Queries []query `toml:"query"`
+ Log telegraf.Logger `toml:"-"`
+ DisconnectedServersBehavior string `toml:"disconnected_servers_behavior"`
+
+ driverName string
+ db *dbsql.DB
+ serverConnected bool
+}
+
+type query struct {
Query string `toml:"query"`
Script string `toml:"query_script"`
Measurement string `toml:"measurement"`
@@ -55,7 +72,312 @@ type Query struct {
fieldFilterString filter.Filter
}
-func (q *Query) parse(acc telegraf.Accumulator, rows *dbsql.Rows, t time.Time, logger telegraf.Logger) (int, error) {
+func (*SQL) SampleConfig() string {
+ return sampleConfig
+}
+
+func (s *SQL) Init() error {
+ // Option handling
+ if s.Driver == "" {
+ return errors.New("missing SQL driver option")
+ }
+
+ if err := s.checkDSN(); err != nil {
+ return err
+ }
+
+ if s.Timeout <= 0 {
+ s.Timeout = config.Duration(5 * time.Second)
+ }
+
+ if s.MaxIdleConnections == magicIdleCount {
+ // Determine the number by the number of queries + the golang default value
+ s.MaxIdleConnections = len(s.Queries) + 2
+ }
+
+ for i, q := range s.Queries {
+ if q.Query == "" && q.Script == "" {
+ return errors.New("neither 'query' nor 'query_script' specified")
+ }
+
+ if q.Query != "" && q.Script != "" {
+ return errors.New("only one of 'query' and 'query_script' can be specified")
+ }
+
+ // In case we got a script, we should read the query now.
+ if q.Script != "" {
+ query, err := os.ReadFile(q.Script)
+ if err != nil {
+ return fmt.Errorf("reading script %q failed: %w", q.Script, err)
+ }
+ s.Queries[i].Query = string(query)
+ }
+
+ // Time format
+ if q.TimeFormat == "" {
+ s.Queries[i].TimeFormat = "unix"
+ }
+
+ // Compile the tag-filter
+ tagfilter, err := filter.NewIncludeExcludeFilterDefaults(q.TagColumnsInclude, q.TagColumnsExclude, false, false)
+ if err != nil {
+ return fmt.Errorf("creating tag filter failed: %w", err)
+ }
+ s.Queries[i].tagFilter = tagfilter
+
+ // Compile the explicit type field-filter
+ fieldfilterFloat, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsFloat, nil, false, false)
+ if err != nil {
+ return fmt.Errorf("creating field filter for float failed: %w", err)
+ }
+ s.Queries[i].fieldFilterFloat = fieldfilterFloat
+
+ fieldfilterInt, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsInt, nil, false, false)
+ if err != nil {
+ return fmt.Errorf("creating field filter for int failed: %w", err)
+ }
+ s.Queries[i].fieldFilterInt = fieldfilterInt
+
+ fieldfilterUint, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsUint, nil, false, false)
+ if err != nil {
+ return fmt.Errorf("creating field filter for uint failed: %w", err)
+ }
+ s.Queries[i].fieldFilterUint = fieldfilterUint
+
+ fieldfilterBool, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsBool, nil, false, false)
+ if err != nil {
+ return fmt.Errorf("creating field filter for bool failed: %w", err)
+ }
+ s.Queries[i].fieldFilterBool = fieldfilterBool
+
+ fieldfilterString, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsString, nil, false, false)
+ if err != nil {
+ return fmt.Errorf("creating field filter for string failed: %w", err)
+ }
+ s.Queries[i].fieldFilterString = fieldfilterString
+
+ // Compile the field-filter
+ fieldfilter, err := filter.NewIncludeExcludeFilter(q.FieldColumnsInclude, q.FieldColumnsExclude)
+ if err != nil {
+ return fmt.Errorf("creating field filter failed: %w", err)
+ }
+ s.Queries[i].fieldFilter = fieldfilter
+
+ if q.Measurement == "" {
+ s.Queries[i].Measurement = "sql"
+ }
+ }
+
+ // Derive the sql-framework driver name from our config name. This abstracts the actual driver
+ // from the database-type the user wants.
+ aliases := map[string]string{
+ "cockroach": "pgx",
+ "tidb": "mysql",
+ "mssql": "sqlserver",
+ "maria": "mysql",
+ "postgres": "pgx",
+ "oracle": "oracle",
+ }
+ s.driverName = s.Driver
+ if driver, ok := aliases[s.Driver]; ok {
+ s.driverName = driver
+ }
+
+ availDrivers := dbsql.Drivers()
+ if !choice.Contains(s.driverName, availDrivers) {
+ for d, r := range aliases {
+ if choice.Contains(r, availDrivers) {
+ availDrivers = append(availDrivers, d)
+ }
+ }
+
+ // Sort the list of drivers and make them unique
+ sort.Strings(availDrivers)
+ last := 0
+ for _, d := range availDrivers {
+ if d != availDrivers[last] {
+ last++
+ availDrivers[last] = d
+ }
+ }
+ availDrivers = availDrivers[:last+1]
+
+ return fmt.Errorf("driver %q not supported use one of %v", s.Driver, availDrivers)
+ }
+
+ if s.DisconnectedServersBehavior == "" {
+ s.DisconnectedServersBehavior = "error"
+ }
+
+ if !choice.Contains(s.DisconnectedServersBehavior, disconnectedServersBehavior) {
+ return fmt.Errorf("%q is not a valid value for disconnected_servers_behavior", s.DisconnectedServersBehavior)
+ }
+
+ return nil
+}
+
+func (s *SQL) Start(telegraf.Accumulator) error {
+ if err := s.setupConnection(); err != nil {
+ return err
+ }
+
+ if err := s.ping(); err != nil {
+ if s.DisconnectedServersBehavior == "error" {
+ return err
+ }
+ s.Log.Errorf("unable to connect to database: %s", err)
+ }
+ if s.serverConnected {
+ s.prepareStatements()
+ }
+
+ return nil
+}
+
+func (s *SQL) Gather(acc telegraf.Accumulator) error {
+ // during plugin startup, it is possible that the server was not reachable.
+ // we try pinging the server in this collection cycle.
+ // we are only concerned with `prepareStatements` function to complete(return true), just once.
+ if !s.serverConnected {
+ if err := s.ping(); err != nil {
+ return err
+ }
+ s.prepareStatements()
+ }
+
+ var wg sync.WaitGroup
+ tstart := time.Now()
+ for _, q := range s.Queries {
+ wg.Add(1)
+ go func(q query) {
+ defer wg.Done()
+ ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.Timeout))
+ defer cancel()
+ if err := s.executeQuery(ctx, acc, q, tstart); err != nil {
+ acc.AddError(err)
+ }
+ }(q)
+ }
+ wg.Wait()
+ s.Log.Debugf("Executed %d queries in %s", len(s.Queries), time.Since(tstart).String())
+
+ return nil
+}
+
+func (s *SQL) Stop() {
+ // Free the statements
+ for _, q := range s.Queries {
+ if q.statement != nil {
+ if err := q.statement.Close(); err != nil {
+ s.Log.Errorf("closing statement for query %q failed: %v", q.Query, err)
+ }
+ }
+ }
+
+ // Close the connection to the server
+ if s.db != nil {
+ if err := s.db.Close(); err != nil {
+ s.Log.Errorf("closing database connection failed: %v", err)
+ }
+ }
+}
+
+func (s *SQL) setupConnection() error {
+ // Connect to the database server
+ dsnSecret, err := s.Dsn.Get()
+ if err != nil {
+ return fmt.Errorf("getting DSN failed: %w", err)
+ }
+ dsn := dsnSecret.String()
+ dsnSecret.Destroy()
+
+ s.Log.Debug("Connecting...")
+ s.db, err = dbsql.Open(s.driverName, dsn)
+ if err != nil {
+ // should return since the error is most likely with invalid DSN string format
+ return err
+ }
+
+ // Set the connection limits
+ // s.db.SetConnMaxIdleTime(time.Duration(s.MaxIdleTime)) // Requires go >= 1.15
+ s.db.SetConnMaxLifetime(time.Duration(s.MaxLifetime))
+ s.db.SetMaxOpenConns(s.MaxOpenConnections)
+ s.db.SetMaxIdleConns(s.MaxIdleConnections)
+ return nil
+}
+
+func (s *SQL) ping() error {
+ // Test if the connection can be established
+ s.Log.Debug("Testing connectivity...")
+ ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.Timeout))
+ err := s.db.PingContext(ctx)
+ cancel()
+ if err != nil {
+ return fmt.Errorf("unable to connect to database: %w", err)
+ }
+ s.serverConnected = true
+ return nil
+}
+
+func (s *SQL) prepareStatements() {
+ // Prepare the statements
+ for i, q := range s.Queries {
+ s.Log.Debugf("Preparing statement %q...", q.Query)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.Timeout))
+ stmt, err := s.db.PrepareContext(ctx, q.Query)
+ cancel()
+ if err != nil {
+ // Some database drivers or databases do not support prepare
+ // statements and report an error here. However, we can still
+ // execute unprepared queries for those setups so do not bail-out
+ // here but simply do leave the `statement` with a `nil` value
+ // indicating no prepared statement.
+ s.Log.Warnf("preparing query %q failed: %s; falling back to unprepared query", q.Query, err)
+ continue
+ }
+ s.Queries[i].statement = stmt
+ }
+}
+
+func (s *SQL) executeQuery(ctx context.Context, acc telegraf.Accumulator, q query, tquery time.Time) error {
+ // Execute the query either prepared or unprepared
+ var rows *dbsql.Rows
+ if q.statement != nil {
+ // Use the previously prepared query
+ var err error
+ rows, err = q.statement.QueryContext(ctx)
+ if err != nil {
+ return err
+ }
+ } else {
+ // Fallback to unprepared query
+ var err error
+ rows, err = s.db.Query(q.Query)
+ if err != nil {
+ return err
+ }
+ }
+ defer rows.Close()
+
+ // Handle the rows
+ columnNames, err := rows.Columns()
+ if err != nil {
+ return err
+ }
+ rowCount, err := q.parse(acc, rows, tquery, s.Log)
+ s.Log.Debugf("Received %d rows and %d columns for query %q", rowCount, len(columnNames), q.Query)
+
+ return err
+}
+
+func (s *SQL) checkDSN() error {
+ if s.Dsn.Empty() {
+ return errors.New("missing data source name (DSN) option")
+ }
+ return nil
+}
+
+func (q *query) parse(acc telegraf.Accumulator, rows *dbsql.Rows, t time.Time, logger telegraf.Logger) (int, error) {
columnNames, err := rows.Columns()
if err != nil {
return 0, err
@@ -214,290 +536,6 @@ func (q *Query) parse(acc telegraf.Accumulator, rows *dbsql.Rows, t time.Time, l
return rowCount, nil
}
-type SQL struct {
- Driver string `toml:"driver"`
- Dsn config.Secret `toml:"dsn"`
- Timeout config.Duration `toml:"timeout"`
- MaxIdleTime config.Duration `toml:"connection_max_idle_time"`
- MaxLifetime config.Duration `toml:"connection_max_life_time"`
- MaxOpenConnections int `toml:"connection_max_open"`
- MaxIdleConnections int `toml:"connection_max_idle"`
- Queries []Query `toml:"query"`
- Log telegraf.Logger `toml:"-"`
- DisconnectedServersBehavior string `toml:"disconnected_servers_behavior"`
-
- driverName string
- db *dbsql.DB
- serverConnected bool
-}
-
-func (*SQL) SampleConfig() string {
- return sampleConfig
-}
-
-func (s *SQL) Init() error {
- // Option handling
- if s.Driver == "" {
- return errors.New("missing SQL driver option")
- }
-
- if err := s.checkDSN(); err != nil {
- return err
- }
-
- if s.Timeout <= 0 {
- s.Timeout = config.Duration(5 * time.Second)
- }
-
- if s.MaxIdleConnections == magicIdleCount {
- // Determine the number by the number of queries + the golang default value
- s.MaxIdleConnections = len(s.Queries) + 2
- }
-
- for i, q := range s.Queries {
- if q.Query == "" && q.Script == "" {
- return errors.New("neither 'query' nor 'query_script' specified")
- }
-
- if q.Query != "" && q.Script != "" {
- return errors.New("only one of 'query' and 'query_script' can be specified")
- }
-
- // In case we got a script, we should read the query now.
- if q.Script != "" {
- query, err := os.ReadFile(q.Script)
- if err != nil {
- return fmt.Errorf("reading script %q failed: %w", q.Script, err)
- }
- s.Queries[i].Query = string(query)
- }
-
- // Time format
- if q.TimeFormat == "" {
- s.Queries[i].TimeFormat = "unix"
- }
-
- // Compile the tag-filter
- tagfilter, err := filter.NewIncludeExcludeFilterDefaults(q.TagColumnsInclude, q.TagColumnsExclude, false, false)
- if err != nil {
- return fmt.Errorf("creating tag filter failed: %w", err)
- }
- s.Queries[i].tagFilter = tagfilter
-
- // Compile the explicit type field-filter
- fieldfilterFloat, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsFloat, nil, false, false)
- if err != nil {
- return fmt.Errorf("creating field filter for float failed: %w", err)
- }
- s.Queries[i].fieldFilterFloat = fieldfilterFloat
-
- fieldfilterInt, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsInt, nil, false, false)
- if err != nil {
- return fmt.Errorf("creating field filter for int failed: %w", err)
- }
- s.Queries[i].fieldFilterInt = fieldfilterInt
-
- fieldfilterUint, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsUint, nil, false, false)
- if err != nil {
- return fmt.Errorf("creating field filter for uint failed: %w", err)
- }
- s.Queries[i].fieldFilterUint = fieldfilterUint
-
- fieldfilterBool, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsBool, nil, false, false)
- if err != nil {
- return fmt.Errorf("creating field filter for bool failed: %w", err)
- }
- s.Queries[i].fieldFilterBool = fieldfilterBool
-
- fieldfilterString, err := filter.NewIncludeExcludeFilterDefaults(q.FieldColumnsString, nil, false, false)
- if err != nil {
- return fmt.Errorf("creating field filter for string failed: %w", err)
- }
- s.Queries[i].fieldFilterString = fieldfilterString
-
- // Compile the field-filter
- fieldfilter, err := filter.NewIncludeExcludeFilter(q.FieldColumnsInclude, q.FieldColumnsExclude)
- if err != nil {
- return fmt.Errorf("creating field filter failed: %w", err)
- }
- s.Queries[i].fieldFilter = fieldfilter
-
- if q.Measurement == "" {
- s.Queries[i].Measurement = "sql"
- }
- }
-
- // Derive the sql-framework driver name from our config name. This abstracts the actual driver
- // from the database-type the user wants.
- aliases := map[string]string{
- "cockroach": "pgx",
- "tidb": "mysql",
- "mssql": "sqlserver",
- "maria": "mysql",
- "postgres": "pgx",
- "oracle": "oracle",
- }
- s.driverName = s.Driver
- if driver, ok := aliases[s.Driver]; ok {
- s.driverName = driver
- }
-
- availDrivers := dbsql.Drivers()
- if !choice.Contains(s.driverName, availDrivers) {
- for d, r := range aliases {
- if choice.Contains(r, availDrivers) {
- availDrivers = append(availDrivers, d)
- }
- }
-
- // Sort the list of drivers and make them unique
- sort.Strings(availDrivers)
- last := 0
- for _, d := range availDrivers {
- if d != availDrivers[last] {
- last++
- availDrivers[last] = d
- }
- }
- availDrivers = availDrivers[:last+1]
-
- return fmt.Errorf("driver %q not supported use one of %v", s.Driver, availDrivers)
- }
-
- if s.DisconnectedServersBehavior == "" {
- s.DisconnectedServersBehavior = "error"
- }
-
- if !choice.Contains(s.DisconnectedServersBehavior, disconnectedServersBehavior) {
- return fmt.Errorf("%q is not a valid value for disconnected_servers_behavior", s.DisconnectedServersBehavior)
- }
-
- return nil
-}
-
-func (s *SQL) setupConnection() error {
- // Connect to the database server
- dsnSecret, err := s.Dsn.Get()
- if err != nil {
- return fmt.Errorf("getting DSN failed: %w", err)
- }
- dsn := dsnSecret.String()
- dsnSecret.Destroy()
-
- s.Log.Debug("Connecting...")
- s.db, err = dbsql.Open(s.driverName, dsn)
- if err != nil {
- // should return since the error is most likely with invalid DSN string format
- return err
- }
-
- // Set the connection limits
- // s.db.SetConnMaxIdleTime(time.Duration(s.MaxIdleTime)) // Requires go >= 1.15
- s.db.SetConnMaxLifetime(time.Duration(s.MaxLifetime))
- s.db.SetMaxOpenConns(s.MaxOpenConnections)
- s.db.SetMaxIdleConns(s.MaxIdleConnections)
- return nil
-}
-
-func (s *SQL) ping() error {
- // Test if the connection can be established
- s.Log.Debug("Testing connectivity...")
- ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.Timeout))
- err := s.db.PingContext(ctx)
- cancel()
- if err != nil {
- return fmt.Errorf("unable to connect to database: %w", err)
- }
- s.serverConnected = true
- return nil
-}
-
-func (s *SQL) prepareStatements() {
- // Prepare the statements
- for i, q := range s.Queries {
- s.Log.Debugf("Preparing statement %q...", q.Query)
- ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.Timeout))
- stmt, err := s.db.PrepareContext(ctx, q.Query)
- cancel()
- if err != nil {
- // Some database drivers or databases do not support prepare
- // statements and report an error here. However, we can still
- // execute unprepared queries for those setups so do not bail-out
- // here but simply do leave the `statement` with a `nil` value
- // indicating no prepared statement.
- s.Log.Warnf("preparing query %q failed: %s; falling back to unprepared query", q.Query, err)
- continue
- }
- s.Queries[i].statement = stmt
- }
-}
-
-func (s *SQL) Start(_ telegraf.Accumulator) error {
- if err := s.setupConnection(); err != nil {
- return err
- }
-
- if err := s.ping(); err != nil {
- if s.DisconnectedServersBehavior == "error" {
- return err
- }
- s.Log.Errorf("unable to connect to database: %s", err)
- }
- if s.serverConnected {
- s.prepareStatements()
- }
-
- return nil
-}
-
-func (s *SQL) Stop() {
- // Free the statements
- for _, q := range s.Queries {
- if q.statement != nil {
- if err := q.statement.Close(); err != nil {
- s.Log.Errorf("closing statement for query %q failed: %v", q.Query, err)
- }
- }
- }
-
- // Close the connection to the server
- if s.db != nil {
- if err := s.db.Close(); err != nil {
- s.Log.Errorf("closing database connection failed: %v", err)
- }
- }
-}
-
-func (s *SQL) Gather(acc telegraf.Accumulator) error {
- // during plugin startup, it is possible that the server was not reachable.
- // we try pinging the server in this collection cycle.
- // we are only concerned with `prepareStatements` function to complete(return true), just once.
- if !s.serverConnected {
- if err := s.ping(); err != nil {
- return err
- }
- s.prepareStatements()
- }
-
- var wg sync.WaitGroup
- tstart := time.Now()
- for _, query := range s.Queries {
- wg.Add(1)
- go func(q Query) {
- defer wg.Done()
- ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.Timeout))
- defer cancel()
- if err := s.executeQuery(ctx, acc, q, tstart); err != nil {
- acc.AddError(err)
- }
- }(query)
- }
- wg.Wait()
- s.Log.Debugf("Executed %d queries in %s", len(s.Queries), time.Since(tstart).String())
-
- return nil
-}
-
func init() {
inputs.Add("sql", func() telegraf.Input {
return &SQL{
@@ -508,41 +546,3 @@ func init() {
}
})
}
-
-func (s *SQL) executeQuery(ctx context.Context, acc telegraf.Accumulator, q Query, tquery time.Time) error {
- // Execute the query either prepared or unprepared
- var rows *dbsql.Rows
- if q.statement != nil {
- // Use the previously prepared query
- var err error
- rows, err = q.statement.QueryContext(ctx)
- if err != nil {
- return err
- }
- } else {
- // Fallback to unprepared query
- var err error
- rows, err = s.db.Query(q.Query)
- if err != nil {
- return err
- }
- }
- defer rows.Close()
-
- // Handle the rows
- columnNames, err := rows.Columns()
- if err != nil {
- return err
- }
- rowCount, err := q.parse(acc, rows, tquery, s.Log)
- s.Log.Debugf("Received %d rows and %d columns for query %q", rowCount, len(columnNames), q.Query)
-
- return err
-}
-
-func (s *SQL) checkDSN() error {
- if s.Dsn.Empty() {
- return errors.New("missing data source name (DSN) option")
- }
- return nil
-}
diff --git a/plugins/inputs/sql/sql_test.go b/plugins/inputs/sql/sql_test.go
index a93f11b24..f74d60b8a 100644
--- a/plugins/inputs/sql/sql_test.go
+++ b/plugins/inputs/sql/sql_test.go
@@ -65,12 +65,12 @@ func TestMariaDBIntegration(t *testing.T) {
// Define the testset
var testset = []struct {
name string
- queries []Query
+ queries []query
expected []telegraf.Metric
}{
{
name: "metric_one",
- queries: []Query{
+ queries: []query{
{
Query: "SELECT * FROM metric_one",
TagColumnsInclude: []string{"tag_*"},
@@ -164,12 +164,12 @@ func TestPostgreSQLIntegration(t *testing.T) {
// Define the testset
var testset = []struct {
name string
- queries []Query
+ queries []query
expected []telegraf.Metric
}{
{
name: "metric_one",
- queries: []Query{
+ queries: []query{
{
Query: "SELECT * FROM metric_one",
TagColumnsInclude: []string{"tag_*"},
@@ -259,12 +259,12 @@ func TestClickHouseIntegration(t *testing.T) {
// Define the testset
var testset = []struct {
name string
- queries []Query
+ queries []query
expected []telegraf.Metric
}{
{
name: "metric_one",
- queries: []Query{
+ queries: []query{
{
Query: "SELECT * FROM default.metric_one",
TagColumnsInclude: []string{"tag_*"},
diff --git a/plugins/inputs/sqlserver/sqlserver.go b/plugins/inputs/sqlserver/sqlserver.go
index e2c737497..c889faa75 100644
--- a/plugins/inputs/sqlserver/sqlserver.go
+++ b/plugins/inputs/sqlserver/sqlserver.go
@@ -23,7 +23,25 @@ import (
//go:embed sample.conf
var sampleConfig string
-// SQLServer struct
+const (
+ defaultServer = "Server=.;app name=telegraf;log=1;"
+
+ typeAzureSQLDB = "AzureSQLDB"
+ typeAzureSQLManagedInstance = "AzureSQLManagedInstance"
+ typeAzureSQLPool = "AzureSQLPool"
+ typeSQLServer = "SQLServer"
+ typeAzureArcSQLManagedInstance = "AzureArcSQLManagedInstance"
+
+ healthMetricName = "sqlserver_telegraf_health"
+ healthMetricInstanceTag = "sql_instance"
+ healthMetricDatabaseTag = "database_name"
+ healthMetricAttemptedQueries = "attempted_queries"
+ healthMetricSuccessfulQueries = "successful_queries"
+ healthMetricDatabaseType = "database_type"
+
+ sqlAzureResourceID = "https://database.windows.net/"
+)
+
type SQLServer struct {
Servers []*config.Secret `toml:"servers"`
QueryTimeout config.Duration `toml:"query_timeout"`
@@ -38,213 +56,38 @@ type SQLServer struct {
Log telegraf.Logger `toml:"-"`
pools []*sql.DB
- queries MapQuery
+ queries mapQuery
adalToken *adal.Token
muCacheLock sync.RWMutex
}
-// Query struct
-type Query struct {
+type query struct {
ScriptName string
Script string
ResultByRow bool
OrderedColumns []string
}
-// MapQuery type
-type MapQuery map[string]Query
+type mapQuery map[string]query
-// HealthMetric struct tracking the number of attempted vs successful connections for each connection string
-type HealthMetric struct {
- AttemptedQueries int
- SuccessfulQueries int
+// healthMetric struct tracking the number of attempted vs successful connections for each connection string
+type healthMetric struct {
+ attemptedQueries int
+ successfulQueries int
}
-const defaultServer = "Server=.;app name=telegraf;log=1;"
-
-const (
- typeAzureSQLDB = "AzureSQLDB"
- typeAzureSQLManagedInstance = "AzureSQLManagedInstance"
- typeAzureSQLPool = "AzureSQLPool"
- typeSQLServer = "SQLServer"
- typeAzureArcSQLManagedInstance = "AzureArcSQLManagedInstance"
-)
-
-const (
- healthMetricName = "sqlserver_telegraf_health"
- healthMetricInstanceTag = "sql_instance"
- healthMetricDatabaseTag = "database_name"
- healthMetricAttemptedQueries = "attempted_queries"
- healthMetricSuccessfulQueries = "successful_queries"
- healthMetricDatabaseType = "database_type"
-)
-
-// resource id for Azure SQL Database
-const sqlAzureResourceID = "https://database.windows.net/"
-
type scanner interface {
Scan(dest ...interface{}) error
}
-func (s *SQLServer) initQueries() error {
- s.queries = make(MapQuery)
- queries := s.queries
- s.Log.Infof("Config: database_type: %s , query_version:%d , azuredb: %t", s.DatabaseType, s.QueryVersion, s.AzureDB)
-
- // To prevent query definition conflicts
- // Constant definitions for type "AzureSQLDB" start with sqlAzureDB
- // Constant definitions for type "AzureSQLManagedInstance" start with sqlAzureMI
- // Constant definitions for type "AzureSQLPool" start with sqlAzurePool
- // Constant definitions for type "AzureArcSQLManagedInstance" start with sqlAzureArcMI
- // Constant definitions for type "SQLServer" start with sqlServer
- if s.DatabaseType == typeAzureSQLDB {
- queries["AzureSQLDBResourceStats"] = Query{ScriptName: "AzureSQLDBResourceStats", Script: sqlAzureDBResourceStats, ResultByRow: false}
- queries["AzureSQLDBResourceGovernance"] = Query{ScriptName: "AzureSQLDBResourceGovernance", Script: sqlAzureDBResourceGovernance, ResultByRow: false}
- queries["AzureSQLDBWaitStats"] = Query{ScriptName: "AzureSQLDBWaitStats", Script: sqlAzureDBWaitStats, ResultByRow: false}
- queries["AzureSQLDBDatabaseIO"] = Query{ScriptName: "AzureSQLDBDatabaseIO", Script: sqlAzureDBDatabaseIO, ResultByRow: false}
- queries["AzureSQLDBServerProperties"] = Query{ScriptName: "AzureSQLDBServerProperties", Script: sqlAzureDBProperties, ResultByRow: false}
- queries["AzureSQLDBOsWaitstats"] = Query{ScriptName: "AzureSQLOsWaitstats", Script: sqlAzureDBOsWaitStats, ResultByRow: false}
- queries["AzureSQLDBMemoryClerks"] = Query{ScriptName: "AzureSQLDBMemoryClerks", Script: sqlAzureDBMemoryClerks, ResultByRow: false}
- queries["AzureSQLDBPerformanceCounters"] = Query{ScriptName: "AzureSQLDBPerformanceCounters", Script: sqlAzureDBPerformanceCounters, ResultByRow: false}
- queries["AzureSQLDBRequests"] = Query{ScriptName: "AzureSQLDBRequests", Script: sqlAzureDBRequests, ResultByRow: false}
- queries["AzureSQLDBSchedulers"] = Query{ScriptName: "AzureSQLDBSchedulers", Script: sqlAzureDBSchedulers, ResultByRow: false}
- } else if s.DatabaseType == typeAzureSQLManagedInstance {
- queries["AzureSQLMIResourceStats"] = Query{ScriptName: "AzureSQLMIResourceStats", Script: sqlAzureMIResourceStats, ResultByRow: false}
- queries["AzureSQLMIResourceGovernance"] = Query{ScriptName: "AzureSQLMIResourceGovernance", Script: sqlAzureMIResourceGovernance, ResultByRow: false}
- queries["AzureSQLMIDatabaseIO"] = Query{ScriptName: "AzureSQLMIDatabaseIO", Script: sqlAzureMIDatabaseIO, ResultByRow: false}
- queries["AzureSQLMIServerProperties"] = Query{ScriptName: "AzureSQLMIServerProperties", Script: sqlAzureMIProperties, ResultByRow: false}
- queries["AzureSQLMIOsWaitstats"] = Query{ScriptName: "AzureSQLMIOsWaitstats", Script: sqlAzureMIOsWaitStats, ResultByRow: false}
- queries["AzureSQLMIMemoryClerks"] = Query{ScriptName: "AzureSQLMIMemoryClerks", Script: sqlAzureMIMemoryClerks, ResultByRow: false}
- queries["AzureSQLMIPerformanceCounters"] = Query{ScriptName: "AzureSQLMIPerformanceCounters", Script: sqlAzureMIPerformanceCounters, ResultByRow: false}
- queries["AzureSQLMIRequests"] = Query{ScriptName: "AzureSQLMIRequests", Script: sqlAzureMIRequests, ResultByRow: false}
- queries["AzureSQLMISchedulers"] = Query{ScriptName: "AzureSQLMISchedulers", Script: sqlAzureMISchedulers, ResultByRow: false}
- } else if s.DatabaseType == typeAzureSQLPool {
- queries["AzureSQLPoolResourceStats"] = Query{ScriptName: "AzureSQLPoolResourceStats", Script: sqlAzurePoolResourceStats, ResultByRow: false}
- queries["AzureSQLPoolResourceGovernance"] =
- Query{ScriptName: "AzureSQLPoolResourceGovernance", Script: sqlAzurePoolResourceGovernance, ResultByRow: false}
- queries["AzureSQLPoolDatabaseIO"] = Query{ScriptName: "AzureSQLPoolDatabaseIO", Script: sqlAzurePoolDatabaseIO, ResultByRow: false}
- queries["AzureSQLPoolOsWaitStats"] = Query{ScriptName: "AzureSQLPoolOsWaitStats", Script: sqlAzurePoolOsWaitStats, ResultByRow: false}
- queries["AzureSQLPoolMemoryClerks"] = Query{ScriptName: "AzureSQLPoolMemoryClerks", Script: sqlAzurePoolMemoryClerks, ResultByRow: false}
- queries["AzureSQLPoolPerformanceCounters"] =
- Query{ScriptName: "AzureSQLPoolPerformanceCounters", Script: sqlAzurePoolPerformanceCounters, ResultByRow: false}
- queries["AzureSQLPoolSchedulers"] = Query{ScriptName: "AzureSQLPoolSchedulers", Script: sqlAzurePoolSchedulers, ResultByRow: false}
- } else if s.DatabaseType == typeAzureArcSQLManagedInstance {
- queries["AzureArcSQLMIDatabaseIO"] = Query{ScriptName: "AzureArcSQLMIDatabaseIO", Script: sqlAzureArcMIDatabaseIO, ResultByRow: false}
- queries["AzureArcSQLMIServerProperties"] = Query{ScriptName: "AzureArcSQLMIServerProperties", Script: sqlAzureArcMIProperties, ResultByRow: false}
- queries["AzureArcSQLMIOsWaitstats"] = Query{ScriptName: "AzureArcSQLMIOsWaitstats", Script: sqlAzureArcMIOsWaitStats, ResultByRow: false}
- queries["AzureArcSQLMIMemoryClerks"] = Query{ScriptName: "AzureArcSQLMIMemoryClerks", Script: sqlAzureArcMIMemoryClerks, ResultByRow: false}
- queries["AzureArcSQLMIPerformanceCounters"] =
- Query{ScriptName: "AzureArcSQLMIPerformanceCounters", Script: sqlAzureArcMIPerformanceCounters, ResultByRow: false}
- queries["AzureArcSQLMIRequests"] = Query{ScriptName: "AzureArcSQLMIRequests", Script: sqlAzureArcMIRequests, ResultByRow: false}
- queries["AzureArcSQLMISchedulers"] = Query{ScriptName: "AzureArcSQLMISchedulers", Script: sqlAzureArcMISchedulers, ResultByRow: false}
- } else if s.DatabaseType == typeSQLServer { // These are still V2 queries and have not been refactored yet.
- queries["SQLServerPerformanceCounters"] = Query{ScriptName: "SQLServerPerformanceCounters", Script: sqlServerPerformanceCounters, ResultByRow: false}
- queries["SQLServerWaitStatsCategorized"] = Query{ScriptName: "SQLServerWaitStatsCategorized", Script: sqlServerWaitStatsCategorized, ResultByRow: false}
- queries["SQLServerDatabaseIO"] = Query{ScriptName: "SQLServerDatabaseIO", Script: sqlServerDatabaseIO, ResultByRow: false}
- queries["SQLServerProperties"] = Query{ScriptName: "SQLServerProperties", Script: sqlServerProperties, ResultByRow: false}
- queries["SQLServerMemoryClerks"] = Query{ScriptName: "SQLServerMemoryClerks", Script: sqlServerMemoryClerks, ResultByRow: false}
- queries["SQLServerSchedulers"] = Query{ScriptName: "SQLServerSchedulers", Script: sqlServerSchedulers, ResultByRow: false}
- queries["SQLServerRequests"] = Query{ScriptName: "SQLServerRequests", Script: sqlServerRequests, ResultByRow: false}
- queries["SQLServerVolumeSpace"] = Query{ScriptName: "SQLServerVolumeSpace", Script: sqlServerVolumeSpace, ResultByRow: false}
- queries["SQLServerCpu"] = Query{ScriptName: "SQLServerCpu", Script: sqlServerRingBufferCPU, ResultByRow: false}
- queries["SQLServerAvailabilityReplicaStates"] =
- Query{ScriptName: "SQLServerAvailabilityReplicaStates", Script: sqlServerAvailabilityReplicaStates, ResultByRow: false}
- queries["SQLServerDatabaseReplicaStates"] =
- Query{ScriptName: "SQLServerDatabaseReplicaStates", Script: sqlServerDatabaseReplicaStates, ResultByRow: false}
- queries["SQLServerRecentBackups"] = Query{ScriptName: "SQLServerRecentBackups", Script: sqlServerRecentBackups, ResultByRow: false}
- queries["SQLServerPersistentVersionStore"] =
- Query{ScriptName: "SQLServerPersistentVersionStore", Script: sqlServerPersistentVersionStore, ResultByRow: false}
- } else {
- // If this is an AzureDB instance, grab some extra metrics
- if s.AzureDB {
- queries["AzureDBResourceStats"] = Query{ScriptName: "AzureDBPerformanceCounters", Script: sqlAzureDBResourceStats, ResultByRow: false}
- queries["AzureDBResourceGovernance"] = Query{ScriptName: "AzureDBPerformanceCounters", Script: sqlAzureDBResourceGovernance, ResultByRow: false}
- }
- // Decide if we want to run version 1 or version 2 queries
- if s.QueryVersion == 2 {
- queries["PerformanceCounters"] = Query{ScriptName: "PerformanceCounters", Script: sqlPerformanceCountersV2, ResultByRow: true}
- queries["WaitStatsCategorized"] = Query{ScriptName: "WaitStatsCategorized", Script: sqlWaitStatsCategorizedV2, ResultByRow: false}
- queries["DatabaseIO"] = Query{ScriptName: "DatabaseIO", Script: sqlDatabaseIOV2, ResultByRow: false}
- queries["ServerProperties"] = Query{ScriptName: "ServerProperties", Script: sqlServerPropertiesV2, ResultByRow: false}
- queries["MemoryClerk"] = Query{ScriptName: "MemoryClerk", Script: sqlMemoryClerkV2, ResultByRow: false}
- queries["Schedulers"] = Query{ScriptName: "Schedulers", Script: sqlServerSchedulersV2, ResultByRow: false}
- queries["SqlRequests"] = Query{ScriptName: "SqlRequests", Script: sqlServerRequestsV2, ResultByRow: false}
- queries["VolumeSpace"] = Query{ScriptName: "VolumeSpace", Script: sqlServerVolumeSpaceV2, ResultByRow: false}
- queries["Cpu"] = Query{ScriptName: "Cpu", Script: sqlServerCPUV2, ResultByRow: false}
- } else {
- queries["PerformanceCounters"] = Query{ScriptName: "PerformanceCounters", Script: sqlPerformanceCounters, ResultByRow: true}
- queries["WaitStatsCategorized"] = Query{ScriptName: "WaitStatsCategorized", Script: sqlWaitStatsCategorized, ResultByRow: false}
- queries["CPUHistory"] = Query{ScriptName: "CPUHistory", Script: sqlCPUHistory, ResultByRow: false}
- queries["DatabaseIO"] = Query{ScriptName: "DatabaseIO", Script: sqlDatabaseIO, ResultByRow: false}
- queries["DatabaseSize"] = Query{ScriptName: "DatabaseSize", Script: sqlDatabaseSize, ResultByRow: false}
- queries["DatabaseStats"] = Query{ScriptName: "DatabaseStats", Script: sqlDatabaseStats, ResultByRow: false}
- queries["DatabaseProperties"] = Query{ScriptName: "DatabaseProperties", Script: sqlDatabaseProperties, ResultByRow: false}
- queries["MemoryClerk"] = Query{ScriptName: "MemoryClerk", Script: sqlMemoryClerk, ResultByRow: false}
- queries["VolumeSpace"] = Query{ScriptName: "VolumeSpace", Script: sqlVolumeSpace, ResultByRow: false}
- queries["PerformanceMetrics"] = Query{ScriptName: "PerformanceMetrics", Script: sqlPerformanceMetrics, ResultByRow: false}
- }
- }
-
- filterQueries, err := filter.NewIncludeExcludeFilter(s.IncludeQuery, s.ExcludeQuery)
- if err != nil {
- return err
- }
-
- for query := range queries {
- if !filterQueries.Match(query) {
- delete(queries, query)
- }
- }
-
- queryList := make([]string, 0, len(queries))
- for query := range queries {
- queryList = append(queryList, query)
- }
- s.Log.Infof("Config: Effective Queries: %#v\n", queryList)
-
- return nil
-}
-
func (*SQLServer) SampleConfig() string {
return sampleConfig
}
-// Gather collect data from SQL Server
-func (s *SQLServer) Gather(acc telegraf.Accumulator) error {
- var wg sync.WaitGroup
- var mutex sync.Mutex
- var healthMetrics = make(map[string]*HealthMetric)
-
- for i, pool := range s.pools {
- dnsSecret, err := s.Servers[i].Get()
- if err != nil {
- acc.AddError(err)
- continue
- }
- dsn := dnsSecret.String()
- dnsSecret.Destroy()
-
- for _, query := range s.queries {
- wg.Add(1)
- go func(pool *sql.DB, query Query, dsn string) {
- defer wg.Done()
- queryError := s.gatherServer(pool, query, acc, dsn)
-
- if s.HealthMetric {
- mutex.Lock()
- gatherHealth(healthMetrics, dsn, queryError)
- mutex.Unlock()
- }
-
- acc.AddError(queryError)
- }(pool, query, dsn)
- }
- }
-
- wg.Wait()
-
- if s.HealthMetric {
- s.accHealth(healthMetrics, acc)
+func (s *SQLServer) Init() error {
+ if len(s.Servers) == 0 {
+ srv := config.NewSecret([]byte(defaultServer))
+ s.Servers = append(s.Servers, &srv)
}
return nil
@@ -321,6 +164,46 @@ func (s *SQLServer) Start(acc telegraf.Accumulator) error {
return nil
}
+func (s *SQLServer) Gather(acc telegraf.Accumulator) error {
+ var wg sync.WaitGroup
+ var mutex sync.Mutex
+ var healthMetrics = make(map[string]*healthMetric)
+
+ for i, pool := range s.pools {
+ dnsSecret, err := s.Servers[i].Get()
+ if err != nil {
+ acc.AddError(err)
+ continue
+ }
+ dsn := dnsSecret.String()
+ dnsSecret.Destroy()
+
+ for _, q := range s.queries {
+ wg.Add(1)
+ go func(pool *sql.DB, q query, dsn string) {
+ defer wg.Done()
+ queryError := s.gatherServer(pool, q, acc, dsn)
+
+ if s.HealthMetric {
+ mutex.Lock()
+ gatherHealth(healthMetrics, dsn, queryError)
+ mutex.Unlock()
+ }
+
+ acc.AddError(queryError)
+ }(pool, q, dsn)
+ }
+ }
+
+ wg.Wait()
+
+ if s.HealthMetric {
+ s.accHealth(healthMetrics, acc)
+ }
+
+ return nil
+}
+
// Stop cleanup server connection pools
func (s *SQLServer) Stop() {
for _, pool := range s.pools {
@@ -328,7 +211,126 @@ func (s *SQLServer) Stop() {
}
}
-func (s *SQLServer) gatherServer(pool *sql.DB, query Query, acc telegraf.Accumulator, connectionString string) error {
+func (s *SQLServer) initQueries() error {
+ s.queries = make(mapQuery)
+ queries := s.queries
+ s.Log.Infof("Config: database_type: %s , query_version:%d , azuredb: %t", s.DatabaseType, s.QueryVersion, s.AzureDB)
+
+ // To prevent query definition conflicts
+ // Constant definitions for type "AzureSQLDB" start with sqlAzureDB
+ // Constant definitions for type "AzureSQLManagedInstance" start with sqlAzureMI
+ // Constant definitions for type "AzureSQLPool" start with sqlAzurePool
+ // Constant definitions for type "AzureArcSQLManagedInstance" start with sqlAzureArcMI
+ // Constant definitions for type "SQLServer" start with sqlServer
+ if s.DatabaseType == typeAzureSQLDB {
+ queries["AzureSQLDBResourceStats"] = query{ScriptName: "AzureSQLDBResourceStats", Script: sqlAzureDBResourceStats, ResultByRow: false}
+ queries["AzureSQLDBResourceGovernance"] = query{ScriptName: "AzureSQLDBResourceGovernance", Script: sqlAzureDBResourceGovernance, ResultByRow: false}
+ queries["AzureSQLDBWaitStats"] = query{ScriptName: "AzureSQLDBWaitStats", Script: sqlAzureDBWaitStats, ResultByRow: false}
+ queries["AzureSQLDBDatabaseIO"] = query{ScriptName: "AzureSQLDBDatabaseIO", Script: sqlAzureDBDatabaseIO, ResultByRow: false}
+ queries["AzureSQLDBServerProperties"] = query{ScriptName: "AzureSQLDBServerProperties", Script: sqlAzureDBProperties, ResultByRow: false}
+ queries["AzureSQLDBOsWaitstats"] = query{ScriptName: "AzureSQLOsWaitstats", Script: sqlAzureDBOsWaitStats, ResultByRow: false}
+ queries["AzureSQLDBMemoryClerks"] = query{ScriptName: "AzureSQLDBMemoryClerks", Script: sqlAzureDBMemoryClerks, ResultByRow: false}
+ queries["AzureSQLDBPerformanceCounters"] = query{ScriptName: "AzureSQLDBPerformanceCounters", Script: sqlAzureDBPerformanceCounters, ResultByRow: false}
+ queries["AzureSQLDBRequests"] = query{ScriptName: "AzureSQLDBRequests", Script: sqlAzureDBRequests, ResultByRow: false}
+ queries["AzureSQLDBSchedulers"] = query{ScriptName: "AzureSQLDBSchedulers", Script: sqlAzureDBSchedulers, ResultByRow: false}
+ } else if s.DatabaseType == typeAzureSQLManagedInstance {
+ queries["AzureSQLMIResourceStats"] = query{ScriptName: "AzureSQLMIResourceStats", Script: sqlAzureMIResourceStats, ResultByRow: false}
+ queries["AzureSQLMIResourceGovernance"] = query{ScriptName: "AzureSQLMIResourceGovernance", Script: sqlAzureMIResourceGovernance, ResultByRow: false}
+ queries["AzureSQLMIDatabaseIO"] = query{ScriptName: "AzureSQLMIDatabaseIO", Script: sqlAzureMIDatabaseIO, ResultByRow: false}
+ queries["AzureSQLMIServerProperties"] = query{ScriptName: "AzureSQLMIServerProperties", Script: sqlAzureMIProperties, ResultByRow: false}
+ queries["AzureSQLMIOsWaitstats"] = query{ScriptName: "AzureSQLMIOsWaitstats", Script: sqlAzureMIOsWaitStats, ResultByRow: false}
+ queries["AzureSQLMIMemoryClerks"] = query{ScriptName: "AzureSQLMIMemoryClerks", Script: sqlAzureMIMemoryClerks, ResultByRow: false}
+ queries["AzureSQLMIPerformanceCounters"] = query{ScriptName: "AzureSQLMIPerformanceCounters", Script: sqlAzureMIPerformanceCounters, ResultByRow: false}
+ queries["AzureSQLMIRequests"] = query{ScriptName: "AzureSQLMIRequests", Script: sqlAzureMIRequests, ResultByRow: false}
+ queries["AzureSQLMISchedulers"] = query{ScriptName: "AzureSQLMISchedulers", Script: sqlAzureMISchedulers, ResultByRow: false}
+ } else if s.DatabaseType == typeAzureSQLPool {
+ queries["AzureSQLPoolResourceStats"] = query{ScriptName: "AzureSQLPoolResourceStats", Script: sqlAzurePoolResourceStats, ResultByRow: false}
+ queries["AzureSQLPoolResourceGovernance"] =
+ query{ScriptName: "AzureSQLPoolResourceGovernance", Script: sqlAzurePoolResourceGovernance, ResultByRow: false}
+ queries["AzureSQLPoolDatabaseIO"] = query{ScriptName: "AzureSQLPoolDatabaseIO", Script: sqlAzurePoolDatabaseIO, ResultByRow: false}
+ queries["AzureSQLPoolOsWaitStats"] = query{ScriptName: "AzureSQLPoolOsWaitStats", Script: sqlAzurePoolOsWaitStats, ResultByRow: false}
+ queries["AzureSQLPoolMemoryClerks"] = query{ScriptName: "AzureSQLPoolMemoryClerks", Script: sqlAzurePoolMemoryClerks, ResultByRow: false}
+ queries["AzureSQLPoolPerformanceCounters"] =
+ query{ScriptName: "AzureSQLPoolPerformanceCounters", Script: sqlAzurePoolPerformanceCounters, ResultByRow: false}
+ queries["AzureSQLPoolSchedulers"] = query{ScriptName: "AzureSQLPoolSchedulers", Script: sqlAzurePoolSchedulers, ResultByRow: false}
+ } else if s.DatabaseType == typeAzureArcSQLManagedInstance {
+ queries["AzureArcSQLMIDatabaseIO"] = query{ScriptName: "AzureArcSQLMIDatabaseIO", Script: sqlAzureArcMIDatabaseIO, ResultByRow: false}
+ queries["AzureArcSQLMIServerProperties"] = query{ScriptName: "AzureArcSQLMIServerProperties", Script: sqlAzureArcMIProperties, ResultByRow: false}
+ queries["AzureArcSQLMIOsWaitstats"] = query{ScriptName: "AzureArcSQLMIOsWaitstats", Script: sqlAzureArcMIOsWaitStats, ResultByRow: false}
+ queries["AzureArcSQLMIMemoryClerks"] = query{ScriptName: "AzureArcSQLMIMemoryClerks", Script: sqlAzureArcMIMemoryClerks, ResultByRow: false}
+ queries["AzureArcSQLMIPerformanceCounters"] =
+ query{ScriptName: "AzureArcSQLMIPerformanceCounters", Script: sqlAzureArcMIPerformanceCounters, ResultByRow: false}
+ queries["AzureArcSQLMIRequests"] = query{ScriptName: "AzureArcSQLMIRequests", Script: sqlAzureArcMIRequests, ResultByRow: false}
+ queries["AzureArcSQLMISchedulers"] = query{ScriptName: "AzureArcSQLMISchedulers", Script: sqlAzureArcMISchedulers, ResultByRow: false}
+ } else if s.DatabaseType == typeSQLServer { // These are still V2 queries and have not been refactored yet.
+ queries["SQLServerPerformanceCounters"] = query{ScriptName: "SQLServerPerformanceCounters", Script: sqlServerPerformanceCounters, ResultByRow: false}
+ queries["SQLServerWaitStatsCategorized"] = query{ScriptName: "SQLServerWaitStatsCategorized", Script: sqlServerWaitStatsCategorized, ResultByRow: false}
+ queries["SQLServerDatabaseIO"] = query{ScriptName: "SQLServerDatabaseIO", Script: sqlServerDatabaseIO, ResultByRow: false}
+ queries["SQLServerProperties"] = query{ScriptName: "SQLServerProperties", Script: sqlServerProperties, ResultByRow: false}
+ queries["SQLServerMemoryClerks"] = query{ScriptName: "SQLServerMemoryClerks", Script: sqlServerMemoryClerks, ResultByRow: false}
+ queries["SQLServerSchedulers"] = query{ScriptName: "SQLServerSchedulers", Script: sqlServerSchedulers, ResultByRow: false}
+ queries["SQLServerRequests"] = query{ScriptName: "SQLServerRequests", Script: sqlServerRequests, ResultByRow: false}
+ queries["SQLServerVolumeSpace"] = query{ScriptName: "SQLServerVolumeSpace", Script: sqlServerVolumeSpace, ResultByRow: false}
+ queries["SQLServerCpu"] = query{ScriptName: "SQLServerCpu", Script: sqlServerRingBufferCPU, ResultByRow: false}
+ queries["SQLServerAvailabilityReplicaStates"] =
+ query{ScriptName: "SQLServerAvailabilityReplicaStates", Script: sqlServerAvailabilityReplicaStates, ResultByRow: false}
+ queries["SQLServerDatabaseReplicaStates"] =
+ query{ScriptName: "SQLServerDatabaseReplicaStates", Script: sqlServerDatabaseReplicaStates, ResultByRow: false}
+ queries["SQLServerRecentBackups"] = query{ScriptName: "SQLServerRecentBackups", Script: sqlServerRecentBackups, ResultByRow: false}
+ queries["SQLServerPersistentVersionStore"] =
+ query{ScriptName: "SQLServerPersistentVersionStore", Script: sqlServerPersistentVersionStore, ResultByRow: false}
+ } else {
+ // If this is an AzureDB instance, grab some extra metrics
+ if s.AzureDB {
+ queries["AzureDBResourceStats"] = query{ScriptName: "AzureDBPerformanceCounters", Script: sqlAzureDBResourceStats, ResultByRow: false}
+ queries["AzureDBResourceGovernance"] = query{ScriptName: "AzureDBPerformanceCounters", Script: sqlAzureDBResourceGovernance, ResultByRow: false}
+ }
+ // Decide if we want to run version 1 or version 2 queries
+ if s.QueryVersion == 2 {
+ queries["PerformanceCounters"] = query{ScriptName: "PerformanceCounters", Script: sqlPerformanceCountersV2, ResultByRow: true}
+ queries["WaitStatsCategorized"] = query{ScriptName: "WaitStatsCategorized", Script: sqlWaitStatsCategorizedV2, ResultByRow: false}
+ queries["DatabaseIO"] = query{ScriptName: "DatabaseIO", Script: sqlDatabaseIOV2, ResultByRow: false}
+ queries["ServerProperties"] = query{ScriptName: "ServerProperties", Script: sqlServerPropertiesV2, ResultByRow: false}
+ queries["MemoryClerk"] = query{ScriptName: "MemoryClerk", Script: sqlMemoryClerkV2, ResultByRow: false}
+ queries["Schedulers"] = query{ScriptName: "Schedulers", Script: sqlServerSchedulersV2, ResultByRow: false}
+ queries["SqlRequests"] = query{ScriptName: "SqlRequests", Script: sqlServerRequestsV2, ResultByRow: false}
+ queries["VolumeSpace"] = query{ScriptName: "VolumeSpace", Script: sqlServerVolumeSpaceV2, ResultByRow: false}
+ queries["Cpu"] = query{ScriptName: "Cpu", Script: sqlServerCPUV2, ResultByRow: false}
+ } else {
+ queries["PerformanceCounters"] = query{ScriptName: "PerformanceCounters", Script: sqlPerformanceCounters, ResultByRow: true}
+ queries["WaitStatsCategorized"] = query{ScriptName: "WaitStatsCategorized", Script: sqlWaitStatsCategorized, ResultByRow: false}
+ queries["CPUHistory"] = query{ScriptName: "CPUHistory", Script: sqlCPUHistory, ResultByRow: false}
+ queries["DatabaseIO"] = query{ScriptName: "DatabaseIO", Script: sqlDatabaseIO, ResultByRow: false}
+ queries["DatabaseSize"] = query{ScriptName: "DatabaseSize", Script: sqlDatabaseSize, ResultByRow: false}
+ queries["DatabaseStats"] = query{ScriptName: "DatabaseStats", Script: sqlDatabaseStats, ResultByRow: false}
+ queries["DatabaseProperties"] = query{ScriptName: "DatabaseProperties", Script: sqlDatabaseProperties, ResultByRow: false}
+ queries["MemoryClerk"] = query{ScriptName: "MemoryClerk", Script: sqlMemoryClerk, ResultByRow: false}
+ queries["VolumeSpace"] = query{ScriptName: "VolumeSpace", Script: sqlVolumeSpace, ResultByRow: false}
+ queries["PerformanceMetrics"] = query{ScriptName: "PerformanceMetrics", Script: sqlPerformanceMetrics, ResultByRow: false}
+ }
+ }
+
+ filterQueries, err := filter.NewIncludeExcludeFilter(s.IncludeQuery, s.ExcludeQuery)
+ if err != nil {
+ return err
+ }
+
+ for query := range queries {
+ if !filterQueries.Match(query) {
+ delete(queries, query)
+ }
+ }
+
+ queryList := make([]string, 0, len(queries))
+ for query := range queries {
+ queryList = append(queryList, query)
+ }
+ s.Log.Infof("Config: Effective Queries: %#v\n", queryList)
+
+ return nil
+}
+
+func (s *SQLServer) gatherServer(pool *sql.DB, query query, acc telegraf.Accumulator, connectionString string) error {
// execute query
ctx := context.Background()
// Use the query timeout if any
@@ -368,7 +370,7 @@ func (s *SQLServer) gatherServer(pool *sql.DB, query Query, acc telegraf.Accumul
return rows.Err()
}
-func (s *SQLServer) accRow(query Query, acc telegraf.Accumulator, row scanner) error {
+func (s *SQLServer) accRow(query query, acc telegraf.Accumulator, row scanner) error {
var fields = make(map[string]interface{})
// store the column name with its *interface{}
@@ -425,25 +427,25 @@ func (s *SQLServer) accRow(query Query, acc telegraf.Accumulator, row scanner) e
}
// gatherHealth stores info about any query errors in the healthMetrics map
-func gatherHealth(healthMetrics map[string]*HealthMetric, serv string, queryError error) {
+func gatherHealth(healthMetrics map[string]*healthMetric, serv string, queryError error) {
if healthMetrics[serv] == nil {
- healthMetrics[serv] = &HealthMetric{}
+ healthMetrics[serv] = &healthMetric{}
}
- healthMetrics[serv].AttemptedQueries++
+ healthMetrics[serv].attemptedQueries++
if queryError == nil {
- healthMetrics[serv].SuccessfulQueries++
+ healthMetrics[serv].successfulQueries++
}
}
// accHealth accumulates the query health data contained within the healthMetrics map
-func (s *SQLServer) accHealth(healthMetrics map[string]*HealthMetric, acc telegraf.Accumulator) {
+func (s *SQLServer) accHealth(healthMetrics map[string]*healthMetric, acc telegraf.Accumulator) {
for connectionString, connectionStats := range healthMetrics {
sqlInstance, databaseName := getConnectionIdentifiers(connectionString)
tags := map[string]string{healthMetricInstanceTag: sqlInstance, healthMetricDatabaseTag: databaseName}
fields := map[string]interface{}{
- healthMetricAttemptedQueries: connectionStats.AttemptedQueries,
- healthMetricSuccessfulQueries: connectionStats.SuccessfulQueries,
+ healthMetricAttemptedQueries: connectionStats.attemptedQueries,
+ healthMetricSuccessfulQueries: connectionStats.successfulQueries,
healthMetricDatabaseType: s.getDatabaseTypeToLog(),
}
@@ -464,15 +466,6 @@ func (s *SQLServer) getDatabaseTypeToLog() string {
return logname
}
-func (s *SQLServer) Init() error {
- if len(s.Servers) == 0 {
- srv := config.NewSecret([]byte(defaultServer))
- s.Servers = append(s.Servers, &srv)
- }
-
- return nil
-}
-
// Get Token Provider by loading cached token or refreshed token
func (s *SQLServer) getTokenProvider() (func() (string, error), error) {
var tokenString string
diff --git a/plugins/inputs/sqlserver/sqlserver_test.go b/plugins/inputs/sqlserver/sqlserver_test.go
index c1c1d591f..9a66838fd 100644
--- a/plugins/inputs/sqlserver/sqlserver_test.go
+++ b/plugins/inputs/sqlserver/sqlserver_test.go
@@ -47,17 +47,17 @@ func TestSqlServer_QueriesInclusionExclusion(t *testing.T) {
func TestSqlServer_ParseMetrics(t *testing.T) {
var acc testutil.Accumulator
- queries := make(MapQuery)
- queries["PerformanceCounters"] = Query{ScriptName: "PerformanceCounters", Script: mockPerformanceCounters, ResultByRow: true}
- queries["WaitStatsCategorized"] = Query{ScriptName: "WaitStatsCategorized", Script: mockWaitStatsCategorized, ResultByRow: false}
- queries["CPUHistory"] = Query{ScriptName: "CPUHistory", Script: mockCPUHistory, ResultByRow: false}
- queries["DatabaseIO"] = Query{ScriptName: "DatabaseIO", Script: mockDatabaseIO, ResultByRow: false}
- queries["DatabaseSize"] = Query{ScriptName: "DatabaseSize", Script: mockDatabaseSize, ResultByRow: false}
- queries["DatabaseStats"] = Query{ScriptName: "DatabaseStats", Script: mockDatabaseStats, ResultByRow: false}
- queries["DatabaseProperties"] = Query{ScriptName: "DatabaseProperties", Script: mockDatabaseProperties, ResultByRow: false}
- queries["VolumeSpace"] = Query{ScriptName: "VolumeSpace", Script: mockVolumeSpace, ResultByRow: false}
- queries["MemoryClerk"] = Query{ScriptName: "MemoryClerk", Script: mockMemoryClerk, ResultByRow: false}
- queries["PerformanceMetrics"] = Query{ScriptName: "PerformanceMetrics", Script: mockPerformanceMetrics, ResultByRow: false}
+ queries := make(mapQuery)
+ queries["PerformanceCounters"] = query{ScriptName: "PerformanceCounters", Script: mockPerformanceCounters, ResultByRow: true}
+ queries["WaitStatsCategorized"] = query{ScriptName: "WaitStatsCategorized", Script: mockWaitStatsCategorized, ResultByRow: false}
+ queries["CPUHistory"] = query{ScriptName: "CPUHistory", Script: mockCPUHistory, ResultByRow: false}
+ queries["DatabaseIO"] = query{ScriptName: "DatabaseIO", Script: mockDatabaseIO, ResultByRow: false}
+ queries["DatabaseSize"] = query{ScriptName: "DatabaseSize", Script: mockDatabaseSize, ResultByRow: false}
+ queries["DatabaseStats"] = query{ScriptName: "DatabaseStats", Script: mockDatabaseStats, ResultByRow: false}
+ queries["DatabaseProperties"] = query{ScriptName: "DatabaseProperties", Script: mockDatabaseProperties, ResultByRow: false}
+ queries["VolumeSpace"] = query{ScriptName: "VolumeSpace", Script: mockVolumeSpace, ResultByRow: false}
+ queries["MemoryClerk"] = query{ScriptName: "MemoryClerk", Script: mockMemoryClerk, ResultByRow: false}
+ queries["PerformanceMetrics"] = query{ScriptName: "PerformanceMetrics", Script: mockPerformanceMetrics, ResultByRow: false}
var headers, mock, row []string
var tags = make(map[string]string)
diff --git a/plugins/inputs/stackdriver/stackdriver.go b/plugins/inputs/stackdriver/stackdriver.go
index f432a4e88..8b4e66422 100644
--- a/plugins/inputs/stackdriver/stackdriver.go
+++ b/plugins/inputs/stackdriver/stackdriver.go
@@ -31,19 +31,18 @@ import (
//go:embed sample.conf
var sampleConfig string
-const (
- defaultRateLimit = 14
-)
-
var (
defaultCacheTTL = config.Duration(1 * time.Hour)
defaultWindow = config.Duration(1 * time.Minute)
defaultDelay = config.Duration(5 * time.Minute)
)
+const (
+ defaultRateLimit = 14
+)
+
type (
- // stackdriver is the Google Stackdriver config info.
- stackdriver struct {
+ Stackdriver struct {
Project string `toml:"project"`
RateLimit int `toml:"rate_limit"`
Window config.Duration `toml:"window"`
@@ -55,7 +54,7 @@ type (
DistributionAggregationAligners []string `toml:"distribution_aggregation_aligners"`
Filter *listTimeSeriesFilter `toml:"filter"`
- Log telegraf.Logger
+ Log telegraf.Logger `toml:"-"`
client metricClient
timeSeriesConfCache *timeSeriesConfCache
@@ -106,9 +105,9 @@ type (
// metricClient is convenient for testing
metricClient interface {
- ListMetricDescriptors(ctx context.Context, req *monitoringpb.ListMetricDescriptorsRequest) (<-chan *metricpb.MetricDescriptor, error)
- ListTimeSeries(ctx context.Context, req *monitoringpb.ListTimeSeriesRequest) (<-chan *monitoringpb.TimeSeries, error)
- Close() error
+ listMetricDescriptors(ctx context.Context, req *monitoringpb.ListMetricDescriptorsRequest) (<-chan *metricpb.MetricDescriptor, error)
+ listTimeSeries(ctx context.Context, req *monitoringpb.ListTimeSeriesRequest) (<-chan *monitoringpb.TimeSeries, error)
+ close() error
}
lockedSeriesGrouper struct {
@@ -117,87 +116,11 @@ type (
}
)
-func (g *lockedSeriesGrouper) Add(
- measurement string,
- tags map[string]string,
- tm time.Time,
- field string,
- fieldValue interface{},
-) {
- g.Lock()
- defer g.Unlock()
- g.SeriesGrouper.Add(measurement, tags, tm, field, fieldValue)
-}
-
-// ListMetricDescriptors implements metricClient interface
-func (smc *stackdriverMetricClient) ListMetricDescriptors(
- ctx context.Context,
- req *monitoringpb.ListMetricDescriptorsRequest,
-) (<-chan *metricpb.MetricDescriptor, error) {
- mdChan := make(chan *metricpb.MetricDescriptor, 1000)
-
- go func() {
- smc.log.Debugf("List metric descriptor request filter: %s", req.Filter)
- defer close(mdChan)
-
- // Iterate over metric descriptors and send them to buffered channel
- mdResp := smc.conn.ListMetricDescriptors(ctx, req)
- smc.listMetricDescriptorsCalls.Incr(1)
- for {
- mdDesc, mdErr := mdResp.Next()
- if mdErr != nil {
- if !errors.Is(mdErr, iterator.Done) {
- smc.log.Errorf("Failed iterating metric descriptor responses: %q: %v", req.String(), mdErr)
- }
- break
- }
- mdChan <- mdDesc
- }
- }()
-
- return mdChan, nil
-}
-
-// ListTimeSeries implements metricClient interface
-func (smc *stackdriverMetricClient) ListTimeSeries(
- ctx context.Context,
- req *monitoringpb.ListTimeSeriesRequest,
-) (<-chan *monitoringpb.TimeSeries, error) {
- tsChan := make(chan *monitoringpb.TimeSeries, 1000)
-
- go func() {
- smc.log.Debugf("List time series request filter: %s", req.Filter)
- defer close(tsChan)
-
- // Iterate over timeseries and send them to buffered channel
- tsResp := smc.conn.ListTimeSeries(ctx, req)
- smc.listTimeSeriesCalls.Incr(1)
- for {
- tsDesc, tsErr := tsResp.Next()
- if tsErr != nil {
- if !errors.Is(tsErr, iterator.Done) {
- smc.log.Errorf("Failed iterating time series responses: %q: %v", req.String(), tsErr)
- }
- break
- }
- tsChan <- tsDesc
- }
- }()
-
- return tsChan, nil
-}
-
-// Close implements metricClient interface
-func (smc *stackdriverMetricClient) Close() error {
- return smc.conn.Close()
-}
-
-func (*stackdriver) SampleConfig() string {
+func (*Stackdriver) SampleConfig() string {
return sampleConfig
}
-// Gather implements telegraf.Input interface
-func (s *stackdriver) Gather(acc telegraf.Accumulator) error {
+func (s *Stackdriver) Gather(acc telegraf.Accumulator) error {
ctx := context.Background()
if s.RateLimit == 0 {
@@ -212,7 +135,7 @@ func (s *stackdriver) Gather(acc telegraf.Accumulator) error {
start, end := s.updateWindow(s.prevEnd)
s.prevEnd = end
- tsConfs, err := s.generatetimeSeriesConfs(ctx, start, end)
+ tsConfs, err := s.generateTimeSeriesConfs(ctx, start, end)
if err != nil {
return err
}
@@ -242,8 +165,34 @@ func (s *stackdriver) Gather(acc telegraf.Accumulator) error {
return nil
}
+func (s *Stackdriver) initializeStackdriverClient(ctx context.Context) error {
+ if s.client == nil {
+ client, err := monitoring.NewMetricClient(ctx)
+ if err != nil {
+ return fmt.Errorf("failed to create stackdriver monitoring client: %w", err)
+ }
+
+ tags := map[string]string{
+ "project_id": s.Project,
+ }
+ listMetricDescriptorsCalls := selfstat.Register(
+ "stackdriver", "list_metric_descriptors_calls", tags)
+ listTimeSeriesCalls := selfstat.Register(
+ "stackdriver", "list_timeseries_calls", tags)
+
+ s.client = &stackdriverMetricClient{
+ log: s.Log,
+ conn: client,
+ listMetricDescriptorsCalls: listMetricDescriptorsCalls,
+ listTimeSeriesCalls: listTimeSeriesCalls,
+ }
+ }
+
+ return nil
+}
+
// Returns the start and end time for the next collection.
-func (s *stackdriver) updateWindow(prevEnd time.Time) (time.Time, time.Time) {
+func (s *Stackdriver) updateWindow(prevEnd time.Time) (time.Time, time.Time) {
var start time.Time
if time.Duration(s.Window) != 0 {
start = time.Now().Add(-time.Duration(s.Delay)).Add(-time.Duration(s.Window))
@@ -256,8 +205,90 @@ func (s *stackdriver) updateWindow(prevEnd time.Time) (time.Time, time.Time) {
return start, end
}
+// Generate a list of timeSeriesConfig structs by making a listMetricDescriptors
+// API request and filtering the result against our configuration.
+func (s *Stackdriver) generateTimeSeriesConfs(ctx context.Context, startTime, endTime time.Time) ([]*timeSeriesConf, error) {
+ if s.timeSeriesConfCache != nil && s.timeSeriesConfCache.isValid() {
+ // Update interval for timeseries requests in timeseries cache
+ interval := &monitoringpb.TimeInterval{
+ EndTime: ×tamppb.Timestamp{Seconds: endTime.Unix()},
+ StartTime: ×tamppb.Timestamp{Seconds: startTime.Unix()},
+ }
+ for _, timeSeriesConf := range s.timeSeriesConfCache.TimeSeriesConfs {
+ timeSeriesConf.listTimeSeriesRequest.Interval = interval
+ }
+ return s.timeSeriesConfCache.TimeSeriesConfs, nil
+ }
+
+ ret := make([]*timeSeriesConf, 0)
+ req := &monitoringpb.ListMetricDescriptorsRequest{
+ Name: "projects/" + s.Project,
+ }
+
+ filters := s.newListMetricDescriptorsFilters()
+ if len(filters) == 0 {
+ filters = []string{""}
+ }
+
+ for _, filter := range filters {
+ // Add filter for list metric descriptors if
+ // includeMetricTypePrefixes is specified,
+ // this is more efficient than iterating over
+ // all metric descriptors
+ req.Filter = filter
+ mdRespChan, err := s.client.listMetricDescriptors(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ for metricDescriptor := range mdRespChan {
+ metricType := metricDescriptor.Type
+ valueType := metricDescriptor.ValueType
+
+ if filter == "" && !s.includeMetricType(metricType) {
+ continue
+ }
+
+ if valueType == metricpb.MetricDescriptor_DISTRIBUTION {
+ if s.GatherRawDistributionBuckets {
+ tsConf := s.newTimeSeriesConf(metricType, startTime, endTime)
+ ret = append(ret, tsConf)
+ }
+ for _, alignerStr := range s.DistributionAggregationAligners {
+ tsConf := s.newTimeSeriesConf(metricType, startTime, endTime)
+ tsConf.initForAggregate(alignerStr)
+ ret = append(ret, tsConf)
+ }
+ } else {
+ ret = append(ret, s.newTimeSeriesConf(metricType, startTime, endTime))
+ }
+ }
+ }
+
+ s.timeSeriesConfCache = &timeSeriesConfCache{
+ TimeSeriesConfs: ret,
+ Generated: time.Now(),
+ TTL: time.Duration(s.CacheTTL),
+ }
+
+ return ret, nil
+}
+
+// Generates filter for list metric descriptors request
+func (s *Stackdriver) newListMetricDescriptorsFilters() []string {
+ if len(s.MetricTypePrefixInclude) == 0 {
+ return nil
+ }
+
+ metricTypeFilters := make([]string, 0, len(s.MetricTypePrefixInclude))
+ for _, metricTypePrefix := range s.MetricTypePrefixInclude {
+ metricTypeFilters = append(metricTypeFilters, fmt.Sprintf(`metric.type = starts_with(%q)`, metricTypePrefix))
+ }
+ return metricTypeFilters
+}
+
// Generate filter string for ListTimeSeriesRequest
-func (s *stackdriver) newListTimeSeriesFilter(metricType string) string {
+func (s *Stackdriver) newListTimeSeriesFilter(metricType string) string {
functions := []string{
"starts_with",
"ends_with",
@@ -345,11 +376,8 @@ func (s *stackdriver) newListTimeSeriesFilter(metricType string) string {
return filterString
}
-// Create and initialize a timeSeriesConf for a given GCP metric type with
-// defaults taken from the gcp_stackdriver plugin configuration.
-func (s *stackdriver) newTimeSeriesConf(
- metricType string, startTime, endTime time.Time,
-) *timeSeriesConf {
+// Create and initialize a timeSeriesConf for a given GCP metric type with defaults taken from the gcp_stackdriver plugin configuration.
+func (s *Stackdriver) newTimeSeriesConf(metricType string, startTime, endTime time.Time) *timeSeriesConf {
filter := s.newListTimeSeriesFilter(metricType)
interval := &monitoringpb.TimeInterval{
EndTime: ×tamppb.Timestamp{Seconds: endTime.Unix()},
@@ -376,83 +404,10 @@ func (s *stackdriver) newTimeSeriesConf(
return cfg
}
-// Change this configuration to query an aggregate by specifying an "aligner".
-// In GCP monitoring, "aligning" is aggregation performed *within* a time
-// series, to distill a pile of data points down to a single data point for
-// some given time period (here, we specify 60s as our time period). This is
-// especially useful for scraping GCP "distribution" metric types, whose raw
-// data amounts to a ~60 bucket histogram, which is fairly hard to query and
-// visualize in the TICK stack.
-func (t *timeSeriesConf) initForAggregate(alignerStr string) {
- // Check if alignerStr is valid
- alignerInt, isValid := monitoringpb.Aggregation_Aligner_value[alignerStr]
- if !isValid {
- alignerStr = monitoringpb.Aggregation_Aligner_name[alignerInt]
- }
- aligner := monitoringpb.Aggregation_Aligner(alignerInt)
- agg := &monitoringpb.Aggregation{
- AlignmentPeriod: &durationpb.Duration{Seconds: 60},
- PerSeriesAligner: aligner,
- }
- t.fieldKey = t.fieldKey + "_" + strings.ToLower(alignerStr)
- t.listTimeSeriesRequest.Aggregation = agg
-}
-
-// IsValid checks timeseriesconf cache validity
-func (c *timeSeriesConfCache) IsValid() bool {
- return c.TimeSeriesConfs != nil && time.Since(c.Generated) < c.TTL
-}
-
-func (s *stackdriver) initializeStackdriverClient(ctx context.Context) error {
- if s.client == nil {
- client, err := monitoring.NewMetricClient(ctx)
- if err != nil {
- return fmt.Errorf("failed to create stackdriver monitoring client: %w", err)
- }
-
- tags := map[string]string{
- "project_id": s.Project,
- }
- listMetricDescriptorsCalls := selfstat.Register(
- "stackdriver", "list_metric_descriptors_calls", tags)
- listTimeSeriesCalls := selfstat.Register(
- "stackdriver", "list_timeseries_calls", tags)
-
- s.client = &stackdriverMetricClient{
- log: s.Log,
- conn: client,
- listMetricDescriptorsCalls: listMetricDescriptorsCalls,
- listTimeSeriesCalls: listTimeSeriesCalls,
- }
- }
-
- return nil
-}
-
-func includeExcludeHelper(key string, includes, excludes []string) bool {
- if len(includes) > 0 {
- for _, includeStr := range includes {
- if strings.HasPrefix(key, includeStr) {
- return true
- }
- }
- return false
- }
- if len(excludes) > 0 {
- for _, excludeStr := range excludes {
- if strings.HasPrefix(key, excludeStr) {
- return false
- }
- }
- return true
- }
- return true
-}
-
// Test whether a particular GCP metric type should be scraped by this plugin
// by checking the plugin name against the configuration's
// "includeMetricTypePrefixes" and "excludeMetricTypePrefixes"
-func (s *stackdriver) includeMetricType(metricType string) bool {
+func (s *Stackdriver) includeMetricType(metricType string) bool {
k := metricType
inc := s.MetricTypePrefixInclude
exc := s.MetricTypePrefixExclude
@@ -460,98 +415,11 @@ func (s *stackdriver) includeMetricType(metricType string) bool {
return includeExcludeHelper(k, inc, exc)
}
-// Generates filter for list metric descriptors request
-func (s *stackdriver) newListMetricDescriptorsFilters() []string {
- if len(s.MetricTypePrefixInclude) == 0 {
- return nil
- }
-
- metricTypeFilters := make([]string, 0, len(s.MetricTypePrefixInclude))
- for _, metricTypePrefix := range s.MetricTypePrefixInclude {
- metricTypeFilters = append(metricTypeFilters, fmt.Sprintf(`metric.type = starts_with(%q)`, metricTypePrefix))
- }
- return metricTypeFilters
-}
-
-// Generate a list of timeSeriesConfig structs by making a ListMetricDescriptors
-// API request and filtering the result against our configuration.
-func (s *stackdriver) generatetimeSeriesConfs(
- ctx context.Context, startTime, endTime time.Time,
-) ([]*timeSeriesConf, error) {
- if s.timeSeriesConfCache != nil && s.timeSeriesConfCache.IsValid() {
- // Update interval for timeseries requests in timeseries cache
- interval := &monitoringpb.TimeInterval{
- EndTime: ×tamppb.Timestamp{Seconds: endTime.Unix()},
- StartTime: ×tamppb.Timestamp{Seconds: startTime.Unix()},
- }
- for _, timeSeriesConf := range s.timeSeriesConfCache.TimeSeriesConfs {
- timeSeriesConf.listTimeSeriesRequest.Interval = interval
- }
- return s.timeSeriesConfCache.TimeSeriesConfs, nil
- }
-
- ret := make([]*timeSeriesConf, 0)
- req := &monitoringpb.ListMetricDescriptorsRequest{
- Name: "projects/" + s.Project,
- }
-
- filters := s.newListMetricDescriptorsFilters()
- if len(filters) == 0 {
- filters = []string{""}
- }
-
- for _, filter := range filters {
- // Add filter for list metric descriptors if
- // includeMetricTypePrefixes is specified,
- // this is more efficient than iterating over
- // all metric descriptors
- req.Filter = filter
- mdRespChan, err := s.client.ListMetricDescriptors(ctx, req)
- if err != nil {
- return nil, err
- }
-
- for metricDescriptor := range mdRespChan {
- metricType := metricDescriptor.Type
- valueType := metricDescriptor.ValueType
-
- if filter == "" && !s.includeMetricType(metricType) {
- continue
- }
-
- if valueType == metricpb.MetricDescriptor_DISTRIBUTION {
- if s.GatherRawDistributionBuckets {
- tsConf := s.newTimeSeriesConf(metricType, startTime, endTime)
- ret = append(ret, tsConf)
- }
- for _, alignerStr := range s.DistributionAggregationAligners {
- tsConf := s.newTimeSeriesConf(metricType, startTime, endTime)
- tsConf.initForAggregate(alignerStr)
- ret = append(ret, tsConf)
- }
- } else {
- ret = append(ret, s.newTimeSeriesConf(metricType, startTime, endTime))
- }
- }
- }
-
- s.timeSeriesConfCache = &timeSeriesConfCache{
- TimeSeriesConfs: ret,
- Generated: time.Now(),
- TTL: time.Duration(s.CacheTTL),
- }
-
- return ret, nil
-}
-
-// Do the work to gather an individual time series. Runs inside a
-// timeseries-specific goroutine.
-func (s *stackdriver) gatherTimeSeries(
- ctx context.Context, grouper *lockedSeriesGrouper, tsConf *timeSeriesConf,
-) error {
+// Do the work to gather an individual time series. Runs inside a timeseries-specific goroutine.
+func (s *Stackdriver) gatherTimeSeries(ctx context.Context, grouper *lockedSeriesGrouper, tsConf *timeSeriesConf) error {
tsReq := tsConf.listTimeSeriesRequest
- tsRespChan, err := s.client.ListTimeSeries(ctx, tsReq)
+ tsRespChan, err := s.client.listTimeSeries(ctx, tsReq)
if err != nil {
return err
}
@@ -599,75 +467,10 @@ func (s *stackdriver) gatherTimeSeries(
return nil
}
-type buckets interface {
- Amount() int32
- UpperBound(i int32) float64
-}
-
-type LinearBuckets struct {
- *distributionpb.Distribution_BucketOptions_Linear
-}
-
-func (l *LinearBuckets) Amount() int32 {
- return l.NumFiniteBuckets + 2
-}
-
-func (l *LinearBuckets) UpperBound(i int32) float64 {
- return l.Offset + (l.Width * float64(i))
-}
-
-type ExponentialBuckets struct {
- *distributionpb.Distribution_BucketOptions_Exponential
-}
-
-func (e *ExponentialBuckets) Amount() int32 {
- return e.NumFiniteBuckets + 2
-}
-
-func (e *ExponentialBuckets) UpperBound(i int32) float64 {
- width := math.Pow(e.GrowthFactor, float64(i))
- return e.Scale * width
-}
-
-type ExplicitBuckets struct {
- *distributionpb.Distribution_BucketOptions_Explicit
-}
-
-func (e *ExplicitBuckets) Amount() int32 {
- return int32(len(e.Bounds)) + 1
-}
-
-func (e *ExplicitBuckets) UpperBound(i int32) float64 {
- return e.Bounds[i]
-}
-
-func NewBucket(dist *distributionpb.Distribution) (buckets, error) {
- linearBuckets := dist.BucketOptions.GetLinearBuckets()
- if linearBuckets != nil {
- var l LinearBuckets
- l.Distribution_BucketOptions_Linear = linearBuckets
- return &l, nil
- }
-
- exponentialBuckets := dist.BucketOptions.GetExponentialBuckets()
- if exponentialBuckets != nil {
- var e ExponentialBuckets
- e.Distribution_BucketOptions_Exponential = exponentialBuckets
- return &e, nil
- }
-
- explicitBuckets := dist.BucketOptions.GetExplicitBuckets()
- if explicitBuckets != nil {
- var e ExplicitBuckets
- e.Distribution_BucketOptions_Explicit = explicitBuckets
- return &e, nil
- }
-
- return nil, errors.New("no buckets available")
-}
-
// addDistribution adds metrics from a distribution value type.
-func addDistribution(dist *distributionpb.Distribution, tags map[string]string, ts time.Time, grouper *lockedSeriesGrouper, tsConf *timeSeriesConf) error {
+func addDistribution(dist *distributionpb.Distribution, tags map[string]string, ts time.Time,
+ grouper *lockedSeriesGrouper, tsConf *timeSeriesConf,
+) error {
field := tsConf.fieldKey
name := tsConf.measurement
@@ -680,11 +483,11 @@ func addDistribution(dist *distributionpb.Distribution, tags map[string]string,
grouper.Add(name, tags, ts, field+"_range_max", dist.Range.Max)
}
- bucket, err := NewBucket(dist)
+ bucket, err := newBucket(dist)
if err != nil {
return err
}
- numBuckets := bucket.Amount()
+ numBuckets := bucket.amount()
var i int32
var count int64
@@ -694,7 +497,7 @@ func addDistribution(dist *distributionpb.Distribution, tags map[string]string,
if i == numBuckets-1 {
tags["lt"] = "+Inf"
} else {
- upperBound := bucket.UpperBound(i)
+ upperBound := bucket.upperBound(i)
tags["lt"] = strconv.FormatFloat(upperBound, 'f', -1, 64)
}
@@ -709,9 +512,192 @@ func addDistribution(dist *distributionpb.Distribution, tags map[string]string,
return nil
}
+// Add adds a field key and value to the series.
+func (g *lockedSeriesGrouper) Add(measurement string, tags map[string]string, tm time.Time, field string, fieldValue interface{}) {
+ g.Lock()
+ defer g.Unlock()
+ g.SeriesGrouper.Add(measurement, tags, tm, field, fieldValue)
+}
+
+// listMetricDescriptors implements metricClient interface
+func (smc *stackdriverMetricClient) listMetricDescriptors(ctx context.Context,
+ req *monitoringpb.ListMetricDescriptorsRequest,
+) (<-chan *metricpb.MetricDescriptor, error) {
+ mdChan := make(chan *metricpb.MetricDescriptor, 1000)
+
+ go func() {
+ smc.log.Debugf("List metric descriptor request filter: %s", req.Filter)
+ defer close(mdChan)
+
+ // Iterate over metric descriptors and send them to buffered channel
+ mdResp := smc.conn.ListMetricDescriptors(ctx, req)
+ smc.listMetricDescriptorsCalls.Incr(1)
+ for {
+ mdDesc, mdErr := mdResp.Next()
+ if mdErr != nil {
+ if !errors.Is(mdErr, iterator.Done) {
+ smc.log.Errorf("Failed iterating metric descriptor responses: %q: %v", req.String(), mdErr)
+ }
+ break
+ }
+ mdChan <- mdDesc
+ }
+ }()
+
+ return mdChan, nil
+}
+
+// listTimeSeries implements metricClient interface
+func (smc *stackdriverMetricClient) listTimeSeries(
+ ctx context.Context,
+ req *monitoringpb.ListTimeSeriesRequest,
+) (<-chan *monitoringpb.TimeSeries, error) {
+ tsChan := make(chan *monitoringpb.TimeSeries, 1000)
+
+ go func() {
+ smc.log.Debugf("List time series request filter: %s", req.Filter)
+ defer close(tsChan)
+
+ // Iterate over timeseries and send them to buffered channel
+ tsResp := smc.conn.ListTimeSeries(ctx, req)
+ smc.listTimeSeriesCalls.Incr(1)
+ for {
+ tsDesc, tsErr := tsResp.Next()
+ if tsErr != nil {
+ if !errors.Is(tsErr, iterator.Done) {
+ smc.log.Errorf("Failed iterating time series responses: %q: %v", req.String(), tsErr)
+ }
+ break
+ }
+ tsChan <- tsDesc
+ }
+ }()
+
+ return tsChan, nil
+}
+
+// close implements metricClient interface
+func (smc *stackdriverMetricClient) close() error {
+ return smc.conn.Close()
+}
+
+// Change this configuration to query an aggregate by specifying an "aligner".
+// In GCP monitoring, "aligning" is aggregation performed *within* a time
+// series, to distill a pile of data points down to a single data point for
+// some given time period (here, we specify 60s as our time period). This is
+// especially useful for scraping GCP "distribution" metric types, whose raw
+// data amounts to a ~60 bucket histogram, which is fairly hard to query and
+// visualize in the TICK stack.
+func (t *timeSeriesConf) initForAggregate(alignerStr string) {
+ // Check if alignerStr is valid
+ alignerInt, isValid := monitoringpb.Aggregation_Aligner_value[alignerStr]
+ if !isValid {
+ alignerStr = monitoringpb.Aggregation_Aligner_name[alignerInt]
+ }
+ aligner := monitoringpb.Aggregation_Aligner(alignerInt)
+ agg := &monitoringpb.Aggregation{
+ AlignmentPeriod: &durationpb.Duration{Seconds: 60},
+ PerSeriesAligner: aligner,
+ }
+ t.fieldKey = t.fieldKey + "_" + strings.ToLower(alignerStr)
+ t.listTimeSeriesRequest.Aggregation = agg
+}
+
+// isValid checks timeseriesconf cache validity
+func (c *timeSeriesConfCache) isValid() bool {
+ return c.TimeSeriesConfs != nil && time.Since(c.Generated) < c.TTL
+}
+
+func includeExcludeHelper(key string, includes, excludes []string) bool {
+ if len(includes) > 0 {
+ for _, includeStr := range includes {
+ if strings.HasPrefix(key, includeStr) {
+ return true
+ }
+ }
+ return false
+ }
+ if len(excludes) > 0 {
+ for _, excludeStr := range excludes {
+ if strings.HasPrefix(key, excludeStr) {
+ return false
+ }
+ }
+ return true
+ }
+ return true
+}
+
+type buckets interface {
+ amount() int32
+ upperBound(i int32) float64
+}
+
+type linearBuckets struct {
+ *distributionpb.Distribution_BucketOptions_Linear
+}
+
+func (l *linearBuckets) amount() int32 {
+ return l.NumFiniteBuckets + 2
+}
+
+func (l *linearBuckets) upperBound(i int32) float64 {
+ return l.Offset + (l.Width * float64(i))
+}
+
+type exponentialBuckets struct {
+ *distributionpb.Distribution_BucketOptions_Exponential
+}
+
+func (e *exponentialBuckets) amount() int32 {
+ return e.NumFiniteBuckets + 2
+}
+
+func (e *exponentialBuckets) upperBound(i int32) float64 {
+ width := math.Pow(e.GrowthFactor, float64(i))
+ return e.Scale * width
+}
+
+type explicitBuckets struct {
+ *distributionpb.Distribution_BucketOptions_Explicit
+}
+
+func (e *explicitBuckets) amount() int32 {
+ return int32(len(e.Bounds)) + 1
+}
+
+func (e *explicitBuckets) upperBound(i int32) float64 {
+ return e.Bounds[i]
+}
+
+func newBucket(dist *distributionpb.Distribution) (buckets, error) {
+ linBuckets := dist.BucketOptions.GetLinearBuckets()
+ if linBuckets != nil {
+ var l linearBuckets
+ l.Distribution_BucketOptions_Linear = linBuckets
+ return &l, nil
+ }
+
+ expoBuckets := dist.BucketOptions.GetExponentialBuckets()
+ if expoBuckets != nil {
+ var e exponentialBuckets
+ e.Distribution_BucketOptions_Exponential = expoBuckets
+ return &e, nil
+ }
+
+ explBuckets := dist.BucketOptions.GetExplicitBuckets()
+ if explBuckets != nil {
+ var e explicitBuckets
+ e.Distribution_BucketOptions_Explicit = explBuckets
+ return &e, nil
+ }
+
+ return nil, errors.New("no buckets available")
+}
+
func init() {
inputs.Add("stackdriver", func() telegraf.Input {
- return &stackdriver{
+ return &Stackdriver{
CacheTTL: defaultCacheTTL,
RateLimit: defaultRateLimit,
Delay: defaultDelay,
diff --git a/plugins/inputs/stackdriver/stackdriver_test.go b/plugins/inputs/stackdriver/stackdriver_test.go
index 216d2b323..be1442035 100644
--- a/plugins/inputs/stackdriver/stackdriver_test.go
+++ b/plugins/inputs/stackdriver/stackdriver_test.go
@@ -18,52 +18,52 @@ import (
"github.com/influxdata/telegraf/testutil"
)
-type Call struct {
+type call struct {
name string
args []interface{}
}
-type MockStackdriverClient struct {
- ListMetricDescriptorsF func() (<-chan *metricpb.MetricDescriptor, error)
- ListTimeSeriesF func() (<-chan *monitoringpb.TimeSeries, error)
- CloseF func() error
+type mockStackdriverClient struct {
+ listMetricDescriptorsF func() (<-chan *metricpb.MetricDescriptor, error)
+ listTimeSeriesF func() (<-chan *monitoringpb.TimeSeries, error)
+ closeF func() error
- calls []*Call
+ calls []*call
sync.Mutex
}
-func (m *MockStackdriverClient) ListMetricDescriptors(
+func (m *mockStackdriverClient) listMetricDescriptors(
ctx context.Context,
req *monitoringpb.ListMetricDescriptorsRequest,
) (<-chan *metricpb.MetricDescriptor, error) {
- call := &Call{name: "ListMetricDescriptors", args: []interface{}{ctx, req}}
+ call := &call{name: "listMetricDescriptors", args: []interface{}{ctx, req}}
m.Lock()
m.calls = append(m.calls, call)
m.Unlock()
- return m.ListMetricDescriptorsF()
+ return m.listMetricDescriptorsF()
}
-func (m *MockStackdriverClient) ListTimeSeries(
+func (m *mockStackdriverClient) listTimeSeries(
ctx context.Context,
req *monitoringpb.ListTimeSeriesRequest,
) (<-chan *monitoringpb.TimeSeries, error) {
- call := &Call{name: "ListTimeSeries", args: []interface{}{ctx, req}}
+ call := &call{name: "listTimeSeries", args: []interface{}{ctx, req}}
m.Lock()
m.calls = append(m.calls, call)
m.Unlock()
- return m.ListTimeSeriesF()
+ return m.listTimeSeriesF()
}
-func (m *MockStackdriverClient) Close() error {
- call := &Call{name: "Close", args: make([]interface{}, 0)}
+func (m *mockStackdriverClient) close() error {
+ call := &call{name: "close", args: make([]interface{}, 0)}
m.Lock()
m.calls = append(m.calls, call)
m.Unlock()
- return m.CloseF()
+ return m.closeF()
}
func TestInitAndRegister(t *testing.T) {
- expected := &stackdriver{
+ expected := &Stackdriver{
CacheTTL: defaultCacheTTL,
RateLimit: defaultRateLimit,
Delay: defaultDelay,
@@ -731,15 +731,15 @@ func TestGather(t *testing.T) {
return ch, nil
}
- s := &stackdriver{
+ s := &Stackdriver{
Log: testutil.Logger{},
Project: "test",
RateLimit: 10,
GatherRawDistributionBuckets: true,
- client: &MockStackdriverClient{
- ListMetricDescriptorsF: listMetricDescriptorsF,
- ListTimeSeriesF: listTimeSeriesF,
- CloseF: func() error {
+ client: &mockStackdriverClient{
+ listMetricDescriptorsF: listMetricDescriptorsF,
+ listTimeSeriesF: listTimeSeriesF,
+ closeF: func() error {
return nil
},
},
@@ -839,25 +839,25 @@ func TestGatherAlign(t *testing.T) {
for listCall, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var acc testutil.Accumulator
- client := &MockStackdriverClient{
- ListMetricDescriptorsF: func() (<-chan *metricpb.MetricDescriptor, error) {
+ client := &mockStackdriverClient{
+ listMetricDescriptorsF: func() (<-chan *metricpb.MetricDescriptor, error) {
ch := make(chan *metricpb.MetricDescriptor, 1)
ch <- tt.descriptor
close(ch)
return ch, nil
},
- ListTimeSeriesF: func() (<-chan *monitoringpb.TimeSeries, error) {
+ listTimeSeriesF: func() (<-chan *monitoringpb.TimeSeries, error) {
ch := make(chan *monitoringpb.TimeSeries, 1)
ch <- tt.timeseries[listCall]
close(ch)
return ch, nil
},
- CloseF: func() error {
+ closeF: func() error {
return nil
},
}
- s := &stackdriver{
+ s := &Stackdriver{
Log: testutil.Logger{},
Project: "test",
RateLimit: 10,
@@ -891,13 +891,13 @@ func TestListMetricDescriptorFilter(t *testing.T) {
now := time.Now().Round(time.Second)
tests := []struct {
name string
- stackdriver *stackdriver
+ stackdriver *Stackdriver
descriptor *metricpb.MetricDescriptor
calls []call
}{
{
name: "simple",
- stackdriver: &stackdriver{
+ stackdriver: &Stackdriver{
Project: "test",
MetricTypePrefixInclude: []string{"telegraf/cpu/usage"},
RateLimit: 1,
@@ -908,17 +908,17 @@ func TestListMetricDescriptorFilter(t *testing.T) {
},
calls: []call{
{
- name: "ListMetricDescriptors",
+ name: "listMetricDescriptors",
filter: `metric.type = starts_with("telegraf/cpu/usage")`,
}, {
- name: "ListTimeSeries",
+ name: "listTimeSeries",
filter: `metric.type = "telegraf/cpu/usage"`,
},
},
},
{
name: "single resource labels string",
- stackdriver: &stackdriver{
+ stackdriver: &Stackdriver{
Project: "test",
MetricTypePrefixInclude: []string{"telegraf/cpu/usage"},
Filter: &listTimeSeriesFilter{
@@ -937,17 +937,17 @@ func TestListMetricDescriptorFilter(t *testing.T) {
},
calls: []call{
{
- name: "ListMetricDescriptors",
+ name: "listMetricDescriptors",
filter: `metric.type = starts_with("telegraf/cpu/usage")`,
}, {
- name: "ListTimeSeries",
+ name: "listTimeSeries",
filter: `metric.type = "telegraf/cpu/usage" AND resource.labels.instance_name = "localhost"`,
},
},
},
{
name: "single resource labels function",
- stackdriver: &stackdriver{
+ stackdriver: &Stackdriver{
Project: "test",
MetricTypePrefixInclude: []string{"telegraf/cpu/usage"},
Filter: &listTimeSeriesFilter{
@@ -966,17 +966,17 @@ func TestListMetricDescriptorFilter(t *testing.T) {
},
calls: []call{
{
- name: "ListMetricDescriptors",
+ name: "listMetricDescriptors",
filter: `metric.type = starts_with("telegraf/cpu/usage")`,
}, {
- name: "ListTimeSeries",
+ name: "listTimeSeries",
filter: `metric.type = "telegraf/cpu/usage" AND resource.labels.instance_name = starts_with("localhost")`,
},
},
},
{
name: "multiple resource labels",
- stackdriver: &stackdriver{
+ stackdriver: &Stackdriver{
Project: "test",
MetricTypePrefixInclude: []string{"telegraf/cpu/usage"},
Filter: &listTimeSeriesFilter{
@@ -999,17 +999,17 @@ func TestListMetricDescriptorFilter(t *testing.T) {
},
calls: []call{
{
- name: "ListMetricDescriptors",
+ name: "listMetricDescriptors",
filter: `metric.type = starts_with("telegraf/cpu/usage")`,
}, {
- name: "ListTimeSeries",
+ name: "listTimeSeries",
filter: `metric.type = "telegraf/cpu/usage" AND (resource.labels.instance_name = "localhost" OR resource.labels.zone = starts_with("us-"))`,
},
},
},
{
name: "single metric label string",
- stackdriver: &stackdriver{
+ stackdriver: &Stackdriver{
Project: "test",
MetricTypePrefixInclude: []string{"telegraf/cpu/usage"},
Filter: &listTimeSeriesFilter{
@@ -1028,17 +1028,17 @@ func TestListMetricDescriptorFilter(t *testing.T) {
},
calls: []call{
{
- name: "ListMetricDescriptors",
+ name: "listMetricDescriptors",
filter: `metric.type = starts_with("telegraf/cpu/usage")`,
}, {
- name: "ListTimeSeries",
+ name: "listTimeSeries",
filter: `metric.type = "telegraf/cpu/usage" AND metric.labels.resource_type = "instance"`,
},
},
},
{
name: "single metric label function",
- stackdriver: &stackdriver{
+ stackdriver: &Stackdriver{
Project: "test",
MetricTypePrefixInclude: []string{"telegraf/cpu/usage"},
Filter: &listTimeSeriesFilter{
@@ -1057,17 +1057,17 @@ func TestListMetricDescriptorFilter(t *testing.T) {
},
calls: []call{
{
- name: "ListMetricDescriptors",
+ name: "listMetricDescriptors",
filter: `metric.type = starts_with("telegraf/cpu/usage")`,
}, {
- name: "ListTimeSeries",
+ name: "listTimeSeries",
filter: `metric.type = "telegraf/cpu/usage" AND metric.labels.resource_id = starts_with("abc-")`,
},
},
},
{
name: "multiple metric labels",
- stackdriver: &stackdriver{
+ stackdriver: &Stackdriver{
Project: "test",
MetricTypePrefixInclude: []string{"telegraf/cpu/usage"},
Filter: &listTimeSeriesFilter{
@@ -1090,10 +1090,10 @@ func TestListMetricDescriptorFilter(t *testing.T) {
},
calls: []call{
{
- name: "ListMetricDescriptors",
+ name: "listMetricDescriptors",
filter: `metric.type = starts_with("telegraf/cpu/usage")`,
}, {
- name: "ListTimeSeries",
+ name: "listTimeSeries",
filter: `metric.type = "telegraf/cpu/usage" AND ` +
`(metric.labels.resource_type = "instance" OR metric.labels.resource_id = starts_with("abc-"))`,
},
@@ -1101,7 +1101,7 @@ func TestListMetricDescriptorFilter(t *testing.T) {
},
{
name: "all labels filters",
- stackdriver: &stackdriver{
+ stackdriver: &Stackdriver{
Project: "test",
MetricTypePrefixInclude: []string{"telegraf/cpu/usage"},
Filter: &listTimeSeriesFilter{
@@ -1154,10 +1154,10 @@ func TestListMetricDescriptorFilter(t *testing.T) {
},
calls: []call{
{
- name: "ListMetricDescriptors",
+ name: "listMetricDescriptors",
filter: `metric.type = starts_with("telegraf/cpu/usage")`,
}, {
- name: "ListTimeSeries",
+ name: "listTimeSeries",
filter: `metric.type = "telegraf/cpu/usage" AND ` +
`(resource.labels.instance_name = "localhost" OR resource.labels.zone = starts_with("us-")) AND ` +
`(metric.labels.resource_type = "instance" OR metric.labels.resource_id = starts_with("abc-")) AND ` +
@@ -1170,14 +1170,14 @@ func TestListMetricDescriptorFilter(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var acc testutil.Accumulator
- client := &MockStackdriverClient{
- ListMetricDescriptorsF: func() (<-chan *metricpb.MetricDescriptor, error) {
+ client := &mockStackdriverClient{
+ listMetricDescriptorsF: func() (<-chan *metricpb.MetricDescriptor, error) {
ch := make(chan *metricpb.MetricDescriptor, 1)
ch <- tt.descriptor
close(ch)
return ch, nil
},
- ListTimeSeriesF: func() (<-chan *monitoringpb.TimeSeries, error) {
+ listTimeSeriesF: func() (<-chan *monitoringpb.TimeSeries, error) {
ch := make(chan *monitoringpb.TimeSeries, 1)
ch <- createTimeSeries(
&monitoringpb.Point{
@@ -1197,7 +1197,7 @@ func TestListMetricDescriptorFilter(t *testing.T) {
close(ch)
return ch, nil
},
- CloseF: func() error {
+ closeF: func() error {
return nil
},
}
diff --git a/plugins/inputs/statsd/datadog_test.go b/plugins/inputs/statsd/datadog_test.go
index aaa046f38..265c6ac43 100644
--- a/plugins/inputs/statsd/datadog_test.go
+++ b/plugins/inputs/statsd/datadog_test.go
@@ -73,7 +73,7 @@ func TestEventGather(t *testing.T) {
},
}
acc := &testutil.Accumulator{}
- s := NewTestStatsd()
+ s := newTestStatsd()
require.NoError(t, s.Start(acc))
defer s.Stop()
@@ -380,7 +380,7 @@ func TestEvents(t *testing.T) {
},
},
}
- s := NewTestStatsd()
+ s := newTestStatsd()
acc := &testutil.Accumulator{}
require.NoError(t, s.Start(acc))
defer s.Stop()
@@ -408,7 +408,7 @@ func TestEvents(t *testing.T) {
func TestEventError(t *testing.T) {
now := time.Now()
- s := NewTestStatsd()
+ s := newTestStatsd()
acc := &testutil.Accumulator{}
require.NoError(t, s.Start(acc))
defer s.Stop()
diff --git a/plugins/inputs/statsd/running_stats.go b/plugins/inputs/statsd/running_stats.go
index 1098754aa..de9c6546c 100644
--- a/plugins/inputs/statsd/running_stats.go
+++ b/plugins/inputs/statsd/running_stats.go
@@ -9,57 +9,57 @@ import (
const defaultPercentileLimit = 1000
const defaultMedianLimit = 1000
-// RunningStats calculates a running mean, variance, standard deviation,
+// runningStats calculates a running mean, variance, standard deviation,
// lower bound, upper bound, count, and can calculate estimated percentiles.
// It is based on the incremental algorithm described here:
//
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
-type RunningStats struct {
+type runningStats struct {
k float64
n int64
ex float64
ex2 float64
// Array used to calculate estimated percentiles
- // We will store a maximum of PercLimit values, at which point we will start
+ // We will store a maximum of percLimit values, at which point we will start
// randomly replacing old values, hence it is an estimated percentile.
perc []float64
- PercLimit int
+ percLimit int
- sum float64
+ totalSum float64
- lower float64
- upper float64
+ lowerBound float64
+ upperBound float64
// cache if we have sorted the list so that we never re-sort a sorted list,
// which can have very bad performance.
- SortedPerc bool
+ sortedPerc bool
// Array used to calculate estimated median values
- // We will store a maximum of MedLimit values, at which point we will start
+ // We will store a maximum of medLimit values, at which point we will start
// slicing old values
med []float64
- MedLimit int
- MedInsertIndex int
+ medLimit int
+ medInsertIndex int
}
-func (rs *RunningStats) AddValue(v float64) {
+func (rs *runningStats) addValue(v float64) {
// Whenever a value is added, the list is no longer sorted.
- rs.SortedPerc = false
+ rs.sortedPerc = false
if rs.n == 0 {
rs.k = v
- rs.upper = v
- rs.lower = v
- if rs.PercLimit == 0 {
- rs.PercLimit = defaultPercentileLimit
+ rs.upperBound = v
+ rs.lowerBound = v
+ if rs.percLimit == 0 {
+ rs.percLimit = defaultPercentileLimit
}
- if rs.MedLimit == 0 {
- rs.MedLimit = defaultMedianLimit
- rs.MedInsertIndex = 0
+ if rs.medLimit == 0 {
+ rs.medLimit = defaultMedianLimit
+ rs.medInsertIndex = 0
}
- rs.perc = make([]float64, 0, rs.PercLimit)
- rs.med = make([]float64, 0, rs.MedLimit)
+ rs.perc = make([]float64, 0, rs.percLimit)
+ rs.med = make([]float64, 0, rs.medLimit)
}
// These are used for the running mean and variance
@@ -68,36 +68,36 @@ func (rs *RunningStats) AddValue(v float64) {
rs.ex2 += (v - rs.k) * (v - rs.k)
// add to running sum
- rs.sum += v
+ rs.totalSum += v
// track upper and lower bounds
- if v > rs.upper {
- rs.upper = v
- } else if v < rs.lower {
- rs.lower = v
+ if v > rs.upperBound {
+ rs.upperBound = v
+ } else if v < rs.lowerBound {
+ rs.lowerBound = v
}
- if len(rs.perc) < rs.PercLimit {
+ if len(rs.perc) < rs.percLimit {
rs.perc = append(rs.perc, v)
} else {
// Reached limit, choose random index to overwrite in the percentile array
rs.perc[rand.Intn(len(rs.perc))] = v //nolint:gosec // G404: not security critical
}
- if len(rs.med) < rs.MedLimit {
+ if len(rs.med) < rs.medLimit {
rs.med = append(rs.med, v)
} else {
// Reached limit, start over
- rs.med[rs.MedInsertIndex] = v
+ rs.med[rs.medInsertIndex] = v
}
- rs.MedInsertIndex = (rs.MedInsertIndex + 1) % rs.MedLimit
+ rs.medInsertIndex = (rs.medInsertIndex + 1) % rs.medLimit
}
-func (rs *RunningStats) Mean() float64 {
+func (rs *runningStats) mean() float64 {
return rs.k + rs.ex/float64(rs.n)
}
-func (rs *RunningStats) Median() float64 {
+func (rs *runningStats) median() float64 {
// Need to sort for median, but keep temporal order
var values []float64
values = append(values, rs.med...)
@@ -111,38 +111,38 @@ func (rs *RunningStats) Median() float64 {
return values[count/2]
}
-func (rs *RunningStats) Variance() float64 {
+func (rs *runningStats) variance() float64 {
return (rs.ex2 - (rs.ex*rs.ex)/float64(rs.n)) / float64(rs.n)
}
-func (rs *RunningStats) Stddev() float64 {
- return math.Sqrt(rs.Variance())
+func (rs *runningStats) stddev() float64 {
+ return math.Sqrt(rs.variance())
}
-func (rs *RunningStats) Sum() float64 {
- return rs.sum
+func (rs *runningStats) sum() float64 {
+ return rs.totalSum
}
-func (rs *RunningStats) Upper() float64 {
- return rs.upper
+func (rs *runningStats) upper() float64 {
+ return rs.upperBound
}
-func (rs *RunningStats) Lower() float64 {
- return rs.lower
+func (rs *runningStats) lower() float64 {
+ return rs.lowerBound
}
-func (rs *RunningStats) Count() int64 {
+func (rs *runningStats) count() int64 {
return rs.n
}
-func (rs *RunningStats) Percentile(n float64) float64 {
+func (rs *runningStats) percentile(n float64) float64 {
if n > 100 {
n = 100
}
- if !rs.SortedPerc {
+ if !rs.sortedPerc {
sort.Float64s(rs.perc)
- rs.SortedPerc = true
+ rs.sortedPerc = true
}
i := float64(len(rs.perc)) * n / float64(100)
diff --git a/plugins/inputs/statsd/running_stats_test.go b/plugins/inputs/statsd/running_stats_test.go
index 267f9e156..04411195b 100644
--- a/plugins/inputs/statsd/running_stats_test.go
+++ b/plugins/inputs/statsd/running_stats_test.go
@@ -7,163 +7,163 @@ import (
// Test that a single metric is handled correctly
func TestRunningStats_Single(t *testing.T) {
- rs := RunningStats{}
+ rs := runningStats{}
values := []float64{10.1}
for _, v := range values {
- rs.AddValue(v)
+ rs.addValue(v)
}
- if rs.Mean() != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Mean())
+ if rs.mean() != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.mean())
}
- if rs.Median() != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Median())
+ if rs.median() != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.median())
}
- if rs.Upper() != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Upper())
+ if rs.upper() != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.upper())
}
- if rs.Lower() != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Lower())
+ if rs.lower() != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.lower())
}
- if rs.Percentile(100) != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(100))
+ if rs.percentile(100) != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.percentile(100))
}
- if rs.Percentile(99.95) != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(99.95))
+ if rs.percentile(99.95) != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.percentile(99.95))
}
- if rs.Percentile(90) != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(90))
+ if rs.percentile(90) != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.percentile(90))
}
- if rs.Percentile(50) != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(50))
+ if rs.percentile(50) != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.percentile(50))
}
- if rs.Percentile(0) != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(0))
+ if rs.percentile(0) != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.percentile(0))
}
- if rs.Count() != 1 {
- t.Errorf("Expected %v, got %v", 1, rs.Count())
+ if rs.count() != 1 {
+ t.Errorf("Expected %v, got %v", 1, rs.count())
}
- if rs.Variance() != 0 {
- t.Errorf("Expected %v, got %v", 0, rs.Variance())
+ if rs.variance() != 0 {
+ t.Errorf("Expected %v, got %v", 0, rs.variance())
}
- if rs.Stddev() != 0 {
- t.Errorf("Expected %v, got %v", 0, rs.Stddev())
+ if rs.stddev() != 0 {
+ t.Errorf("Expected %v, got %v", 0, rs.stddev())
}
}
// Test that duplicate values are handled correctly
func TestRunningStats_Duplicate(t *testing.T) {
- rs := RunningStats{}
+ rs := runningStats{}
values := []float64{10.1, 10.1, 10.1, 10.1}
for _, v := range values {
- rs.AddValue(v)
+ rs.addValue(v)
}
- if rs.Mean() != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Mean())
+ if rs.mean() != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.mean())
}
- if rs.Median() != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Median())
+ if rs.median() != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.median())
}
- if rs.Upper() != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Upper())
+ if rs.upper() != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.upper())
}
- if rs.Lower() != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Lower())
+ if rs.lower() != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.lower())
}
- if rs.Percentile(100) != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(100))
+ if rs.percentile(100) != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.percentile(100))
}
- if rs.Percentile(99.95) != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(99.95))
+ if rs.percentile(99.95) != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.percentile(99.95))
}
- if rs.Percentile(90) != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(90))
+ if rs.percentile(90) != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.percentile(90))
}
- if rs.Percentile(50) != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(50))
+ if rs.percentile(50) != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.percentile(50))
}
- if rs.Percentile(0) != 10.1 {
- t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(0))
+ if rs.percentile(0) != 10.1 {
+ t.Errorf("Expected %v, got %v", 10.1, rs.percentile(0))
}
- if rs.Count() != 4 {
- t.Errorf("Expected %v, got %v", 4, rs.Count())
+ if rs.count() != 4 {
+ t.Errorf("Expected %v, got %v", 4, rs.count())
}
- if rs.Variance() != 0 {
- t.Errorf("Expected %v, got %v", 0, rs.Variance())
+ if rs.variance() != 0 {
+ t.Errorf("Expected %v, got %v", 0, rs.variance())
}
- if rs.Stddev() != 0 {
- t.Errorf("Expected %v, got %v", 0, rs.Stddev())
+ if rs.stddev() != 0 {
+ t.Errorf("Expected %v, got %v", 0, rs.stddev())
}
}
// Test a list of sample values, returns all correct values
func TestRunningStats(t *testing.T) {
- rs := RunningStats{}
+ rs := runningStats{}
values := []float64{10, 20, 10, 30, 20, 11, 12, 32, 45, 9, 5, 5, 5, 10, 23, 8}
for _, v := range values {
- rs.AddValue(v)
+ rs.addValue(v)
}
- if rs.Mean() != 15.9375 {
- t.Errorf("Expected %v, got %v", 15.9375, rs.Mean())
+ if rs.mean() != 15.9375 {
+ t.Errorf("Expected %v, got %v", 15.9375, rs.mean())
}
- if rs.Median() != 10.5 {
- t.Errorf("Expected %v, got %v", 10.5, rs.Median())
+ if rs.median() != 10.5 {
+ t.Errorf("Expected %v, got %v", 10.5, rs.median())
}
- if rs.Upper() != 45 {
- t.Errorf("Expected %v, got %v", 45, rs.Upper())
+ if rs.upper() != 45 {
+ t.Errorf("Expected %v, got %v", 45, rs.upper())
}
- if rs.Lower() != 5 {
- t.Errorf("Expected %v, got %v", 5, rs.Lower())
+ if rs.lower() != 5 {
+ t.Errorf("Expected %v, got %v", 5, rs.lower())
}
- if rs.Percentile(100) != 45 {
- t.Errorf("Expected %v, got %v", 45, rs.Percentile(100))
+ if rs.percentile(100) != 45 {
+ t.Errorf("Expected %v, got %v", 45, rs.percentile(100))
}
- if rs.Percentile(99.98) != 45 {
- t.Errorf("Expected %v, got %v", 45, rs.Percentile(99.98))
+ if rs.percentile(99.98) != 45 {
+ t.Errorf("Expected %v, got %v", 45, rs.percentile(99.98))
}
- if rs.Percentile(90) != 32 {
- t.Errorf("Expected %v, got %v", 32, rs.Percentile(90))
+ if rs.percentile(90) != 32 {
+ t.Errorf("Expected %v, got %v", 32, rs.percentile(90))
}
- if rs.Percentile(50.1) != 11 {
- t.Errorf("Expected %v, got %v", 11, rs.Percentile(50.1))
+ if rs.percentile(50.1) != 11 {
+ t.Errorf("Expected %v, got %v", 11, rs.percentile(50.1))
}
- if rs.Percentile(50) != 11 {
- t.Errorf("Expected %v, got %v", 11, rs.Percentile(50))
+ if rs.percentile(50) != 11 {
+ t.Errorf("Expected %v, got %v", 11, rs.percentile(50))
}
- if rs.Percentile(49.9) != 10 {
- t.Errorf("Expected %v, got %v", 10, rs.Percentile(49.9))
+ if rs.percentile(49.9) != 10 {
+ t.Errorf("Expected %v, got %v", 10, rs.percentile(49.9))
}
- if rs.Percentile(0) != 5 {
- t.Errorf("Expected %v, got %v", 5, rs.Percentile(0))
+ if rs.percentile(0) != 5 {
+ t.Errorf("Expected %v, got %v", 5, rs.percentile(0))
}
- if rs.Count() != 16 {
- t.Errorf("Expected %v, got %v", 4, rs.Count())
+ if rs.count() != 16 {
+ t.Errorf("Expected %v, got %v", 4, rs.count())
}
- if !fuzzyEqual(rs.Variance(), 124.93359, .00001) {
- t.Errorf("Expected %v, got %v", 124.93359, rs.Variance())
+ if !fuzzyEqual(rs.variance(), 124.93359, .00001) {
+ t.Errorf("Expected %v, got %v", 124.93359, rs.variance())
}
- if !fuzzyEqual(rs.Stddev(), 11.17736, .00001) {
- t.Errorf("Expected %v, got %v", 11.17736, rs.Stddev())
+ if !fuzzyEqual(rs.stddev(), 11.17736, .00001) {
+ t.Errorf("Expected %v, got %v", 11.17736, rs.stddev())
}
}
// Test that the percentile limit is respected.
func TestRunningStats_PercentileLimit(t *testing.T) {
- rs := RunningStats{}
- rs.PercLimit = 10
+ rs := runningStats{}
+ rs.percLimit = 10
values := []float64{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
for _, v := range values {
- rs.AddValue(v)
+ rs.addValue(v)
}
- if rs.Count() != 11 {
- t.Errorf("Expected %v, got %v", 11, rs.Count())
+ if rs.count() != 11 {
+ t.Errorf("Expected %v, got %v", 11, rs.count())
}
if len(rs.perc) != 10 {
t.Errorf("Expected %v, got %v", 10, len(rs.perc))
@@ -174,23 +174,23 @@ func fuzzyEqual(a, b, epsilon float64) bool {
return math.Abs(a-b) <= epsilon
}
-// Test that the median limit is respected and MedInsertIndex is properly incrementing index.
+// Test that the median limit is respected and medInsertIndex is properly incrementing index.
func TestRunningStats_MedianLimitIndex(t *testing.T) {
- rs := RunningStats{}
- rs.MedLimit = 10
+ rs := runningStats{}
+ rs.medLimit = 10
values := []float64{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
for _, v := range values {
- rs.AddValue(v)
+ rs.addValue(v)
}
- if rs.Count() != 11 {
- t.Errorf("Expected %v, got %v", 11, rs.Count())
+ if rs.count() != 11 {
+ t.Errorf("Expected %v, got %v", 11, rs.count())
}
if len(rs.med) != 10 {
t.Errorf("Expected %v, got %v", 10, len(rs.med))
}
- if rs.MedInsertIndex != 1 {
- t.Errorf("Expected %v, got %v", 0, rs.MedInsertIndex)
+ if rs.medInsertIndex != 1 {
+ t.Errorf("Expected %v, got %v", 0, rs.medInsertIndex)
}
}
diff --git a/plugins/inputs/statsd/statsd.go b/plugins/inputs/statsd/statsd.go
index 1aee72160..92be52852 100644
--- a/plugins/inputs/statsd/statsd.go
+++ b/plugins/inputs/statsd/statsd.go
@@ -26,35 +26,19 @@ import (
//go:embed sample.conf
var sampleConfig string
+var errParsing = errors.New("error parsing statsd line")
+
const (
- // UDPMaxPacketSize is the UDP packet limit, see
+ // udpMaxPacketSize is the UDP packet limit, see
// https://en.wikipedia.org/wiki/User_Datagram_Protocol#Packet_structure
- UDPMaxPacketSize int = 64 * 1024
-
- defaultFieldName = "value"
-
- defaultProtocol = "udp"
+ udpMaxPacketSize int = 64 * 1024
+ defaultFieldName = "value"
+ defaultProtocol = "udp"
defaultSeparator = "_"
defaultAllowPendingMessage = 10000
)
-var errParsing = errors.New("error parsing statsd line")
-
-// Number will get parsed as an int or float depending on what is passed
-type Number float64
-
-func (n *Number) UnmarshalTOML(b []byte) error {
- value, err := strconv.ParseFloat(string(b), 64)
- if err != nil {
- return err
- }
-
- *n = Number(value)
- return nil
-}
-
-// Statsd allows the importing of statsd and dogstatsd data.
type Statsd struct {
// Protocol used on listener - udp or tcp
Protocol string `toml:"protocol"`
@@ -69,7 +53,7 @@ type Statsd struct {
// Percentiles specifies the percentiles that will be calculated for timing
// and histogram stats.
- Percentiles []Number `toml:"percentiles"`
+ Percentiles []number `toml:"percentiles"`
PercentileLimit int `toml:"percentile_limit"`
DeleteGauges bool `toml:"delete_gauges"`
DeleteCounters bool `toml:"delete_counters"`
@@ -171,6 +155,19 @@ type Statsd struct {
lastGatherTime time.Time
}
+// number will get parsed as an int or float depending on what is passed
+type number float64
+
+func (n *number) UnmarshalTOML(b []byte) error {
+ value, err := strconv.ParseFloat(string(b), 64)
+ if err != nil {
+ return err
+ }
+
+ *n = number(value)
+ return nil
+}
+
type input struct {
*bytes.Buffer
time.Time
@@ -215,7 +212,7 @@ type cachedcounter struct {
type cachedtimings struct {
name string
- fields map[string]RunningStats
+ fields map[string]runningStats
tags map[string]string
expiresAt time.Time
}
@@ -230,110 +227,6 @@ func (*Statsd) SampleConfig() string {
return sampleConfig
}
-func (s *Statsd) Gather(acc telegraf.Accumulator) error {
- s.Lock()
- defer s.Unlock()
- now := time.Now()
-
- for _, m := range s.distributions {
- fields := map[string]interface{}{
- defaultFieldName: m.value,
- }
- if s.EnableAggregationTemporality {
- fields["start_time"] = s.lastGatherTime.Format(time.RFC3339)
- }
- acc.AddFields(m.name, fields, m.tags, now)
- }
- s.distributions = make([]cacheddistributions, 0)
-
- for _, m := range s.timings {
- // Defining a template to parse field names for timers allows us to split
- // out multiple fields per timer. In this case we prefix each stat with the
- // field name and store these all in a single measurement.
- fields := make(map[string]interface{})
- for fieldName, stats := range m.fields {
- var prefix string
- if fieldName != defaultFieldName {
- prefix = fieldName + "_"
- }
- fields[prefix+"mean"] = stats.Mean()
- fields[prefix+"median"] = stats.Median()
- fields[prefix+"stddev"] = stats.Stddev()
- fields[prefix+"sum"] = stats.Sum()
- fields[prefix+"upper"] = stats.Upper()
- fields[prefix+"lower"] = stats.Lower()
- if s.FloatTimings {
- fields[prefix+"count"] = float64(stats.Count())
- } else {
- fields[prefix+"count"] = stats.Count()
- }
- for _, percentile := range s.Percentiles {
- name := fmt.Sprintf("%s%v_percentile", prefix, percentile)
- fields[name] = stats.Percentile(float64(percentile))
- }
- }
- if s.EnableAggregationTemporality {
- fields["start_time"] = s.lastGatherTime.Format(time.RFC3339)
- }
-
- acc.AddFields(m.name, fields, m.tags, now)
- }
- if s.DeleteTimings {
- s.timings = make(map[string]cachedtimings)
- }
-
- for _, m := range s.gauges {
- if s.EnableAggregationTemporality && m.fields != nil {
- m.fields["start_time"] = s.lastGatherTime.Format(time.RFC3339)
- }
-
- acc.AddGauge(m.name, m.fields, m.tags, now)
- }
- if s.DeleteGauges {
- s.gauges = make(map[string]cachedgauge)
- }
-
- for _, m := range s.counters {
- if s.EnableAggregationTemporality && m.fields != nil {
- m.fields["start_time"] = s.lastGatherTime.Format(time.RFC3339)
- }
-
- if s.FloatCounters {
- for key := range m.fields {
- m.fields[key] = float64(m.fields[key].(int64))
- }
- }
- acc.AddCounter(m.name, m.fields, m.tags, now)
- }
- if s.DeleteCounters {
- s.counters = make(map[string]cachedcounter)
- }
-
- for _, m := range s.sets {
- fields := make(map[string]interface{})
- for field, set := range m.fields {
- if s.FloatSets {
- fields[field] = float64(len(set))
- } else {
- fields[field] = int64(len(set))
- }
- }
- if s.EnableAggregationTemporality {
- fields["start_time"] = s.lastGatherTime.Format(time.RFC3339)
- }
-
- acc.AddFields(m.name, fields, m.tags, now)
- }
- if s.DeleteSets {
- s.sets = make(map[string]cachedset)
- }
-
- s.expireCachedMetrics()
-
- s.lastGatherTime = now
- return nil
-}
-
func (s *Statsd) Start(ac telegraf.Accumulator) error {
if s.ParseDataDogTags {
s.DataDogExtensions = true
@@ -444,6 +337,147 @@ func (s *Statsd) Start(ac telegraf.Accumulator) error {
return nil
}
+func (s *Statsd) Gather(acc telegraf.Accumulator) error {
+ s.Lock()
+ defer s.Unlock()
+ now := time.Now()
+
+ for _, m := range s.distributions {
+ fields := map[string]interface{}{
+ defaultFieldName: m.value,
+ }
+ if s.EnableAggregationTemporality {
+ fields["start_time"] = s.lastGatherTime.Format(time.RFC3339)
+ }
+ acc.AddFields(m.name, fields, m.tags, now)
+ }
+ s.distributions = make([]cacheddistributions, 0)
+
+ for _, m := range s.timings {
+ // Defining a template to parse field names for timers allows us to split
+ // out multiple fields per timer. In this case we prefix each stat with the
+ // field name and store these all in a single measurement.
+ fields := make(map[string]interface{})
+ for fieldName, stats := range m.fields {
+ var prefix string
+ if fieldName != defaultFieldName {
+ prefix = fieldName + "_"
+ }
+ fields[prefix+"mean"] = stats.mean()
+ fields[prefix+"median"] = stats.median()
+ fields[prefix+"stddev"] = stats.stddev()
+ fields[prefix+"sum"] = stats.sum()
+ fields[prefix+"upper"] = stats.upper()
+ fields[prefix+"lower"] = stats.lower()
+ if s.FloatTimings {
+ fields[prefix+"count"] = float64(stats.count())
+ } else {
+ fields[prefix+"count"] = stats.count()
+ }
+ for _, percentile := range s.Percentiles {
+ name := fmt.Sprintf("%s%v_percentile", prefix, percentile)
+ fields[name] = stats.percentile(float64(percentile))
+ }
+ }
+ if s.EnableAggregationTemporality {
+ fields["start_time"] = s.lastGatherTime.Format(time.RFC3339)
+ }
+
+ acc.AddFields(m.name, fields, m.tags, now)
+ }
+ if s.DeleteTimings {
+ s.timings = make(map[string]cachedtimings)
+ }
+
+ for _, m := range s.gauges {
+ if s.EnableAggregationTemporality && m.fields != nil {
+ m.fields["start_time"] = s.lastGatherTime.Format(time.RFC3339)
+ }
+
+ acc.AddGauge(m.name, m.fields, m.tags, now)
+ }
+ if s.DeleteGauges {
+ s.gauges = make(map[string]cachedgauge)
+ }
+
+ for _, m := range s.counters {
+ if s.EnableAggregationTemporality && m.fields != nil {
+ m.fields["start_time"] = s.lastGatherTime.Format(time.RFC3339)
+ }
+
+ if s.FloatCounters {
+ for key := range m.fields {
+ m.fields[key] = float64(m.fields[key].(int64))
+ }
+ }
+ acc.AddCounter(m.name, m.fields, m.tags, now)
+ }
+ if s.DeleteCounters {
+ s.counters = make(map[string]cachedcounter)
+ }
+
+ for _, m := range s.sets {
+ fields := make(map[string]interface{})
+ for field, set := range m.fields {
+ if s.FloatSets {
+ fields[field] = float64(len(set))
+ } else {
+ fields[field] = int64(len(set))
+ }
+ }
+ if s.EnableAggregationTemporality {
+ fields["start_time"] = s.lastGatherTime.Format(time.RFC3339)
+ }
+
+ acc.AddFields(m.name, fields, m.tags, now)
+ }
+ if s.DeleteSets {
+ s.sets = make(map[string]cachedset)
+ }
+
+ s.expireCachedMetrics()
+
+ s.lastGatherTime = now
+ return nil
+}
+
+func (s *Statsd) Stop() {
+ s.Lock()
+ s.Log.Infof("Stopping the statsd service")
+ close(s.done)
+ if s.isUDP() {
+ if s.UDPlistener != nil {
+ s.UDPlistener.Close()
+ }
+ } else {
+ if s.TCPlistener != nil {
+ s.TCPlistener.Close()
+ }
+
+ // Close all open TCP connections
+ // - get all conns from the s.conns map and put into slice
+ // - this is so the forget() function doesnt conflict with looping
+ // over the s.conns map
+ var conns []*net.TCPConn
+ s.cleanup.Lock()
+ for _, conn := range s.conns {
+ conns = append(conns, conn)
+ }
+ s.cleanup.Unlock()
+ for _, conn := range conns {
+ conn.Close()
+ }
+ }
+ s.Unlock()
+
+ s.wg.Wait()
+
+ s.Lock()
+ close(s.in)
+ s.Log.Infof("Stopped listener service on %q", s.ServiceAddress)
+ s.Unlock()
+}
+
// tcpListen() starts listening for TCP packets on the configured port.
func (s *Statsd) tcpListen(listener *net.TCPListener) error {
for {
@@ -497,7 +531,7 @@ func (s *Statsd) udpListen(conn *net.UDPConn) error {
}
}
- buf := make([]byte, UDPMaxPacketSize)
+ buf := make([]byte, udpMaxPacketSize)
for {
select {
case <-s.done:
@@ -838,7 +872,7 @@ func (s *Statsd) aggregate(m metric) {
if !ok {
cached = cachedtimings{
name: m.name,
- fields: make(map[string]RunningStats),
+ fields: make(map[string]runningStats),
tags: m.tags,
}
}
@@ -846,16 +880,16 @@ func (s *Statsd) aggregate(m metric) {
// this will be the default field name, eg. "value"
field, ok := cached.fields[m.field]
if !ok {
- field = RunningStats{
- PercLimit: s.PercentileLimit,
+ field = runningStats{
+ percLimit: s.PercentileLimit,
}
}
if m.samplerate > 0 {
for i := 0; i < int(1.0/m.samplerate); i++ {
- field.AddValue(m.floatvalue)
+ field.addValue(m.floatvalue)
}
} else {
- field.AddValue(m.floatvalue)
+ field.addValue(m.floatvalue)
}
cached.fields[m.field] = field
cached.expiresAt = time.Now().Add(time.Duration(s.MaxTTL))
@@ -1000,43 +1034,6 @@ func (s *Statsd) remember(id string, conn *net.TCPConn) {
s.conns[id] = conn
}
-func (s *Statsd) Stop() {
- s.Lock()
- s.Log.Infof("Stopping the statsd service")
- close(s.done)
- if s.isUDP() {
- if s.UDPlistener != nil {
- s.UDPlistener.Close()
- }
- } else {
- if s.TCPlistener != nil {
- s.TCPlistener.Close()
- }
-
- // Close all open TCP connections
- // - get all conns from the s.conns map and put into slice
- // - this is so the forget() function doesnt conflict with looping
- // over the s.conns map
- var conns []*net.TCPConn
- s.cleanup.Lock()
- for _, conn := range s.conns {
- conns = append(conns, conn)
- }
- s.cleanup.Unlock()
- for _, conn := range conns {
- conn.Close()
- }
- }
- s.Unlock()
-
- s.wg.Wait()
-
- s.Lock()
- close(s.in)
- s.Log.Infof("Stopped listener service on %q", s.ServiceAddress)
- s.Unlock()
-}
-
// IsUDP returns true if the protocol is UDP, false otherwise.
func (s *Statsd) isUDP() bool {
return strings.HasPrefix(s.Protocol, "udp")
diff --git a/plugins/inputs/statsd/statsd_test.go b/plugins/inputs/statsd/statsd_test.go
index 938834f10..df36b1b8f 100644
--- a/plugins/inputs/statsd/statsd_test.go
+++ b/plugins/inputs/statsd/statsd_test.go
@@ -19,7 +19,7 @@ const (
producerThreads = 10
)
-func NewTestStatsd() *Statsd {
+func newTestStatsd() *Statsd {
s := Statsd{
Log: testutil.Logger{},
NumberWorkerThreads: 5,
@@ -339,7 +339,7 @@ func BenchmarkTCP(b *testing.B) {
// Valid lines should be parsed and their values should be cached
func TestParse_ValidLines(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
validLines := []string{
"valid:45|c",
"valid:45|s",
@@ -355,7 +355,7 @@ func TestParse_ValidLines(t *testing.T) {
// Tests low-level functionality of gauges
func TestParse_Gauges(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
// Test that gauge +- values work
validLines := []string{
@@ -425,7 +425,7 @@ func TestParse_Gauges(t *testing.T) {
// Tests low-level functionality of sets
func TestParse_Sets(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
// Test that sets work
validLines := []string{
@@ -480,7 +480,7 @@ func TestParse_Sets(t *testing.T) {
}
func TestParse_Sets_SetsAsFloat(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.FloatSets = true
// Test that sets work
@@ -526,7 +526,7 @@ func TestParse_Sets_SetsAsFloat(t *testing.T) {
// Tests low-level functionality of counters
func TestParse_Counters(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
// Test that counters work
validLines := []string{
@@ -584,7 +584,7 @@ func TestParse_Counters(t *testing.T) {
}
func TestParse_CountersAsFloat(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.FloatCounters = true
// Test that counters work
@@ -694,8 +694,8 @@ func TestParse_CountersAsFloat(t *testing.T) {
// Tests low-level functionality of timings
func TestParse_Timings(t *testing.T) {
- s := NewTestStatsd()
- s.Percentiles = []Number{90.0}
+ s := newTestStatsd()
+ s.Percentiles = []number{90.0}
acc := &testutil.Accumulator{}
// Test that timings work
@@ -728,9 +728,9 @@ func TestParse_Timings(t *testing.T) {
}
func TestParse_Timings_TimingsAsFloat(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.FloatTimings = true
- s.Percentiles = []Number{90.0}
+ s.Percentiles = []number{90.0}
acc := &testutil.Accumulator{}
// Test that timings work
@@ -760,7 +760,7 @@ func TestParse_Timings_TimingsAsFloat(t *testing.T) {
// Tests low-level functionality of distributions
func TestParse_Distributions(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
acc := &testutil.Accumulator{}
parseMetrics := func() {
@@ -813,7 +813,7 @@ func TestParse_Distributions(t *testing.T) {
}
func TestParseScientificNotation(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
sciNotationLines := []string{
"scientific.notation:4.6968460083008E-5|ms",
"scientific.notation:4.6968460083008E-5|g",
@@ -827,7 +827,7 @@ func TestParseScientificNotation(t *testing.T) {
// Invalid lines should return an error
func TestParse_InvalidLines(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
invalidLines := []string{
"i.dont.have.a.pipe:45g",
"i.dont.have.a.colon45|c",
@@ -846,7 +846,7 @@ func TestParse_InvalidLines(t *testing.T) {
// Invalid sample rates should be ignored and not applied
func TestParse_InvalidSampleRate(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
invalidLines := []string{
"invalid.sample.rate:45|c|0.1",
"invalid.sample.rate.2:45|c|@foo",
@@ -886,7 +886,7 @@ func TestParse_InvalidSampleRate(t *testing.T) {
// Names should be parsed like . -> _
func TestParse_DefaultNameParsing(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
validLines := []string{
"valid:1|c",
"valid.foo-bar:11|c",
@@ -917,7 +917,7 @@ func TestParse_DefaultNameParsing(t *testing.T) {
// Test that template name transformation works
func TestParse_Template(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.Templates = []string{
"measurement.measurement.host.service",
}
@@ -953,7 +953,7 @@ func TestParse_Template(t *testing.T) {
// Test that template filters properly
func TestParse_TemplateFilter(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.Templates = []string{
"cpu.idle.* measurement.measurement.host",
}
@@ -989,7 +989,7 @@ func TestParse_TemplateFilter(t *testing.T) {
// Test that most specific template is chosen
func TestParse_TemplateSpecificity(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.Templates = []string{
"cpu.* measurement.foo.host",
"cpu.idle.* measurement.measurement.host",
@@ -1021,7 +1021,7 @@ func TestParse_TemplateSpecificity(t *testing.T) {
// Test that most specific template is chosen
func TestParse_TemplateFields(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.Templates = []string{
"* measurement.measurement.field",
}
@@ -1123,7 +1123,7 @@ func TestParse_Fields(t *testing.T) {
// Test that tags within the bucket are parsed correctly
func TestParse_Tags(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
tests := []struct {
bucket string
@@ -1276,7 +1276,7 @@ func TestParse_DataDogTags(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
var acc testutil.Accumulator
- s := NewTestStatsd()
+ s := newTestStatsd()
s.DataDogExtensions = true
require.NoError(t, s.parseStatsdLine(tt.line))
@@ -1426,7 +1426,7 @@ func TestParse_DataDogContainerID(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
var acc testutil.Accumulator
- s := NewTestStatsd()
+ s := newTestStatsd()
s.DataDogExtensions = true
s.DataDogKeepContainerTag = tt.keep
@@ -1441,7 +1441,7 @@ func TestParse_DataDogContainerID(t *testing.T) {
// Test that statsd buckets are parsed to measurement names properly
func TestParseName(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
tests := []struct {
inName string
@@ -1496,7 +1496,7 @@ func TestParseName(t *testing.T) {
// Test that measurements with the same name, but different tags, are treated
// as different outputs
func TestParse_MeasurementsWithSameName(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
// Test that counters work
validLines := []string{
@@ -1513,7 +1513,7 @@ func TestParse_MeasurementsWithSameName(t *testing.T) {
// Test that the metric caches expire (clear) an entry after the entry hasn't been updated for the configurable MaxTTL duration.
func TestCachesExpireAfterMaxTTL(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.MaxTTL = config.Duration(10 * time.Millisecond)
acc := &testutil.Accumulator{}
@@ -1611,8 +1611,8 @@ func TestParse_MeasurementsWithMultipleValues(t *testing.T) {
"valid.multiple.mixed:1|c:1|ms:2|s:1|g",
}
- sSingle := NewTestStatsd()
- sMultiple := NewTestStatsd()
+ sSingle := newTestStatsd()
+ sMultiple := newTestStatsd()
for _, line := range singleLines {
require.NoErrorf(t, sSingle.parseStatsdLine(line), "Parsing line %s should not have resulted in an error", line)
@@ -1634,7 +1634,7 @@ func TestParse_MeasurementsWithMultipleValues(t *testing.T) {
// which adds up to 12 individual datapoints to be cached
require.EqualValuesf(t, 12, cachedtiming.fields[defaultFieldName].n, "Expected 12 additions, got %d", cachedtiming.fields[defaultFieldName].n)
- require.InDelta(t, 1, cachedtiming.fields[defaultFieldName].upper, testutil.DefaultDelta)
+ require.InDelta(t, 1, cachedtiming.fields[defaultFieldName].upperBound, testutil.DefaultDelta)
// test if sSingle and sMultiple did compute the same stats for valid.multiple.duplicate
require.NoError(t, testValidateSet("valid_multiple_duplicate", 2, sSingle.sets))
@@ -1666,9 +1666,9 @@ func TestParse_MeasurementsWithMultipleValues(t *testing.T) {
// Tests low-level functionality of timings when multiple fields is enabled
// and a measurement template has been defined which can parse field names
func TestParse_TimingsMultipleFieldsWithTemplate(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.Templates = []string{"measurement.field"}
- s.Percentiles = []Number{90.0}
+ s.Percentiles = []number{90.0}
acc := &testutil.Accumulator{}
validLines := []string{
@@ -1716,9 +1716,9 @@ func TestParse_TimingsMultipleFieldsWithTemplate(t *testing.T) {
// but a measurement template hasn't been defined so we can't parse field names
// In this case the behaviour should be the same as normal behaviour
func TestParse_TimingsMultipleFieldsWithoutTemplate(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.Templates = make([]string, 0)
- s.Percentiles = []Number{90.0}
+ s.Percentiles = []number{90.0}
acc := &testutil.Accumulator{}
validLines := []string{
@@ -1765,7 +1765,7 @@ func TestParse_TimingsMultipleFieldsWithoutTemplate(t *testing.T) {
}
func BenchmarkParse(b *testing.B) {
- s := NewTestStatsd()
+ s := newTestStatsd()
validLines := []string{
"test.timing.success:1|ms",
"test.timing.success:11|ms",
@@ -1789,7 +1789,7 @@ func BenchmarkParse(b *testing.B) {
}
func BenchmarkParseWithTemplate(b *testing.B) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.Templates = []string{"measurement.measurement.field"}
validLines := []string{
"test.timing.success:1|ms",
@@ -1814,7 +1814,7 @@ func BenchmarkParseWithTemplate(b *testing.B) {
}
func BenchmarkParseWithTemplateAndFilter(b *testing.B) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.Templates = []string{"cpu* measurement.measurement.field"}
validLines := []string{
"test.timing.success:1|ms",
@@ -1839,7 +1839,7 @@ func BenchmarkParseWithTemplateAndFilter(b *testing.B) {
}
func BenchmarkParseWith2TemplatesAndFilter(b *testing.B) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.Templates = []string{
"cpu1* measurement.measurement.field",
"cpu2* measurement.measurement.field",
@@ -1867,7 +1867,7 @@ func BenchmarkParseWith2TemplatesAndFilter(b *testing.B) {
}
func BenchmarkParseWith2Templates3TagsAndFilter(b *testing.B) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.Templates = []string{
"cpu1* measurement.measurement.region.city.rack.field",
"cpu2* measurement.measurement.region.city.rack.field",
@@ -1895,7 +1895,7 @@ func BenchmarkParseWith2Templates3TagsAndFilter(b *testing.B) {
}
func TestParse_Timings_Delete(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.DeleteTimings = true
fakeacc := &testutil.Accumulator{}
@@ -1911,7 +1911,7 @@ func TestParse_Timings_Delete(t *testing.T) {
// Tests the delete_gauges option
func TestParse_Gauges_Delete(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.DeleteGauges = true
fakeacc := &testutil.Accumulator{}
@@ -1927,7 +1927,7 @@ func TestParse_Gauges_Delete(t *testing.T) {
// Tests the delete_sets option
func TestParse_Sets_Delete(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.DeleteSets = true
fakeacc := &testutil.Accumulator{}
@@ -1943,7 +1943,7 @@ func TestParse_Sets_Delete(t *testing.T) {
// Tests the delete_counters option
func TestParse_Counters_Delete(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.DeleteCounters = true
fakeacc := &testutil.Accumulator{}
@@ -2186,12 +2186,12 @@ func TestUdpFillQueue(t *testing.T) {
}
func TestParse_Ints(t *testing.T) {
- s := NewTestStatsd()
- s.Percentiles = []Number{90}
+ s := newTestStatsd()
+ s.Percentiles = []number{90}
acc := &testutil.Accumulator{}
require.NoError(t, s.Gather(acc))
- require.Equal(t, []Number{90.0}, s.Percentiles)
+ require.Equal(t, []number{90.0}, s.Percentiles)
}
func TestParse_KeyValue(t *testing.T) {
@@ -2222,7 +2222,7 @@ func TestParse_KeyValue(t *testing.T) {
}
func TestParseSanitize(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.SanitizeNamesMethod = "upstream"
tests := []struct {
@@ -2254,7 +2254,7 @@ func TestParseSanitize(t *testing.T) {
}
func TestParseNoSanitize(t *testing.T) {
- s := NewTestStatsd()
+ s := newTestStatsd()
s.SanitizeNamesMethod = ""
tests := []struct {
diff --git a/plugins/inputs/supervisor/supervisor.go b/plugins/inputs/supervisor/supervisor.go
index 7a98f5d09..b6a000950 100644
--- a/plugins/inputs/supervisor/supervisor.go
+++ b/plugins/inputs/supervisor/supervisor.go
@@ -14,6 +14,9 @@ import (
"github.com/influxdata/telegraf/plugins/inputs"
)
+//go:embed sample.conf
+var sampleConfig string
+
type Supervisor struct {
Server string `toml:"url"`
MetricsInc []string `toml:"metrics_include"`
@@ -46,13 +49,29 @@ type supervisorInfo struct {
Ident string
}
-//go:embed sample.conf
-var sampleConfig string
-
func (*Supervisor) SampleConfig() string {
return sampleConfig
}
+func (s *Supervisor) Init() error {
+ // Using default server URL if none was specified in config
+ if s.Server == "" {
+ s.Server = "http://localhost:9001/RPC2"
+ }
+ var err error
+ // Initializing XML-RPC client
+ s.rpcClient, err = xmlrpc.NewClient(s.Server, nil)
+ if err != nil {
+ return fmt.Errorf("failed to initialize XML-RPC client: %w", err)
+ }
+ // Setting filter for additional metrics
+ s.fieldFilter, err = filter.NewIncludeExcludeFilter(s.MetricsInc, s.MetricsExc)
+ if err != nil {
+ return fmt.Errorf("metrics filter setup failed: %w", err)
+ }
+ return nil
+}
+
func (s *Supervisor) Gather(acc telegraf.Accumulator) error {
// API call to get information about all running processes
var rawProcessData []processInfo
@@ -134,33 +153,6 @@ func (s *Supervisor) parseInstanceData(status supervisorInfo) (map[string]string
return tags, fields, nil
}
-func (s *Supervisor) Init() error {
- // Using default server URL if none was specified in config
- if s.Server == "" {
- s.Server = "http://localhost:9001/RPC2"
- }
- var err error
- // Initializing XML-RPC client
- s.rpcClient, err = xmlrpc.NewClient(s.Server, nil)
- if err != nil {
- return fmt.Errorf("failed to initialize XML-RPC client: %w", err)
- }
- // Setting filter for additional metrics
- s.fieldFilter, err = filter.NewIncludeExcludeFilter(s.MetricsInc, s.MetricsExc)
- if err != nil {
- return fmt.Errorf("metrics filter setup failed: %w", err)
- }
- return nil
-}
-
-func init() {
- inputs.Add("supervisor", func() telegraf.Input {
- return &Supervisor{
- MetricsExc: []string{"pid", "rc"},
- }
- })
-}
-
// Function to get only address and port from URL
func beautifyServerString(rawurl string) ([]string, error) {
parsedURL, err := url.Parse(rawurl)
@@ -177,3 +169,11 @@ func beautifyServerString(rawurl string) ([]string, error) {
}
return splittedURL, nil
}
+
+func init() {
+ inputs.Add("supervisor", func() telegraf.Input {
+ return &Supervisor{
+ MetricsExc: []string{"pid", "rc"},
+ }
+ })
+}
diff --git a/plugins/inputs/suricata/suricata.go b/plugins/inputs/suricata/suricata.go
index 26722474b..d95803908 100644
--- a/plugins/inputs/suricata/suricata.go
+++ b/plugins/inputs/suricata/suricata.go
@@ -23,13 +23,12 @@ import (
var sampleConfig string
const (
- // InBufSize is the input buffer size for JSON received via socket.
+ // inBufSize is the input buffer size for JSON received via socket.
// Set to 10MB, as depending on the number of threads the output might be
// large.
- InBufSize = 10 * 1024 * 1024
+ inBufSize = 10 * 1024 * 1024
)
-// Suricata is a Telegraf input plugin for Suricata runtime statistics.
type Suricata struct {
Source string `toml:"source"`
Delimiter string `toml:"delimiter"`
@@ -68,8 +67,7 @@ func (s *Suricata) Init() error {
return nil
}
-// Start initiates background collection of JSON data from the socket
-// provided to Suricata.
+// Start initiates background collection of JSON data from the socket provided to Suricata.
func (s *Suricata) Start(acc telegraf.Accumulator) error {
var err error
s.inputListener, err = net.ListenUnix("unix", &net.UnixAddr{
@@ -90,8 +88,13 @@ func (s *Suricata) Start(acc telegraf.Accumulator) error {
return nil
}
-// Stop causes the plugin to cease collecting JSON data from the socket provided
-// to Suricata.
+// Gather measures and submits one full set of telemetry to Telegraf.
+// Not used here, submission is completely input-driven.
+func (*Suricata) Gather(telegraf.Accumulator) error {
+ return nil
+}
+
+// Stop causes the plugin to cease collecting JSON data from the socket provided to Suricata.
func (s *Suricata) Stop() {
s.inputListener.Close()
if s.cancel != nil {
@@ -101,7 +104,7 @@ func (s *Suricata) Stop() {
}
func (s *Suricata) readInput(ctx context.Context, acc telegraf.Accumulator, conn net.Conn) error {
- reader := bufio.NewReaderSize(conn, InBufSize)
+ reader := bufio.NewReaderSize(conn, inBufSize)
for {
select {
case <-ctx.Done():
@@ -342,12 +345,6 @@ func (s *Suricata) parse(acc telegraf.Accumulator, sjson []byte) error {
return nil
}
-// Gather measures and submits one full set of telemetry to Telegraf.
-// Not used here, submission is completely input-driven.
-func (*Suricata) Gather(telegraf.Accumulator) error {
- return nil
-}
-
func init() {
inputs.Add("suricata", func() telegraf.Input {
return &Suricata{}
diff --git a/plugins/inputs/swap/swap.go b/plugins/inputs/swap/swap.go
index 60d5b96d6..6fe9e53cf 100644
--- a/plugins/inputs/swap/swap.go
+++ b/plugins/inputs/swap/swap.go
@@ -13,15 +13,15 @@ import (
//go:embed sample.conf
var sampleConfig string
-type SwapStats struct {
+type Swap struct {
ps system.PS
}
-func (*SwapStats) SampleConfig() string {
+func (*Swap) SampleConfig() string {
return sampleConfig
}
-func (ss *SwapStats) Gather(acc telegraf.Accumulator) error {
+func (ss *Swap) Gather(acc telegraf.Accumulator) error {
swap, err := ss.ps.SwapStat()
if err != nil {
return fmt.Errorf("error getting swap memory info: %w", err)
@@ -46,6 +46,6 @@ func (ss *SwapStats) Gather(acc telegraf.Accumulator) error {
func init() {
ps := system.NewSystemPS()
inputs.Add("swap", func() telegraf.Input {
- return &SwapStats{ps: ps}
+ return &Swap{ps: ps}
})
}
diff --git a/plugins/inputs/swap/swap_test.go b/plugins/inputs/swap/swap_test.go
index 652cb56d3..680adb498 100644
--- a/plugins/inputs/swap/swap_test.go
+++ b/plugins/inputs/swap/swap_test.go
@@ -26,7 +26,7 @@ func TestSwapStats(t *testing.T) {
mps.On("SwapStat").Return(sms, nil)
- err = (&SwapStats{&mps}).Gather(&acc)
+ err = (&Swap{&mps}).Gather(&acc)
require.NoError(t, err)
swapfields := map[string]interface{}{
diff --git a/plugins/inputs/synproxy/synproxy.go b/plugins/inputs/synproxy/synproxy.go
index fa1d66ee3..ef92377b2 100644
--- a/plugins/inputs/synproxy/synproxy.go
+++ b/plugins/inputs/synproxy/synproxy.go
@@ -1,9 +1,17 @@
//go:generate ../../../tools/readme_config_includer/generator
+//go:build linux
+
package synproxy
import (
+ "bufio"
_ "embed"
+ "errors"
+ "fmt"
+ "os"
"path"
+ "strconv"
+ "strings"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
@@ -24,6 +32,83 @@ func (*Synproxy) SampleConfig() string {
return sampleConfig
}
+func (s *Synproxy) Gather(acc telegraf.Accumulator) error {
+ data, err := s.getSynproxyStat()
+ if err != nil {
+ return err
+ }
+
+ acc.AddCounter("synproxy", data, make(map[string]string))
+ return nil
+}
+
+func (s *Synproxy) getSynproxyStat() (map[string]interface{}, error) {
+ var hname []string
+ counters := []string{"entries", "syn_received", "cookie_invalid", "cookie_valid", "cookie_retrans", "conn_reopened"}
+ fields := make(map[string]interface{})
+
+ // Open synproxy file in proc filesystem
+ file, err := os.Open(s.statFile)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ // Initialise expected fields
+ for _, val := range counters {
+ fields[val] = uint32(0)
+ }
+
+ scanner := bufio.NewScanner(file)
+ // Read header row
+ if scanner.Scan() {
+ line := scanner.Text()
+ // Parse fields separated by whitespace
+ dataFields := strings.Fields(line)
+ for _, val := range dataFields {
+ if !inSlice(counters, val) {
+ val = ""
+ }
+ hname = append(hname, val)
+ }
+ }
+ if len(hname) == 0 {
+ return nil, errors.New("invalid data")
+ }
+ // Read data rows
+ for scanner.Scan() {
+ line := scanner.Text()
+ // Parse fields separated by whitespace
+ dataFields := strings.Fields(line)
+ // If number of data fields do not match number of header fields
+ if len(dataFields) != len(hname) {
+ return nil, fmt.Errorf("invalid number of columns in data, expected %d found %d", len(hname),
+ len(dataFields))
+ }
+ for i, val := range dataFields {
+ // Convert from hexstring to int32
+ x, err := strconv.ParseUint(val, 16, 32)
+ // If field is not a valid hexstring
+ if err != nil {
+ return nil, fmt.Errorf("invalid value %q found", val)
+ }
+ if hname[i] != "" {
+ fields[hname[i]] = fields[hname[i]].(uint32) + uint32(x)
+ }
+ }
+ }
+ return fields, nil
+}
+
+func inSlice(haystack []string, needle string) bool {
+ for _, val := range haystack {
+ if needle == val {
+ return true
+ }
+ }
+ return false
+}
+
func init() {
inputs.Add("synproxy", func() telegraf.Input {
return &Synproxy{
diff --git a/plugins/inputs/synproxy/synproxy_linux.go b/plugins/inputs/synproxy/synproxy_linux.go
deleted file mode 100644
index 7f3a9928e..000000000
--- a/plugins/inputs/synproxy/synproxy_linux.go
+++ /dev/null
@@ -1,91 +0,0 @@
-//go:build linux
-
-package synproxy
-
-import (
- "bufio"
- "errors"
- "fmt"
- "os"
- "strconv"
- "strings"
-
- "github.com/influxdata/telegraf"
-)
-
-func (k *Synproxy) Gather(acc telegraf.Accumulator) error {
- data, err := k.getSynproxyStat()
- if err != nil {
- return err
- }
-
- acc.AddCounter("synproxy", data, make(map[string]string))
- return nil
-}
-
-func inSlice(haystack []string, needle string) bool {
- for _, val := range haystack {
- if needle == val {
- return true
- }
- }
- return false
-}
-
-func (k *Synproxy) getSynproxyStat() (map[string]interface{}, error) {
- var hname []string
- counters := []string{"entries", "syn_received", "cookie_invalid", "cookie_valid", "cookie_retrans", "conn_reopened"}
- fields := make(map[string]interface{})
-
- // Open synproxy file in proc filesystem
- file, err := os.Open(k.statFile)
- if err != nil {
- return nil, err
- }
- defer file.Close()
-
- // Initialise expected fields
- for _, val := range counters {
- fields[val] = uint32(0)
- }
-
- scanner := bufio.NewScanner(file)
- // Read header row
- if scanner.Scan() {
- line := scanner.Text()
- // Parse fields separated by whitespace
- dataFields := strings.Fields(line)
- for _, val := range dataFields {
- if !inSlice(counters, val) {
- val = ""
- }
- hname = append(hname, val)
- }
- }
- if len(hname) == 0 {
- return nil, errors.New("invalid data")
- }
- // Read data rows
- for scanner.Scan() {
- line := scanner.Text()
- // Parse fields separated by whitespace
- dataFields := strings.Fields(line)
- // If number of data fields do not match number of header fields
- if len(dataFields) != len(hname) {
- return nil, fmt.Errorf("invalid number of columns in data, expected %d found %d", len(hname),
- len(dataFields))
- }
- for i, val := range dataFields {
- // Convert from hexstring to int32
- x, err := strconv.ParseUint(val, 16, 32)
- // If field is not a valid hexstring
- if err != nil {
- return nil, fmt.Errorf("invalid value %q found", val)
- }
- if hname[i] != "" {
- fields[hname[i]] = fields[hname[i]].(uint32) + uint32(x)
- }
- }
- }
- return fields, nil
-}
diff --git a/plugins/inputs/synproxy/synproxy_notlinux.go b/plugins/inputs/synproxy/synproxy_notlinux.go
index 582a7b382..e1ab2a7c2 100644
--- a/plugins/inputs/synproxy/synproxy_notlinux.go
+++ b/plugins/inputs/synproxy/synproxy_notlinux.go
@@ -1,23 +1,33 @@
+//go:generate ../../../tools/readme_config_includer/generator
//go:build !linux
package synproxy
import (
+ _ "embed"
+
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)
-func (k *Synproxy) Init() error {
- k.Log.Warn("Current platform is not supported")
+//go:embed sample.conf
+var sampleConfig string
+
+type Synproxy struct {
+ Log telegraf.Logger `toml:"-"`
+}
+
+func (*Synproxy) SampleConfig() string { return sampleConfig }
+
+func (s *Synproxy) Init() error {
+ s.Log.Warn("Current platform is not supported")
return nil
}
-func (*Synproxy) Gather(_ telegraf.Accumulator) error {
- return nil
-}
+func (*Synproxy) Gather(telegraf.Accumulator) error { return nil }
func init() {
- inputs.Add("synproxy", func() telegraf.Input {
+ inputs.Add("slab", func() telegraf.Input {
return &Synproxy{}
})
}
diff --git a/plugins/inputs/syslog/syslog.go b/plugins/inputs/syslog/syslog.go
index 4fe9f4e8e..4f5153b87 100644
--- a/plugins/inputs/syslog/syslog.go
+++ b/plugins/inputs/syslog/syslog.go
@@ -29,7 +29,6 @@ var sampleConfig string
const readTimeoutMsg = "Read timeout set! Connections, inactive for the set duration, will be closed!"
-// Syslog is a syslog plugin
type Syslog struct {
Address string `toml:"server"`
Framing string `toml:"framing"`
@@ -113,12 +112,6 @@ func (s *Syslog) Init() error {
return nil
}
-// Gather ...
-func (*Syslog) Gather(_ telegraf.Accumulator) error {
- return nil
-}
-
-// Start starts the service.
func (s *Syslog) Start(acc telegraf.Accumulator) error {
s.mu.Lock()
defer s.mu.Unlock()
@@ -148,7 +141,10 @@ func (s *Syslog) Start(acc telegraf.Accumulator) error {
return nil
}
-// Stop cleans up all resources
+func (*Syslog) Gather(telegraf.Accumulator) error {
+ return nil
+}
+
func (s *Syslog) Stop() {
s.mu.Lock()
defer s.mu.Unlock()
diff --git a/plugins/inputs/sysstat/sysstat.go b/plugins/inputs/sysstat/sysstat.go
index 89ff70672..34211d289 100644
--- a/plugins/inputs/sysstat/sysstat.go
+++ b/plugins/inputs/sysstat/sysstat.go
@@ -31,7 +31,10 @@ var (
dfltActivities = []string{"DISK"}
)
-const parseInterval = 1 // parseInterval is the interval (in seconds) where the parsing of the binary file takes place.
+const (
+ parseInterval = 1 // parseInterval is the interval (in seconds) where the parsing of the binary file takes place.
+ cmd = "sadf"
+)
type Sysstat struct {
// Sadc represents the path to the sadc collector utility.
@@ -46,7 +49,7 @@ type Sysstat struct {
// Activities is a list of activities that are passed as argument to the
// collector utility (e.g: DISK, SNMP etc...)
// The more activities that are added, the more data is collected.
- Activities []string
+ Activities []string `toml:"activities"`
// Options is a map of options.
//
@@ -62,23 +65,21 @@ type Sysstat struct {
// and represents itself a measurement.
//
// If Group is true, metrics are grouped to a single measurement with the corresponding description as name.
- Options map[string]string
+ Options map[string]string `toml:"options"`
// Group determines if metrics are grouped or not.
- Group bool
+ Group bool `toml:"group"`
// DeviceTags adds the possibility to add additional tags for devices.
DeviceTags map[string][]map[string]string `toml:"device_tags"`
- Log telegraf.Logger
+ Log telegraf.Logger `toml:"-"`
// Used to autodetect how long the sadc command should run for
interval int
firstTimestamp time.Time
}
-const cmd = "sadf"
-
func (*Sysstat) SampleConfig() string {
return sampleConfig
}
diff --git a/plugins/inputs/sysstat/sysstat_notlinux.go b/plugins/inputs/sysstat/sysstat_notlinux.go
index 3878df16a..5162786b4 100644
--- a/plugins/inputs/sysstat/sysstat_notlinux.go
+++ b/plugins/inputs/sysstat/sysstat_notlinux.go
@@ -17,12 +17,14 @@ type Sysstat struct {
Log telegraf.Logger `toml:"-"`
}
+func (*Sysstat) SampleConfig() string { return sampleConfig }
+
func (s *Sysstat) Init() error {
- s.Log.Warn("current platform is not supported")
+ s.Log.Warn("Current platform is not supported")
return nil
}
-func (*Sysstat) SampleConfig() string { return sampleConfig }
-func (*Sysstat) Gather(_ telegraf.Accumulator) error { return nil }
+
+func (*Sysstat) Gather(telegraf.Accumulator) error { return nil }
func init() {
inputs.Add("sysstat", func() telegraf.Input {
diff --git a/plugins/inputs/systemd_units/systemd_units.go b/plugins/inputs/systemd_units/systemd_units.go
index 870bfc1af..bc2fbe4d8 100644
--- a/plugins/inputs/systemd_units/systemd_units.go
+++ b/plugins/inputs/systemd_units/systemd_units.go
@@ -1,8 +1,18 @@
//go:generate ../../../tools/readme_config_includer/generator
+//go:build linux
+
package systemd_units
import (
+ "context"
_ "embed"
+ "fmt"
+ "github.com/coreos/go-systemd/v22/dbus"
+ "github.com/influxdata/telegraf/filter"
+ "math"
+ "os/user"
+ "path"
+ "strings"
"time"
"github.com/influxdata/telegraf"
@@ -13,6 +23,107 @@ import (
//go:embed sample.conf
var sampleConfig string
+var (
+ // Below are mappings of systemd state tables as defined in
+ // https://github.com/systemd/systemd/blob/c87700a1335f489be31cd3549927da68b5638819/src/basic/unit-def.c
+ // Duplicate strings are removed from this list.
+ // This map is used by `subcommand_show` and `subcommand_list`. Changes must be
+ // compatible with both subcommands.
+ loadMap = map[string]int{
+ "loaded": 0,
+ "stub": 1,
+ "not-found": 2,
+ "bad-setting": 3,
+ "error": 4,
+ "merged": 5,
+ "masked": 6,
+ }
+
+ activeMap = map[string]int{
+ "active": 0,
+ "reloading": 1,
+ "inactive": 2,
+ "failed": 3,
+ "activating": 4,
+ "deactivating": 5,
+ }
+
+ subMap = map[string]int{
+ // service_state_table, offset 0x0000
+ "running": 0x0000,
+ "dead": 0x0001,
+ "start-pre": 0x0002,
+ "start": 0x0003,
+ "exited": 0x0004,
+ "reload": 0x0005,
+ "stop": 0x0006,
+ "stop-watchdog": 0x0007,
+ "stop-sigterm": 0x0008,
+ "stop-sigkill": 0x0009,
+ "stop-post": 0x000a,
+ "final-sigterm": 0x000b,
+ "failed": 0x000c,
+ "auto-restart": 0x000d,
+ "condition": 0x000e,
+ "cleaning": 0x000f,
+
+ // automount_state_table, offset 0x0010
+ // continuation of service_state_table
+ "waiting": 0x0010,
+ "reload-signal": 0x0011,
+ "reload-notify": 0x0012,
+ "final-watchdog": 0x0013,
+ "dead-before-auto-restart": 0x0014,
+ "failed-before-auto-restart": 0x0015,
+ "dead-resources-pinned": 0x0016,
+ "auto-restart-queued": 0x0017,
+
+ // device_state_table, offset 0x0020
+ "tentative": 0x0020,
+ "plugged": 0x0021,
+
+ // mount_state_table, offset 0x0030
+ "mounting": 0x0030,
+ "mounting-done": 0x0031,
+ "mounted": 0x0032,
+ "remounting": 0x0033,
+ "unmounting": 0x0034,
+ "remounting-sigterm": 0x0035,
+ "remounting-sigkill": 0x0036,
+ "unmounting-sigterm": 0x0037,
+ "unmounting-sigkill": 0x0038,
+
+ // path_state_table, offset 0x0040
+
+ // scope_state_table, offset 0x0050
+ "abandoned": 0x0050,
+
+ // slice_state_table, offset 0x0060
+ "active": 0x0060,
+
+ // socket_state_table, offset 0x0070
+ "start-chown": 0x0070,
+ "start-post": 0x0071,
+ "listening": 0x0072,
+ "stop-pre": 0x0073,
+ "stop-pre-sigterm": 0x0074,
+ "stop-pre-sigkill": 0x0075,
+ "final-sigkill": 0x0076,
+
+ // swap_state_table, offset 0x0080
+ "activating": 0x0080,
+ "activating-done": 0x0081,
+ "deactivating": 0x0082,
+ "deactivating-sigterm": 0x0083,
+ "deactivating-sigkill": 0x0084,
+
+ // target_state_table, offset 0x0090
+
+ // timer_state_table, offset 0x00a0
+ "elapsed": 0x00a0,
+ }
+)
+
// SystemdUnits is a telegraf plugin to gather systemd unit status
type SystemdUnits struct {
Pattern string `toml:"pattern"`
@@ -25,10 +136,330 @@ type SystemdUnits struct {
archParams
}
+type archParams struct {
+ client client
+ pattern []string
+ filter filter.Filter
+ unitTypeDBus string
+ scope string
+ user string
+ warnUnitProps map[string]bool
+}
+
+type client interface {
+ // Connected returns whether client is connected
+ Connected() bool
+
+ // Close closes an established connection.
+ Close()
+
+ // ListUnitFilesByPatternsContext returns an array of all available units on disk matched the patterns.
+ ListUnitFilesByPatternsContext(ctx context.Context, states, pattern []string) ([]dbus.UnitFile, error)
+
+ // ListUnitsByNamesContext returns an array with units.
+ ListUnitsByNamesContext(ctx context.Context, units []string) ([]dbus.UnitStatus, error)
+
+ // GetUnitTypePropertiesContext returns the extra properties for a unit, specific to the unit type.
+ GetUnitTypePropertiesContext(ctx context.Context, unit, unitType string) (map[string]interface{}, error)
+
+ // GetUnitPropertiesContext takes the (unescaped) unit name and returns all of its dbus object properties.
+ GetUnitPropertiesContext(ctx context.Context, unit string) (map[string]interface{}, error)
+
+ // ListUnitsContext returns an array with all currently loaded units.
+ ListUnitsContext(ctx context.Context) ([]dbus.UnitStatus, error)
+}
+
func (*SystemdUnits) SampleConfig() string {
return sampleConfig
}
+func (s *SystemdUnits) Init() error {
+ // Set default pattern
+ if s.Pattern == "" {
+ s.Pattern = "*"
+ }
+
+ // Check unit-type and convert the first letter to uppercase as this is
+ // what dbus expects.
+ switch s.UnitType {
+ case "":
+ s.UnitType = "service"
+ case "service", "socket", "target", "device", "mount", "automount", "swap",
+ "timer", "path", "slice", "scope":
+ default:
+ return fmt.Errorf("invalid 'unittype' %q", s.UnitType)
+ }
+ s.unitTypeDBus = strings.ToUpper(s.UnitType[0:1]) + strings.ToLower(s.UnitType[1:])
+
+ s.pattern = strings.Split(s.Pattern, " ")
+ f, err := filter.Compile(s.pattern)
+ if err != nil {
+ return fmt.Errorf("compiling filter failed: %w", err)
+ }
+ s.filter = f
+
+ switch s.Scope {
+ case "", "system":
+ s.scope = "system"
+ case "user":
+ u, err := user.Current()
+ if err != nil {
+ return fmt.Errorf("unable to determine user: %w", err)
+ }
+
+ s.scope = "user"
+ s.user = u.Username
+ default:
+ return fmt.Errorf("invalid 'scope' %q", s.Scope)
+ }
+
+ s.warnUnitProps = make(map[string]bool)
+
+ return nil
+}
+
+func (s *SystemdUnits) Start(telegraf.Accumulator) error {
+ ctx := context.Background()
+
+ var client *dbus.Conn
+ var err error
+ if s.scope == "user" {
+ client, err = dbus.NewUserConnectionContext(ctx)
+ } else {
+ client, err = dbus.NewSystemConnectionContext(ctx)
+ }
+ if err != nil {
+ return err
+ }
+
+ s.client = client
+
+ return nil
+}
+
+func (s *SystemdUnits) Gather(acc telegraf.Accumulator) error {
+ // Reconnect in case the connection was lost
+ if !s.client.Connected() {
+ s.Log.Debug("Connection to systemd daemon lost, trying to reconnect...")
+ s.Stop()
+ if err := s.Start(acc); err != nil {
+ return err
+ }
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.Timeout))
+ defer cancel()
+
+ // List all loaded units to handle multi-instance units correctly
+ loaded, err := s.client.ListUnitsContext(ctx)
+ if err != nil {
+ return fmt.Errorf("listing loaded units failed: %w", err)
+ }
+
+ var files []dbus.UnitFile
+ if s.CollectDisabled {
+ // List all unit files matching the pattern to also get disabled units
+ list := []string{"enabled", "disabled", "static"}
+ files, err = s.client.ListUnitFilesByPatternsContext(ctx, list, s.pattern)
+ if err != nil {
+ return fmt.Errorf("listing unit files failed: %w", err)
+ }
+ }
+
+ // Collect all matching units, the loaded ones and the disabled ones
+ states := make([]dbus.UnitStatus, 0, len(loaded))
+
+ // Match all loaded units first
+ seen := make(map[string]bool)
+ for _, u := range loaded {
+ if !s.filter.Match(u.Name) {
+ continue
+ }
+ states = append(states, u)
+
+ // Remember multi-instance units to remove duplicates from files
+ instance := u.Name
+ if strings.Contains(u.Name, "@") {
+ prefix, _, _ := strings.Cut(u.Name, "@")
+ suffix := path.Ext(u.Name)
+ instance = prefix + "@" + suffix
+ }
+ seen[instance] = true
+ }
+
+ // Now split the unit-files into disabled ones and static ones, ignore
+ // enabled units as those are already contained in the "loaded" list.
+ if len(files) > 0 {
+ disabled := make([]string, 0, len(files))
+ static := make([]string, 0, len(files))
+ for _, f := range files {
+ name := path.Base(f.Path)
+
+ switch f.Type {
+ case "disabled":
+ if seen[name] {
+ continue
+ }
+ seen[name] = true
+
+ // Detect disabled multi-instance units and declare them as static
+ _, suffix, found := strings.Cut(name, "@")
+ instance, _, _ := strings.Cut(suffix, ".")
+ if found && instance == "" {
+ static = append(static, name)
+ continue
+ }
+ disabled = append(disabled, name)
+ case "static":
+ // Make sure we filter already loaded static multi-instance units
+ instance := name
+ if strings.Contains(name, "@") {
+ prefix, _, _ := strings.Cut(name, "@")
+ suffix := path.Ext(name)
+ instance = prefix + "@" + suffix
+ }
+ if seen[instance] || seen[name] {
+ continue
+ }
+ seen[instance] = true
+ static = append(static, name)
+ }
+ }
+
+ // Resolve the disabled and remaining static units
+ disabledStates, err := s.client.ListUnitsByNamesContext(ctx, disabled)
+ if err != nil {
+ return fmt.Errorf("listing unit states failed: %w", err)
+ }
+ states = append(states, disabledStates...)
+
+ // Add special information about unused static units
+ for _, name := range static {
+ if !strings.EqualFold(strings.TrimPrefix(path.Ext(name), "."), s.UnitType) {
+ continue
+ }
+
+ states = append(states, dbus.UnitStatus{
+ Name: name,
+ LoadState: "stub",
+ ActiveState: "inactive",
+ SubState: "dead",
+ })
+ }
+ }
+
+ // Merge the unit information into one struct
+ for _, state := range states {
+ // Filter units of the wrong type
+ if idx := strings.LastIndex(state.Name, "."); idx < 0 || state.Name[idx+1:] != s.UnitType {
+ continue
+ }
+
+ // Map the state names to numerical values
+ load, ok := loadMap[state.LoadState]
+ if !ok {
+ acc.AddError(fmt.Errorf("parsing field 'load' failed, value not in map: %s", state.LoadState))
+ continue
+ }
+ active, ok := activeMap[state.ActiveState]
+ if !ok {
+ acc.AddError(fmt.Errorf("parsing field 'active' failed, value not in map: %s", state.ActiveState))
+ continue
+ }
+ subState, ok := subMap[state.SubState]
+ if !ok {
+ acc.AddError(fmt.Errorf("parsing field 'sub' failed, value not in map: %s", state.SubState))
+ continue
+ }
+
+ // Create the metric
+ tags := map[string]string{
+ "name": state.Name,
+ "load": state.LoadState,
+ "active": state.ActiveState,
+ "sub": state.SubState,
+ }
+ if s.scope == "user" {
+ tags["user"] = s.user
+ }
+
+ fields := map[string]interface{}{
+ "load_code": load,
+ "active_code": active,
+ "sub_code": subState,
+ }
+
+ if s.Details {
+ properties, err := s.client.GetUnitTypePropertiesContext(ctx, state.Name, s.unitTypeDBus)
+ if err != nil {
+ // Skip units returning "Unknown interface" errors as those indicate
+ // that the unit is of the wrong type.
+ if strings.Contains(err.Error(), "Unknown interface") {
+ continue
+ }
+ // For other units we make up properties, usually those are
+ // disabled multi-instance units
+ properties = map[string]interface{}{
+ "StatusErrno": int64(-1),
+ "NRestarts": uint64(0),
+ }
+ }
+
+ // Get required unit file properties
+ unitProperties, err := s.client.GetUnitPropertiesContext(ctx, state.Name)
+ if err != nil && !s.warnUnitProps[state.Name] {
+ s.Log.Warnf("Cannot read unit properties for %q: %v", state.Name, err)
+ s.warnUnitProps[state.Name] = true
+ }
+
+ // Set tags
+ if v, found := unitProperties["UnitFileState"]; found {
+ tags["state"] = v.(string)
+ }
+ if v, found := unitProperties["UnitFilePreset"]; found {
+ tags["preset"] = v.(string)
+ }
+
+ // Set fields
+ if v, found := unitProperties["ActiveEnterTimestamp"]; found {
+ fields["active_enter_timestamp_us"] = v
+ }
+
+ fields["status_errno"] = properties["StatusErrno"]
+ fields["restarts"] = properties["NRestarts"]
+ fields["pid"] = properties["MainPID"]
+
+ fields["mem_current"] = properties["MemoryCurrent"]
+ fields["mem_peak"] = properties["MemoryPeak"]
+ fields["mem_avail"] = properties["MemoryAvailable"]
+
+ fields["swap_current"] = properties["MemorySwapCurrent"]
+ fields["swap_peak"] = properties["MemorySwapPeak"]
+
+ // Sanitize unset memory fields
+ for k, value := range fields {
+ switch {
+ case strings.HasPrefix(k, "mem_"), strings.HasPrefix(k, "swap_"):
+ v, ok := value.(uint64)
+ if ok && v == math.MaxUint64 || value == nil {
+ fields[k] = uint64(0)
+ }
+ }
+ }
+ }
+ acc.AddFields("systemd_units", fields, tags)
+ }
+
+ return nil
+}
+
+func (s *SystemdUnits) Stop() {
+ if s.client != nil && s.client.Connected() {
+ s.client.Close()
+ }
+ s.client = nil
+}
+
func init() {
inputs.Add("systemd_units", func() telegraf.Input {
return &SystemdUnits{Timeout: config.Duration(5 * time.Second)}
diff --git a/plugins/inputs/systemd_units/systemd_units_linux.go b/plugins/inputs/systemd_units/systemd_units_linux.go
deleted file mode 100644
index 2500b6cce..000000000
--- a/plugins/inputs/systemd_units/systemd_units_linux.go
+++ /dev/null
@@ -1,425 +0,0 @@
-//go:build linux
-
-package systemd_units
-
-import (
- "context"
- "fmt"
- "math"
- "os/user"
- "path"
- "strings"
- "time"
-
- "github.com/coreos/go-systemd/v22/dbus"
-
- "github.com/influxdata/telegraf"
- "github.com/influxdata/telegraf/filter"
-)
-
-// Below are mappings of systemd state tables as defined in
-// https://github.com/systemd/systemd/blob/c87700a1335f489be31cd3549927da68b5638819/src/basic/unit-def.c
-// Duplicate strings are removed from this list.
-// This map is used by `subcommand_show` and `subcommand_list`. Changes must be
-// compatible with both subcommands.
-var loadMap = map[string]int{
- "loaded": 0,
- "stub": 1,
- "not-found": 2,
- "bad-setting": 3,
- "error": 4,
- "merged": 5,
- "masked": 6,
-}
-
-var activeMap = map[string]int{
- "active": 0,
- "reloading": 1,
- "inactive": 2,
- "failed": 3,
- "activating": 4,
- "deactivating": 5,
-}
-
-var subMap = map[string]int{
- // service_state_table, offset 0x0000
- "running": 0x0000,
- "dead": 0x0001,
- "start-pre": 0x0002,
- "start": 0x0003,
- "exited": 0x0004,
- "reload": 0x0005,
- "stop": 0x0006,
- "stop-watchdog": 0x0007,
- "stop-sigterm": 0x0008,
- "stop-sigkill": 0x0009,
- "stop-post": 0x000a,
- "final-sigterm": 0x000b,
- "failed": 0x000c,
- "auto-restart": 0x000d,
- "condition": 0x000e,
- "cleaning": 0x000f,
-
- // automount_state_table, offset 0x0010
- // continuation of service_state_table
- "waiting": 0x0010,
- "reload-signal": 0x0011,
- "reload-notify": 0x0012,
- "final-watchdog": 0x0013,
- "dead-before-auto-restart": 0x0014,
- "failed-before-auto-restart": 0x0015,
- "dead-resources-pinned": 0x0016,
- "auto-restart-queued": 0x0017,
-
- // device_state_table, offset 0x0020
- "tentative": 0x0020,
- "plugged": 0x0021,
-
- // mount_state_table, offset 0x0030
- "mounting": 0x0030,
- "mounting-done": 0x0031,
- "mounted": 0x0032,
- "remounting": 0x0033,
- "unmounting": 0x0034,
- "remounting-sigterm": 0x0035,
- "remounting-sigkill": 0x0036,
- "unmounting-sigterm": 0x0037,
- "unmounting-sigkill": 0x0038,
-
- // path_state_table, offset 0x0040
-
- // scope_state_table, offset 0x0050
- "abandoned": 0x0050,
-
- // slice_state_table, offset 0x0060
- "active": 0x0060,
-
- // socket_state_table, offset 0x0070
- "start-chown": 0x0070,
- "start-post": 0x0071,
- "listening": 0x0072,
- "stop-pre": 0x0073,
- "stop-pre-sigterm": 0x0074,
- "stop-pre-sigkill": 0x0075,
- "final-sigkill": 0x0076,
-
- // swap_state_table, offset 0x0080
- "activating": 0x0080,
- "activating-done": 0x0081,
- "deactivating": 0x0082,
- "deactivating-sigterm": 0x0083,
- "deactivating-sigkill": 0x0084,
-
- // target_state_table, offset 0x0090
-
- // timer_state_table, offset 0x00a0
- "elapsed": 0x00a0,
-}
-
-type client interface {
- Connected() bool
- Close()
-
- ListUnitFilesByPatternsContext(ctx context.Context, states, pattern []string) ([]dbus.UnitFile, error)
- ListUnitsByNamesContext(ctx context.Context, units []string) ([]dbus.UnitStatus, error)
- GetUnitTypePropertiesContext(ctx context.Context, unit, unitType string) (map[string]interface{}, error)
- GetUnitPropertiesContext(ctx context.Context, unit string) (map[string]interface{}, error)
- ListUnitsContext(ctx context.Context) ([]dbus.UnitStatus, error)
-}
-
-type archParams struct {
- client client
- pattern []string
- filter filter.Filter
- unitTypeDBus string
- scope string
- user string
- warnUnitProps map[string]bool
-}
-
-func (s *SystemdUnits) Init() error {
- // Set default pattern
- if s.Pattern == "" {
- s.Pattern = "*"
- }
-
- // Check unit-type and convert the first letter to uppercase as this is
- // what dbus expects.
- switch s.UnitType {
- case "":
- s.UnitType = "service"
- case "service", "socket", "target", "device", "mount", "automount", "swap",
- "timer", "path", "slice", "scope":
- default:
- return fmt.Errorf("invalid 'unittype' %q", s.UnitType)
- }
- s.unitTypeDBus = strings.ToUpper(s.UnitType[0:1]) + strings.ToLower(s.UnitType[1:])
-
- s.pattern = strings.Split(s.Pattern, " ")
- f, err := filter.Compile(s.pattern)
- if err != nil {
- return fmt.Errorf("compiling filter failed: %w", err)
- }
- s.filter = f
-
- switch s.Scope {
- case "", "system":
- s.scope = "system"
- case "user":
- u, err := user.Current()
- if err != nil {
- return fmt.Errorf("unable to determine user: %w", err)
- }
-
- s.scope = "user"
- s.user = u.Username
- default:
- return fmt.Errorf("invalid 'scope' %q", s.Scope)
- }
-
- s.warnUnitProps = make(map[string]bool)
-
- return nil
-}
-
-func (s *SystemdUnits) Start(telegraf.Accumulator) error {
- ctx := context.Background()
-
- var client *dbus.Conn
- var err error
- if s.scope == "user" {
- client, err = dbus.NewUserConnectionContext(ctx)
- } else {
- client, err = dbus.NewSystemConnectionContext(ctx)
- }
- if err != nil {
- return err
- }
-
- s.client = client
-
- return nil
-}
-
-func (s *SystemdUnits) Stop() {
- if s.client != nil && s.client.Connected() {
- s.client.Close()
- }
- s.client = nil
-}
-
-func (s *SystemdUnits) Gather(acc telegraf.Accumulator) error {
- // Reconnect in case the connection was lost
- if !s.client.Connected() {
- s.Log.Debug("Connection to systemd daemon lost, trying to reconnect...")
- s.Stop()
- if err := s.Start(acc); err != nil {
- return err
- }
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.Timeout))
- defer cancel()
-
- // List all loaded units to handle multi-instance units correctly
- loaded, err := s.client.ListUnitsContext(ctx)
- if err != nil {
- return fmt.Errorf("listing loaded units failed: %w", err)
- }
-
- var files []dbus.UnitFile
- if s.CollectDisabled {
- // List all unit files matching the pattern to also get disabled units
- list := []string{"enabled", "disabled", "static"}
- files, err = s.client.ListUnitFilesByPatternsContext(ctx, list, s.pattern)
- if err != nil {
- return fmt.Errorf("listing unit files failed: %w", err)
- }
- }
-
- // Collect all matching units, the loaded ones and the disabled ones
- states := make([]dbus.UnitStatus, 0, len(loaded))
-
- // Match all loaded units first
- seen := make(map[string]bool)
- for _, u := range loaded {
- if !s.filter.Match(u.Name) {
- continue
- }
- states = append(states, u)
-
- // Remember multi-instance units to remove duplicates from files
- instance := u.Name
- if strings.Contains(u.Name, "@") {
- prefix, _, _ := strings.Cut(u.Name, "@")
- suffix := path.Ext(u.Name)
- instance = prefix + "@" + suffix
- }
- seen[instance] = true
- }
-
- // Now split the unit-files into disabled ones and static ones, ignore
- // enabled units as those are already contained in the "loaded" list.
- if len(files) > 0 {
- disabled := make([]string, 0, len(files))
- static := make([]string, 0, len(files))
- for _, f := range files {
- name := path.Base(f.Path)
-
- switch f.Type {
- case "disabled":
- if seen[name] {
- continue
- }
- seen[name] = true
-
- // Detect disabled multi-instance units and declare them as static
- _, suffix, found := strings.Cut(name, "@")
- instance, _, _ := strings.Cut(suffix, ".")
- if found && instance == "" {
- static = append(static, name)
- continue
- }
- disabled = append(disabled, name)
- case "static":
- // Make sure we filter already loaded static multi-instance units
- instance := name
- if strings.Contains(name, "@") {
- prefix, _, _ := strings.Cut(name, "@")
- suffix := path.Ext(name)
- instance = prefix + "@" + suffix
- }
- if seen[instance] || seen[name] {
- continue
- }
- seen[instance] = true
- static = append(static, name)
- }
- }
-
- // Resolve the disabled and remaining static units
- disabledStates, err := s.client.ListUnitsByNamesContext(ctx, disabled)
- if err != nil {
- return fmt.Errorf("listing unit states failed: %w", err)
- }
- states = append(states, disabledStates...)
-
- // Add special information about unused static units
- for _, name := range static {
- if !strings.EqualFold(strings.TrimPrefix(path.Ext(name), "."), s.UnitType) {
- continue
- }
-
- states = append(states, dbus.UnitStatus{
- Name: name,
- LoadState: "stub",
- ActiveState: "inactive",
- SubState: "dead",
- })
- }
- }
-
- // Merge the unit information into one struct
- for _, state := range states {
- // Filter units of the wrong type
- if idx := strings.LastIndex(state.Name, "."); idx < 0 || state.Name[idx+1:] != s.UnitType {
- continue
- }
-
- // Map the state names to numerical values
- load, ok := loadMap[state.LoadState]
- if !ok {
- acc.AddError(fmt.Errorf("parsing field 'load' failed, value not in map: %s", state.LoadState))
- continue
- }
- active, ok := activeMap[state.ActiveState]
- if !ok {
- acc.AddError(fmt.Errorf("parsing field 'active' failed, value not in map: %s", state.ActiveState))
- continue
- }
- subState, ok := subMap[state.SubState]
- if !ok {
- acc.AddError(fmt.Errorf("parsing field 'sub' failed, value not in map: %s", state.SubState))
- continue
- }
-
- // Create the metric
- tags := map[string]string{
- "name": state.Name,
- "load": state.LoadState,
- "active": state.ActiveState,
- "sub": state.SubState,
- }
- if s.scope == "user" {
- tags["user"] = s.user
- }
-
- fields := map[string]interface{}{
- "load_code": load,
- "active_code": active,
- "sub_code": subState,
- }
-
- if s.Details {
- properties, err := s.client.GetUnitTypePropertiesContext(ctx, state.Name, s.unitTypeDBus)
- if err != nil {
- // Skip units returning "Unknown interface" errors as those indicate
- // that the unit is of the wrong type.
- if strings.Contains(err.Error(), "Unknown interface") {
- continue
- }
- // For other units we make up properties, usually those are
- // disabled multi-instance units
- properties = map[string]interface{}{
- "StatusErrno": int64(-1),
- "NRestarts": uint64(0),
- }
- }
-
- // Get required unit file properties
- unitProperties, err := s.client.GetUnitPropertiesContext(ctx, state.Name)
- if err != nil && !s.warnUnitProps[state.Name] {
- s.Log.Warnf("Cannot read unit properties for %q: %v", state.Name, err)
- s.warnUnitProps[state.Name] = true
- }
-
- // Set tags
- if v, found := unitProperties["UnitFileState"]; found {
- tags["state"] = v.(string)
- }
- if v, found := unitProperties["UnitFilePreset"]; found {
- tags["preset"] = v.(string)
- }
-
- // Set fields
- if v, found := unitProperties["ActiveEnterTimestamp"]; found {
- fields["active_enter_timestamp_us"] = v
- }
-
- fields["status_errno"] = properties["StatusErrno"]
- fields["restarts"] = properties["NRestarts"]
- fields["pid"] = properties["MainPID"]
-
- fields["mem_current"] = properties["MemoryCurrent"]
- fields["mem_peak"] = properties["MemoryPeak"]
- fields["mem_avail"] = properties["MemoryAvailable"]
-
- fields["swap_current"] = properties["MemorySwapCurrent"]
- fields["swap_peak"] = properties["MemorySwapPeak"]
-
- // Sanitize unset memory fields
- for k, value := range fields {
- switch {
- case strings.HasPrefix(k, "mem_"), strings.HasPrefix(k, "swap_"):
- v, ok := value.(uint64)
- if ok && v == math.MaxUint64 || value == nil {
- fields[k] = uint64(0)
- }
- }
- }
- }
- acc.AddFields("systemd_units", fields, tags)
- }
-
- return nil
-}
diff --git a/plugins/inputs/systemd_units/systemd_units_notlinux.go b/plugins/inputs/systemd_units/systemd_units_notlinux.go
index 05136ec0d..5a979183e 100644
--- a/plugins/inputs/systemd_units/systemd_units_notlinux.go
+++ b/plugins/inputs/systemd_units/systemd_units_notlinux.go
@@ -1,20 +1,33 @@
+//go:generate ../../../tools/readme_config_includer/generator
//go:build !linux
package systemd_units
-import "github.com/influxdata/telegraf"
+import (
+ _ "embed"
-type archParams struct{}
+ "github.com/influxdata/telegraf"
+ "github.com/influxdata/telegraf/plugins/inputs"
+)
+
+//go:embed sample.conf
+var sampleConfig string
+
+type SystemdUnits struct {
+ Log telegraf.Logger `toml:"-"`
+}
+
+func (*SystemdUnits) SampleConfig() string { return sampleConfig }
func (s *SystemdUnits) Init() error {
- s.Log.Info("Skipping plugin as it is not supported by this platform!")
-
- // Required to remove linter-warning on unused struct member
- _ = s.archParams
-
+ s.Log.Warn("Current platform is not supported")
return nil
}
-func (*SystemdUnits) Gather(_ telegraf.Accumulator) error {
- return nil
+func (*SystemdUnits) Gather(telegraf.Accumulator) error { return nil }
+
+func init() {
+ inputs.Add("systemd_units", func() telegraf.Input {
+ return &SystemdUnits{}
+ })
}