master
go 206 lines 4.96 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package phpfpm
4
5 import (
6 "encoding/json"
7 "fmt"
8 "io"
9 "net/http"
10 "net/url"
11 "strconv"
12 "time"
13
14 "github.com/netdata/netdata/go/plugins/logger"
15 "github.com/netdata/netdata/go/plugins/pkg/web"
16
17 fcgiclient "github.com/kanocz/fcgi_client"
18 )
19
20 type (
21 status struct {
22 Active int64 `json:"active processes" stm:"active"`
23 MaxActive int64 `json:"max active processes" stm:"maxActive"`
24 Idle int64 `json:"idle processes" stm:"idle"`
25 Requests int64 `json:"accepted conn" stm:"requests"`
26 Reached int64 `json:"max children reached" stm:"reached"`
27 Slow int64 `json:"slow requests" stm:"slow"`
28 Processes []proc `json:"processes"`
29 }
30 proc struct {
31 PID int64 `json:"pid"`
32 State string `json:"state"`
33 Duration requestDuration `json:"request duration"`
34 CPU float64 `json:"last request cpu"`
35 Memory int64 `json:"last request memory"`
36 }
37 requestDuration int64
38 )
39
40 // UnmarshalJSON customise JSON for timestamp.
41 func (rd *requestDuration) UnmarshalJSON(b []byte) error {
42 if rdc, err := strconv.Atoi(string(b)); err != nil {
43 *rd = 0
44 } else {
45 *rd = requestDuration(rdc)
46 }
47 return nil
48 }
49
50 type client interface {
51 getStatus() (*status, error)
52 }
53
54 type httpClient struct {
55 client *http.Client
56 req web.RequestConfig
57 dec decoder
58 }
59
60 func newHTTPClient(c *http.Client, r web.RequestConfig) (*httpClient, error) {
61 u, err := url.Parse(r.URL)
62 if err != nil {
63 return nil, err
64 }
65
66 dec := decodeText
67 if _, ok := u.Query()["json"]; ok {
68 dec = decodeJSON
69 }
70 return &httpClient{
71 client: c,
72 req: r,
73 dec: dec,
74 }, nil
75 }
76
77 func (c *httpClient) getStatus() (*status, error) {
78 req, err := web.NewHTTPRequest(c.req)
79 if err != nil {
80 return nil, fmt.Errorf("failed to create HTTP request: %v", err)
81 }
82
83 st := &status{}
84
85 if err := web.DoHTTP(c.client).Request(req, func(body io.Reader) error {
86 return c.dec(body, st)
87 }); err != nil {
88 return nil, err
89 }
90
91 return st, nil
92 }
93
94 type socketClient struct {
95 *logger.Logger
96
97 socket string
98 timeout time.Duration
99 env map[string]string
100 }
101
102 func newSocketClient(log *logger.Logger, socket string, timeout time.Duration, fcgiPath string) *socketClient {
103 return &socketClient{
104 Logger: log,
105 socket: socket,
106 timeout: timeout,
107 env: map[string]string{
108 "SCRIPT_NAME": fcgiPath,
109 "SCRIPT_FILENAME": fcgiPath,
110 "SERVER_SOFTWARE": "go / fcgiclient ",
111 "REMOTE_ADDR": "127.0.0.1",
112 "QUERY_STRING": "json&full",
113 "REQUEST_METHOD": "GET",
114 "CONTENT_TYPE": "application/json",
115 },
116 }
117 }
118
119 func (c *socketClient) getStatus() (*status, error) {
120 socket, err := fcgiclient.DialTimeout("unix", c.socket, c.timeout)
121 if err != nil {
122 return nil, fmt.Errorf("error on connecting to socket '%s': %v", c.socket, err)
123 }
124 defer socket.Close()
125
126 if err := socket.SetTimeout(c.timeout); err != nil {
127 return nil, fmt.Errorf("error on setting socket timeout: %v", err)
128 }
129
130 resp, err := socket.Get(c.env)
131 if err != nil {
132 return nil, fmt.Errorf("error on getting data from socket '%s': %v", c.socket, err)
133 }
134
135 content, err := io.ReadAll(resp.Body)
136 if err != nil {
137 return nil, fmt.Errorf("error on reading response from socket '%s': %v", c.socket, err)
138 }
139
140 if len(content) == 0 {
141 return nil, fmt.Errorf("no data returned from socket '%s'", c.socket)
142 }
143
144 st := &status{}
145 if err := json.Unmarshal(content, st); err != nil {
146 c.Debugf("failed to JSON decode data: %s", string(content))
147 return nil, fmt.Errorf("error on decoding response from socket '%s': %v", c.socket, err)
148 }
149
150 return st, nil
151 }
152
153 type tcpClient struct {
154 *logger.Logger
155
156 address string
157 timeout time.Duration
158 env map[string]string
159 }
160
161 func newTcpClient(log *logger.Logger, address string, timeout time.Duration, fcgiPath string) *tcpClient {
162 return &tcpClient{
163 Logger: log,
164 address: address,
165 timeout: timeout,
166 env: map[string]string{
167 "SCRIPT_NAME": fcgiPath,
168 "SCRIPT_FILENAME": fcgiPath,
169 "SERVER_SOFTWARE": "go / fcgiclient ",
170 "REMOTE_ADDR": "127.0.0.1",
171 "QUERY_STRING": "json&full",
172 "REQUEST_METHOD": "GET",
173 "CONTENT_TYPE": "application/json",
174 },
175 }
176 }
177
178 func (c *tcpClient) getStatus() (*status, error) {
179 client, err := fcgiclient.DialTimeout("tcp", c.address, c.timeout)
180 if err != nil {
181 return nil, fmt.Errorf("error on connecting to address '%s': %v", c.address, err)
182 }
183 defer client.Close()
184
185 resp, err := client.Get(c.env)
186 if err != nil {
187 return nil, fmt.Errorf("error on getting data from address '%s': %v", c.address, err)
188 }
189
190 content, err := io.ReadAll(resp.Body)
191 if err != nil {
192 return nil, fmt.Errorf("error on reading response from address '%s': %v", c.address, err)
193 }
194
195 if len(content) == 0 {
196 return nil, fmt.Errorf("no data returned from address '%s'", c.address)
197 }
198
199 st := &status{}
200 if err := json.Unmarshal(content, st); err != nil {
201 c.Debugf("failed to JSON decode data: %s", string(content))
202 return nil, fmt.Errorf("error on decoding response from address '%s': %v", c.address, err)
203 }
204
205 return st, nil
206 }