github.com/netdata/go.d.plugin@v0.58.1/modules/lighttpd/apiclient.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package lighttpd
     4  
     5  import (
     6  	"bufio"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/netdata/go.d.plugin/pkg/web"
    14  )
    15  
    16  const (
    17  	busyWorkers = "BusyWorkers"
    18  	idleWorkers = "IdleWorkers"
    19  
    20  	busyServers   = "BusyServers"
    21  	idleServers   = "IdleServers"
    22  	totalAccesses = "Total Accesses"
    23  	totalkBytes   = "Total kBytes"
    24  	uptime        = "Uptime"
    25  	scoreBoard    = "Scoreboard"
    26  )
    27  
    28  func newAPIClient(client *http.Client, request web.Request) *apiClient {
    29  	return &apiClient{httpClient: client, request: request}
    30  }
    31  
    32  type apiClient struct {
    33  	httpClient *http.Client
    34  	request    web.Request
    35  }
    36  
    37  func (a apiClient) getServerStatus() (*serverStatus, error) {
    38  	req, err := web.NewHTTPRequest(a.request)
    39  
    40  	if err != nil {
    41  		return nil, fmt.Errorf("error on creating request : %v", err)
    42  	}
    43  
    44  	resp, err := a.doRequestOK(req)
    45  
    46  	defer closeBody(resp)
    47  
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	status, err := parseResponse(resp.Body)
    53  
    54  	if err != nil {
    55  		return nil, fmt.Errorf("error on parsing response from %s : %v", req.URL, err)
    56  	}
    57  
    58  	return status, nil
    59  }
    60  
    61  func (a apiClient) doRequestOK(req *http.Request) (*http.Response, error) {
    62  	resp, err := a.httpClient.Do(req)
    63  	if err != nil {
    64  		return nil, fmt.Errorf("error on request : %v", err)
    65  	}
    66  	if resp.StatusCode != http.StatusOK {
    67  		return resp, fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode)
    68  	}
    69  	return resp, nil
    70  }
    71  
    72  func parseResponse(r io.Reader) (*serverStatus, error) {
    73  	s := bufio.NewScanner(r)
    74  	var status serverStatus
    75  
    76  	for s.Scan() {
    77  		parts := strings.Split(s.Text(), ":")
    78  		if len(parts) != 2 {
    79  			continue
    80  		}
    81  		key, value := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
    82  
    83  		switch key {
    84  		default:
    85  		case busyWorkers, idleWorkers:
    86  			return nil, fmt.Errorf("found '%s', apache data", key)
    87  		case busyServers:
    88  			status.Servers.Busy = mustParseInt(value)
    89  		case idleServers:
    90  			status.Servers.Idle = mustParseInt(value)
    91  		case totalAccesses:
    92  			status.Total.Accesses = mustParseInt(value)
    93  		case totalkBytes:
    94  			status.Total.KBytes = mustParseInt(value)
    95  		case uptime:
    96  			status.Uptime = mustParseInt(value)
    97  		case scoreBoard:
    98  			status.Scoreboard = parseScoreboard(value)
    99  		}
   100  	}
   101  
   102  	return &status, nil
   103  }
   104  
   105  func parseScoreboard(value string) *scoreboard {
   106  	// Descriptions from https://blog.serverdensity.com/monitor-lighttpd/
   107  	//
   108  	// “.” = Opening the TCP connection (connect)
   109  	// “C” = Closing the TCP connection if no other HTTP request will use it (close)
   110  	// “E” = hard error
   111  	// “k” = Keeping the TCP connection open for more HTTP requests from the same client to avoid the TCP handling overhead (keep-alive)
   112  	// “r” = ReadAsMap the content of the HTTP request (read)
   113  	// “R” = ReadAsMap the content of the HTTP request (read-POST)
   114  	// “W” = Write the HTTP response to the socket (write)
   115  	// “h” = Decide action to take with the request (handle-request)
   116  	// “q” = Start of HTTP request (request-start)
   117  	// “Q” = End of HTTP request (request-end)
   118  	// “s” = Start of the HTTP request response (response-start)
   119  	// “S” = End of the HTTP request response (response-end)
   120  	// “_” Waiting for Connection (NOTE: not sure, copied the description from apache score board)
   121  
   122  	var sb scoreboard
   123  	for _, s := range strings.Split(value, "") {
   124  		switch s {
   125  		case "_":
   126  			sb.Waiting++
   127  		case ".":
   128  			sb.Open++
   129  		case "C":
   130  			sb.Close++
   131  		case "E":
   132  			sb.HardError++
   133  		case "k":
   134  			sb.KeepAlive++
   135  		case "r":
   136  			sb.Read++
   137  		case "R":
   138  			sb.ReadPost++
   139  		case "W":
   140  			sb.Write++
   141  		case "h":
   142  			sb.HandleRequest++
   143  		case "q":
   144  			sb.RequestStart++
   145  		case "Q":
   146  			sb.RequestEnd++
   147  		case "s":
   148  			sb.ResponseStart++
   149  		case "S":
   150  			sb.ResponseEnd++
   151  		}
   152  	}
   153  
   154  	return &sb
   155  }
   156  
   157  func mustParseInt(value string) *int64 {
   158  	v, err := strconv.ParseInt(value, 10, 64)
   159  	if err != nil {
   160  		panic(err)
   161  	}
   162  	return &v
   163  }
   164  
   165  func closeBody(resp *http.Response) {
   166  	if resp != nil && resp.Body != nil {
   167  		_, _ = io.Copy(io.Discard, resp.Body)
   168  		_ = resp.Body.Close()
   169  	}
   170  }