diff options
Diffstat (limited to 'src/go/plugin/go.d/modules/phpfpm/client.go')
-rw-r--r-- | src/go/plugin/go.d/modules/phpfpm/client.go | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/src/go/plugin/go.d/modules/phpfpm/client.go b/src/go/plugin/go.d/modules/phpfpm/client.go new file mode 100644 index 000000000..4e8e8cec8 --- /dev/null +++ b/src/go/plugin/go.d/modules/phpfpm/client.go @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpfpm + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/netdata/netdata/go/plugins/logger" + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web" + + fcgiclient "github.com/kanocz/fcgi_client" +) + +type ( + status struct { + Active int64 `json:"active processes" stm:"active"` + MaxActive int64 `json:"max active processes" stm:"maxActive"` + Idle int64 `json:"idle processes" stm:"idle"` + Requests int64 `json:"accepted conn" stm:"requests"` + Reached int64 `json:"max children reached" stm:"reached"` + Slow int64 `json:"slow requests" stm:"slow"` + Processes []proc `json:"processes"` + } + proc struct { + PID int64 `json:"pid"` + State string `json:"state"` + Duration requestDuration `json:"request duration"` + CPU float64 `json:"last request cpu"` + Memory int64 `json:"last request memory"` + } + requestDuration int64 +) + +// UnmarshalJSON customise JSON for timestamp. +func (rd *requestDuration) UnmarshalJSON(b []byte) error { + if rdc, err := strconv.Atoi(string(b)); err != nil { + *rd = 0 + } else { + *rd = requestDuration(rdc) + } + return nil +} + +type client interface { + getStatus() (*status, error) +} + +type httpClient struct { + client *http.Client + req web.Request + dec decoder +} + +func newHTTPClient(c *http.Client, r web.Request) (*httpClient, error) { + u, err := url.Parse(r.URL) + if err != nil { + return nil, err + } + + dec := decodeText + if _, ok := u.Query()["json"]; ok { + dec = decodeJSON + } + return &httpClient{ + client: c, + req: r, + dec: dec, + }, nil +} + +func (c *httpClient) getStatus() (*status, error) { + req, err := web.NewHTTPRequest(c.req) + if err != nil { + return nil, fmt.Errorf("error on creating HTTP request: %v", err) + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error on HTTP request to '%s': %v", req.URL, err) + } + defer func() { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode) + } + + st := &status{} + if err := c.dec(resp.Body, st); err != nil { + return nil, fmt.Errorf("error parsing HTTP response from '%s': %v", req.URL, err) + } + + return st, nil +} + +type socketClient struct { + *logger.Logger + + socket string + timeout time.Duration + env map[string]string +} + +func newSocketClient(log *logger.Logger, socket string, timeout time.Duration, fcgiPath string) *socketClient { + return &socketClient{ + Logger: log, + socket: socket, + timeout: timeout, + env: map[string]string{ + "SCRIPT_NAME": fcgiPath, + "SCRIPT_FILENAME": fcgiPath, + "SERVER_SOFTWARE": "go / fcgiclient ", + "REMOTE_ADDR": "127.0.0.1", + "QUERY_STRING": "json&full", + "REQUEST_METHOD": "GET", + "CONTENT_TYPE": "application/json", + }, + } +} + +func (c *socketClient) getStatus() (*status, error) { + socket, err := fcgiclient.DialTimeout("unix", c.socket, c.timeout) + if err != nil { + return nil, fmt.Errorf("error on connecting to socket '%s': %v", c.socket, err) + } + defer socket.Close() + + if err := socket.SetTimeout(c.timeout); err != nil { + return nil, fmt.Errorf("error on setting socket timeout: %v", err) + } + + resp, err := socket.Get(c.env) + if err != nil { + return nil, fmt.Errorf("error on getting data from socket '%s': %v", c.socket, err) + } + + content, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error on reading response from socket '%s': %v", c.socket, err) + } + + if len(content) == 0 { + return nil, fmt.Errorf("no data returned from socket '%s'", c.socket) + } + + st := &status{} + if err := json.Unmarshal(content, st); err != nil { + c.Debugf("failed to JSON decode data: %s", string(content)) + return nil, fmt.Errorf("error on decoding response from socket '%s': %v", c.socket, err) + } + + return st, nil +} + +type tcpClient struct { + *logger.Logger + + address string + timeout time.Duration + env map[string]string +} + +func newTcpClient(log *logger.Logger, address string, timeout time.Duration, fcgiPath string) *tcpClient { + return &tcpClient{ + Logger: log, + address: address, + timeout: timeout, + env: map[string]string{ + "SCRIPT_NAME": fcgiPath, + "SCRIPT_FILENAME": fcgiPath, + "SERVER_SOFTWARE": "go / fcgiclient ", + "REMOTE_ADDR": "127.0.0.1", + "QUERY_STRING": "json&full", + "REQUEST_METHOD": "GET", + "CONTENT_TYPE": "application/json", + }, + } +} + +func (c *tcpClient) getStatus() (*status, error) { + client, err := fcgiclient.DialTimeout("tcp", c.address, c.timeout) + if err != nil { + return nil, fmt.Errorf("error on connecting to address '%s': %v", c.address, err) + } + defer client.Close() + + resp, err := client.Get(c.env) + if err != nil { + return nil, fmt.Errorf("error on getting data from address '%s': %v", c.address, err) + } + + content, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error on reading response from address '%s': %v", c.address, err) + } + + if len(content) == 0 { + return nil, fmt.Errorf("no data returned from address '%s'", c.address) + } + + st := &status{} + if err := json.Unmarshal(content, st); err != nil { + c.Debugf("failed to JSON decode data: %s", string(content)) + return nil, fmt.Errorf("error on decoding response from address '%s': %v", c.address, err) + } + + return st, nil +} |