1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View 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
```

View 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"]

View 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"},
}
})
}

View 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"))
}

View 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