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  }