summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/supervisord/client.go
blob: da62ca21c8976e2c3c65d76fa7f519c3be8f8820 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// SPDX-License-Identifier: GPL-3.0-or-later

package supervisord

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strings"

	"github.com/mattn/go-xmlrpc"
)

type supervisorRPCClient struct {
	client *xmlrpc.Client
}

func newSupervisorRPCClient(serverURL *url.URL, httpClient *http.Client) (supervisorClient, error) {
	switch serverURL.Scheme {
	case "http", "https":
		c := xmlrpc.NewClient(serverURL.String())
		c.HttpClient = httpClient
		return &supervisorRPCClient{client: c}, nil
	case "unix":
		c := xmlrpc.NewClient("http://unix/RPC2")
		t, ok := httpClient.Transport.(*http.Transport)
		if !ok {
			return nil, errors.New("unexpected HTTP client transport")
		}
		t.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
			d := net.Dialer{Timeout: httpClient.Timeout}
			return d.DialContext(ctx, "unix", serverURL.Path)
		}
		c.HttpClient = httpClient
		return &supervisorRPCClient{client: c}, nil
	default:
		return nil, fmt.Errorf("unexpected URL scheme: %s", serverURL)
	}
}

// http://supervisord.org/api.html#process-control
type processStatus struct {
	name       string // name of the process.
	group      string // name of the process’ group.
	start      int    // UNIX timestamp of when the process was started.
	stop       int    // UNIX timestamp of when the process last ended, or 0 if the process has never been stopped.
	now        int    // UNIX timestamp of the current time, which can be used to calculate process up-time.
	state      int    // state code.
	stateName  string // string description of state.
	exitStatus int    // exit status (errorlevel) of process, or 0 if the process is still running.
}

func (c *supervisorRPCClient) getAllProcessInfo() ([]processStatus, error) {
	const fn = "supervisor.getAllProcessInfo"
	resp, err := c.client.Call(fn)
	if err != nil {
		return nil, fmt.Errorf("error on '%s' function call: %v", fn, err)
	}
	return parseGetAllProcessInfo(resp)
}

func (c *supervisorRPCClient) closeIdleConnections() {
	c.client.HttpClient.CloseIdleConnections()
}

func parseGetAllProcessInfo(resp interface{}) ([]processStatus, error) {
	arr, ok := resp.(xmlrpc.Array)
	if !ok {
		return nil, fmt.Errorf("unexpected response type, want=xmlrpc.Array, got=%T", resp)
	}

	var info []processStatus

	for _, item := range arr {
		s, ok := item.(xmlrpc.Struct)
		if !ok {
			continue
		}

		var p processStatus
		for k, v := range s {
			switch strings.ToLower(k) {
			case "name":
				p.name, _ = v.(string)
			case "group":
				p.group, _ = v.(string)
			case "start":
				p.start, _ = v.(int)
			case "stop":
				p.stop, _ = v.(int)
			case "now":
				p.now, _ = v.(int)
			case "state":
				p.state, _ = v.(int)
			case "statename":
				p.stateName, _ = v.(string)
			case "exitstatus":
				p.exitStatus, _ = v.(int)
			}
		}
		if p.name != "" && p.group != "" && p.stateName != "" {
			info = append(info, p)
		}
	}
	return info, nil
}