Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
111
plugins/inputs/supervisor/README.md
Normal file
111
plugins/inputs/supervisor/README.md
Normal file
|
@ -0,0 +1,111 @@
|
|||
# Supervisor Input Plugin
|
||||
|
||||
This plugin gathers information about processes that
|
||||
running under supervisor using XML-RPC API.
|
||||
|
||||
Minimum tested version of supervisor: 3.3.2
|
||||
|
||||
## Supervisor configuration
|
||||
|
||||
This plugin needs an HTTP server to be enabled in supervisor,
|
||||
also it's recommended to enable basic authentication on the
|
||||
HTTP server. When using basic authentication make sure to
|
||||
include the username and password in the plugin's url setting.
|
||||
Here is an example of the `inet_http_server` section in supervisor's
|
||||
config that will work with default plugin configuration:
|
||||
|
||||
```ini
|
||||
[inet_http_server]
|
||||
port = 127.0.0.1:9001
|
||||
username = user
|
||||
password = pass
|
||||
```
|
||||
|
||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||
|
||||
In addition to the plugin-specific configuration settings, plugins support
|
||||
additional global and plugin configuration settings. These settings are used to
|
||||
modify metrics, tags, and field or create aliases and configure ordering, etc.
|
||||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||
|
||||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml @sample.conf
|
||||
# Gathers information about processes that running under supervisor using XML-RPC API
|
||||
[[inputs.supervisor]]
|
||||
## Url of supervisor's XML-RPC endpoint if basic auth enabled in supervisor http server,
|
||||
## than you have to add credentials to url (ex. http://login:pass@localhost:9001/RPC2)
|
||||
# url="http://localhost:9001/RPC2"
|
||||
## With settings below you can manage gathering additional information about processes
|
||||
## If both of them empty, then all additional information will be collected.
|
||||
## Currently supported supported additional metrics are: pid, rc
|
||||
# metrics_include = []
|
||||
# metrics_exclude = ["pid", "rc"]
|
||||
```
|
||||
|
||||
### Optional metrics
|
||||
|
||||
You can control gathering of some supervisor's metrics (processes PIDs
|
||||
and exit codes) by setting metrics_include and metrics_exclude parameters
|
||||
in configuration file.
|
||||
|
||||
### Server tag
|
||||
|
||||
Server tag is used to identify metrics source server. You have an option
|
||||
to use host:port pair of supervisor's http endpoint by default or you
|
||||
can use supervisor's identification string, which is set in supervisor's
|
||||
configuration file.
|
||||
|
||||
## Metrics
|
||||
|
||||
- supervisor_processes
|
||||
- Tags:
|
||||
- source (Hostname or IP address of supervisor's instance)
|
||||
- port (Port number of supervisor's HTTP server)
|
||||
- id (Supervisor's identification string)
|
||||
- name (Process name)
|
||||
- group (Process group)
|
||||
- Fields:
|
||||
- state (int, see reference)
|
||||
- uptime (int, seconds)
|
||||
- pid (int, optional)
|
||||
- exitCode (int, optional)
|
||||
|
||||
- supervisor_instance
|
||||
- Tags:
|
||||
- source (Hostname or IP address of supervisor's instance)
|
||||
- port (Port number of supervisor's HTTP server)
|
||||
- id (Supervisor's identification string)
|
||||
- Fields:
|
||||
- state (int, see reference)
|
||||
|
||||
### Supervisor process state field reference table
|
||||
|
||||
|Statecode|Statename| Description |
|
||||
|--------|----------|--------------------------------------------------------------------------------------------------------|
|
||||
| 0 | STOPPED | The process has been stopped due to a stop request or has never been started. |
|
||||
| 10 | STARTING | The process is starting due to a start request. |
|
||||
| 20 | RUNNING | The process is running. |
|
||||
| 30 | BACKOFF |The process entered the STARTING state but subsequently exited too quickly to move to the RUNNING state.|
|
||||
| 40 | STOPPING | The process is stopping due to a stop request. |
|
||||
| 100 | EXITED | The process exited from the RUNNING state (expectedly or unexpectedly). |
|
||||
| 200 | FATAL | The process could not be started successfully. |
|
||||
| 1000 | UNKNOWN | The process is in an unknown state (supervisord programming error). |
|
||||
|
||||
### Supervisor instance state field reference
|
||||
|
||||
|Statecode| Statename | Description |
|
||||
|---------|------------|----------------------------------------------|
|
||||
| 2 | FATAL | Supervisor has experienced a serious error. |
|
||||
| 1 | RUNNING | Supervisor is working normally. |
|
||||
| 0 | RESTARTING | Supervisor is in the process of restarting. |
|
||||
| -1 | SHUTDOWN |Supervisor is in the process of shutting down.|
|
||||
|
||||
## Example Output
|
||||
|
||||
```text
|
||||
supervisor_processes,group=ExampleGroup,id=supervisor,port=9001,process=ExampleProcess,source=localhost state=20i,uptime=75958i 1659786637000000000
|
||||
supervisor_instance,id=supervisor,port=9001,source=localhost state=1i 1659786637000000000
|
||||
```
|
10
plugins/inputs/supervisor/sample.conf
Normal file
10
plugins/inputs/supervisor/sample.conf
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Gathers information about processes that running under supervisor using XML-RPC API
|
||||
[[inputs.supervisor]]
|
||||
## Url of supervisor's XML-RPC endpoint if basic auth enabled in supervisor http server,
|
||||
## than you have to add credentials to url (ex. http://login:pass@localhost:9001/RPC2)
|
||||
# url="http://localhost:9001/RPC2"
|
||||
## With settings below you can manage gathering additional information about processes
|
||||
## If both of them empty, then all additional information will be collected.
|
||||
## Currently supported supported additional metrics are: pid, rc
|
||||
# metrics_include = []
|
||||
# metrics_exclude = ["pid", "rc"]
|
179
plugins/inputs/supervisor/supervisor.go
Normal file
179
plugins/inputs/supervisor/supervisor.go
Normal file
|
@ -0,0 +1,179 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package supervisor
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/kolo/xmlrpc"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/filter"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
type Supervisor struct {
|
||||
Server string `toml:"url"`
|
||||
MetricsInc []string `toml:"metrics_include"`
|
||||
MetricsExc []string `toml:"metrics_exclude"`
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
|
||||
rpcClient *xmlrpc.Client
|
||||
fieldFilter filter.Filter
|
||||
}
|
||||
|
||||
type processInfo struct {
|
||||
Name string `xmlrpc:"name"`
|
||||
Group string `xmlrpc:"group"`
|
||||
Description string `xmlrpc:"description"`
|
||||
Start int32 `xmlrpc:"start"`
|
||||
Stop int32 `xmlrpc:"stop"`
|
||||
Now int32 `xmlrpc:"now"`
|
||||
State int16 `xmlrpc:"state"`
|
||||
Statename string `xmlrpc:"statename"`
|
||||
StdoutLogfile string `xmlrpc:"stdout_logfile"`
|
||||
StderrLogfile string `xmlrpc:"stderr_logfile"`
|
||||
SpawnErr string `xmlrpc:"spawnerr"`
|
||||
ExitStatus int8 `xmlrpc:"exitstatus"`
|
||||
Pid int32 `xmlrpc:"pid"`
|
||||
}
|
||||
|
||||
type supervisorInfo struct {
|
||||
StateCode int8 `xmlrpc:"statecode"`
|
||||
StateName string `xmlrpc:"statename"`
|
||||
Ident string
|
||||
}
|
||||
|
||||
func (*Supervisor) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (s *Supervisor) Init() error {
|
||||
// Using default server URL if none was specified in config
|
||||
if s.Server == "" {
|
||||
s.Server = "http://localhost:9001/RPC2"
|
||||
}
|
||||
var err error
|
||||
// Initializing XML-RPC client
|
||||
s.rpcClient, err = xmlrpc.NewClient(s.Server, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize XML-RPC client: %w", err)
|
||||
}
|
||||
// Setting filter for additional metrics
|
||||
s.fieldFilter, err = filter.NewIncludeExcludeFilter(s.MetricsInc, s.MetricsExc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("metrics filter setup failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Supervisor) Gather(acc telegraf.Accumulator) error {
|
||||
// API call to get information about all running processes
|
||||
var rawProcessData []processInfo
|
||||
err := s.rpcClient.Call("supervisor.getAllProcessInfo", nil, &rawProcessData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get processes info: %w", err)
|
||||
}
|
||||
|
||||
// API call to get information about instance status
|
||||
var status supervisorInfo
|
||||
err = s.rpcClient.Call("supervisor.getState", nil, &status)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get processes info: %w", err)
|
||||
}
|
||||
|
||||
// API call to get identification string
|
||||
err = s.rpcClient.Call("supervisor.getIdentification", nil, &status.Ident)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get instance identification: %w", err)
|
||||
}
|
||||
|
||||
// Iterating through array of structs with processes info and adding fields to accumulator
|
||||
for _, process := range rawProcessData {
|
||||
processTags, processFields, err := s.parseProcessData(process, status)
|
||||
if err != nil {
|
||||
acc.AddError(err)
|
||||
continue
|
||||
}
|
||||
acc.AddFields("supervisor_processes", processFields, processTags)
|
||||
}
|
||||
// Adding instance info fields to accumulator
|
||||
instanceTags, instanceFields, err := s.parseInstanceData(status)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse instance data: %w", err)
|
||||
}
|
||||
acc.AddFields("supervisor_instance", instanceFields, instanceTags)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Supervisor) parseProcessData(pInfo processInfo, status supervisorInfo) (map[string]string, map[string]interface{}, error) {
|
||||
tags := map[string]string{
|
||||
"process": pInfo.Name,
|
||||
"group": pInfo.Group,
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"uptime": pInfo.Now - pInfo.Start,
|
||||
"state": pInfo.State,
|
||||
}
|
||||
if s.fieldFilter.Match("pid") {
|
||||
fields["pid"] = pInfo.Pid
|
||||
}
|
||||
if s.fieldFilter.Match("rc") {
|
||||
fields["exitCode"] = pInfo.ExitStatus
|
||||
}
|
||||
splittedURL, err := beautifyServerString(s.Server)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse server string: %w", err)
|
||||
}
|
||||
tags["id"] = status.Ident
|
||||
tags["source"] = splittedURL[0]
|
||||
tags["port"] = splittedURL[1]
|
||||
return tags, fields, nil
|
||||
}
|
||||
|
||||
// Parsing of supervisor instance data
|
||||
func (s *Supervisor) parseInstanceData(status supervisorInfo) (map[string]string, map[string]interface{}, error) {
|
||||
splittedURL, err := beautifyServerString(s.Server)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse server string: %w", err)
|
||||
}
|
||||
tags := map[string]string{
|
||||
"id": status.Ident,
|
||||
"source": splittedURL[0],
|
||||
"port": splittedURL[1],
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"state": status.StateCode,
|
||||
}
|
||||
return tags, fields, nil
|
||||
}
|
||||
|
||||
// Function to get only address and port from URL
|
||||
func beautifyServerString(rawurl string) ([]string, error) {
|
||||
parsedURL, err := url.Parse(rawurl)
|
||||
splittedURL := strings.Split(parsedURL.Host, ":")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(splittedURL) < 2 {
|
||||
if parsedURL.Scheme == "https" {
|
||||
splittedURL[1] = "443"
|
||||
} else {
|
||||
splittedURL[1] = "80"
|
||||
}
|
||||
}
|
||||
return splittedURL, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("supervisor", func() telegraf.Input {
|
||||
return &Supervisor{
|
||||
MetricsExc: []string{"pid", "rc"},
|
||||
}
|
||||
})
|
||||
}
|
171
plugins/inputs/supervisor/supervisor_test.go
Normal file
171
plugins/inputs/supervisor/supervisor_test.go
Normal file
|
@ -0,0 +1,171 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
func TestShort_SampleData(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
supervisorData supervisorInfo
|
||||
sampleProcInfo []processInfo
|
||||
expProcessFields []map[string]interface{}
|
||||
expProcessTags []map[string]string
|
||||
expInstanceFields map[string]interface{}
|
||||
expInstancesTags map[string]string
|
||||
}{
|
||||
{
|
||||
desc: "Case 1",
|
||||
sampleProcInfo: []processInfo{
|
||||
{
|
||||
Name: "Process0",
|
||||
Group: "ProcessGroup0",
|
||||
Description: "pid 112 uptime 0:12:11",
|
||||
Start: 1615632853,
|
||||
Stop: 0,
|
||||
Now: 1615632853 + 731,
|
||||
State: 20,
|
||||
Statename: "RUNNING",
|
||||
StdoutLogfile: "/var/log/supervisor/process0-stdout.log",
|
||||
StderrLogfile: "/var/log/supervisor/process0-stdout.log",
|
||||
SpawnErr: "",
|
||||
ExitStatus: 0,
|
||||
Pid: 112,
|
||||
},
|
||||
{
|
||||
Name: "Process1",
|
||||
Group: "ProcessGroup1",
|
||||
Description: "pid 113 uptime 0:12:11",
|
||||
Start: 1615632853,
|
||||
Stop: 0,
|
||||
Now: 1615632853 + 731,
|
||||
State: 20,
|
||||
Statename: "RUNNING",
|
||||
StdoutLogfile: "/var/log/supervisor/process1-stdout.log",
|
||||
StderrLogfile: "/var/log/supervisor/process1-stderr.log",
|
||||
SpawnErr: "",
|
||||
ExitStatus: 0,
|
||||
Pid: 113,
|
||||
},
|
||||
},
|
||||
supervisorData: supervisorInfo{
|
||||
StateCode: int8(1),
|
||||
StateName: "RUNNING",
|
||||
Ident: "supervisor",
|
||||
},
|
||||
expProcessFields: []map[string]interface{}{
|
||||
{
|
||||
"uptime": int32(731),
|
||||
"state": int16(20),
|
||||
"pid": int32(112),
|
||||
"exitCode": int8(0),
|
||||
},
|
||||
{
|
||||
"uptime": int32(731),
|
||||
"state": int16(20),
|
||||
"pid": int32(113),
|
||||
"exitCode": int8(0),
|
||||
},
|
||||
},
|
||||
expProcessTags: []map[string]string{
|
||||
{
|
||||
"process": "Process0",
|
||||
"group": "ProcessGroup0",
|
||||
"source": "example.org",
|
||||
"port": "9001",
|
||||
"id": "supervisor",
|
||||
},
|
||||
{
|
||||
"process": "Process1",
|
||||
"group": "ProcessGroup1",
|
||||
"source": "example.org",
|
||||
"port": "9001",
|
||||
"id": "supervisor",
|
||||
},
|
||||
},
|
||||
expInstanceFields: map[string]interface{}{
|
||||
"state": int8(1),
|
||||
},
|
||||
expInstancesTags: map[string]string{
|
||||
"source": "example.org",
|
||||
"port": "9001",
|
||||
"id": "supervisor",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tC := range testCases {
|
||||
t.Run(tC.desc, func(t *testing.T) {
|
||||
s := &Supervisor{
|
||||
Server: "http://example.org:9001/RPC2",
|
||||
}
|
||||
status := supervisorInfo{
|
||||
StateCode: tC.supervisorData.StateCode,
|
||||
StateName: tC.supervisorData.StateName,
|
||||
Ident: tC.supervisorData.Ident,
|
||||
}
|
||||
err := s.Init()
|
||||
if err != nil {
|
||||
t.Errorf("failed to run Init function: %v", err)
|
||||
}
|
||||
for k, v := range tC.sampleProcInfo {
|
||||
processTags, processFields, err := s.parseProcessData(v, status)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tC.expProcessFields[k], processFields)
|
||||
require.Equal(t, tC.expProcessTags[k], processTags)
|
||||
}
|
||||
instanceTags, instanceFields, err := s.parseInstanceData(status)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tC.expInstancesTags, instanceTags)
|
||||
require.Equal(t, tC.expInstanceFields, instanceFields)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_BasicGathering(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
const supervisorPort = "9001"
|
||||
supervisorConfig, err := filepath.Abs("testdata/supervisord.conf")
|
||||
require.NoError(t, err, "Failed to get absolute path of supervisord config")
|
||||
ctr := testutil.Container{
|
||||
Image: "niasar/supervisor:stretch-3.3",
|
||||
ExposedPorts: []string{supervisorPort},
|
||||
Files: map[string]string{
|
||||
"/etc/supervisor/supervisord.conf": supervisorConfig,
|
||||
},
|
||||
WaitingFor: wait.ForAll(
|
||||
wait.ForLog("entered RUNNING state").WithOccurrence(6),
|
||||
wait.ForListeningPort(nat.Port(supervisorPort)),
|
||||
),
|
||||
}
|
||||
err = ctr.Start()
|
||||
require.NoError(t, err, "failed to start container")
|
||||
defer ctr.Terminate()
|
||||
s := &Supervisor{
|
||||
Server: "http://login:pass@" + testutil.GetLocalHost() + ":" + ctr.Ports[supervisorPort] + "/RPC2",
|
||||
}
|
||||
err = s.Init()
|
||||
require.NoError(t, err, "failed to run Init function")
|
||||
var acc testutil.Accumulator
|
||||
err = acc.GatherError(s.Gather)
|
||||
require.NoError(t, err)
|
||||
require.True(t, acc.HasField("supervisor_processes", "uptime"))
|
||||
require.True(t, acc.HasField("supervisor_processes", "state"))
|
||||
require.True(t, acc.HasField("supervisor_processes", "pid"))
|
||||
require.True(t, acc.HasField("supervisor_processes", "exitCode"))
|
||||
require.True(t, acc.HasField("supervisor_instance", "state"))
|
||||
require.True(t, acc.HasTag("supervisor_processes", "id"))
|
||||
require.True(t, acc.HasTag("supervisor_processes", "source"))
|
||||
require.True(t, acc.HasTag("supervisor_processes", "port"))
|
||||
require.True(t, acc.HasTag("supervisor_instance", "id"))
|
||||
require.True(t, acc.HasTag("supervisor_instance", "source"))
|
||||
require.True(t, acc.HasTag("supervisor_instance", "port"))
|
||||
}
|
22
plugins/inputs/supervisor/testdata/supervisord.conf
vendored
Normal file
22
plugins/inputs/supervisor/testdata/supervisord.conf
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
[inet_http_server]
|
||||
port = 0.0.0.0:9001
|
||||
username = login
|
||||
password = pass
|
||||
|
||||
[supervisord]
|
||||
logfile=/dev/stdout
|
||||
pidfile=/var/run/supervisord.pid
|
||||
logfile_maxbytes = 0
|
||||
nodaemon = true
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=http://localhost:9001
|
||||
|
||||
[program:sleep]
|
||||
process_name = %(program_name)s-%(process_num)s
|
||||
command=/bin/sleep infinity
|
||||
numprocs=3
|
||||
autorestart=true
|
Loading…
Add table
Add a link
Reference in a new issue