github.com/netdata/go.d.plugin@v0.58.1/modules/apache/collect.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package apache
     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/stm"
    14  	"github.com/netdata/go.d.plugin/pkg/web"
    15  )
    16  
    17  func (a *Apache) collect() (map[string]int64, error) {
    18  	status, err := a.scrapeStatus()
    19  	if err != nil {
    20  		return nil, err
    21  	}
    22  
    23  	mx := stm.ToMap(status)
    24  	if len(mx) == 0 {
    25  		return nil, fmt.Errorf("nothing was collected from %s", a.URL)
    26  	}
    27  
    28  	a.once.Do(func() { a.charts = newCharts(status) })
    29  
    30  	return mx, nil
    31  }
    32  
    33  func (a *Apache) scrapeStatus() (*serverStatus, error) {
    34  	req, err := web.NewHTTPRequest(a.Request)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	resp, err := a.httpClient.Do(req)
    40  	if err != nil {
    41  		return nil, fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
    42  	}
    43  	defer closeBody(resp)
    44  
    45  	if resp.StatusCode != http.StatusOK {
    46  		return nil, fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
    47  	}
    48  
    49  	return parseResponse(resp.Body)
    50  }
    51  
    52  func parseResponse(r io.Reader) (*serverStatus, error) {
    53  	s := bufio.NewScanner(r)
    54  	var status serverStatus
    55  
    56  	for s.Scan() {
    57  		parts := strings.Split(s.Text(), ":")
    58  		if len(parts) != 2 {
    59  			continue
    60  		}
    61  
    62  		key, value := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
    63  
    64  		switch key {
    65  		default:
    66  		case "BusyServers", "IdleServers":
    67  			return nil, fmt.Errorf("found '%s', Lighttpd data", key)
    68  		case "BusyWorkers":
    69  			status.Workers.Busy = parseInt(value)
    70  		case "IdleWorkers":
    71  			status.Workers.Idle = parseInt(value)
    72  		case "ConnsTotal":
    73  			status.Connections.Total = parseInt(value)
    74  		case "ConnsAsyncWriting":
    75  			status.Connections.Async.Writing = parseInt(value)
    76  		case "ConnsAsyncKeepAlive":
    77  			status.Connections.Async.KeepAlive = parseInt(value)
    78  		case "ConnsAsyncClosing":
    79  			status.Connections.Async.Closing = parseInt(value)
    80  		case "Total Accesses":
    81  			status.Total.Accesses = parseInt(value)
    82  		case "Total kBytes":
    83  			status.Total.KBytes = parseInt(value)
    84  		case "Uptime":
    85  			status.Uptime = parseInt(value)
    86  		case "ReqPerSec":
    87  			status.Averages.ReqPerSec = parseFloat(value)
    88  		case "BytesPerSec":
    89  			status.Averages.BytesPerSec = parseFloat(value)
    90  		case "BytesPerReq":
    91  			status.Averages.BytesPerReq = parseFloat(value)
    92  		case "Scoreboard":
    93  			status.Scoreboard = parseScoreboard(value)
    94  		}
    95  	}
    96  
    97  	return &status, nil
    98  }
    99  
   100  func parseScoreboard(line string) *scoreboard {
   101  	//  “_” Waiting for Connection
   102  	// “S” Starting up
   103  	// “R” Reading Request
   104  	// “W” Sending Reply
   105  	// “K” Keepalive (read)
   106  	// “D” DNS Lookup
   107  	// “C” Closing connection
   108  	// “L” Logging
   109  	// “G” Gracefully finishing
   110  	// “I” Idle cleanup of worker
   111  	// “.” Open slot with no current process
   112  	var sb scoreboard
   113  	for _, s := range strings.Split(line, "") {
   114  		switch s {
   115  		case "_":
   116  			sb.Waiting++
   117  		case "S":
   118  			sb.Starting++
   119  		case "R":
   120  			sb.Reading++
   121  		case "W":
   122  			sb.Sending++
   123  		case "K":
   124  			sb.KeepAlive++
   125  		case "D":
   126  			sb.DNSLookup++
   127  		case "C":
   128  			sb.Closing++
   129  		case "L":
   130  			sb.Logging++
   131  		case "G":
   132  			sb.Finishing++
   133  		case "I":
   134  			sb.IdleCleanup++
   135  		case ".":
   136  			sb.Open++
   137  		}
   138  	}
   139  	return &sb
   140  }
   141  
   142  func parseInt(value string) *int64 {
   143  	v, err := strconv.ParseInt(value, 10, 64)
   144  	if err != nil {
   145  		return nil
   146  	}
   147  	return &v
   148  }
   149  
   150  func parseFloat(value string) *float64 {
   151  	v, err := strconv.ParseFloat(value, 64)
   152  	if err != nil {
   153  		return nil
   154  	}
   155  	return &v
   156  }
   157  
   158  func closeBody(resp *http.Response) {
   159  	if resp != nil && resp.Body != nil {
   160  		_, _ = io.Copy(io.Discard, resp.Body)
   161  		_ = resp.Body.Close()
   162  	}
   163  }