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