1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
package client
import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
)
// NginxClient allows you to fetch NGINX metrics from the stub_status page.
type NginxClient struct {
apiEndpoint string
httpClient *http.Client
}
// StubStats represents NGINX stub_status metrics.
type StubStats struct {
Connections StubConnections
Requests int64
}
// StubConnections represents connections related metrics.
type StubConnections struct {
Active int64
Accepted int64
Handled int64
Reading int64
Writing int64
Waiting int64
}
// NewNginxClient creates an NginxClient.
func NewNginxClient(httpClient *http.Client, apiEndpoint string) (*NginxClient, error) {
client := &NginxClient{
apiEndpoint: apiEndpoint,
httpClient: httpClient,
}
_, err := client.GetStubStats()
return client, err
}
// GetStubStats fetches the stub_status metrics.
func (client *NginxClient) GetStubStats() (*StubStats, error) {
resp, err := client.httpClient.Get(client.apiEndpoint)
if err != nil {
return nil, fmt.Errorf("failed to get %v: %v", client.apiEndpoint, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("expected %v response, got %v", http.StatusOK, resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read the response body: %v", err)
}
var stats StubStats
err = parseStubStats(body, &stats)
if err != nil {
return nil, fmt.Errorf("failed to parse response body %q: %v", string(body), err)
}
return &stats, nil
}
func parseStubStats(data []byte, stats *StubStats) error {
dataStr := string(data)
parts := strings.Split(dataStr, "\n")
if len(parts) != 5 {
return fmt.Errorf("invalid input %q", dataStr)
}
activeConsParts := strings.Split(strings.TrimSpace(parts[0]), " ")
if len(activeConsParts) != 3 {
return fmt.Errorf("invalid input for active connections %q", parts[0])
}
actCons, err := strconv.ParseInt(activeConsParts[2], 10, 64)
if err != nil {
return fmt.Errorf("invalid input for active connections %q: %v", activeConsParts[2], err)
}
stats.Connections.Active = actCons
miscParts := strings.Split(strings.TrimSpace(parts[2]), " ")
if len(miscParts) != 3 {
return fmt.Errorf("invalid input for connections and requests %q", parts[2])
}
acceptedCons, err := strconv.ParseInt(miscParts[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid input for accepted connections %q: %v", miscParts[0], err)
}
stats.Connections.Accepted = acceptedCons
handledCons, err := strconv.ParseInt(miscParts[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid input for handled connections %q: %v", miscParts[1], err)
}
stats.Connections.Handled = handledCons
requests, err := strconv.ParseInt(miscParts[2], 10, 64)
if err != nil {
return fmt.Errorf("invalid input for requests %q: %v", miscParts[2], err)
}
stats.Requests = requests
consParts := strings.Split(strings.TrimSpace(parts[3]), " ")
if len(consParts) != 6 {
return fmt.Errorf("invalid input for connections %q", parts[3])
}
readingCons, err := strconv.ParseInt(consParts[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid input for reading connections %q: %v", consParts[1], err)
}
stats.Connections.Reading = readingCons
writingCons, err := strconv.ParseInt(consParts[3], 10, 64)
if err != nil {
return fmt.Errorf("invalid input for writing connections %q: %v", consParts[3], err)
}
stats.Connections.Writing = writingCons
waitingCons, err := strconv.ParseInt(consParts[5], 10, 64)
if err != nil {
return fmt.Errorf("invalid input for waiting connections %q: %v", consParts[5], err)
}
stats.Connections.Waiting = waitingCons
return nil
}
|