chore: Fix linter findings for `revive:exported` in `plugins/inputs/m*` (#16191)

This commit is contained in:
Paweł Żak 2024-11-18 12:27:17 +01:00 committed by GitHub
parent d075815f29
commit 8a7947abbb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 923 additions and 1014 deletions

View File

@ -22,7 +22,7 @@ const (
var mailchimpDatacenter = regexp.MustCompile("[a-z]+[0-9]+$") var mailchimpDatacenter = regexp.MustCompile("[a-z]+[0-9]+$")
type chimpAPI struct { type chimpAPI struct {
Transport http.RoundTripper transport http.RoundTripper
debug bool debug bool
sync.Mutex sync.Mutex
@ -32,30 +32,30 @@ type chimpAPI struct {
} }
type reportsParams struct { type reportsParams struct {
Count string count string
Offset string offset string
SinceSendTime string sinceSendTime string
BeforeSendTime string beforeSendTime string
} }
func (p *reportsParams) String() string { func (p *reportsParams) String() string {
v := url.Values{} v := url.Values{}
if p.Count != "" { if p.count != "" {
v.Set("count", p.Count) v.Set("count", p.count)
} }
if p.Offset != "" { if p.offset != "" {
v.Set("offset", p.Offset) v.Set("offset", p.offset)
} }
if p.BeforeSendTime != "" { if p.beforeSendTime != "" {
v.Set("before_send_time", p.BeforeSendTime) v.Set("before_send_time", p.beforeSendTime)
} }
if p.SinceSendTime != "" { if p.sinceSendTime != "" {
v.Set("since_send_time", p.SinceSendTime) v.Set("since_send_time", p.sinceSendTime)
} }
return v.Encode() return v.Encode()
} }
func NewChimpAPI(apiKey string, log telegraf.Logger) *chimpAPI { func newChimpAPI(apiKey string, log telegraf.Logger) *chimpAPI {
u := &url.URL{} u := &url.URL{}
u.Scheme = "https" u.Scheme = "https"
u.Host = mailchimpDatacenter.FindString(apiKey) + ".api.mailchimp.com" u.Host = mailchimpDatacenter.FindString(apiKey) + ".api.mailchimp.com"
@ -86,7 +86,7 @@ func chimpErrorCheck(body []byte) error {
return nil return nil
} }
func (a *chimpAPI) GetReports(params reportsParams) (reportsResponse, error) { func (a *chimpAPI) getReports(params reportsParams) (reportsResponse, error) {
a.Lock() a.Lock()
defer a.Unlock() defer a.Unlock()
a.url.Path = reportsEndpoint a.url.Path = reportsEndpoint
@ -105,7 +105,7 @@ func (a *chimpAPI) GetReports(params reportsParams) (reportsResponse, error) {
return response, nil return response, nil
} }
func (a *chimpAPI) GetReport(campaignID string) (report, error) { func (a *chimpAPI) getReport(campaignID string) (report, error) {
a.Lock() a.Lock()
defer a.Unlock() defer a.Unlock()
a.url.Path = fmt.Sprintf(reportsEndpointCampaign, campaignID) a.url.Path = fmt.Sprintf(reportsEndpointCampaign, campaignID)
@ -126,7 +126,7 @@ func (a *chimpAPI) GetReport(campaignID string) (report, error) {
func (a *chimpAPI) runChimp(params reportsParams) ([]byte, error) { func (a *chimpAPI) runChimp(params reportsParams) ([]byte, error) {
client := &http.Client{ client := &http.Client{
Transport: a.Transport, Transport: a.transport,
Timeout: 4 * time.Second, Timeout: 4 * time.Second,
} }

View File

@ -14,13 +14,12 @@ import (
var sampleConfig string var sampleConfig string
type MailChimp struct { type MailChimp struct {
APIKey string `toml:"api_key"`
DaysOld int `toml:"days_old"`
CampaignID string `toml:"campaign_id"`
Log telegraf.Logger `toml:"-"`
api *chimpAPI api *chimpAPI
APIKey string `toml:"api_key"`
DaysOld int `toml:"days_old"`
CampaignID string `toml:"campaign_id"`
Log telegraf.Logger `toml:"-"`
} }
func (*MailChimp) SampleConfig() string { func (*MailChimp) SampleConfig() string {
@ -28,7 +27,7 @@ func (*MailChimp) SampleConfig() string {
} }
func (m *MailChimp) Init() error { func (m *MailChimp) Init() error {
m.api = NewChimpAPI(m.APIKey, m.Log) m.api = newChimpAPI(m.APIKey, m.Log)
return nil return nil
} }
@ -45,8 +44,8 @@ func (m *MailChimp) Gather(acc telegraf.Accumulator) error {
since = now.Add(-d).Format(time.RFC3339) since = now.Add(-d).Format(time.RFC3339)
} }
reports, err := m.api.GetReports(reportsParams{ reports, err := m.api.getReports(reportsParams{
SinceSendTime: since, sinceSendTime: since,
}) })
if err != nil { if err != nil {
return err return err
@ -57,7 +56,7 @@ func (m *MailChimp) Gather(acc telegraf.Accumulator) error {
gatherReport(acc, report, now) gatherReport(acc, report, now)
} }
} else { } else {
report, err := m.api.GetReport(m.CampaignID) report, err := m.api.getReport(m.CampaignID)
if err != nil { if err != nil {
return err return err
} }

View File

@ -19,69 +19,69 @@ import (
//go:embed sample.conf //go:embed sample.conf
var sampleConfig string var sampleConfig string
// Marklogic configuration toml const (
// MarkLogic v2 management api endpoints for hosts status
statsPath = "/manage/v2/hosts/"
viewFormat = "view=status&format=json"
)
type Marklogic struct { type Marklogic struct {
URL string `toml:"url"` URL string `toml:"url"`
Hosts []string `toml:"hosts"` Hosts []string `toml:"hosts"`
Username string `toml:"username"` Username string `toml:"username"`
Password string `toml:"password"` Password string `toml:"password"`
Sources []string
tls.ClientConfig tls.ClientConfig
client *http.Client client *http.Client
sources []string
} }
type MlPointInt struct { type mlPointInt struct {
Value int `json:"value"` Value int `json:"value"`
} }
type MlPointFloat struct { type mlPointFloat struct {
Value float64 `json:"value"` Value float64 `json:"value"`
} }
type MlPointBool struct { type mlPointBool struct {
Value bool `json:"value"` Value bool `json:"value"`
} }
// MarkLogic v2 management api endpoints for hosts status type mlHost struct {
const statsPath = "/manage/v2/hosts/"
const viewFormat = "view=status&format=json"
type MlHost struct {
HostStatus struct { HostStatus struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
StatusProperties struct { StatusProperties struct {
Online MlPointBool `json:"online"` Online mlPointBool `json:"online"`
LoadProperties struct { LoadProperties struct {
TotalLoad MlPointFloat `json:"total-load"` TotalLoad mlPointFloat `json:"total-load"`
} `json:"load-properties"` } `json:"load-properties"`
RateProperties struct { RateProperties struct {
TotalRate MlPointFloat `json:"total-rate"` TotalRate mlPointFloat `json:"total-rate"`
} `json:"rate-properties"` } `json:"rate-properties"`
StatusDetail struct { StatusDetail struct {
Cpus MlPointInt `json:"cpus"` Cpus mlPointInt `json:"cpus"`
Cores MlPointInt `json:"cores"` Cores mlPointInt `json:"cores"`
TotalCPUStatUser float64 `json:"total-cpu-stat-user"` TotalCPUStatUser float64 `json:"total-cpu-stat-user"`
TotalCPUStatSystem float64 `json:"total-cpu-stat-system"` TotalCPUStatSystem float64 `json:"total-cpu-stat-system"`
TotalCPUStatIdle float64 `json:"total-cpu-stat-idle"` TotalCPUStatIdle float64 `json:"total-cpu-stat-idle"`
TotalCPUStatIowait float64 `json:"total-cpu-stat-iowait"` TotalCPUStatIowait float64 `json:"total-cpu-stat-iowait"`
MemoryProcessSize MlPointInt `json:"memory-process-size"` MemoryProcessSize mlPointInt `json:"memory-process-size"`
MemoryProcessRss MlPointInt `json:"memory-process-rss"` MemoryProcessRss mlPointInt `json:"memory-process-rss"`
MemorySystemTotal MlPointInt `json:"memory-system-total"` MemorySystemTotal mlPointInt `json:"memory-system-total"`
MemorySystemFree MlPointInt `json:"memory-system-free"` MemorySystemFree mlPointInt `json:"memory-system-free"`
MemoryProcessSwapSize MlPointInt `json:"memory-process-swap-size"` MemoryProcessSwapSize mlPointInt `json:"memory-process-swap-size"`
MemorySize MlPointInt `json:"memory-size"` MemorySize mlPointInt `json:"memory-size"`
HostSize MlPointInt `json:"host-size"` HostSize mlPointInt `json:"host-size"`
LogDeviceSpace MlPointInt `json:"log-device-space"` LogDeviceSpace mlPointInt `json:"log-device-space"`
DataDirSpace MlPointInt `json:"data-dir-space"` DataDirSpace mlPointInt `json:"data-dir-space"`
QueryReadBytes MlPointInt `json:"query-read-bytes"` QueryReadBytes mlPointInt `json:"query-read-bytes"`
QueryReadLoad MlPointInt `json:"query-read-load"` QueryReadLoad mlPointInt `json:"query-read-load"`
MergeReadLoad MlPointInt `json:"merge-read-load"` MergeReadLoad mlPointInt `json:"merge-read-load"`
MergeWriteLoad MlPointInt `json:"merge-write-load"` MergeWriteLoad mlPointInt `json:"merge-write-load"`
HTTPServerReceiveBytes MlPointInt `json:"http-server-receive-bytes"` HTTPServerReceiveBytes mlPointInt `json:"http-server-receive-bytes"`
HTTPServerSendBytes MlPointInt `json:"http-server-send-bytes"` HTTPServerSendBytes mlPointInt `json:"http-server-send-bytes"`
} `json:"status-detail"` } `json:"status-detail"`
} `json:"status-properties"` } `json:"status-properties"`
} `json:"host-status"` } `json:"host-status"`
@ -91,7 +91,6 @@ func (*Marklogic) SampleConfig() string {
return sampleConfig return sampleConfig
} }
// Init parse all source URLs and place on the Marklogic struct
func (c *Marklogic) Init() error { func (c *Marklogic) Init() error {
if len(c.URL) == 0 { if len(c.URL) == 0 {
c.URL = "http://localhost:8002/" c.URL = "http://localhost:8002/"
@ -108,12 +107,11 @@ func (c *Marklogic) Init() error {
addr.RawQuery = viewFormat addr.RawQuery = viewFormat
u := addr.String() u := addr.String()
c.Sources = append(c.Sources, u) c.sources = append(c.sources, u)
} }
return nil return nil
} }
// Gather metrics from HTTP Server.
func (c *Marklogic) Gather(accumulator telegraf.Accumulator) error { func (c *Marklogic) Gather(accumulator telegraf.Accumulator) error {
var wg sync.WaitGroup var wg sync.WaitGroup
@ -127,7 +125,7 @@ func (c *Marklogic) Gather(accumulator telegraf.Accumulator) error {
} }
// Range over all source URL's appended to the struct // Range over all source URL's appended to the struct
for _, serv := range c.Sources { for _, serv := range c.sources {
wg.Add(1) wg.Add(1)
go func(serv string) { go func(serv string) {
defer wg.Done() defer wg.Done()
@ -143,7 +141,7 @@ func (c *Marklogic) Gather(accumulator telegraf.Accumulator) error {
} }
func (c *Marklogic) fetchAndInsertData(acc telegraf.Accumulator, address string) error { func (c *Marklogic) fetchAndInsertData(acc telegraf.Accumulator, address string) error {
ml := &MlHost{} ml := &mlHost{}
if err := c.gatherJSONData(address, ml); err != nil { if err := c.gatherJSONData(address, ml); err != nil {
return err return err
} }

View File

@ -33,7 +33,7 @@ func TestMarklogic(t *testing.T) {
ml := &Marklogic{ ml := &Marklogic{
Hosts: []string{"example1"}, Hosts: []string{"example1"},
URL: ts.URL, URL: ts.URL,
// Sources: []string{"http://localhost:8002/manage/v2/hosts/hostname1?view=status&format=json"}, // sources: []string{"http://localhost:8002/manage/v2/hosts/hostname1?view=status&format=json"},
} }
// Create a test accumulator // Create a test accumulator

View File

@ -21,12 +21,6 @@ import (
//go:embed sample.conf //go:embed sample.conf
var sampleConfig string var sampleConfig string
// Mcrouter is a mcrouter plugin
type Mcrouter struct {
Servers []string
Timeout config.Duration
}
// enum for statType // enum for statType
type statType int type statType int
@ -35,86 +29,90 @@ const (
typeFloat statType = iota typeFloat statType = iota
) )
var defaultTimeout = 5 * time.Second var (
defaultTimeout = 5 * time.Second
defaultServerURL = url.URL{
Scheme: "tcp",
Host: "localhost:11211",
}
// The list of metrics that should be sent
sendMetrics = map[string]statType{
"uptime": typeInt,
"num_servers": typeInt,
"num_servers_new": typeInt,
"num_servers_up": typeInt,
"num_servers_down": typeInt,
"num_servers_closed": typeInt,
"num_clients": typeInt,
"num_suspect_servers": typeInt,
"destination_batches_sum": typeInt,
"destination_requests_sum": typeInt,
"outstanding_route_get_reqs_queued": typeInt,
"outstanding_route_update_reqs_queued": typeInt,
"outstanding_route_get_avg_queue_size": typeInt,
"outstanding_route_update_avg_queue_size": typeInt,
"outstanding_route_get_avg_wait_time_sec": typeInt,
"outstanding_route_update_avg_wait_time_sec": typeInt,
"retrans_closed_connections": typeInt,
"destination_pending_reqs": typeInt,
"destination_inflight_reqs": typeInt,
"destination_batch_size": typeInt,
"asynclog_requests": typeInt,
"proxy_reqs_processing": typeInt,
"proxy_reqs_waiting": typeInt,
"client_queue_notify_period": typeInt,
"rusage_system": typeFloat,
"rusage_user": typeFloat,
"ps_num_minor_faults": typeInt,
"ps_num_major_faults": typeInt,
"ps_user_time_sec": typeFloat,
"ps_system_time_sec": typeFloat,
"ps_vsize": typeInt,
"ps_rss": typeInt,
"fibers_allocated": typeInt,
"fibers_pool_size": typeInt,
"fibers_stack_high_watermark": typeInt,
"successful_client_connections": typeInt,
"duration_us": typeInt,
"destination_max_pending_reqs": typeInt,
"destination_max_inflight_reqs": typeInt,
"retrans_per_kbyte_max": typeInt,
"cmd_get_count": typeInt,
"cmd_delete_out": typeInt,
"cmd_lease_get": typeInt,
"cmd_set": typeInt,
"cmd_get_out_all": typeInt,
"cmd_get_out": typeInt,
"cmd_lease_set_count": typeInt,
"cmd_other_out_all": typeInt,
"cmd_lease_get_out": typeInt,
"cmd_set_count": typeInt,
"cmd_lease_set_out": typeInt,
"cmd_delete_count": typeInt,
"cmd_other": typeInt,
"cmd_delete": typeInt,
"cmd_get": typeInt,
"cmd_lease_set": typeInt,
"cmd_set_out": typeInt,
"cmd_lease_get_count": typeInt,
"cmd_other_out": typeInt,
"cmd_lease_get_out_all": typeInt,
"cmd_set_out_all": typeInt,
"cmd_other_count": typeInt,
"cmd_delete_out_all": typeInt,
"cmd_lease_set_out_all": typeInt,
}
)
var defaultServerURL = url.URL{ type Mcrouter struct {
Scheme: "tcp", Servers []string `toml:"servers"`
Host: "localhost:11211", Timeout config.Duration `toml:"timeout"`
}
// The list of metrics that should be sent
var sendMetrics = map[string]statType{
"uptime": typeInt,
"num_servers": typeInt,
"num_servers_new": typeInt,
"num_servers_up": typeInt,
"num_servers_down": typeInt,
"num_servers_closed": typeInt,
"num_clients": typeInt,
"num_suspect_servers": typeInt,
"destination_batches_sum": typeInt,
"destination_requests_sum": typeInt,
"outstanding_route_get_reqs_queued": typeInt,
"outstanding_route_update_reqs_queued": typeInt,
"outstanding_route_get_avg_queue_size": typeInt,
"outstanding_route_update_avg_queue_size": typeInt,
"outstanding_route_get_avg_wait_time_sec": typeInt,
"outstanding_route_update_avg_wait_time_sec": typeInt,
"retrans_closed_connections": typeInt,
"destination_pending_reqs": typeInt,
"destination_inflight_reqs": typeInt,
"destination_batch_size": typeInt,
"asynclog_requests": typeInt,
"proxy_reqs_processing": typeInt,
"proxy_reqs_waiting": typeInt,
"client_queue_notify_period": typeInt,
"rusage_system": typeFloat,
"rusage_user": typeFloat,
"ps_num_minor_faults": typeInt,
"ps_num_major_faults": typeInt,
"ps_user_time_sec": typeFloat,
"ps_system_time_sec": typeFloat,
"ps_vsize": typeInt,
"ps_rss": typeInt,
"fibers_allocated": typeInt,
"fibers_pool_size": typeInt,
"fibers_stack_high_watermark": typeInt,
"successful_client_connections": typeInt,
"duration_us": typeInt,
"destination_max_pending_reqs": typeInt,
"destination_max_inflight_reqs": typeInt,
"retrans_per_kbyte_max": typeInt,
"cmd_get_count": typeInt,
"cmd_delete_out": typeInt,
"cmd_lease_get": typeInt,
"cmd_set": typeInt,
"cmd_get_out_all": typeInt,
"cmd_get_out": typeInt,
"cmd_lease_set_count": typeInt,
"cmd_other_out_all": typeInt,
"cmd_lease_get_out": typeInt,
"cmd_set_count": typeInt,
"cmd_lease_set_out": typeInt,
"cmd_delete_count": typeInt,
"cmd_other": typeInt,
"cmd_delete": typeInt,
"cmd_get": typeInt,
"cmd_lease_set": typeInt,
"cmd_set_out": typeInt,
"cmd_lease_get_count": typeInt,
"cmd_other_out": typeInt,
"cmd_lease_get_out_all": typeInt,
"cmd_set_out_all": typeInt,
"cmd_other_count": typeInt,
"cmd_delete_out_all": typeInt,
"cmd_lease_set_out_all": typeInt,
} }
func (*Mcrouter) SampleConfig() string { func (*Mcrouter) SampleConfig() string {
return sampleConfig return sampleConfig
} }
// Gather reads stats from all configured servers accumulates stats
func (m *Mcrouter) Gather(acc telegraf.Accumulator) error { func (m *Mcrouter) Gather(acc telegraf.Accumulator) error {
ctx := context.Background() ctx := context.Background()
@ -136,8 +134,8 @@ func (m *Mcrouter) Gather(acc telegraf.Accumulator) error {
return nil return nil
} }
// ParseAddress parses an address string into 'host:port' and 'protocol' parts // parseAddress parses an address string into 'host:port' and 'protocol' parts
func (m *Mcrouter) ParseAddress(address string) (parsedAddress, protocol string, err error) { func (m *Mcrouter) parseAddress(address string) (parsedAddress, protocol string, err error) {
var host string var host string
var port string var port string
@ -189,7 +187,7 @@ func (m *Mcrouter) gatherServer(ctx context.Context, address string, acc telegra
var protocol string var protocol string
var dialer net.Dialer var dialer net.Dialer
address, protocol, err = m.ParseAddress(address) address, protocol, err = m.parseAddress(address)
if err != nil { if err != nil {
return err return err
} }

View File

@ -32,7 +32,7 @@ func TestAddressParsing(t *testing.T) {
} }
for _, args := range acceptTests { for _, args := range acceptTests {
address, protocol, err := m.ParseAddress(args[0]) address, protocol, err := m.parseAddress(args[0])
require.NoError(t, err, args[0]) require.NoError(t, err, args[0])
require.Equal(t, args[1], address, args[0]) require.Equal(t, args[1], address, args[0])
@ -40,7 +40,7 @@ func TestAddressParsing(t *testing.T) {
} }
for _, addr := range rejectTests { for _, addr := range rejectTests {
address, protocol, err := m.ParseAddress(addr) address, protocol, err := m.parseAddress(addr)
require.Error(t, err, addr) require.Error(t, err, addr)
require.Empty(t, address, addr) require.Empty(t, address, addr)

View File

@ -44,6 +44,10 @@ var (
componentDeviceRE = regexp.MustCompile(`(.*)\[\d+\]`) componentDeviceRE = regexp.MustCompile(`(.*)\[\d+\]`)
) )
type Mdstat struct {
FileName string `toml:"file_name"`
}
type statusLine struct { type statusLine struct {
active int64 active int64
total int64 total int64
@ -58,10 +62,6 @@ type recoveryLine struct {
speed float64 speed float64
} }
type MdstatConf struct {
FileName string `toml:"file_name"`
}
func evalStatusLine(deviceLine, statusLineStr string) (statusLine, error) { func evalStatusLine(deviceLine, statusLineStr string) (statusLine, error) {
sizeFields := strings.Fields(statusLineStr) sizeFields := strings.Fields(statusLineStr)
if len(sizeFields) < 1 { if len(sizeFields) < 1 {
@ -173,11 +173,11 @@ func evalComponentDevices(deviceFields []string) string {
return strings.Join(mdComponentDevices, ",") return strings.Join(mdComponentDevices, ",")
} }
func (*MdstatConf) SampleConfig() string { func (*Mdstat) SampleConfig() string {
return sampleConfig return sampleConfig
} }
func (k *MdstatConf) Gather(acc telegraf.Accumulator) error { func (k *Mdstat) Gather(acc telegraf.Accumulator) error {
data, err := k.getProcMdstat() data, err := k.getProcMdstat()
if err != nil { if err != nil {
return err return err
@ -267,7 +267,7 @@ func (k *MdstatConf) Gather(acc telegraf.Accumulator) error {
return nil return nil
} }
func (k *MdstatConf) getProcMdstat() ([]byte, error) { func (k *Mdstat) getProcMdstat() ([]byte, error) {
var mdStatFile string var mdStatFile string
if k.FileName == "" { if k.FileName == "" {
mdStatFile = internal.GetProcPath() + "/mdstat" mdStatFile = internal.GetProcPath() + "/mdstat"
@ -289,5 +289,5 @@ func (k *MdstatConf) getProcMdstat() ([]byte, error) {
} }
func init() { func init() {
inputs.Add("mdstat", func() telegraf.Input { return &MdstatConf{} }) inputs.Add("mdstat", func() telegraf.Input { return &Mdstat{} })
} }

View File

@ -17,12 +17,14 @@ type Mdstat struct {
Log telegraf.Logger `toml:"-"` Log telegraf.Logger `toml:"-"`
} }
func (*Mdstat) SampleConfig() string { return sampleConfig }
func (m *Mdstat) Init() error { func (m *Mdstat) Init() error {
m.Log.Warn("current platform is not supported") m.Log.Warn("Current platform is not supported")
return nil return nil
} }
func (*Mdstat) SampleConfig() string { return sampleConfig }
func (*Mdstat) Gather(_ telegraf.Accumulator) error { return nil } func (*Mdstat) Gather(telegraf.Accumulator) error { return nil }
func init() { func init() {
inputs.Add("mdstat", func() telegraf.Input { inputs.Add("mdstat", func() telegraf.Input {

View File

@ -14,7 +14,7 @@ import (
func TestFullMdstatProcFile(t *testing.T) { func TestFullMdstatProcFile(t *testing.T) {
filename := makeFakeMDStatFile([]byte(mdStatFileFull)) filename := makeFakeMDStatFile([]byte(mdStatFileFull))
defer os.Remove(filename) defer os.Remove(filename)
k := MdstatConf{ k := Mdstat{
FileName: filename, FileName: filename,
} }
acc := testutil.Accumulator{} acc := testutil.Accumulator{}
@ -39,7 +39,7 @@ func TestFullMdstatProcFile(t *testing.T) {
func TestMdstatSyncStart(t *testing.T) { func TestMdstatSyncStart(t *testing.T) {
filename := makeFakeMDStatFile([]byte(mdStatSyncStart)) filename := makeFakeMDStatFile([]byte(mdStatSyncStart))
defer os.Remove(filename) defer os.Remove(filename)
k := MdstatConf{ k := Mdstat{
FileName: filename, FileName: filename,
} }
acc := testutil.Accumulator{} acc := testutil.Accumulator{}
@ -65,7 +65,7 @@ func TestFailedDiskMdStatProcFile1(t *testing.T) {
filename := makeFakeMDStatFile([]byte(mdStatFileFailedDisk)) filename := makeFakeMDStatFile([]byte(mdStatFileFailedDisk))
defer os.Remove(filename) defer os.Remove(filename)
k := MdstatConf{ k := Mdstat{
FileName: filename, FileName: filename,
} }
@ -92,7 +92,7 @@ func TestEmptyMdStatProcFile1(t *testing.T) {
filename := makeFakeMDStatFile([]byte(mdStatFileEmpty)) filename := makeFakeMDStatFile([]byte(mdStatFileEmpty))
defer os.Remove(filename) defer os.Remove(filename)
k := MdstatConf{ k := Mdstat{
FileName: filename, FileName: filename,
} }
@ -105,7 +105,7 @@ func TestInvalidMdStatProcFile1(t *testing.T) {
filename := makeFakeMDStatFile([]byte(mdStatFileInvalid)) filename := makeFakeMDStatFile([]byte(mdStatFileInvalid))
defer os.Remove(filename) defer os.Remove(filename)
k := MdstatConf{ k := Mdstat{
FileName: filename, FileName: filename,
} }

View File

@ -14,21 +14,21 @@ import (
//go:embed sample.conf //go:embed sample.conf
var sampleConfig string var sampleConfig string
type MemStats struct { type Mem struct {
ps system.PS ps system.PS
platform string platform string
} }
func (*MemStats) SampleConfig() string { func (*Mem) SampleConfig() string {
return sampleConfig return sampleConfig
} }
func (ms *MemStats) Init() error { func (ms *Mem) Init() error {
ms.platform = runtime.GOOS ms.platform = runtime.GOOS
return nil return nil
} }
func (ms *MemStats) Gather(acc telegraf.Accumulator) error { func (ms *Mem) Gather(acc telegraf.Accumulator) error {
vm, err := ms.ps.VMStat() vm, err := ms.ps.VMStat()
if err != nil { if err != nil {
return fmt.Errorf("error getting virtual memory info: %w", err) return fmt.Errorf("error getting virtual memory info: %w", err)
@ -102,6 +102,6 @@ func (ms *MemStats) Gather(acc telegraf.Accumulator) error {
func init() { func init() {
ps := system.NewSystemPS() ps := system.NewSystemPS()
inputs.Add("mem", func() telegraf.Input { inputs.Add("mem", func() telegraf.Input {
return &MemStats{ps: ps} return &Mem{ps: ps}
}) })
} }

View File

@ -4,11 +4,12 @@ import (
"testing" "testing"
"time" "time"
"github.com/shirou/gopsutil/v4/mem"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs/system" "github.com/influxdata/telegraf/plugins/inputs/system"
"github.com/influxdata/telegraf/testutil" "github.com/influxdata/telegraf/testutil"
"github.com/shirou/gopsutil/v4/mem"
"github.com/stretchr/testify/require"
) )
func TestMemStats(t *testing.T) { func TestMemStats(t *testing.T) {
@ -55,7 +56,7 @@ func TestMemStats(t *testing.T) {
} }
mps.On("VMStat").Return(vms, nil) mps.On("VMStat").Return(vms, nil)
plugin := &MemStats{ps: &mps} plugin := &Mem{ps: &mps}
err = plugin.Init() err = plugin.Init()
require.NoError(t, err) require.NoError(t, err)

View File

@ -22,7 +22,82 @@ import (
//go:embed sample.conf //go:embed sample.conf
var sampleConfig string var sampleConfig string
// Memcached is a memcached plugin var (
defaultTimeout = 5 * time.Second
// The list of metrics that should be sent
sendMetrics = []string{
"accepting_conns",
"auth_cmds",
"auth_errors",
"bytes",
"bytes_read",
"bytes_written",
"cas_badval",
"cas_hits",
"cas_misses",
"cmd_flush",
"cmd_get",
"cmd_set",
"cmd_touch",
"conn_yields",
"connection_structures",
"curr_connections",
"curr_items",
"decr_hits",
"decr_misses",
"delete_hits",
"delete_misses",
"evicted_active",
"evicted_unfetched",
"evictions",
"expired_unfetched",
"extstore_compact_lost",
"extstore_compact_rescues",
"extstore_compact_resc_cold",
"extstore_compact_resc_old",
"extstore_compact_skipped",
"extstore_page_allocs",
"extstore_page_evictions",
"extstore_page_reclaims",
"extstore_pages_free",
"extstore_pages_used",
"extstore_objects_evicted",
"extstore_objects_read",
"extstore_objects_written",
"extstore_objects_used",
"extstore_bytes_evicted",
"extstore_bytes_written",
"extstore_bytes_read",
"extstore_bytes_used",
"extstore_bytes_fragmented",
"extstore_limit_maxbytes",
"extstore_io_queue",
"get_expired",
"get_flushed",
"get_hits",
"get_misses",
"hash_bytes",
"hash_is_expanding",
"hash_power_level",
"incr_hits",
"incr_misses",
"limit_maxbytes",
"listen_disabled_num",
"max_connections",
"reclaimed",
"rejected_connections",
"store_no_memory",
"store_too_large",
"threads",
"total_connections",
"total_items",
"touch_hits",
"touch_misses",
"uptime",
}
)
type Memcached struct { type Memcached struct {
Servers []string `toml:"servers"` Servers []string `toml:"servers"`
UnixSockets []string `toml:"unix_sockets"` UnixSockets []string `toml:"unix_sockets"`
@ -30,85 +105,10 @@ type Memcached struct {
common_tls.ClientConfig common_tls.ClientConfig
} }
var defaultTimeout = 5 * time.Second
// The list of metrics that should be sent
var sendMetrics = []string{
"accepting_conns",
"auth_cmds",
"auth_errors",
"bytes",
"bytes_read",
"bytes_written",
"cas_badval",
"cas_hits",
"cas_misses",
"cmd_flush",
"cmd_get",
"cmd_set",
"cmd_touch",
"conn_yields",
"connection_structures",
"curr_connections",
"curr_items",
"decr_hits",
"decr_misses",
"delete_hits",
"delete_misses",
"evicted_active",
"evicted_unfetched",
"evictions",
"expired_unfetched",
"extstore_compact_lost",
"extstore_compact_rescues",
"extstore_compact_resc_cold",
"extstore_compact_resc_old",
"extstore_compact_skipped",
"extstore_page_allocs",
"extstore_page_evictions",
"extstore_page_reclaims",
"extstore_pages_free",
"extstore_pages_used",
"extstore_objects_evicted",
"extstore_objects_read",
"extstore_objects_written",
"extstore_objects_used",
"extstore_bytes_evicted",
"extstore_bytes_written",
"extstore_bytes_read",
"extstore_bytes_used",
"extstore_bytes_fragmented",
"extstore_limit_maxbytes",
"extstore_io_queue",
"get_expired",
"get_flushed",
"get_hits",
"get_misses",
"hash_bytes",
"hash_is_expanding",
"hash_power_level",
"incr_hits",
"incr_misses",
"limit_maxbytes",
"listen_disabled_num",
"max_connections",
"reclaimed",
"rejected_connections",
"store_no_memory",
"store_too_large",
"threads",
"total_connections",
"total_items",
"touch_hits",
"touch_misses",
"uptime",
}
func (*Memcached) SampleConfig() string { func (*Memcached) SampleConfig() string {
return sampleConfig return sampleConfig
} }
// Gather reads stats from all configured servers accumulates stats
func (m *Memcached) Gather(acc telegraf.Accumulator) error { func (m *Memcached) Gather(acc telegraf.Accumulator) error {
if len(m.Servers) == 0 && len(m.UnixSockets) == 0 { if len(m.Servers) == 0 && len(m.UnixSockets) == 0 {
return m.gatherServer(":11211", false, acc) return m.gatherServer(":11211", false, acc)
@ -125,11 +125,7 @@ func (m *Memcached) Gather(acc telegraf.Accumulator) error {
return nil return nil
} }
func (m *Memcached) gatherServer( func (m *Memcached) gatherServer(address string, unix bool, acc telegraf.Accumulator) error {
address string,
unix bool,
acc telegraf.Accumulator,
) error {
var conn net.Conn var conn net.Conn
var err error var err error
var dialer proxy.Dialer var dialer proxy.Dialer

View File

@ -23,22 +23,27 @@ import (
//go:embed sample.conf //go:embed sample.conf
var sampleConfig string var sampleConfig string
type Role string type role string
const ( const (
MASTER Role = "master" master role = "master"
SLAVE Role = "slave" slave role = "slave"
) )
var allMetrics = map[role][]string{
master: {"resources", "master", "system", "agents", "frameworks", "framework_offers", "tasks", "messages", "evqueue", "registrar", "allocator"},
slave: {"resources", "agent", "system", "executors", "tasks", "messages"},
}
type Mesos struct { type Mesos struct {
Timeout int Timeout int `toml:"timeout"`
Masters []string Masters []string `toml:"masters"`
MasterCols []string `toml:"master_collections"` MasterCols []string `toml:"master_collections"`
Slaves []string Slaves []string `toml:"slaves"`
SlaveCols []string `toml:"slave_collections"` SlaveCols []string `toml:"slave_collections"`
tls.ClientConfig tls.ClientConfig
Log telegraf.Logger Log telegraf.Logger `toml:"-"`
initialized bool initialized bool
client *http.Client client *http.Client
@ -46,21 +51,52 @@ type Mesos struct {
slaveURLs []*url.URL slaveURLs []*url.URL
} }
var allMetrics = map[Role][]string{ func (*Mesos) SampleConfig() string {
MASTER: {"resources", "master", "system", "agents", "frameworks", "framework_offers", "tasks", "messages", "evqueue", "registrar", "allocator"}, return sampleConfig
SLAVE: {"resources", "agent", "system", "executors", "tasks", "messages"},
} }
func (m *Mesos) parseURL(s string, role Role) (*url.URL, error) { func (m *Mesos) Gather(acc telegraf.Accumulator) error {
if !m.initialized {
err := m.initialize()
if err != nil {
return err
}
m.initialized = true
}
var wg sync.WaitGroup
for _, mstr := range m.masterURLs {
wg.Add(1)
go func(mstr *url.URL) {
acc.AddError(m.gatherMainMetrics(mstr, master, acc))
wg.Done()
}(mstr)
}
for _, slv := range m.slaveURLs {
wg.Add(1)
go func(slv *url.URL) {
acc.AddError(m.gatherMainMetrics(slv, slave, acc))
wg.Done()
}(slv)
}
wg.Wait()
return nil
}
func (m *Mesos) parseURL(s string, role role) (*url.URL, error) {
if !strings.HasPrefix(s, "http://") && !strings.HasPrefix(s, "https://") { if !strings.HasPrefix(s, "http://") && !strings.HasPrefix(s, "https://") {
host, port, err := net.SplitHostPort(s) host, port, err := net.SplitHostPort(s)
// no port specified // no port specified
if err != nil { if err != nil {
host = s host = s
switch role { switch role {
case MASTER: case master:
port = "5050" port = "5050"
case SLAVE: case slave:
port = "5051" port = "5051"
} }
} }
@ -74,11 +110,11 @@ func (m *Mesos) parseURL(s string, role Role) (*url.URL, error) {
func (m *Mesos) initialize() error { func (m *Mesos) initialize() error {
if len(m.MasterCols) == 0 { if len(m.MasterCols) == 0 {
m.MasterCols = allMetrics[MASTER] m.MasterCols = allMetrics[master]
} }
if len(m.SlaveCols) == 0 { if len(m.SlaveCols) == 0 {
m.SlaveCols = allMetrics[SLAVE] m.SlaveCols = allMetrics[slave]
} }
if m.Timeout == 0 { if m.Timeout == 0 {
@ -89,8 +125,8 @@ func (m *Mesos) initialize() error {
rawQuery := "timeout=" + strconv.Itoa(m.Timeout) + "ms" rawQuery := "timeout=" + strconv.Itoa(m.Timeout) + "ms"
m.masterURLs = make([]*url.URL, 0, len(m.Masters)) m.masterURLs = make([]*url.URL, 0, len(m.Masters))
for _, master := range m.Masters { for _, mstr := range m.Masters {
u, err := m.parseURL(master, MASTER) u, err := m.parseURL(mstr, master)
if err != nil { if err != nil {
return err return err
} }
@ -100,8 +136,8 @@ func (m *Mesos) initialize() error {
} }
m.slaveURLs = make([]*url.URL, 0, len(m.Slaves)) m.slaveURLs = make([]*url.URL, 0, len(m.Slaves))
for _, slave := range m.Slaves { for _, slv := range m.Slaves {
u, err := m.parseURL(slave, SLAVE) u, err := m.parseURL(slv, slave)
if err != nil { if err != nil {
return err return err
} }
@ -119,43 +155,6 @@ func (m *Mesos) initialize() error {
return nil return nil
} }
func (*Mesos) SampleConfig() string {
return sampleConfig
}
// Gather() metrics from given list of Mesos Masters
func (m *Mesos) Gather(acc telegraf.Accumulator) error {
if !m.initialized {
err := m.initialize()
if err != nil {
return err
}
m.initialized = true
}
var wg sync.WaitGroup
for _, master := range m.masterURLs {
wg.Add(1)
go func(master *url.URL) {
acc.AddError(m.gatherMainMetrics(master, MASTER, acc))
wg.Done()
}(master)
}
for _, slave := range m.slaveURLs {
wg.Add(1)
go func(slave *url.URL) {
acc.AddError(m.gatherMainMetrics(slave, SLAVE, acc))
wg.Done()
}(slave)
}
wg.Wait()
return nil
}
func (m *Mesos) createHTTPClient() (*http.Client, error) { func (m *Mesos) createHTTPClient() (*http.Client, error) {
tlsCfg, err := m.ClientConfig.TLSConfig() tlsCfg, err := m.ClientConfig.TLSConfig()
if err != nil { if err != nil {
@ -174,7 +173,7 @@ func (m *Mesos) createHTTPClient() (*http.Client, error) {
} }
// metricsDiff() returns set names for removal // metricsDiff() returns set names for removal
func metricsDiff(role Role, w []string) []string { func metricsDiff(role role, w []string) []string {
b := make([]string, 0, len(allMetrics[role])) b := make([]string, 0, len(allMetrics[role]))
s := make(map[string]bool) s := make(map[string]bool)
@ -196,10 +195,10 @@ func metricsDiff(role Role, w []string) []string {
} }
// masterBlocks serves as kind of metrics registry grouping them in sets // masterBlocks serves as kind of metrics registry grouping them in sets
func (m *Mesos) getMetrics(role Role, group string) []string { func (m *Mesos) getMetrics(role role, group string) []string {
metrics := make(map[string][]string) metrics := make(map[string][]string)
if role == MASTER { if role == master {
metrics["resources"] = []string{ metrics["resources"] = []string{
"master/cpus_percent", "master/cpus_percent",
"master/cpus_used", "master/cpus_used",
@ -356,7 +355,7 @@ func (m *Mesos) getMetrics(role Role, group string) []string {
"registrar/registry_size_bytes", "registrar/registry_size_bytes",
"registrar/state_store_ms/count", "registrar/state_store_ms/count",
} }
} else if role == SLAVE { } else if role == slave {
metrics["resources"] = []string{ metrics["resources"] = []string{
"slave/cpus_percent", "slave/cpus_percent",
"slave/cpus_used", "slave/cpus_used",
@ -430,7 +429,6 @@ func (m *Mesos) getMetrics(role Role, group string) []string {
} }
ret, ok := metrics[group] ret, ok := metrics[group]
if !ok { if !ok {
m.Log.Infof("Unknown role %q metrics group: %s", role, group) m.Log.Infof("Unknown role %q metrics group: %s", role, group)
return nil return nil
@ -439,13 +437,13 @@ func (m *Mesos) getMetrics(role Role, group string) []string {
return ret return ret
} }
func (m *Mesos) filterMetrics(role Role, metrics *map[string]interface{}) { func (m *Mesos) filterMetrics(role role, metrics *map[string]interface{}) {
var ok bool var ok bool
var selectedMetrics []string var selectedMetrics []string
if role == MASTER { if role == master {
selectedMetrics = m.MasterCols selectedMetrics = m.MasterCols
} else if role == SLAVE { } else if role == slave {
selectedMetrics = m.SlaveCols selectedMetrics = m.SlaveCols
} }
@ -476,13 +474,6 @@ func (m *Mesos) filterMetrics(role Role, metrics *map[string]interface{}) {
} }
} }
// TaskStats struct for JSON API output /monitor/statistics
type TaskStats struct {
ExecutorID string `json:"executor_id"`
FrameworkID string `json:"framework_id"`
Statistics map[string]interface{} `json:"statistics"`
}
func withPath(u *url.URL, path string) *url.URL { func withPath(u *url.URL, path string) *url.URL {
c := *u c := *u
c.Path = path c.Path = path
@ -498,7 +489,7 @@ func urlTag(u *url.URL) string {
} }
// This should not belong to the object // This should not belong to the object
func (m *Mesos) gatherMainMetrics(u *url.URL, role Role, acc telegraf.Accumulator) error { func (m *Mesos) gatherMainMetrics(u *url.URL, role role, acc telegraf.Accumulator) error {
var jsonOut map[string]interface{} var jsonOut map[string]interface{}
tags := map[string]string{ tags := map[string]string{
@ -533,7 +524,7 @@ func (m *Mesos) gatherMainMetrics(u *url.URL, role Role, acc telegraf.Accumulato
return err return err
} }
if role == MASTER { if role == master {
if jf.Fields["master/elected"] != 0.0 { if jf.Fields["master/elected"] != 0.0 {
tags["state"] = "leader" tags["state"] = "leader"
} else { } else {

View File

@ -333,11 +333,11 @@ func TestMasterFilter(t *testing.T) {
"messages", "evqueue", "tasks", "messages", "evqueue", "tasks",
} }
m.filterMetrics(MASTER, &masterMetrics) m.filterMetrics(master, &masterMetrics)
// Assert expected metrics are present. // Assert expected metrics are present.
for _, v := range m.MasterCols { for _, v := range m.MasterCols {
for _, x := range m.getMetrics(MASTER, v) { for _, x := range m.getMetrics(master, v) {
_, ok := masterMetrics[x] _, ok := masterMetrics[x]
require.Truef(t, ok, "Didn't find key %s, it should present.", x) require.Truef(t, ok, "Didn't find key %s, it should present.", x)
} }
@ -354,7 +354,7 @@ func TestMasterFilter(t *testing.T) {
// Assert unexpected metrics are not present. // Assert unexpected metrics are not present.
for _, v := range b { for _, v := range b {
for _, x := range m.getMetrics(MASTER, v) { for _, x := range m.getMetrics(master, v) {
_, ok := masterMetrics[x] _, ok := masterMetrics[x]
require.Falsef(t, ok, "Found key %s, it should be gone.", x) require.Falsef(t, ok, "Found key %s, it should be gone.", x)
} }
@ -395,16 +395,16 @@ func TestSlaveFilter(t *testing.T) {
"system", "executors", "messages", "system", "executors", "messages",
} }
m.filterMetrics(SLAVE, &slaveMetrics) m.filterMetrics(slave, &slaveMetrics)
for _, v := range b { for _, v := range b {
for _, x := range m.getMetrics(SLAVE, v) { for _, x := range m.getMetrics(slave, v) {
_, ok := slaveMetrics[x] _, ok := slaveMetrics[x]
require.Falsef(t, ok, "Found key %s, it should be gone.", x) require.Falsef(t, ok, "Found key %s, it should be gone.", x)
} }
} }
for _, v := range m.MasterCols { for _, v := range m.MasterCols {
for _, x := range m.getMetrics(SLAVE, v) { for _, x := range m.getMetrics(slave, v) {
_, ok := slaveMetrics[x] _, ok := slaveMetrics[x]
require.Truef(t, ok, "Didn't find key %s, it should present.", x) require.Truef(t, ok, "Didn't find key %s, it should present.", x)
} }

View File

@ -13,16 +13,16 @@ var (
scoreboardRegex = regexp.MustCompile(`\[(?P<name>[^\]]+)\]: (?P<value>\d+)`) scoreboardRegex = regexp.MustCompile(`\[(?P<name>[^\]]+)\]: (?P<value>\d+)`)
) )
// Connection is an established connection to the Minecraft server. // connection is an established connection to the Minecraft server.
type Connection interface { type connection interface {
// Execute runs a command. // Execute runs a command.
Execute(command string) (string, error) Execute(command string) (string, error)
} }
// Connector is used to create connections to the Minecraft server. // conn is used to create connections to the Minecraft server.
type Connector interface { type conn interface {
// Connect establishes a connection to the server. // connect establishes a connection to the server.
Connect() (Connection, error) connect() (connection, error)
} }
func newConnector(hostname, port, password string) *connector { func newConnector(hostname, port, password string) *connector {
@ -39,7 +39,7 @@ type connector struct {
password string password string
} }
func (c *connector) Connect() (Connection, error) { func (c *connector) connect() (connection, error) {
client, err := rcon.Dial(c.hostname+":"+c.port, c.password) client, err := rcon.Dial(c.hostname+":"+c.port, c.password)
if err != nil { if err != nil {
return nil, err return nil, err
@ -48,17 +48,17 @@ func (c *connector) Connect() (Connection, error) {
return client, nil return client, nil
} }
func newClient(connector Connector) *client { func newClient(connector conn) *client {
return &client{connector: connector} return &client{connector: connector}
} }
type client struct { type client struct {
connector Connector connector conn
conn Connection conn connection
} }
func (c *client) Connect() error { func (c *client) connect() error {
conn, err := c.connector.Connect() conn, err := c.connector.connect()
if err != nil { if err != nil {
return err return err
} }
@ -66,9 +66,9 @@ func (c *client) Connect() error {
return nil return nil
} }
func (c *client) Players() ([]string, error) { func (c *client) players() ([]string, error) {
if c.conn == nil { if c.conn == nil {
err := c.Connect() err := c.connect()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -83,9 +83,9 @@ func (c *client) Players() ([]string, error) {
return parsePlayers(resp), nil return parsePlayers(resp), nil
} }
func (c *client) Scores(player string) ([]Score, error) { func (c *client) scores(player string) ([]score, error) {
if c.conn == nil { if c.conn == nil {
err := c.Connect() err := c.connect()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -127,13 +127,13 @@ func parsePlayers(input string) []string {
return players return players
} }
// Score is an individual tracked scoreboard stat. // score is an individual tracked scoreboard stat.
type Score struct { type score struct {
Name string name string
Value int64 value int64
} }
func parseScores(input string) []Score { func parseScores(input string) []score {
if strings.Contains(input, "has no scores") { if strings.Contains(input, "has no scores") {
return nil return nil
} }
@ -147,19 +147,19 @@ func parseScores(input string) []Score {
} }
matches := re.FindAllStringSubmatch(input, -1) matches := re.FindAllStringSubmatch(input, -1)
scores := make([]Score, 0, len(matches)) scores := make([]score, 0, len(matches))
for _, match := range matches { for _, match := range matches {
score := Score{} score := score{}
for i, subexp := range re.SubexpNames() { for i, subexp := range re.SubexpNames() {
switch subexp { switch subexp {
case "name": case "name":
score.Name = match[i] score.name = match[i]
case "value": case "value":
value, err := strconv.ParseInt(match[i], 10, 64) value, err := strconv.ParseInt(match[i], 10, 64)
if err != nil { if err != nil {
continue continue
} }
score.Value = value score.value = value
default: default:
continue continue
} }

View File

@ -6,19 +6,19 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
type MockConnection struct { type mockConnection struct {
commands map[string]string commands map[string]string
} }
func (c *MockConnection) Execute(command string) (string, error) { func (c *mockConnection) Execute(command string) (string, error) {
return c.commands[command], nil return c.commands[command], nil
} }
type MockConnector struct { type mockConnector struct {
conn *MockConnection conn *mockConnection
} }
func (c *MockConnector) Connect() (Connection, error) { func (c *mockConnector) connect() (connection, error) {
return c.conn, nil return c.conn, nil
} }
@ -92,12 +92,12 @@ func TestClient_Player(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
connector := &MockConnector{ connector := &mockConnector{
conn: &MockConnection{commands: tt.commands}, conn: &mockConnection{commands: tt.commands},
} }
client := newClient(connector) client := newClient(connector)
actual, err := client.Players() actual, err := client.players()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, tt.expected, actual) require.Equal(t, tt.expected, actual)
@ -110,7 +110,7 @@ func TestClient_Scores(t *testing.T) {
name string name string
player string player string
commands map[string]string commands map[string]string
expected []Score expected []score
}{ }{
{ {
name: "minecraft 1.12 player with no scores", name: "minecraft 1.12 player with no scores",
@ -125,8 +125,8 @@ func TestClient_Scores(t *testing.T) {
commands: map[string]string{ commands: map[string]string{
"scoreboard players list Etho": "Showing 1 tracked objective(s) for Etho:- jump: 2 (jump)", "scoreboard players list Etho": "Showing 1 tracked objective(s) for Etho:- jump: 2 (jump)",
}, },
expected: []Score{ expected: []score{
{Name: "jump", Value: 2}, {name: "jump", value: 2},
}, },
}, },
{ {
@ -135,10 +135,10 @@ func TestClient_Scores(t *testing.T) {
commands: map[string]string{ commands: map[string]string{
"scoreboard players list Etho": "Showing 3 tracked objective(s) for Etho:- hopper: 2 (hopper)- dropper: 2 (dropper)- redstone: 1 (redstone)", "scoreboard players list Etho": "Showing 3 tracked objective(s) for Etho:- hopper: 2 (hopper)- dropper: 2 (dropper)- redstone: 1 (redstone)",
}, },
expected: []Score{ expected: []score{
{Name: "hopper", Value: 2}, {name: "hopper", value: 2},
{Name: "dropper", Value: 2}, {name: "dropper", value: 2},
{Name: "redstone", Value: 1}, {name: "redstone", value: 1},
}, },
}, },
{ {
@ -154,8 +154,8 @@ func TestClient_Scores(t *testing.T) {
commands: map[string]string{ commands: map[string]string{
"scoreboard players list Etho": "Etho has 1 scores:[jumps]: 1", "scoreboard players list Etho": "Etho has 1 scores:[jumps]: 1",
}, },
expected: []Score{ expected: []score{
{Name: "jumps", Value: 1}, {name: "jumps", value: 1},
}, },
}, },
{ {
@ -164,21 +164,21 @@ func TestClient_Scores(t *testing.T) {
commands: map[string]string{ commands: map[string]string{
"scoreboard players list Etho": "Etho has 3 scores:[hopper]: 2[dropper]: 2[redstone]: 1", "scoreboard players list Etho": "Etho has 3 scores:[hopper]: 2[dropper]: 2[redstone]: 1",
}, },
expected: []Score{ expected: []score{
{Name: "hopper", Value: 2}, {name: "hopper", value: 2},
{Name: "dropper", Value: 2}, {name: "dropper", value: 2},
{Name: "redstone", Value: 1}, {name: "redstone", value: 1},
}, },
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
connector := &MockConnector{ connector := &mockConnector{
conn: &MockConnection{commands: tt.commands}, conn: &mockConnection{commands: tt.commands},
} }
client := newClient(connector) client := newClient(connector)
actual, err := client.Scores(tt.player) actual, err := client.scores(tt.player)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, tt.expected, actual) require.Equal(t, tt.expected, actual)

View File

@ -11,25 +11,24 @@ import (
//go:embed sample.conf //go:embed sample.conf
var sampleConfig string var sampleConfig string
// Client is a client for the Minecraft server.
type Client interface {
// Connect establishes a connection to the server.
Connect() error
// Players returns the players on the scoreboard.
Players() ([]string, error)
// Scores return the objective scores for a player.
Scores(player string) ([]Score, error)
}
// Minecraft is the plugin type.
type Minecraft struct { type Minecraft struct {
Server string `toml:"server"` Server string `toml:"server"`
Port string `toml:"port"` Port string `toml:"port"`
Password string `toml:"password"` Password string `toml:"password"`
client Client client cli
}
// cli is a client for the Minecraft server.
type cli interface {
// connect establishes a connection to the server.
connect() error
// players returns the players on the scoreboard.
players() ([]string, error)
// scores returns the objective scores for a player.
scores(player string) ([]score, error)
} }
func (*Minecraft) SampleConfig() string { func (*Minecraft) SampleConfig() string {
@ -42,13 +41,13 @@ func (s *Minecraft) Gather(acc telegraf.Accumulator) error {
s.client = newClient(connector) s.client = newClient(connector)
} }
players, err := s.client.Players() players, err := s.client.players()
if err != nil { if err != nil {
return err return err
} }
for _, player := range players { for _, player := range players {
scores, err := s.client.Scores(player) scores, err := s.client.scores(player)
if err != nil { if err != nil {
return err return err
} }
@ -62,7 +61,7 @@ func (s *Minecraft) Gather(acc telegraf.Accumulator) error {
var fields = make(map[string]interface{}, len(scores)) var fields = make(map[string]interface{}, len(scores))
for _, score := range scores { for _, score := range scores {
fields[score.Name] = score.Value fields[score.name] = score.value
} }
acc.AddFields("minecraft", fields, tags) acc.AddFields("minecraft", fields, tags)

View File

@ -9,22 +9,22 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
type MockClient struct { type mockClient struct {
ConnectF func() error connectF func() error
PlayersF func() ([]string, error) playersF func() ([]string, error)
ScoresF func(player string) ([]Score, error) scoresF func(player string) ([]score, error)
} }
func (c *MockClient) Connect() error { func (c *mockClient) connect() error {
return c.ConnectF() return c.connectF()
} }
func (c *MockClient) Players() ([]string, error) { func (c *mockClient) players() ([]string, error) {
return c.PlayersF() return c.playersF()
} }
func (c *MockClient) Scores(player string) ([]Score, error) { func (c *mockClient) scores(player string) ([]score, error) {
return c.ScoresF(player) return c.scoresF(player)
} }
func TestGather(t *testing.T) { func TestGather(t *testing.T) {
@ -32,31 +32,31 @@ func TestGather(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
client *MockClient client *mockClient
metrics []telegraf.Metric metrics []telegraf.Metric
err error err error
}{ }{
{ {
name: "no players", name: "no players",
client: &MockClient{ client: &mockClient{
ConnectF: func() error { connectF: func() error {
return nil return nil
}, },
PlayersF: func() ([]string, error) { playersF: func() ([]string, error) {
return nil, nil return nil, nil
}, },
}, },
}, },
{ {
name: "one player without scores", name: "one player without scores",
client: &MockClient{ client: &mockClient{
ConnectF: func() error { connectF: func() error {
return nil return nil
}, },
PlayersF: func() ([]string, error) { playersF: func() ([]string, error) {
return []string{"Etho"}, nil return []string{"Etho"}, nil
}, },
ScoresF: func(player string) ([]Score, error) { scoresF: func(player string) ([]score, error) {
switch player { switch player {
case "Etho": case "Etho":
return nil, nil return nil, nil
@ -68,17 +68,17 @@ func TestGather(t *testing.T) {
}, },
{ {
name: "one player with scores", name: "one player with scores",
client: &MockClient{ client: &mockClient{
ConnectF: func() error { connectF: func() error {
return nil return nil
}, },
PlayersF: func() ([]string, error) { playersF: func() ([]string, error) {
return []string{"Etho"}, nil return []string{"Etho"}, nil
}, },
ScoresF: func(player string) ([]Score, error) { scoresF: func(player string) ([]score, error) {
switch player { switch player {
case "Etho": case "Etho":
return []Score{{Name: "jumps", Value: 42}}, nil return []score{{name: "jumps", value: 42}}, nil
default: default:
panic("unknown player") panic("unknown player")
} }

View File

@ -48,22 +48,22 @@ type sineWave struct {
} }
type step struct { type step struct {
latest float64
Name string `toml:"name"` Name string `toml:"name"`
Start float64 `toml:"start"` Start float64 `toml:"start"`
Step float64 `toml:"step"` Step float64 `toml:"step"`
Min float64 `toml:"min" deprecated:"1.28.2;1.35.0;use 'start' instead"` Min float64 `toml:"min" deprecated:"1.28.2;1.35.0;use 'start' instead"`
Max float64 `toml:"max" deprecated:"1.28.2;1.35.0;use 'step' instead"` Max float64 `toml:"max" deprecated:"1.28.2;1.35.0;use 'step' instead"`
latest float64
} }
type stock struct { type stock struct {
latest float64
Name string `toml:"name"` Name string `toml:"name"`
Price float64 `toml:"price"` Price float64 `toml:"price"`
Volatility float64 `toml:"volatility"` Volatility float64 `toml:"volatility"`
latest float64
} }
func (*Mock) SampleConfig() string { func (*Mock) SampleConfig() string {

View File

@ -9,10 +9,10 @@ const (
maxQuantityHoldingRegisters = uint16(125) maxQuantityHoldingRegisters = uint16(125)
) )
type Configuration interface { type configuration interface {
Check() error check() error
Process() (map[byte]requestSet, error) process() (map[byte]requestSet, error)
SampleConfigPart() string sampleConfigPart() string
} }
func removeDuplicates(elements []uint16) []uint16 { func removeDuplicates(elements []uint16) []uint16 {

View File

@ -32,21 +32,21 @@ type metricDefinition struct {
Tags map[string]string `toml:"tags"` Tags map[string]string `toml:"tags"`
} }
type ConfigurationPerMetric struct { type configurationPerMetric struct {
Optimization string `toml:"optimization"` Optimization string `toml:"optimization"`
MaxExtraRegisters uint16 `toml:"optimization_max_register_fill"` MaxExtraRegisters uint16 `toml:"optimization_max_register_fill"`
Metrics []metricDefinition `toml:"metric"` Metrics []metricDefinition `toml:"metric"`
workarounds ModbusWorkarounds workarounds workarounds
excludeRegisterType bool excludeRegisterType bool
logger telegraf.Logger logger telegraf.Logger
} }
func (c *ConfigurationPerMetric) SampleConfigPart() string { func (c *configurationPerMetric) sampleConfigPart() string {
return sampleConfigPartPerMetric return sampleConfigPartPerMetric
} }
func (c *ConfigurationPerMetric) Check() error { func (c *configurationPerMetric) check() error {
switch c.workarounds.StringRegisterLocation { switch c.workarounds.StringRegisterLocation {
case "", "both", "lower", "upper": case "", "both", "lower", "upper":
// Do nothing as those are valid // Do nothing as those are valid
@ -178,7 +178,7 @@ func (c *ConfigurationPerMetric) Check() error {
return nil return nil
} }
func (c *ConfigurationPerMetric) Process() (map[byte]requestSet, error) { func (c *configurationPerMetric) process() (map[byte]requestSet, error) {
collection := make(map[byte]map[string][]field) collection := make(map[byte]map[string][]field)
// Collect the requested registers across metrics and transform them into // Collect the requested registers across metrics and transform them into
@ -206,40 +206,40 @@ func (c *ConfigurationPerMetric) Process() (map[byte]requestSet, error) {
result := make(map[byte]requestSet) result := make(map[byte]requestSet)
params := groupingParams{ params := groupingParams{
Optimization: c.Optimization, optimization: c.Optimization,
MaxExtraRegisters: c.MaxExtraRegisters, maxExtraRegisters: c.MaxExtraRegisters,
Log: c.logger, log: c.logger,
} }
for sid, scollection := range collection { for sid, scollection := range collection {
var set requestSet var set requestSet
for registerType, fields := range scollection { for registerType, fields := range scollection {
switch registerType { switch registerType {
case "coil": case "coil":
params.MaxBatchSize = maxQuantityCoils params.maxBatchSize = maxQuantityCoils
if c.workarounds.OnRequestPerField { if c.workarounds.OnRequestPerField {
params.MaxBatchSize = 1 params.maxBatchSize = 1
} }
params.EnforceFromZero = c.workarounds.ReadCoilsStartingAtZero params.enforceFromZero = c.workarounds.ReadCoilsStartingAtZero
requests := groupFieldsToRequests(fields, params) requests := groupFieldsToRequests(fields, params)
set.coil = append(set.coil, requests...) set.coil = append(set.coil, requests...)
case "discrete": case "discrete":
params.MaxBatchSize = maxQuantityDiscreteInput params.maxBatchSize = maxQuantityDiscreteInput
if c.workarounds.OnRequestPerField { if c.workarounds.OnRequestPerField {
params.MaxBatchSize = 1 params.maxBatchSize = 1
} }
requests := groupFieldsToRequests(fields, params) requests := groupFieldsToRequests(fields, params)
set.discrete = append(set.discrete, requests...) set.discrete = append(set.discrete, requests...)
case "holding": case "holding":
params.MaxBatchSize = maxQuantityHoldingRegisters params.maxBatchSize = maxQuantityHoldingRegisters
if c.workarounds.OnRequestPerField { if c.workarounds.OnRequestPerField {
params.MaxBatchSize = 1 params.maxBatchSize = 1
} }
requests := groupFieldsToRequests(fields, params) requests := groupFieldsToRequests(fields, params)
set.holding = append(set.holding, requests...) set.holding = append(set.holding, requests...)
case "input": case "input":
params.MaxBatchSize = maxQuantityInputRegisters params.maxBatchSize = maxQuantityInputRegisters
if c.workarounds.OnRequestPerField { if c.workarounds.OnRequestPerField {
params.MaxBatchSize = 1 params.maxBatchSize = 1
} }
requests := groupFieldsToRequests(fields, params) requests := groupFieldsToRequests(fields, params)
set.input = append(set.input, requests...) set.input = append(set.input, requests...)
@ -247,7 +247,7 @@ func (c *ConfigurationPerMetric) Process() (map[byte]requestSet, error) {
return nil, fmt.Errorf("unknown register type %q", registerType) return nil, fmt.Errorf("unknown register type %q", registerType)
} }
} }
if !set.Empty() { if !set.empty() {
result[sid] = set result[sid] = set
} }
} }
@ -255,7 +255,7 @@ func (c *ConfigurationPerMetric) Process() (map[byte]requestSet, error) {
return result, nil return result, nil
} }
func (c *ConfigurationPerMetric) newField(def metricFieldDefinition, mdef metricDefinition) (field, error) { func (c *configurationPerMetric) newField(def metricFieldDefinition, mdef metricDefinition) (field, error) {
typed := def.RegisterType == "holding" || def.RegisterType == "input" typed := def.RegisterType == "holding" || def.RegisterType == "input"
fieldLength := uint16(1) fieldLength := uint16(1)
@ -339,7 +339,7 @@ func (c *ConfigurationPerMetric) newField(def metricFieldDefinition, mdef metric
return f, nil return f, nil
} }
func (c *ConfigurationPerMetric) fieldID(seed maphash.Seed, def metricDefinition, field metricFieldDefinition) uint64 { func (c *configurationPerMetric) fieldID(seed maphash.Seed, def metricDefinition, field metricFieldDefinition) uint64 {
var mh maphash.Hash var mh maphash.Hash
mh.SetSeed(seed) mh.SetSeed(seed)
@ -354,7 +354,7 @@ func (c *ConfigurationPerMetric) fieldID(seed maphash.Seed, def metricDefinition
mh.WriteString(field.Name) mh.WriteString(field.Name)
mh.WriteByte(0) mh.WriteByte(0)
// Tags // tags
for k, v := range def.Tags { for k, v := range def.Tags {
mh.WriteString(k) mh.WriteString(k)
mh.WriteByte('=') mh.WriteByte('=')
@ -366,7 +366,7 @@ func (c *ConfigurationPerMetric) fieldID(seed maphash.Seed, def metricDefinition
return mh.Sum64() return mh.Sum64()
} }
func (c *ConfigurationPerMetric) determineOutputDatatype(input string) (string, error) { func (c *configurationPerMetric) determineOutputDatatype(input string) (string, error) {
// Handle our special types // Handle our special types
switch input { switch input {
case "INT8L", "INT8H", "INT16", "INT32", "INT64": case "INT8L", "INT8H", "INT16", "INT32", "INT64":
@ -381,7 +381,7 @@ func (c *ConfigurationPerMetric) determineOutputDatatype(input string) (string,
return "unknown", fmt.Errorf("invalid input datatype %q for determining output", input) return "unknown", fmt.Errorf("invalid input datatype %q for determining output", input)
} }
func (c *ConfigurationPerMetric) determineFieldLength(input string, length uint16) (uint16, error) { func (c *configurationPerMetric) determineFieldLength(input string, length uint16) (uint16, error) {
// Handle our special types // Handle our special types
switch input { switch input {
case "BIT", "INT8L", "INT8H", "UINT8L", "UINT8H": case "BIT", "INT8L", "INT8H", "UINT8L", "UINT8H":

View File

@ -371,7 +371,7 @@ func TestMetricAddressOverflow(t *testing.T) {
Controller: "tcp://localhost:1502", Controller: "tcp://localhost:1502",
ConfigurationType: "metric", ConfigurationType: "metric",
Log: logger, Log: logger,
Workarounds: ModbusWorkarounds{ReadCoilsStartingAtZero: true}, Workarounds: workarounds{ReadCoilsStartingAtZero: true},
} }
plugin.Metrics = []metricDefinition{ plugin.Metrics = []metricDefinition{
{ {

View File

@ -21,21 +21,21 @@ type fieldDefinition struct {
Bit uint8 `toml:"bit"` Bit uint8 `toml:"bit"`
} }
type ConfigurationOriginal struct { type configurationOriginal struct {
SlaveID byte `toml:"slave_id"` SlaveID byte `toml:"slave_id"`
DiscreteInputs []fieldDefinition `toml:"discrete_inputs"` DiscreteInputs []fieldDefinition `toml:"discrete_inputs"`
Coils []fieldDefinition `toml:"coils"` Coils []fieldDefinition `toml:"coils"`
HoldingRegisters []fieldDefinition `toml:"holding_registers"` HoldingRegisters []fieldDefinition `toml:"holding_registers"`
InputRegisters []fieldDefinition `toml:"input_registers"` InputRegisters []fieldDefinition `toml:"input_registers"`
workarounds ModbusWorkarounds workarounds workarounds
logger telegraf.Logger logger telegraf.Logger
} }
func (c *ConfigurationOriginal) SampleConfigPart() string { func (c *configurationOriginal) sampleConfigPart() string {
return sampleConfigPartPerRegister return sampleConfigPartPerRegister
} }
func (c *ConfigurationOriginal) Check() error { func (c *configurationOriginal) check() error {
switch c.workarounds.StringRegisterLocation { switch c.workarounds.StringRegisterLocation {
case "", "both", "lower", "upper": case "", "both", "lower", "upper":
// Do nothing as those are valid // Do nothing as those are valid
@ -58,7 +58,7 @@ func (c *ConfigurationOriginal) Check() error {
return c.validateFieldDefinitions(c.InputRegisters, cInputRegisters) return c.validateFieldDefinitions(c.InputRegisters, cInputRegisters)
} }
func (c *ConfigurationOriginal) Process() (map[byte]requestSet, error) { func (c *configurationOriginal) process() (map[byte]requestSet, error) {
maxQuantity := uint16(1) maxQuantity := uint16(1)
if !c.workarounds.OnRequestPerField { if !c.workarounds.OnRequestPerField {
maxQuantity = maxQuantityCoils maxQuantity = maxQuantityCoils
@ -102,22 +102,22 @@ func (c *ConfigurationOriginal) Process() (map[byte]requestSet, error) {
}, nil }, nil
} }
func (c *ConfigurationOriginal) initRequests(fieldDefs []fieldDefinition, maxQuantity uint16, typed bool) ([]request, error) { func (c *configurationOriginal) initRequests(fieldDefs []fieldDefinition, maxQuantity uint16, typed bool) ([]request, error) {
fields, err := c.initFields(fieldDefs, typed) fields, err := c.initFields(fieldDefs, typed)
if err != nil { if err != nil {
return nil, err return nil, err
} }
params := groupingParams{ params := groupingParams{
MaxBatchSize: maxQuantity, maxBatchSize: maxQuantity,
Optimization: "none", optimization: "none",
EnforceFromZero: c.workarounds.ReadCoilsStartingAtZero, enforceFromZero: c.workarounds.ReadCoilsStartingAtZero,
Log: c.logger, log: c.logger,
} }
return groupFieldsToRequests(fields, params), nil return groupFieldsToRequests(fields, params), nil
} }
func (c *ConfigurationOriginal) initFields(fieldDefs []fieldDefinition, typed bool) ([]field, error) { func (c *configurationOriginal) initFields(fieldDefs []fieldDefinition, typed bool) ([]field, error) {
// Construct the fields from the field definitions // Construct the fields from the field definitions
fields := make([]field, 0, len(fieldDefs)) fields := make([]field, 0, len(fieldDefs))
for _, def := range fieldDefs { for _, def := range fieldDefs {
@ -131,7 +131,7 @@ func (c *ConfigurationOriginal) initFields(fieldDefs []fieldDefinition, typed bo
return fields, nil return fields, nil
} }
func (c *ConfigurationOriginal) newFieldFromDefinition(def fieldDefinition, typed bool) (field, error) { func (c *configurationOriginal) newFieldFromDefinition(def fieldDefinition, typed bool) (field, error) {
// Check if the addresses are consecutive // Check if the addresses are consecutive
expected := def.Address[0] expected := def.Address[0]
for _, current := range def.Address[1:] { for _, current := range def.Address[1:] {
@ -182,7 +182,7 @@ func (c *ConfigurationOriginal) newFieldFromDefinition(def fieldDefinition, type
return f, nil return f, nil
} }
func (c *ConfigurationOriginal) validateFieldDefinitions(fieldDefs []fieldDefinition, registerType string) error { func (c *configurationOriginal) validateFieldDefinitions(fieldDefs []fieldDefinition, registerType string) error {
nameEncountered := make(map[string]bool, len(fieldDefs)) nameEncountered := make(map[string]bool, len(fieldDefs))
for _, item := range fieldDefs { for _, item := range fieldDefs {
// check empty name // check empty name
@ -276,7 +276,7 @@ func (c *ConfigurationOriginal) validateFieldDefinitions(fieldDefs []fieldDefini
return nil return nil
} }
func (c *ConfigurationOriginal) normalizeInputDatatype(dataType string, words int) (string, error) { func (c *configurationOriginal) normalizeInputDatatype(dataType string, words int) (string, error) {
if dataType == "FLOAT32" { if dataType == "FLOAT32" {
config.PrintOptionValueDeprecationNotice("input.modbus", "data_type", "FLOAT32", telegraf.DeprecationInfo{ config.PrintOptionValueDeprecationNotice("input.modbus", "data_type", "FLOAT32", telegraf.DeprecationInfo{
Since: "1.16.0", Since: "1.16.0",
@ -323,7 +323,7 @@ func (c *ConfigurationOriginal) normalizeInputDatatype(dataType string, words in
return normalizeInputDatatype(dataType) return normalizeInputDatatype(dataType)
} }
func (c *ConfigurationOriginal) normalizeOutputDatatype(dataType string) (string, error) { func (c *configurationOriginal) normalizeOutputDatatype(dataType string) (string, error) {
// Handle our special types // Handle our special types
switch dataType { switch dataType {
case "FIXED", "FLOAT32", "UFIXED": case "FIXED", "FLOAT32", "UFIXED":
@ -332,7 +332,7 @@ func (c *ConfigurationOriginal) normalizeOutputDatatype(dataType string) (string
return normalizeOutputDatatype("native") return normalizeOutputDatatype("native")
} }
func (c *ConfigurationOriginal) normalizeByteOrder(byteOrder string) (string, error) { func (c *configurationOriginal) normalizeByteOrder(byteOrder string) (string, error) {
// Handle our special types // Handle our special types
switch byteOrder { switch byteOrder {
case "AB", "ABCDEFGH": case "AB", "ABCDEFGH":

View File

@ -37,19 +37,19 @@ type requestDefinition struct {
Tags map[string]string `toml:"tags"` Tags map[string]string `toml:"tags"`
} }
type ConfigurationPerRequest struct { type configurationPerRequest struct {
Requests []requestDefinition `toml:"request"` Requests []requestDefinition `toml:"request"`
workarounds ModbusWorkarounds workarounds workarounds
excludeRegisterType bool excludeRegisterType bool
logger telegraf.Logger logger telegraf.Logger
} }
func (c *ConfigurationPerRequest) SampleConfigPart() string { func (c *configurationPerRequest) sampleConfigPart() string {
return sampleConfigPartPerRequest return sampleConfigPartPerRequest
} }
func (c *ConfigurationPerRequest) Check() error { func (c *configurationPerRequest) check() error {
switch c.workarounds.StringRegisterLocation { switch c.workarounds.StringRegisterLocation {
case "", "both", "lower", "upper": case "", "both", "lower", "upper":
// Do nothing as those are valid // Do nothing as those are valid
@ -213,7 +213,7 @@ func (c *ConfigurationPerRequest) Check() error {
return nil return nil
} }
func (c *ConfigurationPerRequest) Process() (map[byte]requestSet, error) { func (c *configurationPerRequest) process() (map[byte]requestSet, error) {
result := make(map[byte]requestSet, len(c.Requests)) result := make(map[byte]requestSet, len(c.Requests))
for _, def := range c.Requests { for _, def := range c.Requests {
// Set default // Set default
@ -235,45 +235,45 @@ func (c *ConfigurationPerRequest) Process() (map[byte]requestSet, error) {
} }
params := groupingParams{ params := groupingParams{
MaxExtraRegisters: def.MaxExtraRegisters, maxExtraRegisters: def.MaxExtraRegisters,
Optimization: def.Optimization, optimization: def.Optimization,
Tags: def.Tags, tags: def.Tags,
Log: c.logger, log: c.logger,
} }
switch def.RegisterType { switch def.RegisterType {
case "coil": case "coil":
params.MaxBatchSize = maxQuantityCoils params.maxBatchSize = maxQuantityCoils
if c.workarounds.OnRequestPerField { if c.workarounds.OnRequestPerField {
params.MaxBatchSize = 1 params.maxBatchSize = 1
} }
params.EnforceFromZero = c.workarounds.ReadCoilsStartingAtZero params.enforceFromZero = c.workarounds.ReadCoilsStartingAtZero
requests := groupFieldsToRequests(fields, params) requests := groupFieldsToRequests(fields, params)
set.coil = append(set.coil, requests...) set.coil = append(set.coil, requests...)
case "discrete": case "discrete":
params.MaxBatchSize = maxQuantityDiscreteInput params.maxBatchSize = maxQuantityDiscreteInput
if c.workarounds.OnRequestPerField { if c.workarounds.OnRequestPerField {
params.MaxBatchSize = 1 params.maxBatchSize = 1
} }
requests := groupFieldsToRequests(fields, params) requests := groupFieldsToRequests(fields, params)
set.discrete = append(set.discrete, requests...) set.discrete = append(set.discrete, requests...)
case "holding": case "holding":
params.MaxBatchSize = maxQuantityHoldingRegisters params.maxBatchSize = maxQuantityHoldingRegisters
if c.workarounds.OnRequestPerField { if c.workarounds.OnRequestPerField {
params.MaxBatchSize = 1 params.maxBatchSize = 1
} }
requests := groupFieldsToRequests(fields, params) requests := groupFieldsToRequests(fields, params)
set.holding = append(set.holding, requests...) set.holding = append(set.holding, requests...)
case "input": case "input":
params.MaxBatchSize = maxQuantityInputRegisters params.maxBatchSize = maxQuantityInputRegisters
if c.workarounds.OnRequestPerField { if c.workarounds.OnRequestPerField {
params.MaxBatchSize = 1 params.maxBatchSize = 1
} }
requests := groupFieldsToRequests(fields, params) requests := groupFieldsToRequests(fields, params)
set.input = append(set.input, requests...) set.input = append(set.input, requests...)
default: default:
return nil, fmt.Errorf("unknown register type %q", def.RegisterType) return nil, fmt.Errorf("unknown register type %q", def.RegisterType)
} }
if !set.Empty() { if !set.empty() {
result[def.SlaveID] = set result[def.SlaveID] = set
} }
} }
@ -281,7 +281,7 @@ func (c *ConfigurationPerRequest) Process() (map[byte]requestSet, error) {
return result, nil return result, nil
} }
func (c *ConfigurationPerRequest) initFields(fieldDefs []requestFieldDefinition, typed bool, byteOrder string) ([]field, error) { func (c *configurationPerRequest) initFields(fieldDefs []requestFieldDefinition, typed bool, byteOrder string) ([]field, error) {
// Construct the fields from the field definitions // Construct the fields from the field definitions
fields := make([]field, 0, len(fieldDefs)) fields := make([]field, 0, len(fieldDefs))
for _, def := range fieldDefs { for _, def := range fieldDefs {
@ -295,7 +295,7 @@ func (c *ConfigurationPerRequest) initFields(fieldDefs []requestFieldDefinition,
return fields, nil return fields, nil
} }
func (c *ConfigurationPerRequest) newFieldFromDefinition(def requestFieldDefinition, typed bool, byteOrder string) (field, error) { func (c *configurationPerRequest) newFieldFromDefinition(def requestFieldDefinition, typed bool, byteOrder string) (field, error) {
var err error var err error
fieldLength := uint16(1) fieldLength := uint16(1)
@ -379,7 +379,7 @@ func (c *ConfigurationPerRequest) newFieldFromDefinition(def requestFieldDefinit
return f, nil return f, nil
} }
func (c *ConfigurationPerRequest) fieldID(seed maphash.Seed, def requestDefinition, field requestFieldDefinition) uint64 { func (c *configurationPerRequest) fieldID(seed maphash.Seed, def requestDefinition, field requestFieldDefinition) uint64 {
var mh maphash.Hash var mh maphash.Hash
mh.SetSeed(seed) mh.SetSeed(seed)
@ -394,7 +394,7 @@ func (c *ConfigurationPerRequest) fieldID(seed maphash.Seed, def requestDefiniti
mh.WriteString(field.Name) mh.WriteString(field.Name)
mh.WriteByte(0) mh.WriteByte(0)
// Tags // tags
for k, v := range def.Tags { for k, v := range def.Tags {
mh.WriteString(k) mh.WriteString(k)
mh.WriteByte('=') mh.WriteByte('=')
@ -406,7 +406,7 @@ func (c *ConfigurationPerRequest) fieldID(seed maphash.Seed, def requestDefiniti
return mh.Sum64() return mh.Sum64()
} }
func (c *ConfigurationPerRequest) determineOutputDatatype(input string) (string, error) { func (c *configurationPerRequest) determineOutputDatatype(input string) (string, error) {
// Handle our special types // Handle our special types
switch input { switch input {
case "INT8L", "INT8H", "INT16", "INT32", "INT64": case "INT8L", "INT8H", "INT16", "INT32", "INT64":
@ -421,7 +421,7 @@ func (c *ConfigurationPerRequest) determineOutputDatatype(input string) (string,
return "unknown", fmt.Errorf("invalid input datatype %q for determining output", input) return "unknown", fmt.Errorf("invalid input datatype %q for determining output", input)
} }
func (c *ConfigurationPerRequest) determineFieldLength(input string, length uint16) (uint16, error) { func (c *configurationPerRequest) determineFieldLength(input string, length uint16) (uint16, error) {
// Handle our special types // Handle our special types
switch input { switch input {
case "BIT", "INT8L", "INT8H", "UINT8L", "UINT8H": case "BIT", "INT8L", "INT8H", "UINT8L", "UINT8H":

View File

@ -3177,7 +3177,7 @@ func TestRequestWorkaroundsOneRequestPerField(t *testing.T) {
Controller: "tcp://localhost:1502", Controller: "tcp://localhost:1502",
ConfigurationType: "request", ConfigurationType: "request",
Log: testutil.Logger{}, Log: testutil.Logger{},
Workarounds: ModbusWorkarounds{OnRequestPerField: true}, Workarounds: workarounds{OnRequestPerField: true},
} }
plugin.Requests = []requestDefinition{ plugin.Requests = []requestDefinition{
{ {
@ -3223,7 +3223,7 @@ func TestRequestWorkaroundsReadCoilsStartingAtZeroRequest(t *testing.T) {
Controller: "tcp://localhost:1502", Controller: "tcp://localhost:1502",
ConfigurationType: "request", ConfigurationType: "request",
Log: testutil.Logger{}, Log: testutil.Logger{},
Workarounds: ModbusWorkarounds{ReadCoilsStartingAtZero: true}, Workarounds: workarounds{ReadCoilsStartingAtZero: true},
} }
plugin.SlaveID = 1 plugin.SlaveID = 1
plugin.Requests = []requestDefinition{ plugin.Requests = []requestDefinition{
@ -3262,7 +3262,7 @@ func TestRequestOverlap(t *testing.T) {
Controller: "tcp://localhost:1502", Controller: "tcp://localhost:1502",
ConfigurationType: "request", ConfigurationType: "request",
Log: logger, Log: logger,
Workarounds: ModbusWorkarounds{ReadCoilsStartingAtZero: true}, Workarounds: workarounds{ReadCoilsStartingAtZero: true},
} }
plugin.Requests = []requestDefinition{ plugin.Requests = []requestDefinition{
{ {
@ -3320,7 +3320,7 @@ func TestRequestAddressOverflow(t *testing.T) {
Controller: "tcp://localhost:1502", Controller: "tcp://localhost:1502",
ConfigurationType: "request", ConfigurationType: "request",
Log: logger, Log: logger,
Workarounds: ModbusWorkarounds{ReadCoilsStartingAtZero: true}, Workarounds: workarounds{ReadCoilsStartingAtZero: true},
} }
plugin.Requests = []requestDefinition{ plugin.Requests = []requestDefinition{
{ {

View File

@ -27,7 +27,45 @@ var sampleConfigEnd string
var errAddressOverflow = errors.New("address overflow") var errAddressOverflow = errors.New("address overflow")
type ModbusWorkarounds struct { const (
cDiscreteInputs = "discrete_input"
cCoils = "coil"
cHoldingRegisters = "holding_register"
cInputRegisters = "input_register"
)
type Modbus struct {
Name string `toml:"name"`
Controller string `toml:"controller"`
TransmissionMode string `toml:"transmission_mode"`
BaudRate int `toml:"baud_rate"`
DataBits int `toml:"data_bits"`
Parity string `toml:"parity"`
StopBits int `toml:"stop_bits"`
RS485 *rs485Config `toml:"rs485"`
Timeout config.Duration `toml:"timeout"`
Retries int `toml:"busy_retries"`
RetriesWaitTime config.Duration `toml:"busy_retries_wait"`
DebugConnection bool `toml:"debug_connection" deprecated:"1.35.0;use 'log_level' 'trace' instead"`
Workarounds workarounds `toml:"workarounds"`
ConfigurationType string `toml:"configuration_type"`
ExcludeRegisterTypeTag bool `toml:"exclude_register_type_tag"`
Log telegraf.Logger `toml:"-"`
// configuration type specific settings
configurationOriginal
configurationPerRequest
configurationPerMetric
// Connection handling
client mb.Client
handler mb.ClientHandler
isConnected bool
// Request handling
requests map[byte]requestSet
}
type workarounds struct {
AfterConnectPause config.Duration `toml:"pause_after_connect"` AfterConnectPause config.Duration `toml:"pause_after_connect"`
PollPause config.Duration `toml:"pause_between_requests"` PollPause config.Duration `toml:"pause_between_requests"`
CloseAfterGather bool `toml:"close_connection_after_gather"` CloseAfterGather bool `toml:"close_connection_after_gather"`
@ -37,7 +75,7 @@ type ModbusWorkarounds struct {
} }
// According to github.com/grid-x/serial // According to github.com/grid-x/serial
type RS485Config struct { type rs485Config struct {
DelayRtsBeforeSend config.Duration `toml:"delay_rts_before_send"` DelayRtsBeforeSend config.Duration `toml:"delay_rts_before_send"`
DelayRtsAfterSend config.Duration `toml:"delay_rts_after_send"` DelayRtsAfterSend config.Duration `toml:"delay_rts_after_send"`
RtsHighDuringSend bool `toml:"rts_high_during_send"` RtsHighDuringSend bool `toml:"rts_high_during_send"`
@ -45,38 +83,6 @@ type RS485Config struct {
RxDuringTx bool `toml:"rx_during_tx"` RxDuringTx bool `toml:"rx_during_tx"`
} }
// Modbus holds all data relevant to the plugin
type Modbus struct {
Name string `toml:"name"`
Controller string `toml:"controller"`
TransmissionMode string `toml:"transmission_mode"`
BaudRate int `toml:"baud_rate"`
DataBits int `toml:"data_bits"`
Parity string `toml:"parity"`
StopBits int `toml:"stop_bits"`
RS485 *RS485Config `toml:"rs485"`
Timeout config.Duration `toml:"timeout"`
Retries int `toml:"busy_retries"`
RetriesWaitTime config.Duration `toml:"busy_retries_wait"`
DebugConnection bool `toml:"debug_connection" deprecated:"1.35.0;use 'log_level' 'trace' instead"`
Workarounds ModbusWorkarounds `toml:"workarounds"`
ConfigurationType string `toml:"configuration_type"`
ExcludeRegisterTypeTag bool `toml:"exclude_register_type_tag"`
Log telegraf.Logger `toml:"-"`
// Configuration type specific settings
ConfigurationOriginal
ConfigurationPerRequest
ConfigurationPerMetric
// Connection handling
client mb.Client
handler mb.ClientHandler
isConnected bool
// Request handling
requests map[byte]requestSet
}
type fieldConverterFunc func(bytes []byte) interface{} type fieldConverterFunc func(bytes []byte) interface{}
type requestSet struct { type requestSet struct {
@ -86,7 +92,7 @@ type requestSet struct {
input []request input []request
} }
func (r requestSet) Empty() bool { func (r requestSet) empty() bool {
l := len(r.coil) l := len(r.coil)
l += len(r.discrete) l += len(r.discrete)
l += len(r.holding) l += len(r.holding)
@ -105,24 +111,16 @@ type field struct {
tags map[string]string tags map[string]string
} }
const (
cDiscreteInputs = "discrete_input"
cCoils = "coil"
cHoldingRegisters = "holding_register"
cInputRegisters = "input_register"
)
// SampleConfig returns a basic configuration for the plugin
func (m *Modbus) SampleConfig() string { func (m *Modbus) SampleConfig() string {
configs := []Configuration{ configs := []configuration{
&m.ConfigurationOriginal, &m.configurationOriginal,
&m.ConfigurationPerRequest, &m.configurationPerRequest,
&m.ConfigurationPerMetric, &m.configurationPerMetric,
} }
totalConfig := sampleConfigStart totalConfig := sampleConfigStart
for _, c := range configs { for _, c := range configs {
totalConfig += c.SampleConfigPart() + "\n" totalConfig += c.sampleConfigPart() + "\n"
} }
totalConfig += "\n" totalConfig += "\n"
totalConfig += sampleConfigEnd totalConfig += sampleConfigEnd
@ -140,32 +138,32 @@ func (m *Modbus) Init() error {
} }
// Determine the configuration style // Determine the configuration style
var cfg Configuration var cfg configuration
switch m.ConfigurationType { switch m.ConfigurationType {
case "", "register": case "", "register":
m.ConfigurationOriginal.workarounds = m.Workarounds m.configurationOriginal.workarounds = m.Workarounds
m.ConfigurationOriginal.logger = m.Log m.configurationOriginal.logger = m.Log
cfg = &m.ConfigurationOriginal cfg = &m.configurationOriginal
case "request": case "request":
m.ConfigurationPerRequest.workarounds = m.Workarounds m.configurationPerRequest.workarounds = m.Workarounds
m.ConfigurationPerRequest.excludeRegisterType = m.ExcludeRegisterTypeTag m.configurationPerRequest.excludeRegisterType = m.ExcludeRegisterTypeTag
m.ConfigurationPerRequest.logger = m.Log m.configurationPerRequest.logger = m.Log
cfg = &m.ConfigurationPerRequest cfg = &m.configurationPerRequest
case "metric": case "metric":
m.ConfigurationPerMetric.workarounds = m.Workarounds m.configurationPerMetric.workarounds = m.Workarounds
m.ConfigurationPerMetric.excludeRegisterType = m.ExcludeRegisterTypeTag m.configurationPerMetric.excludeRegisterType = m.ExcludeRegisterTypeTag
m.ConfigurationPerMetric.logger = m.Log m.configurationPerMetric.logger = m.Log
cfg = &m.ConfigurationPerMetric cfg = &m.configurationPerMetric
default: default:
return fmt.Errorf("unknown configuration type %q in device %q", m.ConfigurationType, m.Name) return fmt.Errorf("unknown configuration type %q in device %q", m.ConfigurationType, m.Name)
} }
// Check and process the configuration // Check and process the configuration
if err := cfg.Check(); err != nil { if err := cfg.check(); err != nil {
return fmt.Errorf("configuration invalid for device %q: %w", m.Name, err) return fmt.Errorf("configuration invalid for device %q: %w", m.Name, err)
} }
r, err := cfg.Process() r, err := cfg.process()
if err != nil { if err != nil {
return fmt.Errorf("cannot process configuration for device %q: %w", m.Name, err) return fmt.Errorf("cannot process configuration for device %q: %w", m.Name, err)
} }
@ -219,7 +217,6 @@ func (m *Modbus) Init() error {
return nil return nil
} }
// Gather implements the telegraf plugin interface method for data accumulation
func (m *Modbus) Gather(acc telegraf.Accumulator) error { func (m *Modbus) Gather(acc telegraf.Accumulator) error {
if !m.isConnected { if !m.isConnected {
if err := m.connect(); err != nil { if err := m.connect(); err != nil {
@ -558,7 +555,7 @@ func (m *Modbus) collectFields(grouper *metric.SeriesGrouper, timestamp time.Tim
} }
} }
// Implement the logger interface of the modbus client // Printf implements the logger interface of the modbus client
func (m *Modbus) Printf(format string, v ...interface{}) { func (m *Modbus) Printf(format string, v ...interface{}) {
m.Log.Tracef(format, v...) m.Log.Tracef(format, v...)
} }

View File

@ -491,7 +491,7 @@ func TestRegisterWorkaroundsOneRequestPerField(t *testing.T) {
Controller: "tcp://localhost:1502", Controller: "tcp://localhost:1502",
ConfigurationType: "register", ConfigurationType: "register",
Log: testutil.Logger{Quiet: true}, Log: testutil.Logger{Quiet: true},
Workarounds: ModbusWorkarounds{OnRequestPerField: true}, Workarounds: workarounds{OnRequestPerField: true},
} }
plugin.SlaveID = 1 plugin.SlaveID = 1
plugin.HoldingRegisters = []fieldDefinition{ plugin.HoldingRegisters = []fieldDefinition{
@ -541,7 +541,7 @@ func TestRequestsWorkaroundsReadCoilsStartingAtZeroRegister(t *testing.T) {
Controller: "tcp://localhost:1502", Controller: "tcp://localhost:1502",
ConfigurationType: "register", ConfigurationType: "register",
Log: testutil.Logger{Quiet: true}, Log: testutil.Logger{Quiet: true},
Workarounds: ModbusWorkarounds{ReadCoilsStartingAtZero: true}, Workarounds: workarounds{ReadCoilsStartingAtZero: true},
} }
plugin.SlaveID = 1 plugin.SlaveID = 1
plugin.Coils = []fieldDefinition{ plugin.Coils = []fieldDefinition{
@ -688,8 +688,8 @@ func TestWorkaroundsStringRegisterLocation(t *testing.T) {
Controller: "tcp://localhost:1502", Controller: "tcp://localhost:1502",
ConfigurationType: "request", ConfigurationType: "request",
Log: testutil.Logger{Quiet: true}, Log: testutil.Logger{Quiet: true},
Workarounds: ModbusWorkarounds{StringRegisterLocation: tt.location}, Workarounds: workarounds{StringRegisterLocation: tt.location},
ConfigurationPerRequest: ConfigurationPerRequest{ configurationPerRequest: configurationPerRequest{
Requests: []requestDefinition{ Requests: []requestDefinition{
{ {
SlaveID: 1, SlaveID: 1,
@ -738,7 +738,7 @@ func TestWorkaroundsStringRegisterLocationInvalid(t *testing.T) {
Controller: "tcp://localhost:1502", Controller: "tcp://localhost:1502",
ConfigurationType: "request", ConfigurationType: "request",
Log: testutil.Logger{Quiet: true}, Log: testutil.Logger{Quiet: true},
Workarounds: ModbusWorkarounds{StringRegisterLocation: "foo"}, Workarounds: workarounds{StringRegisterLocation: "foo"},
} }
require.ErrorContains(t, plugin.Init(), `invalid 'string_register_location'`) require.ErrorContains(t, plugin.Init(), `invalid 'string_register_location'`)
} }

View File

@ -138,7 +138,7 @@ func optimizeGroup(g request, maxBatchSize uint16) []request {
return requests return requests
} }
func optimitzeGroupWithinLimits(g request, params groupingParams) []request { func optimizeGroupWithinLimits(g request, params groupingParams) []request {
if len(g.fields) == 0 { if len(g.fields) == 0 {
return nil return nil
} }
@ -153,14 +153,14 @@ func optimitzeGroupWithinLimits(g request, params groupingParams) []request {
// Check if we need to interrupt the current chunk and require a new one // Check if we need to interrupt the current chunk and require a new one
holeSize := g.fields[i].address - (g.fields[i-1].address + g.fields[i-1].length) holeSize := g.fields[i].address - (g.fields[i-1].address + g.fields[i-1].length)
if g.fields[i].address < g.fields[i-1].address+g.fields[i-1].length { if g.fields[i].address < g.fields[i-1].address+g.fields[i-1].length {
params.Log.Warnf( params.log.Warnf(
"Request at %d with length %d overlaps with next request at %d", "Request at %d with length %d overlaps with next request at %d",
g.fields[i-1].address, g.fields[i-1].length, g.fields[i].address, g.fields[i-1].address, g.fields[i-1].length, g.fields[i].address,
) )
holeSize = 0 holeSize = 0
} }
needInterrupt := holeSize > params.MaxExtraRegisters // too far apart needInterrupt := holeSize > params.maxExtraRegisters // too far apart
needInterrupt = needInterrupt || currentRequest.length+holeSize+g.fields[i].length > params.MaxBatchSize // too large needInterrupt = needInterrupt || currentRequest.length+holeSize+g.fields[i].length > params.maxBatchSize // too large
if !needInterrupt { if !needInterrupt {
// Still safe to add the field to the current request // Still safe to add the field to the current request
currentRequest.length = g.fields[i].address + g.fields[i].length - currentRequest.address currentRequest.length = g.fields[i].address + g.fields[i].length - currentRequest.address
@ -181,18 +181,16 @@ func optimitzeGroupWithinLimits(g request, params groupingParams) []request {
type groupingParams struct { type groupingParams struct {
// Maximum size of a request in registers // Maximum size of a request in registers
MaxBatchSize uint16 maxBatchSize uint16
// Optimization to use for grouping register groups to requests. // optimization to use for grouping register groups to requests, Also put potential optimization parameters here
// Also put potential optimization parameters here optimization string
Optimization string maxExtraRegisters uint16
MaxExtraRegisters uint16 // Will force reads to start at zero (if possible) while respecting the max-batch size.
// Will force reads to start at zero (if possible) while respecting enforceFromZero bool
// the max-batch size. // tags to add for the requests
EnforceFromZero bool tags map[string]string
// Tags to add for the requests // log facility to inform the user
Tags map[string]string log telegraf.Logger
// Log facility to inform the user
Log telegraf.Logger
} }
func groupFieldsToRequests(fields []field, params groupingParams) []request { func groupFieldsToRequests(fields []field, params groupingParams) []request {
@ -216,9 +214,9 @@ func groupFieldsToRequests(fields []field, params groupingParams) []request {
for _, f := range fields { for _, f := range fields {
// Add tags from higher up // Add tags from higher up
if f.tags == nil { if f.tags == nil {
f.tags = make(map[string]string, len(params.Tags)) f.tags = make(map[string]string, len(params.tags))
} }
for k, v := range params.Tags { for k, v := range params.tags {
f.tags[k] = v f.tags[k] = v
} }
@ -253,18 +251,18 @@ func groupFieldsToRequests(fields []field, params groupingParams) []request {
} }
// Enforce the first read to start at zero if the option is set // Enforce the first read to start at zero if the option is set
if params.EnforceFromZero { if params.enforceFromZero {
groups[0].length += groups[0].address groups[0].length += groups[0].address
groups[0].address = 0 groups[0].address = 0
} }
var requests []request var requests []request
switch params.Optimization { switch params.optimization {
case "shrink": case "shrink":
// Shrink request by striping leading and trailing fields with an omit flag set // Shrink request by striping leading and trailing fields with an omit flag set
for _, g := range groups { for _, g := range groups {
if len(g.fields) > 0 { if len(g.fields) > 0 {
requests = append(requests, shrinkGroup(g, params.MaxBatchSize)...) requests = append(requests, shrinkGroup(g, params.maxBatchSize)...)
} }
} }
case "rearrange": case "rearrange":
@ -272,7 +270,7 @@ func groupFieldsToRequests(fields []field, params groupingParams) []request {
// registers while keeping the number of requests // registers while keeping the number of requests
for _, g := range groups { for _, g := range groups {
if len(g.fields) > 0 { if len(g.fields) > 0 {
requests = append(requests, optimizeGroup(g, params.MaxBatchSize)...) requests = append(requests, optimizeGroup(g, params.maxBatchSize)...)
} }
} }
case "aggressive": case "aggressive":
@ -284,7 +282,7 @@ func groupFieldsToRequests(fields []field, params groupingParams) []request {
total.fields = append(total.fields, g.fields...) total.fields = append(total.fields, g.fields...)
} }
} }
requests = optimizeGroup(total, params.MaxBatchSize) requests = optimizeGroup(total, params.maxBatchSize)
case "max_insert": case "max_insert":
// Similar to aggressive but keeps the number of touched registers below a threshold // Similar to aggressive but keeps the number of touched registers below a threshold
var total request var total request
@ -293,12 +291,12 @@ func groupFieldsToRequests(fields []field, params groupingParams) []request {
total.fields = append(total.fields, g.fields...) total.fields = append(total.fields, g.fields...)
} }
} }
requests = optimitzeGroupWithinLimits(total, params) requests = optimizeGroupWithinLimits(total, params)
default: default:
// no optimization // no optimization
for _, g := range groups { for _, g := range groups {
if len(g.fields) > 0 { if len(g.fields) > 0 {
requests = append(requests, splitMaxBatchSize(g, params.MaxBatchSize)...) requests = append(requests, splitMaxBatchSize(g, params.maxBatchSize)...)
} }
} }
} }

View File

@ -26,26 +26,26 @@ import (
//go:embed sample.conf //go:embed sample.conf
var sampleConfig string var sampleConfig string
var DisconnectedServersBehaviors = []string{"error", "skip"} var disconnectedServersBehaviors = []string{"error", "skip"}
type MongoDB struct { type MongoDB struct {
Servers []string Servers []string `toml:"servers"`
Ssl Ssl GatherClusterStatus bool `toml:"gather_cluster_status"`
GatherClusterStatus bool GatherPerdbStats bool `toml:"gather_perdb_stats"`
GatherPerdbStats bool GatherColStats bool `toml:"gather_col_stats"`
GatherColStats bool GatherTopStat bool `toml:"gather_top_stat"`
GatherTopStat bool DisconnectedServersBehavior string `toml:"disconnected_servers_behavior"`
DisconnectedServersBehavior string ColStatsDbs []string `toml:"col_stats_dbs"`
ColStatsDbs []string
common_tls.ClientConfig common_tls.ClientConfig
Ssl ssl
Log telegraf.Logger `toml:"-"` Log telegraf.Logger `toml:"-"`
clients []*Server clients []*server
tlsConfig *tls.Config tlsConfig *tls.Config
} }
type Ssl struct { type ssl struct {
Enabled bool `toml:"ssl_enabled" deprecated:"1.3.0;1.35.0;use 'tls_*' options instead"` Enabled bool `toml:"ssl_enabled" deprecated:"1.3.0;1.35.0;use 'tls_*' options instead"`
CaCerts []string `toml:"cacerts" deprecated:"1.3.0;1.35.0;use 'tls_ca' instead"` CaCerts []string `toml:"cacerts" deprecated:"1.3.0;1.35.0;use 'tls_ca' instead"`
} }
@ -59,7 +59,7 @@ func (m *MongoDB) Init() error {
m.DisconnectedServersBehavior = "error" m.DisconnectedServersBehavior = "error"
} }
if err := choice.Check(m.DisconnectedServersBehavior, DisconnectedServersBehaviors); err != nil { if err := choice.Check(m.DisconnectedServersBehavior, disconnectedServersBehaviors); err != nil {
return fmt.Errorf("disconnected_servers_behavior: %w", err) return fmt.Errorf("disconnected_servers_behavior: %w", err)
} }
@ -105,6 +105,41 @@ func (m *MongoDB) Start(telegraf.Accumulator) error {
return nil return nil
} }
func (m *MongoDB) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
for _, client := range m.clients {
wg.Add(1)
go func(srv *server) {
defer wg.Done()
if m.DisconnectedServersBehavior == "skip" {
if err := srv.ping(); err != nil {
m.Log.Debugf("Failed to ping server: %s", err)
return
}
}
err := srv.gatherData(acc, m.GatherClusterStatus, m.GatherPerdbStats, m.GatherColStats, m.GatherTopStat, m.ColStatsDbs)
if err != nil {
m.Log.Errorf("Failed to gather data: %s", err)
}
}(client)
}
wg.Wait()
return nil
}
// Stop disconnects mongo connections when stop or reload
func (m *MongoDB) Stop() {
for _, server := range m.clients {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
if err := server.client.Disconnect(ctx); err != nil {
m.Log.Errorf("Disconnecting from %q failed: %v", server.hostname, err)
}
cancel()
}
}
func (m *MongoDB) setupConnection(connURL string) error { func (m *MongoDB) setupConnection(connURL string) error {
if !strings.HasPrefix(connURL, "mongodb://") && !strings.HasPrefix(connURL, "mongodb+srv://") { if !strings.HasPrefix(connURL, "mongodb://") && !strings.HasPrefix(connURL, "mongodb+srv://") {
// Preserve backwards compatibility for hostnames without a // Preserve backwards compatibility for hostnames without a
@ -143,52 +178,15 @@ func (m *MongoDB) setupConnection(connURL string) error {
m.Log.Errorf("Unable to ping MongoDB: %s", err) m.Log.Errorf("Unable to ping MongoDB: %s", err)
} }
server := &Server{ server := &server{
client: client, client: client,
hostname: u.Host, hostname: u.Host,
Log: m.Log, log: m.Log,
} }
m.clients = append(m.clients, server) m.clients = append(m.clients, server)
return nil return nil
} }
// Stop disconnect mongo connections when stop or reload
func (m *MongoDB) Stop() {
for _, server := range m.clients {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
if err := server.client.Disconnect(ctx); err != nil {
m.Log.Errorf("Disconnecting from %q failed: %v", server.hostname, err)
}
cancel()
}
}
// Reads stats from all configured servers accumulates stats.
// Returns one of the errors encountered while gather stats (if any).
func (m *MongoDB) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
for _, client := range m.clients {
wg.Add(1)
go func(srv *Server) {
defer wg.Done()
if m.DisconnectedServersBehavior == "skip" {
if err := srv.ping(); err != nil {
m.Log.Debugf("Failed to ping server: %s", err)
return
}
}
err := srv.gatherData(acc, m.GatherClusterStatus, m.GatherPerdbStats, m.GatherColStats, m.GatherTopStat, m.ColStatsDbs)
if err != nil {
m.Log.Errorf("Failed to gather data: %s", err)
}
}(client)
}
wg.Wait()
return nil
}
func init() { func init() {
inputs.Add("mongodb", func() telegraf.Input { inputs.Add("mongodb", func() telegraf.Input {
return &MongoDB{ return &MongoDB{

View File

@ -8,29 +8,29 @@ import (
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
) )
type MongodbData struct { type mongodbData struct {
StatLine *statLine StatLine *statLine
Fields map[string]interface{} Fields map[string]interface{}
Tags map[string]string Tags map[string]string
DbData []DbData DbData []bbData
ColData []ColData ColData []colData
ShardHostData []DbData ShardHostData []bbData
TopStatsData []DbData TopStatsData []bbData
} }
type DbData struct { type bbData struct {
Name string Name string
Fields map[string]interface{} Fields map[string]interface{}
} }
type ColData struct { type colData struct {
Name string Name string
DbName string DbName string
Fields map[string]interface{} Fields map[string]interface{}
} }
func NewMongodbData(statLine *statLine, tags map[string]string) *MongodbData { func newMongodbData(statLine *statLine, tags map[string]string) *mongodbData {
return &MongodbData{ return &mongodbData{
StatLine: statLine, StatLine: statLine,
Tags: tags, Tags: tags,
Fields: make(map[string]interface{}), Fields: make(map[string]interface{}),
@ -297,11 +297,11 @@ var topDataStats = map[string]string{
"commands_count": "CommandsCount", "commands_count": "CommandsCount",
} }
func (d *MongodbData) AddDbStats() { func (d *mongodbData) addDbStats() {
for i := range d.StatLine.DbStatsLines { for i := range d.StatLine.DbStatsLines {
dbstat := d.StatLine.DbStatsLines[i] dbstat := d.StatLine.DbStatsLines[i]
dbStatLine := reflect.ValueOf(&dbstat).Elem() dbStatLine := reflect.ValueOf(&dbstat).Elem()
newDbData := &DbData{ newDbData := &bbData{
Name: dbstat.Name, Name: dbstat.Name,
Fields: make(map[string]interface{}), Fields: make(map[string]interface{}),
} }
@ -314,11 +314,11 @@ func (d *MongodbData) AddDbStats() {
} }
} }
func (d *MongodbData) AddColStats() { func (d *mongodbData) addColStats() {
for i := range d.StatLine.ColStatsLines { for i := range d.StatLine.ColStatsLines {
colstat := d.StatLine.ColStatsLines[i] colstat := d.StatLine.ColStatsLines[i]
colStatLine := reflect.ValueOf(&colstat).Elem() colStatLine := reflect.ValueOf(&colstat).Elem()
newColData := &ColData{ newColData := &colData{
Name: colstat.Name, Name: colstat.Name,
DbName: colstat.DbName, DbName: colstat.DbName,
Fields: make(map[string]interface{}), Fields: make(map[string]interface{}),
@ -332,11 +332,11 @@ func (d *MongodbData) AddColStats() {
} }
} }
func (d *MongodbData) AddShardHostStats() { func (d *mongodbData) addShardHostStats() {
for host := range d.StatLine.ShardHostStatsLines { for host := range d.StatLine.ShardHostStatsLines {
hostStat := d.StatLine.ShardHostStatsLines[host] hostStat := d.StatLine.ShardHostStatsLines[host]
hostStatLine := reflect.ValueOf(&hostStat).Elem() hostStatLine := reflect.ValueOf(&hostStat).Elem()
newDbData := &DbData{ newDbData := &bbData{
Name: host, Name: host,
Fields: make(map[string]interface{}), Fields: make(map[string]interface{}),
} }
@ -349,11 +349,11 @@ func (d *MongodbData) AddShardHostStats() {
} }
} }
func (d *MongodbData) AddTopStats() { func (d *mongodbData) addTopStats() {
for i := range d.StatLine.TopStatLines { for i := range d.StatLine.TopStatLines {
topStat := d.StatLine.TopStatLines[i] topStat := d.StatLine.TopStatLines[i]
topStatLine := reflect.ValueOf(&topStat).Elem() topStatLine := reflect.ValueOf(&topStat).Elem()
newTopStatData := &DbData{ newTopStatData := &bbData{
Name: topStat.CollectionName, Name: topStat.CollectionName,
Fields: make(map[string]interface{}), Fields: make(map[string]interface{}),
} }
@ -366,7 +366,7 @@ func (d *MongodbData) AddTopStats() {
} }
} }
func (d *MongodbData) AddDefaultStats() { func (d *mongodbData) addDefaultStats() {
statLine := reflect.ValueOf(d.StatLine).Elem() statLine := reflect.ValueOf(d.StatLine).Elem()
d.addStat(statLine, defaultStats) d.addStat(statLine, defaultStats)
if d.StatLine.NodeType != "" { if d.StatLine.NodeType != "" {
@ -414,18 +414,18 @@ func (d *MongodbData) AddDefaultStats() {
} }
} }
func (d *MongodbData) addStat(statLine reflect.Value, stats map[string]string) { func (d *mongodbData) addStat(statLine reflect.Value, stats map[string]string) {
for key, value := range stats { for key, value := range stats {
val := statLine.FieldByName(value).Interface() val := statLine.FieldByName(value).Interface()
d.add(key, val) d.add(key, val)
} }
} }
func (d *MongodbData) add(key string, val interface{}) { func (d *mongodbData) add(key string, val interface{}) {
d.Fields[key] = val d.Fields[key] = val
} }
func (d *MongodbData) flush(acc telegraf.Accumulator) { func (d *mongodbData) flush(acc telegraf.Accumulator) {
acc.AddFields( acc.AddFields(
"mongodb", "mongodb",
d.Fields, d.Fields,

View File

@ -13,7 +13,7 @@ import (
var tags = make(map[string]string) var tags = make(map[string]string)
func TestAddNonReplStats(t *testing.T) { func TestAddNonReplStats(t *testing.T) {
d := NewMongodbData( d := newMongodbData(
&statLine{ &statLine{
StorageEngine: "", StorageEngine: "",
Time: time.Now(), Time: time.Now(),
@ -62,7 +62,7 @@ func TestAddNonReplStats(t *testing.T) {
) )
var acc testutil.Accumulator var acc testutil.Accumulator
d.AddDefaultStats() d.addDefaultStats()
d.flush(&acc) d.flush(&acc)
for key := range defaultStats { for key := range defaultStats {
@ -71,7 +71,7 @@ func TestAddNonReplStats(t *testing.T) {
} }
func TestAddReplStats(t *testing.T) { func TestAddReplStats(t *testing.T) {
d := NewMongodbData( d := newMongodbData(
&statLine{ &statLine{
StorageEngine: "mmapv1", StorageEngine: "mmapv1",
Mapped: 0, Mapped: 0,
@ -83,7 +83,7 @@ func TestAddReplStats(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
d.AddDefaultStats() d.addDefaultStats()
d.flush(&acc) d.flush(&acc)
for key := range mmapStats { for key := range mmapStats {
@ -92,7 +92,7 @@ func TestAddReplStats(t *testing.T) {
} }
func TestAddWiredTigerStats(t *testing.T) { func TestAddWiredTigerStats(t *testing.T) {
d := NewMongodbData( d := newMongodbData(
&statLine{ &statLine{
StorageEngine: "wiredTiger", StorageEngine: "wiredTiger",
CacheDirtyPercent: 0, CacheDirtyPercent: 0,
@ -124,7 +124,7 @@ func TestAddWiredTigerStats(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
d.AddDefaultStats() d.addDefaultStats()
d.flush(&acc) d.flush(&acc)
for key := range wiredTigerStats { for key := range wiredTigerStats {
@ -139,7 +139,7 @@ func TestAddWiredTigerStats(t *testing.T) {
} }
func TestAddShardStats(t *testing.T) { func TestAddShardStats(t *testing.T) {
d := NewMongodbData( d := newMongodbData(
&statLine{ &statLine{
TotalInUse: 0, TotalInUse: 0,
TotalAvailable: 0, TotalAvailable: 0,
@ -151,7 +151,7 @@ func TestAddShardStats(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
d.AddDefaultStats() d.addDefaultStats()
d.flush(&acc) d.flush(&acc)
for key := range defaultShardStats { for key := range defaultShardStats {
@ -160,7 +160,7 @@ func TestAddShardStats(t *testing.T) {
} }
func TestAddLatencyStats(t *testing.T) { func TestAddLatencyStats(t *testing.T) {
d := NewMongodbData( d := newMongodbData(
&statLine{ &statLine{
CommandOpsCnt: 73, CommandOpsCnt: 73,
CommandLatency: 364, CommandLatency: 364,
@ -174,7 +174,7 @@ func TestAddLatencyStats(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
d.AddDefaultStats() d.addDefaultStats()
d.flush(&acc) d.flush(&acc)
for key := range defaultLatencyStats { for key := range defaultLatencyStats {
@ -183,7 +183,7 @@ func TestAddLatencyStats(t *testing.T) {
} }
func TestAddAssertsStats(t *testing.T) { func TestAddAssertsStats(t *testing.T) {
d := NewMongodbData( d := newMongodbData(
&statLine{ &statLine{
Regular: 3, Regular: 3,
Warning: 9, Warning: 9,
@ -196,7 +196,7 @@ func TestAddAssertsStats(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
d.AddDefaultStats() d.addDefaultStats()
d.flush(&acc) d.flush(&acc)
for key := range defaultAssertsStats { for key := range defaultAssertsStats {
@ -205,7 +205,7 @@ func TestAddAssertsStats(t *testing.T) {
} }
func TestAddCommandsStats(t *testing.T) { func TestAddCommandsStats(t *testing.T) {
d := NewMongodbData( d := newMongodbData(
&statLine{ &statLine{
AggregateCommandTotal: 12, AggregateCommandTotal: 12,
AggregateCommandFailed: 2, AggregateCommandFailed: 2,
@ -231,7 +231,7 @@ func TestAddCommandsStats(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
d.AddDefaultStats() d.addDefaultStats()
d.flush(&acc) d.flush(&acc)
for key := range defaultCommandsStats { for key := range defaultCommandsStats {
@ -240,7 +240,7 @@ func TestAddCommandsStats(t *testing.T) {
} }
func TestAddTCMallocStats(t *testing.T) { func TestAddTCMallocStats(t *testing.T) {
d := NewMongodbData( d := newMongodbData(
&statLine{ &statLine{
TCMallocCurrentAllocatedBytes: 5877253096, TCMallocCurrentAllocatedBytes: 5877253096,
TCMallocHeapSize: 8067108864, TCMallocHeapSize: 8067108864,
@ -267,7 +267,7 @@ func TestAddTCMallocStats(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
d.AddDefaultStats() d.addDefaultStats()
d.flush(&acc) d.flush(&acc)
for key := range defaultTCMallocStats { for key := range defaultTCMallocStats {
@ -276,7 +276,7 @@ func TestAddTCMallocStats(t *testing.T) {
} }
func TestAddStorageStats(t *testing.T) { func TestAddStorageStats(t *testing.T) {
d := NewMongodbData( d := newMongodbData(
&statLine{ &statLine{
StorageFreelistSearchBucketExhausted: 0, StorageFreelistSearchBucketExhausted: 0,
StorageFreelistSearchRequests: 0, StorageFreelistSearchRequests: 0,
@ -287,7 +287,7 @@ func TestAddStorageStats(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
d.AddDefaultStats() d.addDefaultStats()
d.flush(&acc) d.flush(&acc)
for key := range defaultStorageStats { for key := range defaultStorageStats {
@ -307,7 +307,7 @@ func TestAddShardHostStats(t *testing.T) {
} }
} }
d := NewMongodbData( d := newMongodbData(
&statLine{ &statLine{
ShardHostStatsLines: hostStatLines, ShardHostStatsLines: hostStatLines,
}, },
@ -315,7 +315,7 @@ func TestAddShardHostStats(t *testing.T) {
) )
var acc testutil.Accumulator var acc testutil.Accumulator
d.AddShardHostStats() d.addShardHostStats()
d.flush(&acc) d.flush(&acc)
hostsFound := make([]string, 0, len(hostStatLines)) hostsFound := make([]string, 0, len(hostStatLines))
@ -333,7 +333,7 @@ func TestAddShardHostStats(t *testing.T) {
} }
func TestStateTag(t *testing.T) { func TestStateTag(t *testing.T) {
d := NewMongodbData( d := newMongodbData(
&statLine{ &statLine{
StorageEngine: "", StorageEngine: "",
Time: time.Now(), Time: time.Now(),
@ -353,7 +353,7 @@ func TestStateTag(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
d.AddDefaultStats() d.addDefaultStats()
d.flush(&acc) d.flush(&acc)
fields := map[string]interface{}{ fields := map[string]interface{}{
"active_reads": int64(0), "active_reads": int64(0),
@ -524,7 +524,7 @@ func TestAddTopStats(t *testing.T) {
topStatLines = append(topStatLines, topStatLine) topStatLines = append(topStatLines, topStatLine)
} }
d := NewMongodbData( d := newMongodbData(
&statLine{ &statLine{
TopStatLines: topStatLines, TopStatLines: topStatLines,
}, },
@ -532,7 +532,7 @@ func TestAddTopStats(t *testing.T) {
) )
var acc testutil.Accumulator var acc testutil.Accumulator
d.AddTopStats() d.addTopStats()
d.flush(&acc) d.flush(&acc)
for range topStatLines { for range topStatLines {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"slices"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -16,44 +17,44 @@ import (
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
) )
type Server struct { type server struct {
client *mongo.Client client *mongo.Client
hostname string hostname string
lastResult *mongoStatus lastResult *mongoStatus
Log telegraf.Logger log telegraf.Logger
}
func (s *Server) getDefaultTags() map[string]string {
tags := make(map[string]string)
tags["hostname"] = s.hostname
return tags
} }
type oplogEntry struct { type oplogEntry struct {
Timestamp primitive.Timestamp `bson:"ts"` Timestamp primitive.Timestamp `bson:"ts"`
} }
func IsAuthorization(err error) bool { func isAuthorization(err error) bool {
return strings.Contains(err.Error(), "not authorized") return strings.Contains(err.Error(), "not authorized")
} }
func (s *Server) ping() error { func (s *server) getDefaultTags() map[string]string {
tags := make(map[string]string)
tags["hostname"] = s.hostname
return tags
}
func (s *server) ping() error {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel() defer cancel()
return s.client.Ping(ctx, nil) return s.client.Ping(ctx, nil)
} }
func (s *Server) authLog(err error) { func (s *server) authLog(err error) {
if IsAuthorization(err) { if isAuthorization(err) {
s.Log.Debug(err.Error()) s.log.Debug(err.Error())
} else { } else {
s.Log.Error(err.Error()) s.log.Error(err.Error())
} }
} }
func (s *Server) runCommand(database string, cmd, result interface{}) error { func (s *server) runCommand(database string, cmd, result interface{}) error {
r := s.client.Database(database).RunCommand(context.Background(), cmd) r := s.client.Database(database).RunCommand(context.Background(), cmd)
if r.Err() != nil { if r.Err() != nil {
return r.Err() return r.Err()
@ -61,7 +62,7 @@ func (s *Server) runCommand(database string, cmd, result interface{}) error {
return r.Decode(result) return r.Decode(result)
} }
func (s *Server) gatherServerStatus() (*serverStatus, error) { func (s *server) gatherServerStatus() (*serverStatus, error) {
serverStatus := &serverStatus{} serverStatus := &serverStatus{}
err := s.runCommand("admin", bson.D{ err := s.runCommand("admin", bson.D{
{ {
@ -79,7 +80,7 @@ func (s *Server) gatherServerStatus() (*serverStatus, error) {
return serverStatus, nil return serverStatus, nil
} }
func (s *Server) gatherReplSetStatus() (*replSetStatus, error) { func (s *server) gatherReplSetStatus() (*replSetStatus, error) {
replSetStatus := &replSetStatus{} replSetStatus := &replSetStatus{}
err := s.runCommand("admin", bson.D{ err := s.runCommand("admin", bson.D{
{ {
@ -93,7 +94,7 @@ func (s *Server) gatherReplSetStatus() (*replSetStatus, error) {
return replSetStatus, nil return replSetStatus, nil
} }
func (s *Server) gatherTopStatData() (*topStats, error) { func (s *server) gatherTopStatData() (*topStats, error) {
var dest map[string]interface{} var dest map[string]interface{}
err := s.runCommand("admin", bson.D{ err := s.runCommand("admin", bson.D{
{ {
@ -124,7 +125,7 @@ func (s *Server) gatherTopStatData() (*topStats, error) {
return &topStats{Totals: topInfo}, nil return &topStats{Totals: topInfo}, nil
} }
func (s *Server) gatherClusterStatus() (*clusterStatus, error) { func (s *server) gatherClusterStatus() (*clusterStatus, error) {
chunkCount, err := s.client.Database("config").Collection("chunks").CountDocuments(context.Background(), bson.M{"jumbo": true}) chunkCount, err := s.client.Database("config").Collection("chunks").CountDocuments(context.Background(), bson.M{"jumbo": true})
if err != nil { if err != nil {
return nil, err return nil, err
@ -148,7 +149,7 @@ func poolStatsCommand(version string) (string, error) {
return "shardConnPoolStats", nil return "shardConnPoolStats", nil
} }
func (s *Server) gatherShardConnPoolStats(version string) (*shardStats, error) { func (s *server) gatherShardConnPoolStats(version string) (*shardStats, error) {
command, err := poolStatsCommand(version) command, err := poolStatsCommand(version)
if err != nil { if err != nil {
return nil, err return nil, err
@ -167,7 +168,7 @@ func (s *Server) gatherShardConnPoolStats(version string) (*shardStats, error) {
return shardStats, nil return shardStats, nil
} }
func (s *Server) gatherDBStats(name string) (*db, error) { func (s *server) gatherDBStats(name string) (*db, error) {
stats := &dbStatsData{} stats := &dbStatsData{}
err := s.runCommand(name, bson.D{ err := s.runCommand(name, bson.D{
{ {
@ -185,7 +186,7 @@ func (s *Server) gatherDBStats(name string) (*db, error) {
}, nil }, nil
} }
func (s *Server) getOplogReplLag(collection string) (*oplogStats, error) { func (s *server) getOplogReplLag(collection string) (*oplogStats, error) {
query := bson.M{"ts": bson.M{"$exists": true}} query := bson.M{"ts": bson.M{"$exists": true}}
var first oplogEntry var first oplogEntry
@ -219,7 +220,7 @@ func (s *Server) getOplogReplLag(collection string) (*oplogStats, error) {
// The "oplog.$main" collection is created on the master node of a // The "oplog.$main" collection is created on the master node of a
// master-slave replicated deployment. As of MongoDB 3.2, master-slave // master-slave replicated deployment. As of MongoDB 3.2, master-slave
// replication has been deprecated. // replication has been deprecated.
func (s *Server) gatherOplogStats() (*oplogStats, error) { func (s *server) gatherOplogStats() (*oplogStats, error) {
stats, err := s.getOplogReplLag("oplog.rs") stats, err := s.getOplogReplLag("oplog.rs")
if err == nil { if err == nil {
return stats, nil return stats, nil
@ -228,7 +229,7 @@ func (s *Server) gatherOplogStats() (*oplogStats, error) {
return s.getOplogReplLag("oplog.$main") return s.getOplogReplLag("oplog.$main")
} }
func (s *Server) gatherCollectionStats(colStatsDbs []string) (*colStats, error) { func (s *server) gatherCollectionStats(colStatsDbs []string) (*colStats, error) {
names, err := s.client.ListDatabaseNames(context.Background(), bson.D{}) names, err := s.client.ListDatabaseNames(context.Background(), bson.D{})
if err != nil { if err != nil {
return nil, err return nil, err
@ -236,14 +237,14 @@ func (s *Server) gatherCollectionStats(colStatsDbs []string) (*colStats, error)
results := &colStats{} results := &colStats{}
for _, dbName := range names { for _, dbName := range names {
if stringInSlice(dbName, colStatsDbs) || len(colStatsDbs) == 0 { if slices.Contains(colStatsDbs, dbName) || len(colStatsDbs) == 0 {
// skip views as they fail on collStats below // skip views as they fail on collStats below
filter := bson.M{"type": bson.M{"$in": bson.A{"collection", "timeseries"}}} filter := bson.M{"type": bson.M{"$in": bson.A{"collection", "timeseries"}}}
var colls []string var colls []string
colls, err = s.client.Database(dbName).ListCollectionNames(context.Background(), filter) colls, err = s.client.Database(dbName).ListCollectionNames(context.Background(), filter)
if err != nil { if err != nil {
s.Log.Errorf("Error getting collection names: %s", err.Error()) s.log.Errorf("Error getting collection names: %s", err.Error())
continue continue
} }
for _, colName := range colls { for _, colName := range colls {
@ -270,7 +271,7 @@ func (s *Server) gatherCollectionStats(colStatsDbs []string) (*colStats, error)
return results, nil return results, nil
} }
func (s *Server) gatherData(acc telegraf.Accumulator, gatherClusterStatus, gatherDbStats, gatherColStats, gatherTopStat bool, colStatsDbs []string) error { func (s *server) gatherData(acc telegraf.Accumulator, gatherClusterStatus, gatherDbStats, gatherColStats, gatherTopStat bool, colStatsDbs []string) error {
serverStatus, err := s.gatherServerStatus() serverStatus, err := s.gatherServerStatus()
if err != nil { if err != nil {
return err return err
@ -280,7 +281,7 @@ func (s *Server) gatherData(acc telegraf.Accumulator, gatherClusterStatus, gathe
// member of a replica set. // member of a replica set.
replSetStatus, err := s.gatherReplSetStatus() replSetStatus, err := s.gatherReplSetStatus()
if err != nil { if err != nil {
s.Log.Debugf("Unable to gather replica set status: %s", err.Error()) s.log.Debugf("Unable to gather replica set status: %s", err.Error())
} }
// Gather the oplog if we are a member of a replica set. Non-replica set // Gather the oplog if we are a member of a replica set. Non-replica set
@ -297,7 +298,7 @@ func (s *Server) gatherData(acc telegraf.Accumulator, gatherClusterStatus, gathe
if gatherClusterStatus { if gatherClusterStatus {
status, err := s.gatherClusterStatus() status, err := s.gatherClusterStatus()
if err != nil { if err != nil {
s.Log.Debugf("Unable to gather cluster status: %s", err.Error()) s.log.Debugf("Unable to gather cluster status: %s", err.Error())
} }
clusterStatus = status clusterStatus = status
} }
@ -326,7 +327,7 @@ func (s *Server) gatherData(acc telegraf.Accumulator, gatherClusterStatus, gathe
for _, name := range names { for _, name := range names {
db, err := s.gatherDBStats(name) db, err := s.gatherDBStats(name)
if err != nil { if err != nil {
s.Log.Debugf("Error getting db stats from %q: %s", name, err.Error()) s.log.Debugf("Error getting db stats from %q: %s", name, err.Error())
} }
dbStats.Dbs = append(dbStats.Dbs, *db) dbStats.Dbs = append(dbStats.Dbs, *db)
} }
@ -336,7 +337,7 @@ func (s *Server) gatherData(acc telegraf.Accumulator, gatherClusterStatus, gathe
if gatherTopStat { if gatherTopStat {
topStats, err := s.gatherTopStatData() topStats, err := s.gatherTopStatData()
if err != nil { if err != nil {
s.Log.Debugf("Unable to gather top stat data: %s", err.Error()) s.log.Debugf("Unable to gather top stat data: %s", err.Error())
return err return err
} }
topStatData = topStats topStatData = topStats
@ -360,27 +361,18 @@ func (s *Server) gatherData(acc telegraf.Accumulator, gatherClusterStatus, gathe
if durationInSeconds == 0 { if durationInSeconds == 0 {
durationInSeconds = 1 durationInSeconds = 1
} }
data := NewMongodbData( data := newMongodbData(
NewStatLine(*s.lastResult, *result, s.hostname, true, durationInSeconds), newStatLine(*s.lastResult, *result, s.hostname, durationInSeconds),
s.getDefaultTags(), s.getDefaultTags(),
) )
data.AddDefaultStats() data.addDefaultStats()
data.AddDbStats() data.addDbStats()
data.AddColStats() data.addColStats()
data.AddShardHostStats() data.addShardHostStats()
data.AddTopStats() data.addTopStats()
data.flush(acc) data.flush(acc)
} }
s.lastResult = result s.lastResult = result
return nil return nil
} }
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}

View File

@ -11,15 +11,15 @@ import (
"github.com/influxdata/telegraf/testutil" "github.com/influxdata/telegraf/testutil"
) )
var ServicePort = "27017" var servicePort = "27017"
var unreachableMongoEndpoint = "mongodb://user:pass@127.0.0.1:27017/nop" var unreachableMongoEndpoint = "mongodb://user:pass@127.0.0.1:27017/nop"
func createTestServer(t *testing.T) *testutil.Container { func createTestServer(t *testing.T) *testutil.Container {
container := testutil.Container{ container := testutil.Container{
Image: "mongo", Image: "mongo",
ExposedPorts: []string{ServicePort}, ExposedPorts: []string{servicePort},
WaitingFor: wait.ForAll( WaitingFor: wait.ForAll(
wait.NewHTTPStrategy("/").WithPort(nat.Port(ServicePort)), wait.NewHTTPStrategy("/").WithPort(nat.Port(servicePort)),
wait.ForLog("Waiting for connections"), wait.ForLog("Waiting for connections"),
), ),
} }
@ -40,7 +40,7 @@ func TestGetDefaultTagsIntegration(t *testing.T) {
m := &MongoDB{ m := &MongoDB{
Log: testutil.Logger{}, Log: testutil.Logger{},
Servers: []string{ Servers: []string{
fmt.Sprintf("mongodb://%s:%s", container.Address, container.Ports[ServicePort]), fmt.Sprintf("mongodb://%s:%s", container.Address, container.Ports[servicePort]),
}, },
} }
err := m.Init() err := m.Init()
@ -76,7 +76,7 @@ func TestAddDefaultStatsIntegration(t *testing.T) {
m := &MongoDB{ m := &MongoDB{
Log: testutil.Logger{}, Log: testutil.Logger{},
Servers: []string{ Servers: []string{
fmt.Sprintf("mongodb://%s:%s", container.Address, container.Ports[ServicePort]), fmt.Sprintf("mongodb://%s:%s", container.Address, container.Ports[servicePort]),
}, },
} }
err := m.Init() err := m.Init()

View File

@ -12,18 +12,7 @@ import (
) )
const ( const (
MongosProcess = "mongos" mongosProcess = "mongos"
)
// Flags to determine cases when to activate/deactivate columns for output.
const (
Always = 1 << iota // always activate the column
Discover // only active when mongostat is in discover mode
Repl // only active if one of the nodes being monitored is in a replset
Locks // only active if node is capable of calculating lock info
AllOnly // only active if mongostat was run with --all option
MMAPOnly // only active if node has mmap-specific fields
WTOnly // only active if node has wiredtiger-specific fields
) )
type mongoStatus struct { type mongoStatus struct {
@ -557,48 +546,6 @@ type storageStats struct {
FreelistSearchScanned int64 `bson:"freelist.search.scanned"` FreelistSearchScanned int64 `bson:"freelist.search.scanned"`
} }
// statHeader describes a single column for mongostat's terminal output, its formatting, and in which modes it should be displayed.
type statHeader struct {
// The text to appear in the column's header cell
HeaderText string
// Bitmask containing flags to determine if this header is active or not
ActivateFlags int
}
// StatHeaders are the complete set of data metrics supported by mongostat.
var StatHeaders = []statHeader{
{"", Always}, // placeholder for hostname column (blank header text)
{"insert", Always},
{"query", Always},
{"update", Always},
{"delete", Always},
{"getmore", Always},
{"command", Always},
{"% dirty", WTOnly},
{"% used", WTOnly},
{"flushes", Always},
{"mapped", MMAPOnly},
{"vsize", Always},
{"res", Always},
{"non-mapped", MMAPOnly | AllOnly},
{"faults", MMAPOnly},
{"lr|lw %", MMAPOnly | AllOnly},
{"lrt|lwt", MMAPOnly | AllOnly},
{" locked db", Locks},
{"qr|qw", Always},
{"ar|aw", Always},
{"netIn", Always},
{"netOut", Always},
{"conn", Always},
{"set", Repl},
{"repl", Repl},
{"time", Always},
}
// NamespacedLocks stores information on the lockStatus of namespaces.
type NamespacedLocks map[string]lockStatus
// lockUsage stores information related to a namespace's lock usage. // lockUsage stores information related to a namespace's lock usage.
type lockUsage struct { type lockUsage struct {
Namespace string Namespace string
@ -931,8 +878,8 @@ func diff(newVal, oldVal, sampleTime int64) (avg, newValue int64) {
return d / sampleTime, newVal return d / sampleTime, newVal
} }
// NewStatLine constructs a statLine object from two mongoStatus objects. // newStatLine constructs a statLine object from two mongoStatus objects.
func NewStatLine(oldMongo, newMongo mongoStatus, key string, all bool, sampleSecs int64) *statLine { func newStatLine(oldMongo, newMongo mongoStatus, key string, sampleSecs int64) *statLine {
oldStat := *oldMongo.ServerStatus oldStat := *oldMongo.ServerStatus
newStat := *newMongo.ServerStatus newStat := *newMongo.ServerStatus
@ -1179,7 +1126,7 @@ func NewStatLine(oldMongo, newMongo mongoStatus, key string, all bool, sampleSec
returnVal.Time = newMongo.SampleTime returnVal.Time = newMongo.SampleTime
returnVal.IsMongos = returnVal.IsMongos =
newStat.ShardCursorType != nil || strings.HasPrefix(newStat.Process, MongosProcess) newStat.ShardCursorType != nil || strings.HasPrefix(newStat.Process, mongosProcess)
// BEGIN code modification // BEGIN code modification
if oldStat.Mem.Supported.(bool) { if oldStat.Mem.Supported.(bool) {
@ -1190,7 +1137,7 @@ func NewStatLine(oldMongo, newMongo mongoStatus, key string, all bool, sampleSec
returnVal.Virtual = newStat.Mem.Virtual returnVal.Virtual = newStat.Mem.Virtual
returnVal.Resident = newStat.Mem.Resident returnVal.Resident = newStat.Mem.Resident
if !returnVal.IsMongos && all { if !returnVal.IsMongos {
returnVal.NonMapped = newStat.Mem.Virtual - newStat.Mem.Mapped returnVal.NonMapped = newStat.Mem.Virtual - newStat.Mem.Mapped
} }
} }

View File

@ -7,7 +7,7 @@ import (
) )
func TestLatencyStats(t *testing.T) { func TestLatencyStats(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -49,7 +49,6 @@ func TestLatencyStats(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )
@ -62,7 +61,7 @@ func TestLatencyStats(t *testing.T) {
} }
func TestLatencyStatsDiffZero(t *testing.T) { func TestLatencyStatsDiffZero(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -118,7 +117,6 @@ func TestLatencyStatsDiffZero(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )
@ -131,7 +129,7 @@ func TestLatencyStatsDiffZero(t *testing.T) {
} }
func TestLatencyStatsDiff(t *testing.T) { func TestLatencyStatsDiff(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -187,7 +185,6 @@ func TestLatencyStatsDiff(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )
@ -200,7 +197,7 @@ func TestLatencyStatsDiff(t *testing.T) {
} }
func TestLocksStatsNilWhenLocksMissingInOldStat(t *testing.T) { func TestLocksStatsNilWhenLocksMissingInOldStat(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -223,7 +220,6 @@ func TestLocksStatsNilWhenLocksMissingInOldStat(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )
@ -231,7 +227,7 @@ func TestLocksStatsNilWhenLocksMissingInOldStat(t *testing.T) {
} }
func TestLocksStatsNilWhenGlobalLockStatsMissingInOldStat(t *testing.T) { func TestLocksStatsNilWhenGlobalLockStatsMissingInOldStat(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -255,7 +251,6 @@ func TestLocksStatsNilWhenGlobalLockStatsMissingInOldStat(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )
@ -263,7 +258,7 @@ func TestLocksStatsNilWhenGlobalLockStatsMissingInOldStat(t *testing.T) {
} }
func TestLocksStatsNilWhenGlobalLockStatsEmptyInOldStat(t *testing.T) { func TestLocksStatsNilWhenGlobalLockStatsEmptyInOldStat(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -289,7 +284,6 @@ func TestLocksStatsNilWhenGlobalLockStatsEmptyInOldStat(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )
@ -297,7 +291,7 @@ func TestLocksStatsNilWhenGlobalLockStatsEmptyInOldStat(t *testing.T) {
} }
func TestLocksStatsNilWhenCollectionLockStatsMissingInOldStat(t *testing.T) { func TestLocksStatsNilWhenCollectionLockStatsMissingInOldStat(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -325,7 +319,6 @@ func TestLocksStatsNilWhenCollectionLockStatsMissingInOldStat(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )
@ -333,7 +326,7 @@ func TestLocksStatsNilWhenCollectionLockStatsMissingInOldStat(t *testing.T) {
} }
func TestLocksStatsNilWhenCollectionLockStatsEmptyInOldStat(t *testing.T) { func TestLocksStatsNilWhenCollectionLockStatsEmptyInOldStat(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -362,7 +355,6 @@ func TestLocksStatsNilWhenCollectionLockStatsEmptyInOldStat(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )
@ -370,7 +362,7 @@ func TestLocksStatsNilWhenCollectionLockStatsEmptyInOldStat(t *testing.T) {
} }
func TestLocksStatsNilWhenLocksMissingInNewStat(t *testing.T) { func TestLocksStatsNilWhenLocksMissingInNewStat(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -393,7 +385,6 @@ func TestLocksStatsNilWhenLocksMissingInNewStat(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )
@ -401,7 +392,7 @@ func TestLocksStatsNilWhenLocksMissingInNewStat(t *testing.T) {
} }
func TestLocksStatsNilWhenGlobalLockStatsMissingInNewStat(t *testing.T) { func TestLocksStatsNilWhenGlobalLockStatsMissingInNewStat(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -425,7 +416,6 @@ func TestLocksStatsNilWhenGlobalLockStatsMissingInNewStat(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )
@ -433,7 +423,7 @@ func TestLocksStatsNilWhenGlobalLockStatsMissingInNewStat(t *testing.T) {
} }
func TestLocksStatsNilWhenGlobalLockStatsEmptyInNewStat(t *testing.T) { func TestLocksStatsNilWhenGlobalLockStatsEmptyInNewStat(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -459,7 +449,6 @@ func TestLocksStatsNilWhenGlobalLockStatsEmptyInNewStat(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )
@ -467,7 +456,7 @@ func TestLocksStatsNilWhenGlobalLockStatsEmptyInNewStat(t *testing.T) {
} }
func TestLocksStatsNilWhenCollectionLockStatsMissingInNewStat(t *testing.T) { func TestLocksStatsNilWhenCollectionLockStatsMissingInNewStat(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -495,7 +484,6 @@ func TestLocksStatsNilWhenCollectionLockStatsMissingInNewStat(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )
@ -503,7 +491,7 @@ func TestLocksStatsNilWhenCollectionLockStatsMissingInNewStat(t *testing.T) {
} }
func TestLocksStatsNilWhenCollectionLockStatsEmptyInNewStat(t *testing.T) { func TestLocksStatsNilWhenCollectionLockStatsEmptyInNewStat(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -532,7 +520,6 @@ func TestLocksStatsNilWhenCollectionLockStatsEmptyInNewStat(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )
@ -540,7 +527,7 @@ func TestLocksStatsNilWhenCollectionLockStatsEmptyInNewStat(t *testing.T) {
} }
func TestLocksStatsPopulated(t *testing.T) { func TestLocksStatsPopulated(t *testing.T) {
sl := NewStatLine( sl := newStatLine(
mongoStatus{ mongoStatus{
ServerStatus: &serverStatus{ ServerStatus: &serverStatus{
Connections: &connectionStats{}, Connections: &connectionStats{},
@ -596,7 +583,6 @@ func TestLocksStatsPopulated(t *testing.T) {
}, },
}, },
"foo", "foo",
true,
60, 60,
) )

View File

@ -19,6 +19,8 @@ import (
//go:embed sample.conf //go:embed sample.conf
var sampleConfig string var sampleConfig string
var pendingActions = []string{"ignore", "alert", "restart", "stop", "exec", "unmonitor", "start", "monitor"}
const ( const (
fileSystem = "0" fileSystem = "0"
directory = "1" directory = "1"
@ -31,7 +33,14 @@ const (
network = "8" network = "8"
) )
var pendingActions = []string{"ignore", "alert", "restart", "stop", "exec", "unmonitor", "start", "monitor"} type Monit struct {
Address string `toml:"address"`
Username string `toml:"username"`
Password string `toml:"password"`
Timeout config.Duration `toml:"timeout"`
client http.Client
tls.ClientConfig
}
type status struct { type status struct {
Server server `xml:"server"` Server server `xml:"server"`
@ -179,15 +188,6 @@ type system struct {
} `xml:"swap"` } `xml:"swap"`
} }
type Monit struct {
Address string `toml:"address"`
Username string `toml:"username"`
Password string `toml:"password"`
client http.Client
tls.ClientConfig
Timeout config.Duration `toml:"timeout"`
}
func (*Monit) SampleConfig() string { func (*Monit) SampleConfig() string {
return sampleConfig return sampleConfig
} }

View File

@ -24,32 +24,18 @@ import (
//go:embed sample.conf //go:embed sample.conf
var sampleConfig string var sampleConfig string
var once sync.Once
var ( var (
once sync.Once
// 30 Seconds is the default used by paho.mqtt.golang // 30 Seconds is the default used by paho.mqtt.golang
defaultConnectionTimeout = config.Duration(30 * time.Second) defaultConnectionTimeout = config.Duration(30 * time.Second)
defaultMaxUndeliveredMessages = 1000 defaultMaxUndeliveredMessages = 1000
) )
type empty struct{}
type semaphore chan empty
type Client interface {
Connect() mqtt.Token
SubscribeMultiple(filters map[string]byte, callback mqtt.MessageHandler) mqtt.Token
AddRoute(topic string, callback mqtt.MessageHandler)
Disconnect(quiesce uint)
IsConnected() bool
}
type ClientFactory func(o *mqtt.ClientOptions) Client
type MQTTConsumer struct { type MQTTConsumer struct {
Servers []string `toml:"servers"` Servers []string `toml:"servers"`
Topics []string `toml:"topics"` Topics []string `toml:"topics"`
TopicTag *string `toml:"topic_tag"` TopicTag *string `toml:"topic_tag"`
TopicParserConfig []TopicParsingConfig `toml:"topic_parsing"` TopicParserConfig []topicParsingConfig `toml:"topic_parsing"`
Username config.Secret `toml:"username"` Username config.Secret `toml:"username"`
Password config.Secret `toml:"password"` Password config.Secret `toml:"password"`
QoS int `toml:"qos"` QoS int `toml:"qos"`
@ -64,15 +50,15 @@ type MQTTConsumer struct {
tls.ClientConfig tls.ClientConfig
parser telegraf.Parser parser telegraf.Parser
clientFactory ClientFactory clientFactory clientFactory
client Client client client
opts *mqtt.ClientOptions opts *mqtt.ClientOptions
acc telegraf.TrackingAccumulator acc telegraf.TrackingAccumulator
sem semaphore sem semaphore
messages map[telegraf.TrackingID]mqtt.Message messages map[telegraf.TrackingID]mqtt.Message
messagesMutex sync.Mutex messagesMutex sync.Mutex
topicTagParse string topicTagParse string
topicParsers []*TopicParser topicParsers []*topicParser
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
payloadSize selfstat.Stat payloadSize selfstat.Stat
@ -80,13 +66,22 @@ type MQTTConsumer struct {
wg sync.WaitGroup wg sync.WaitGroup
} }
type client interface {
Connect() mqtt.Token
SubscribeMultiple(filters map[string]byte, callback mqtt.MessageHandler) mqtt.Token
AddRoute(topic string, callback mqtt.MessageHandler)
Disconnect(quiesce uint)
IsConnected() bool
}
type empty struct{}
type semaphore chan empty
type clientFactory func(o *mqtt.ClientOptions) client
func (*MQTTConsumer) SampleConfig() string { func (*MQTTConsumer) SampleConfig() string {
return sampleConfig return sampleConfig
} }
func (m *MQTTConsumer) SetParser(parser telegraf.Parser) {
m.parser = parser
}
func (m *MQTTConsumer) Init() error { func (m *MQTTConsumer) Init() error {
if m.ClientTrace { if m.ClientTrace {
log := &mqttLogger{m.Log} log := &mqttLogger{m.Log}
@ -116,9 +111,9 @@ func (m *MQTTConsumer) Init() error {
m.opts = opts m.opts = opts
m.messages = make(map[telegraf.TrackingID]mqtt.Message) m.messages = make(map[telegraf.TrackingID]mqtt.Message)
m.topicParsers = make([]*TopicParser, 0, len(m.TopicParserConfig)) m.topicParsers = make([]*topicParser, 0, len(m.TopicParserConfig))
for _, cfg := range m.TopicParserConfig { for _, cfg := range m.TopicParserConfig {
p, err := cfg.NewParser() p, err := cfg.newParser()
if err != nil { if err != nil {
return fmt.Errorf("config error topic parsing: %w", err) return fmt.Errorf("config error topic parsing: %w", err)
} }
@ -129,6 +124,11 @@ func (m *MQTTConsumer) Init() error {
m.messagesRecv = selfstat.Register("mqtt_consumer", "messages_received", make(map[string]string)) m.messagesRecv = selfstat.Register("mqtt_consumer", "messages_received", make(map[string]string))
return nil return nil
} }
func (m *MQTTConsumer) SetParser(parser telegraf.Parser) {
m.parser = parser
}
func (m *MQTTConsumer) Start(acc telegraf.Accumulator) error { func (m *MQTTConsumer) Start(acc telegraf.Accumulator) error {
m.acc = acc.WithTracking(m.MaxUndeliveredMessages) m.acc = acc.WithTracking(m.MaxUndeliveredMessages)
m.sem = make(semaphore, m.MaxUndeliveredMessages) m.sem = make(semaphore, m.MaxUndeliveredMessages)
@ -149,6 +149,26 @@ func (m *MQTTConsumer) Start(acc telegraf.Accumulator) error {
return m.connect() return m.connect()
} }
func (m *MQTTConsumer) Gather(_ telegraf.Accumulator) error {
if !m.client.IsConnected() {
m.Log.Debugf("Connecting %v", m.Servers)
return m.connect()
}
return nil
}
func (m *MQTTConsumer) Stop() {
if m.client.IsConnected() {
m.Log.Debugf("Disconnecting %v", m.Servers)
m.client.Disconnect(200)
m.Log.Debugf("Disconnected %v", m.Servers)
}
if m.cancel != nil {
m.cancel()
}
}
func (m *MQTTConsumer) connect() error { func (m *MQTTConsumer) connect() error {
m.client = m.clientFactory(m.opts) m.client = m.clientFactory(m.opts)
// AddRoute sets up the function for handling messages. These need to be // AddRoute sets up the function for handling messages. These need to be
@ -196,6 +216,7 @@ func (m *MQTTConsumer) connect() error {
} }
return nil return nil
} }
func (m *MQTTConsumer) onConnectionLost(_ mqtt.Client, err error) { func (m *MQTTConsumer) onConnectionLost(_ mqtt.Client, err error) {
// Should already be disconnected, but make doubly sure // Should already be disconnected, but make doubly sure
m.client.Disconnect(5) m.client.Disconnect(5)
@ -250,7 +271,7 @@ func (m *MQTTConsumer) onMessage(_ mqtt.Client, msg mqtt.Message) {
metric.AddTag(m.topicTagParse, msg.Topic()) metric.AddTag(m.topicTagParse, msg.Topic())
} }
for _, p := range m.topicParsers { for _, p := range m.topicParsers {
if err := p.Parse(metric, msg.Topic()); err != nil { if err := p.parse(metric, msg.Topic()); err != nil {
if m.PersistentSession { if m.PersistentSession {
msg.Ack() msg.Ack()
} }
@ -265,23 +286,7 @@ func (m *MQTTConsumer) onMessage(_ mqtt.Client, msg mqtt.Message) {
m.messages[id] = msg m.messages[id] = msg
m.messagesMutex.Unlock() m.messagesMutex.Unlock()
} }
func (m *MQTTConsumer) Stop() {
if m.client.IsConnected() {
m.Log.Debugf("Disconnecting %v", m.Servers)
m.client.Disconnect(200)
m.Log.Debugf("Disconnected %v", m.Servers)
}
if m.cancel != nil {
m.cancel()
}
}
func (m *MQTTConsumer) Gather(_ telegraf.Accumulator) error {
if !m.client.IsConnected() {
m.Log.Debugf("Connecting %v", m.Servers)
return m.connect()
}
return nil
}
func (m *MQTTConsumer) createOpts() (*mqtt.ClientOptions, error) { func (m *MQTTConsumer) createOpts() (*mqtt.ClientOptions, error) {
opts := mqtt.NewClientOptions() opts := mqtt.NewClientOptions()
opts.ConnectTimeout = time.Duration(m.ConnectionTimeout) opts.ConnectTimeout = time.Duration(m.ConnectionTimeout)
@ -342,7 +347,7 @@ func (m *MQTTConsumer) createOpts() (*mqtt.ClientOptions, error) {
return opts, nil return opts, nil
} }
func New(factory ClientFactory) *MQTTConsumer { func newMQTTConsumer(factory clientFactory) *MQTTConsumer {
return &MQTTConsumer{ return &MQTTConsumer{
Servers: []string{"tcp://127.0.0.1:1883"}, Servers: []string{"tcp://127.0.0.1:1883"},
MaxUndeliveredMessages: defaultMaxUndeliveredMessages, MaxUndeliveredMessages: defaultMaxUndeliveredMessages,
@ -354,7 +359,7 @@ func New(factory ClientFactory) *MQTTConsumer {
} }
func init() { func init() {
inputs.Add("mqtt_consumer", func() telegraf.Input { inputs.Add("mqtt_consumer", func() telegraf.Input {
return New(func(o *mqtt.ClientOptions) Client { return newMQTTConsumer(func(o *mqtt.ClientOptions) client {
return mqtt.NewClient(o) return mqtt.NewClient(o)
}) })
}) })

View File

@ -18,11 +18,11 @@ import (
"github.com/influxdata/telegraf/testutil" "github.com/influxdata/telegraf/testutil"
) )
type FakeClient struct { type fakeClient struct {
ConnectF func() mqtt.Token connectF func() mqtt.Token
SubscribeMultipleF func() mqtt.Token subscribeMultipleF func() mqtt.Token
AddRouteF func(callback mqtt.MessageHandler) addRouteF func(callback mqtt.MessageHandler)
DisconnectF func() disconnectF func()
connectCallCount int connectCallCount int
subscribeCallCount int subscribeCallCount int
@ -32,75 +32,75 @@ type FakeClient struct {
connected bool connected bool
} }
func (c *FakeClient) Connect() mqtt.Token { func (c *fakeClient) Connect() mqtt.Token {
c.connectCallCount++ c.connectCallCount++
token := c.ConnectF() token := c.connectF()
c.connected = token.Error() == nil c.connected = token.Error() == nil
return token return token
} }
func (c *FakeClient) SubscribeMultiple(map[string]byte, mqtt.MessageHandler) mqtt.Token { func (c *fakeClient) SubscribeMultiple(map[string]byte, mqtt.MessageHandler) mqtt.Token {
c.subscribeCallCount++ c.subscribeCallCount++
return c.SubscribeMultipleF() return c.subscribeMultipleF()
} }
func (c *FakeClient) AddRoute(_ string, callback mqtt.MessageHandler) { func (c *fakeClient) AddRoute(_ string, callback mqtt.MessageHandler) {
c.addRouteCallCount++ c.addRouteCallCount++
c.AddRouteF(callback) c.addRouteF(callback)
} }
func (c *FakeClient) Disconnect(uint) { func (c *fakeClient) Disconnect(uint) {
c.disconnectCallCount++ c.disconnectCallCount++
c.DisconnectF() c.disconnectF()
c.connected = false c.connected = false
} }
func (c *FakeClient) IsConnected() bool { func (c *fakeClient) IsConnected() bool {
return c.connected return c.connected
} }
type FakeParser struct{} type fakeParser struct{}
// FakeParser satisfies telegraf.Parser // fakeParser satisfies telegraf.Parser
var _ telegraf.Parser = &FakeParser{} var _ telegraf.Parser = &fakeParser{}
func (p *FakeParser) Parse(_ []byte) ([]telegraf.Metric, error) { func (p *fakeParser) Parse(_ []byte) ([]telegraf.Metric, error) {
panic("not implemented") panic("not implemented")
} }
func (p *FakeParser) ParseLine(_ string) (telegraf.Metric, error) { func (p *fakeParser) ParseLine(_ string) (telegraf.Metric, error) {
panic("not implemented") panic("not implemented")
} }
func (p *FakeParser) SetDefaultTags(_ map[string]string) { func (p *fakeParser) SetDefaultTags(_ map[string]string) {
panic("not implemented") panic("not implemented")
} }
type FakeToken struct { type fakeToken struct {
sessionPresent bool sessionPresent bool
complete chan struct{} complete chan struct{}
} }
// FakeToken satisfies mqtt.Token // fakeToken satisfies mqtt.Token
var _ mqtt.Token = &FakeToken{} var _ mqtt.Token = &fakeToken{}
func (t *FakeToken) Wait() bool { func (t *fakeToken) Wait() bool {
return true return true
} }
func (t *FakeToken) WaitTimeout(time.Duration) bool { func (t *fakeToken) WaitTimeout(time.Duration) bool {
return true return true
} }
func (t *FakeToken) Error() error { func (t *fakeToken) Error() error {
return nil return nil
} }
func (t *FakeToken) SessionPresent() bool { func (t *fakeToken) SessionPresent() bool {
return t.sessionPresent return t.sessionPresent
} }
func (t *FakeToken) Done() <-chan struct{} { func (t *fakeToken) Done() <-chan struct{} {
return t.complete return t.complete
} }
@ -108,24 +108,24 @@ func (t *FakeToken) Done() <-chan struct{} {
func TestLifecycleSanity(t *testing.T) { func TestLifecycleSanity(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
plugin := New(func(*mqtt.ClientOptions) Client { plugin := newMQTTConsumer(func(*mqtt.ClientOptions) client {
return &FakeClient{ return &fakeClient{
ConnectF: func() mqtt.Token { connectF: func() mqtt.Token {
return &FakeToken{} return &fakeToken{}
}, },
AddRouteF: func(mqtt.MessageHandler) { addRouteF: func(mqtt.MessageHandler) {
}, },
SubscribeMultipleF: func() mqtt.Token { subscribeMultipleF: func() mqtt.Token {
return &FakeToken{} return &fakeToken{}
}, },
DisconnectF: func() { disconnectF: func() {
}, },
} }
}) })
plugin.Log = testutil.Logger{} plugin.Log = testutil.Logger{}
plugin.Servers = []string{"tcp://127.0.0.1"} plugin.Servers = []string{"tcp://127.0.0.1"}
parser := &FakeParser{} parser := &fakeParser{}
plugin.SetParser(parser) plugin.SetParser(parser)
require.NoError(t, plugin.Init()) require.NoError(t, plugin.Init())
@ -138,12 +138,12 @@ func TestLifecycleSanity(t *testing.T) {
func TestRandomClientID(t *testing.T) { func TestRandomClientID(t *testing.T) {
var err error var err error
m1 := New(nil) m1 := newMQTTConsumer(nil)
m1.Log = testutil.Logger{} m1.Log = testutil.Logger{}
err = m1.Init() err = m1.Init()
require.NoError(t, err) require.NoError(t, err)
m2 := New(nil) m2 := newMQTTConsumer(nil)
m2.Log = testutil.Logger{} m2.Log = testutil.Logger{}
err = m2.Init() err = m2.Init()
require.NoError(t, err) require.NoError(t, err)
@ -153,7 +153,7 @@ func TestRandomClientID(t *testing.T) {
// PersistentSession requires ClientID // PersistentSession requires ClientID
func TestPersistentClientIDFail(t *testing.T) { func TestPersistentClientIDFail(t *testing.T) {
plugin := New(nil) plugin := newMQTTConsumer(nil)
plugin.Log = testutil.Logger{} plugin.Log = testutil.Logger{}
plugin.PersistentSession = true plugin.PersistentSession = true
@ -161,36 +161,36 @@ func TestPersistentClientIDFail(t *testing.T) {
require.Error(t, err) require.Error(t, err)
} }
type Message struct { type message struct {
topic string topic string
qos byte qos byte
} }
func (m *Message) Duplicate() bool { func (m *message) Duplicate() bool {
panic("not implemented") panic("not implemented")
} }
func (m *Message) Qos() byte { func (m *message) Qos() byte {
return m.qos return m.qos
} }
func (m *Message) Retained() bool { func (m *message) Retained() bool {
panic("not implemented") panic("not implemented")
} }
func (m *Message) Topic() string { func (m *message) Topic() string {
return m.topic return m.topic
} }
func (m *Message) MessageID() uint16 { func (m *message) MessageID() uint16 {
panic("not implemented") panic("not implemented")
} }
func (m *Message) Payload() []byte { func (m *message) Payload() []byte {
return []byte("cpu time_idle=42i") return []byte("cpu time_idle=42i")
} }
func (m *Message) Ack() { func (m *message) Ack() {
panic("not implemented") panic("not implemented")
} }
@ -200,7 +200,7 @@ func TestTopicTag(t *testing.T) {
topic string topic string
topicTag func() *string topicTag func() *string
expectedError string expectedError string
topicParsing []TopicParsingConfig topicParsing []topicParsingConfig
expected []telegraf.Metric expected []telegraf.Metric
}{ }{
{ {
@ -267,7 +267,7 @@ func TestTopicTag(t *testing.T) {
tag := "" tag := ""
return &tag return &tag
}, },
topicParsing: []TopicParsingConfig{ topicParsing: []topicParsingConfig{
{ {
Topic: "telegraf/123/test", Topic: "telegraf/123/test",
Measurement: "_/_/measurement", Measurement: "_/_/measurement",
@ -299,7 +299,7 @@ func TestTopicTag(t *testing.T) {
tag := "" tag := ""
return &tag return &tag
}, },
topicParsing: []TopicParsingConfig{ topicParsing: []topicParsingConfig{
{ {
Topic: "telegraf/+/test/hello", Topic: "telegraf/+/test/hello",
Measurement: "_/_/measurement/_", Measurement: "_/_/measurement/_",
@ -333,7 +333,7 @@ func TestTopicTag(t *testing.T) {
return &tag return &tag
}, },
expectedError: "config error topic parsing: fields length does not equal topic length", expectedError: "config error topic parsing: fields length does not equal topic length",
topicParsing: []TopicParsingConfig{ topicParsing: []topicParsingConfig{
{ {
Topic: "telegraf/+/test/hello", Topic: "telegraf/+/test/hello",
Measurement: "_/_/measurement/_", Measurement: "_/_/measurement/_",
@ -366,7 +366,7 @@ func TestTopicTag(t *testing.T) {
tag := "" tag := ""
return &tag return &tag
}, },
topicParsing: []TopicParsingConfig{ topicParsing: []topicParsingConfig{
{ {
Topic: "telegraf/+/test/hello", Topic: "telegraf/+/test/hello",
Measurement: "_/_/measurement/_", Measurement: "_/_/measurement/_",
@ -396,7 +396,7 @@ func TestTopicTag(t *testing.T) {
tag := "" tag := ""
return &tag return &tag
}, },
topicParsing: []TopicParsingConfig{ topicParsing: []topicParsingConfig{
{ {
Topic: "telegraf/+/test/hello", Topic: "telegraf/+/test/hello",
Tags: "testTag/_/_/_", Tags: "testTag/_/_/_",
@ -428,7 +428,7 @@ func TestTopicTag(t *testing.T) {
tag := "" tag := ""
return &tag return &tag
}, },
topicParsing: []TopicParsingConfig{ topicParsing: []topicParsingConfig{
{ {
Topic: "/telegraf/+/test/hello", Topic: "/telegraf/+/test/hello",
Measurement: "/_/_/measurement/_", Measurement: "/_/_/measurement/_",
@ -461,7 +461,7 @@ func TestTopicTag(t *testing.T) {
tag := "" tag := ""
return &tag return &tag
}, },
topicParsing: []TopicParsingConfig{ topicParsing: []topicParsingConfig{
{ {
Topic: "/telegraf/#/test/hello", Topic: "/telegraf/#/test/hello",
Measurement: "/#/measurement/_", Measurement: "/#/measurement/_",
@ -495,7 +495,7 @@ func TestTopicTag(t *testing.T) {
tag := "" tag := ""
return &tag return &tag
}, },
topicParsing: []TopicParsingConfig{ topicParsing: []topicParsingConfig{
{ {
Topic: "/telegraf/#", Topic: "/telegraf/#",
Measurement: "/#/measurement/_", Measurement: "/#/measurement/_",
@ -521,22 +521,22 @@ func TestTopicTag(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
var handler mqtt.MessageHandler var handler mqtt.MessageHandler
client := &FakeClient{ fClient := &fakeClient{
ConnectF: func() mqtt.Token { connectF: func() mqtt.Token {
return &FakeToken{} return &fakeToken{}
}, },
AddRouteF: func(callback mqtt.MessageHandler) { addRouteF: func(callback mqtt.MessageHandler) {
handler = callback handler = callback
}, },
SubscribeMultipleF: func() mqtt.Token { subscribeMultipleF: func() mqtt.Token {
return &FakeToken{} return &fakeToken{}
}, },
DisconnectF: func() { disconnectF: func() {
}, },
} }
plugin := New(func(*mqtt.ClientOptions) Client { plugin := newMQTTConsumer(func(*mqtt.ClientOptions) client {
return client return fClient
}) })
plugin.Log = testutil.Logger{} plugin.Log = testutil.Logger{}
plugin.Topics = []string{tt.topic} plugin.Topics = []string{tt.topic}
@ -557,7 +557,7 @@ func TestTopicTag(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
require.NoError(t, plugin.Start(&acc)) require.NoError(t, plugin.Start(&acc))
var m Message var m message
m.topic = tt.topic m.topic = tt.topic
handler(nil, &m) handler(nil, &m)
@ -570,20 +570,20 @@ func TestTopicTag(t *testing.T) {
} }
func TestAddRouteCalledForEachTopic(t *testing.T) { func TestAddRouteCalledForEachTopic(t *testing.T) {
client := &FakeClient{ fClient := &fakeClient{
ConnectF: func() mqtt.Token { connectF: func() mqtt.Token {
return &FakeToken{} return &fakeToken{}
}, },
AddRouteF: func(mqtt.MessageHandler) { addRouteF: func(mqtt.MessageHandler) {
}, },
SubscribeMultipleF: func() mqtt.Token { subscribeMultipleF: func() mqtt.Token {
return &FakeToken{} return &fakeToken{}
}, },
DisconnectF: func() { disconnectF: func() {
}, },
} }
plugin := New(func(*mqtt.ClientOptions) Client { plugin := newMQTTConsumer(func(*mqtt.ClientOptions) client {
return client return fClient
}) })
plugin.Log = testutil.Logger{} plugin.Log = testutil.Logger{}
plugin.Topics = []string{"a", "b"} plugin.Topics = []string{"a", "b"}
@ -595,24 +595,24 @@ func TestAddRouteCalledForEachTopic(t *testing.T) {
plugin.Stop() plugin.Stop()
require.Equal(t, 2, client.addRouteCallCount) require.Equal(t, 2, fClient.addRouteCallCount)
} }
func TestSubscribeCalledIfNoSession(t *testing.T) { func TestSubscribeCalledIfNoSession(t *testing.T) {
client := &FakeClient{ fClient := &fakeClient{
ConnectF: func() mqtt.Token { connectF: func() mqtt.Token {
return &FakeToken{} return &fakeToken{}
}, },
AddRouteF: func(mqtt.MessageHandler) { addRouteF: func(mqtt.MessageHandler) {
}, },
SubscribeMultipleF: func() mqtt.Token { subscribeMultipleF: func() mqtt.Token {
return &FakeToken{} return &fakeToken{}
}, },
DisconnectF: func() { disconnectF: func() {
}, },
} }
plugin := New(func(*mqtt.ClientOptions) Client { plugin := newMQTTConsumer(func(*mqtt.ClientOptions) client {
return client return fClient
}) })
plugin.Log = testutil.Logger{} plugin.Log = testutil.Logger{}
plugin.Topics = []string{"b"} plugin.Topics = []string{"b"}
@ -624,24 +624,24 @@ func TestSubscribeCalledIfNoSession(t *testing.T) {
plugin.Stop() plugin.Stop()
require.Equal(t, 1, client.subscribeCallCount) require.Equal(t, 1, fClient.subscribeCallCount)
} }
func TestSubscribeNotCalledIfSession(t *testing.T) { func TestSubscribeNotCalledIfSession(t *testing.T) {
client := &FakeClient{ fClient := &fakeClient{
ConnectF: func() mqtt.Token { connectF: func() mqtt.Token {
return &FakeToken{sessionPresent: true} return &fakeToken{sessionPresent: true}
}, },
AddRouteF: func(mqtt.MessageHandler) { addRouteF: func(mqtt.MessageHandler) {
}, },
SubscribeMultipleF: func() mqtt.Token { subscribeMultipleF: func() mqtt.Token {
return &FakeToken{} return &fakeToken{}
}, },
DisconnectF: func() { disconnectF: func() {
}, },
} }
plugin := New(func(*mqtt.ClientOptions) Client { plugin := newMQTTConsumer(func(*mqtt.ClientOptions) client {
return client return fClient
}) })
plugin.Log = testutil.Logger{} plugin.Log = testutil.Logger{}
plugin.Topics = []string{"b"} plugin.Topics = []string{"b"}
@ -652,7 +652,7 @@ func TestSubscribeNotCalledIfSession(t *testing.T) {
require.NoError(t, plugin.Start(&acc)) require.NoError(t, plugin.Start(&acc))
plugin.Stop() plugin.Stop()
require.Equal(t, 0, client.subscribeCallCount) require.Equal(t, 0, fClient.subscribeCallCount)
} }
func TestIntegration(t *testing.T) { func TestIntegration(t *testing.T) {
@ -679,7 +679,7 @@ func TestIntegration(t *testing.T) {
// Setup the plugin and connect to the broker // Setup the plugin and connect to the broker
url := fmt.Sprintf("tcp://%s:%s", container.Address, container.Ports[servicePort]) url := fmt.Sprintf("tcp://%s:%s", container.Address, container.Ports[servicePort])
topic := "/telegraf/test" topic := "/telegraf/test"
factory := func(o *mqtt.ClientOptions) Client { return mqtt.NewClient(o) } factory := func(o *mqtt.ClientOptions) client { return mqtt.NewClient(o) }
plugin := &MQTTConsumer{ plugin := &MQTTConsumer{
Servers: []string{url}, Servers: []string{url},
Topics: []string{topic}, Topics: []string{topic},
@ -768,7 +768,7 @@ func TestStartupErrorBehaviorErrorIntegration(t *testing.T) {
// Setup the plugin and connect to the broker // Setup the plugin and connect to the broker
url := fmt.Sprintf("tcp://%s:%s", container.Address, container.Ports[servicePort]) url := fmt.Sprintf("tcp://%s:%s", container.Address, container.Ports[servicePort])
topic := "/telegraf/test" topic := "/telegraf/test"
factory := func(o *mqtt.ClientOptions) Client { return mqtt.NewClient(o) } factory := func(o *mqtt.ClientOptions) client { return mqtt.NewClient(o) }
plugin := &MQTTConsumer{ plugin := &MQTTConsumer{
Servers: []string{url}, Servers: []string{url},
Topics: []string{topic}, Topics: []string{topic},
@ -827,7 +827,7 @@ func TestStartupErrorBehaviorIgnoreIntegration(t *testing.T) {
// Setup the plugin and connect to the broker // Setup the plugin and connect to the broker
url := fmt.Sprintf("tcp://%s:%s", container.Address, container.Ports[servicePort]) url := fmt.Sprintf("tcp://%s:%s", container.Address, container.Ports[servicePort])
topic := "/telegraf/test" topic := "/telegraf/test"
factory := func(o *mqtt.ClientOptions) Client { return mqtt.NewClient(o) } factory := func(o *mqtt.ClientOptions) client { return mqtt.NewClient(o) }
plugin := &MQTTConsumer{ plugin := &MQTTConsumer{
Servers: []string{url}, Servers: []string{url},
Topics: []string{topic}, Topics: []string{topic},
@ -892,7 +892,7 @@ func TestStartupErrorBehaviorRetryIntegration(t *testing.T) {
// Setup the plugin and connect to the broker // Setup the plugin and connect to the broker
url := fmt.Sprintf("tcp://%s:%s", container.Address, container.Ports[servicePort]) url := fmt.Sprintf("tcp://%s:%s", container.Address, container.Ports[servicePort])
topic := "/telegraf/test" topic := "/telegraf/test"
factory := func(o *mqtt.ClientOptions) Client { return mqtt.NewClient(o) } factory := func(o *mqtt.ClientOptions) client { return mqtt.NewClient(o) }
plugin := &MQTTConsumer{ plugin := &MQTTConsumer{
Servers: []string{url}, Servers: []string{url},
Topics: []string{topic}, Topics: []string{topic},
@ -997,7 +997,7 @@ func TestReconnectIntegration(t *testing.T) {
// Setup the plugin and connect to the broker // Setup the plugin and connect to the broker
url := fmt.Sprintf("tcp://%s:%s", container.Address, container.Ports[servicePort]) url := fmt.Sprintf("tcp://%s:%s", container.Address, container.Ports[servicePort])
topic := "/telegraf/test" topic := "/telegraf/test"
factory := func(o *mqtt.ClientOptions) Client { return mqtt.NewClient(o) } factory := func(o *mqtt.ClientOptions) client { return mqtt.NewClient(o) }
plugin := &MQTTConsumer{ plugin := &MQTTConsumer{
Servers: []string{url}, Servers: []string{url},
Topics: []string{topic}, Topics: []string{topic},

View File

@ -8,9 +8,12 @@ type mqttLogger struct {
telegraf.Logger telegraf.Logger
} }
// Printf implements mqtt.Logger
func (l mqttLogger) Printf(fmt string, args ...interface{}) { func (l mqttLogger) Printf(fmt string, args ...interface{}) {
l.Logger.Debugf(fmt, args...) l.Logger.Debugf(fmt, args...)
} }
// Println implements mqtt.Logger
func (l mqttLogger) Println(args ...interface{}) { func (l mqttLogger) Println(args ...interface{}) {
l.Logger.Debug(args...) l.Logger.Debug(args...)
} }

View File

@ -9,7 +9,7 @@ import (
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
) )
type TopicParsingConfig struct { type topicParsingConfig struct {
Topic string `toml:"topic"` Topic string `toml:"topic"`
Measurement string `toml:"measurement"` Measurement string `toml:"measurement"`
Tags string `toml:"tags"` Tags string `toml:"tags"`
@ -17,7 +17,7 @@ type TopicParsingConfig struct {
FieldTypes map[string]string `toml:"types"` FieldTypes map[string]string `toml:"types"`
} }
type TopicParser struct { type topicParser struct {
topicIndices map[string]int topicIndices map[string]int
topicVarLength bool topicVarLength bool
topicMinLength int topicMinLength int
@ -29,8 +29,8 @@ type TopicParser struct {
fieldTypes map[string]string fieldTypes map[string]string
} }
func (cfg *TopicParsingConfig) NewParser() (*TopicParser, error) { func (cfg *topicParsingConfig) newParser() (*topicParser, error) {
p := &TopicParser{ p := &topicParser{
fieldTypes: cfg.FieldTypes, fieldTypes: cfg.FieldTypes,
} }
@ -150,7 +150,7 @@ func (cfg *TopicParsingConfig) NewParser() (*TopicParser, error) {
return p, nil return p, nil
} }
func (p *TopicParser) Parse(metric telegraf.Metric, topic string) error { func (p *topicParser) parse(metric telegraf.Metric, topic string) error {
// Split the actual topic into its elements and check for a match // Split the actual topic into its elements and check for a match
topicParts := strings.Split(topic, "/") topicParts := strings.Split(topic, "/")
if p.topicVarLength && len(topicParts) < p.topicMinLength || !p.topicVarLength && len(topicParts) != p.topicMinLength { if p.topicVarLength && len(topicParts) < p.topicMinLength || !p.topicVarLength && len(topicParts) != p.topicMinLength {
@ -200,7 +200,7 @@ func (p *TopicParser) Parse(metric telegraf.Metric, topic string) error {
return nil return nil
} }
func (p *TopicParser) convertToFieldType(value, key string) (interface{}, error) { func (p *topicParser) convertToFieldType(value, key string) (interface{}, error) {
// If the user configured inputs.mqtt_consumer.topic.types, check for the desired type // If the user configured inputs.mqtt_consumer.topic.types, check for the desired type
desiredType, ok := p.fieldTypes[key] desiredType, ok := p.fieldTypes[key]
if !ok { if !ok {

View File

@ -19,15 +19,15 @@ import (
var sampleConfig string var sampleConfig string
type MultiFile struct { type MultiFile struct {
BaseDir string BaseDir string `toml:"base_dir"`
FailEarly bool FailEarly bool `toml:"fail_early"`
Files []File `toml:"file"` Files []file `toml:"file"`
} }
type File struct { type file struct {
Name string `toml:"file"` Name string `toml:"file"`
Dest string Dest string `toml:"dest"`
Conversion string Conversion string `toml:"conversion"`
} }
func (*MultiFile) SampleConfig() string { func (*MultiFile) SampleConfig() string {

View File

@ -17,7 +17,7 @@ func TestFileTypes(t *testing.T) {
m := MultiFile{ m := MultiFile{
BaseDir: path.Join(wd, `testdata`), BaseDir: path.Join(wd, `testdata`),
FailEarly: true, FailEarly: true,
Files: []File{ Files: []file{
{Name: `bool.txt`, Dest: `examplebool`, Conversion: `bool`}, {Name: `bool.txt`, Dest: `examplebool`, Conversion: `bool`},
{Name: `float.txt`, Dest: `examplefloat`, Conversion: `float`}, {Name: `float.txt`, Dest: `examplefloat`, Conversion: `float`},
{Name: `int.txt`, Dest: `examplefloatX`, Conversion: `float(3)`}, {Name: `int.txt`, Dest: `examplefloatX`, Conversion: `float(3)`},
@ -43,14 +43,14 @@ func TestFileTypes(t *testing.T) {
}, acc.Metrics[0].Fields) }, acc.Metrics[0].Fields)
} }
func FailEarly(failEarly bool, t *testing.T) error { func failEarly(failEarly bool, t *testing.T) error {
wd, err := os.Getwd() wd, err := os.Getwd()
require.NoError(t, err) require.NoError(t, err)
m := MultiFile{ m := MultiFile{
BaseDir: path.Join(wd, `testdata`), BaseDir: path.Join(wd, `testdata`),
FailEarly: failEarly, FailEarly: failEarly,
Files: []File{ Files: []file{
{Name: `int.txt`, Dest: `exampleint`, Conversion: `int`}, {Name: `int.txt`, Dest: `exampleint`, Conversion: `int`},
{Name: `int.txt`, Dest: `exampleerror`, Conversion: `bool`}, {Name: `int.txt`, Dest: `exampleerror`, Conversion: `bool`},
}, },
@ -71,8 +71,8 @@ func FailEarly(failEarly bool, t *testing.T) error {
} }
func TestFailEarly(t *testing.T) { func TestFailEarly(t *testing.T) {
err := FailEarly(false, t) err := failEarly(false, t)
require.NoError(t, err) require.NoError(t, err)
err = FailEarly(true, t) err = failEarly(true, t)
require.Error(t, err) require.Error(t, err)
} }

View File

@ -29,6 +29,14 @@ var sampleConfig string
var tlsRe = regexp.MustCompile(`([\?&])(?:tls=custom)($|&)`) var tlsRe = regexp.MustCompile(`([\?&])(?:tls=custom)($|&)`)
const (
defaultPerfEventsStatementsDigestTextLimit = 120
defaultPerfEventsStatementsLimit = 250
defaultPerfEventsStatementsTimeLimit = 86400
defaultGatherGlobalVars = true
localhost = ""
)
type Mysql struct { type Mysql struct {
Servers []*config.Secret `toml:"servers"` Servers []*config.Secret `toml:"servers"`
PerfEventsStatementsDigestTextLimit int64 `toml:"perf_events_statements_digest_text_limit"` PerfEventsStatementsDigestTextLimit int64 `toml:"perf_events_statements_digest_text_limit"`
@ -64,15 +72,6 @@ type Mysql struct {
loggedConvertFields map[string]bool loggedConvertFields map[string]bool
} }
const (
defaultPerfEventsStatementsDigestTextLimit = 120
defaultPerfEventsStatementsLimit = 250
defaultPerfEventsStatementsTimeLimit = 86400
defaultGatherGlobalVars = true
)
const localhost = ""
func (*Mysql) SampleConfig() string { func (*Mysql) SampleConfig() string {
return sampleConfig return sampleConfig
} }

View File

@ -8,7 +8,7 @@ import (
"strconv" "strconv"
) )
type ConversionFunc func(value sql.RawBytes) (interface{}, error) type conversionFunc func(value sql.RawBytes) (interface{}, error)
func ParseInt(value sql.RawBytes) (interface{}, error) { func ParseInt(value sql.RawBytes) (interface{}, error) {
v, err := strconv.ParseInt(string(value), 10, 64) v, err := strconv.ParseInt(string(value), 10, 64)
@ -86,7 +86,7 @@ func ParseValue(value sql.RawBytes) (interface{}, error) {
return nil, fmt.Errorf("unconvertible value: %q", string(value)) return nil, fmt.Errorf("unconvertible value: %q", string(value))
} }
var GlobalStatusConversions = map[string]ConversionFunc{ var globalStatusConversions = map[string]conversionFunc{
"innodb_available_undo_logs": ParseUint, "innodb_available_undo_logs": ParseUint,
"innodb_buffer_pool_pages_misc": ParseUint, "innodb_buffer_pool_pages_misc": ParseUint,
"innodb_data_pending_fsyncs": ParseUint, "innodb_data_pending_fsyncs": ParseUint,
@ -108,7 +108,7 @@ var GlobalStatusConversions = map[string]ConversionFunc{
"wsrep_local_send_queue_avg": ParseFloat, "wsrep_local_send_queue_avg": ParseFloat,
} }
var GlobalVariableConversions = map[string]ConversionFunc{ var globalVariableConversions = map[string]conversionFunc{
// see https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html // see https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html
// see https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html // see https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html
"delay_key_write": ParseString, // ON, OFF, ALL "delay_key_write": ParseString, // ON, OFF, ALL
@ -140,7 +140,7 @@ func ConvertGlobalStatus(key string, value sql.RawBytes) (interface{}, error) {
return nil, nil return nil, nil
} }
if conv, ok := GlobalStatusConversions[key]; ok { if conv, ok := globalStatusConversions[key]; ok {
return conv(value) return conv(value)
} }
@ -152,7 +152,7 @@ func ConvertGlobalVariables(key string, value sql.RawBytes) (interface{}, error)
return nil, nil return nil, nil
} }
if conv, ok := GlobalVariableConversions[key]; ok { if conv, ok := globalVariableConversions[key]; ok {
return conv(value) return conv(value)
} }