Skip to content

Commit

Permalink
Merge pull request #31 from rokett/dev
Browse files Browse the repository at this point in the history
More efficient retrieval of service group member stats
  • Loading branch information
rokett committed Dec 31, 2019
2 parents 2d0d806 + eb7720a commit 8b6c12f
Show file tree
Hide file tree
Showing 23 changed files with 122 additions and 176 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [4.2.0] - 2019-12-31
### Changed
- Minimising the number of API requests needed to retrieve Service Group member stats by getting all service group members for a specific service group in one go. We still have to query for every single service group, but it cuts down the number of API requests by over 50% on the assumption that each service group has multiple members.
- Refactored the filenames to better reflect the functions contained within them.

## [4.1.0] - 2019-12-30
### Changed
- Debug logging is now hidden behind the `--debug` flag.
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
@@ -1,6 +1,6 @@
FROM golang:alpine as builder

ENV VERSION="4.1.0"
ENV VERSION="4.2.0"

WORKDIR $GOPATH/src/github.com/rokett
RUN \
Expand Down
2 changes: 1 addition & 1 deletion Makefile
@@ -1,7 +1,7 @@
.PHONY: build

APP = Citrix-NetScaler-Exporter
VERSION = 4.1.0
VERSION = 4.2.0
BINARY-LINUX = ${APP}_${VERSION}_Linux_amd64

BUILD_VER = $(shell git rev-parse HEAD)
Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -25,6 +25,7 @@ You can monitor multiple NetScaler instances by passing in the URL, username, an
| username | Username with which to connect to the NetScaler API | none |
| password | Password with which to connect to the NetScaler API | none |
| bind_port | Port to bind the exporter endpoint to | 9280 |
| debug | Enable debug logging | false |

Run the exporter manually using the following command:

Expand Down
65 changes: 28 additions & 37 deletions collector/collector.go
Expand Up @@ -2,6 +2,7 @@ package collector

import (
"strconv"
"strings"

"github.com/go-kit/kit/log/level"
"github.com/prometheus/client_golang/prometheus"
Expand Down Expand Up @@ -353,59 +354,49 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
}

for _, sg := range servicegroups.ServiceGroups {
bindings, err2 := netscaler.GetServiceGroupMemberBindings(nsClient, sg.Name)
stats, err2 := netscaler.GetServiceGroupMemberStats(nsClient, sg.Name)
if err2 != nil {
level.Error(e.logger).Log("msg", err2)
}

for _, member := range bindings.ServiceGroupMemberBindings {
// NetScaler API has a bug which means it throws errors if you try to retrieve stats for a wildcard port (* in GUI, 65535 in API and CLI).
// Until Citrix resolve the issue we skip attempting to retrieve stats for those service groups.
if member.Port != 65535 {
port := strconv.FormatInt(member.Port, 10)
for _, s := range stats.ServiceGroups[0].ServiceGroupMembers {
servicegroupnameParts := strings.Split(s.ServiceGroupName, "?")

qs := "args=servicegroupname:" + sg.Name + ",servername:" + member.ServerName + ",port:" + port
stats, err2 := netscaler.GetServiceGroupMemberStats(nsClient, qs)
if err2 != nil {
level.Error(e.logger).Log("msg", err2)
}
e.collectServiceGroupsState(s, sg.Name, servicegroupnameParts[1])
e.serviceGroupsState.Collect(ch)

e.collectServiceGroupsState(stats, sg.Name, member.ServerName)
e.serviceGroupsState.Collect(ch)
e.collectServiceGroupsAvgTTFB(s, sg.Name, servicegroupnameParts[1])
e.serviceGroupsAvgTTFB.Collect(ch)

e.collectServiceGroupsAvgTTFB(stats, sg.Name, member.ServerName)
e.serviceGroupsAvgTTFB.Collect(ch)
e.collectServiceGroupsTotalRequests(s, sg.Name, servicegroupnameParts[1])
e.serviceGroupsTotalRequests.Collect(ch)

e.collectServiceGroupsTotalRequests(stats, sg.Name, member.ServerName)
e.serviceGroupsTotalRequests.Collect(ch)
e.collectServiceGroupsTotalResponses(s, sg.Name, servicegroupnameParts[1])
e.serviceGroupsTotalResponses.Collect(ch)

e.collectServiceGroupsTotalResponses(stats, sg.Name, member.ServerName)
e.serviceGroupsTotalResponses.Collect(ch)
e.collectServiceGroupsTotalRequestBytes(s, sg.Name, servicegroupnameParts[1])
e.serviceGroupsTotalRequestBytes.Collect(ch)

e.collectServiceGroupsTotalRequestBytes(stats, sg.Name, member.ServerName)
e.serviceGroupsTotalRequestBytes.Collect(ch)
e.collectServiceGroupsTotalResponseBytes(s, sg.Name, servicegroupnameParts[1])
e.serviceGroupsTotalResponseBytes.Collect(ch)

e.collectServiceGroupsTotalResponseBytes(stats, sg.Name, member.ServerName)
e.serviceGroupsTotalResponseBytes.Collect(ch)
e.collectServiceGroupsCurrentClientConnections(s, sg.Name, servicegroupnameParts[1])
e.serviceGroupsCurrentClientConnections.Collect(ch)

e.collectServiceGroupsCurrentClientConnections(stats, sg.Name, member.ServerName)
e.serviceGroupsCurrentClientConnections.Collect(ch)
e.collectServiceGroupsSurgeCount(s, sg.Name, servicegroupnameParts[1])
e.serviceGroupsSurgeCount.Collect(ch)

e.collectServiceGroupsSurgeCount(stats, sg.Name, member.ServerName)
e.serviceGroupsSurgeCount.Collect(ch)
e.collectServiceGroupsCurrentServerConnections(s, sg.Name, servicegroupnameParts[1])
e.serviceGroupsCurrentServerConnections.Collect(ch)

e.collectServiceGroupsCurrentServerConnections(stats, sg.Name, member.ServerName)
e.serviceGroupsCurrentServerConnections.Collect(ch)
e.collectServiceGroupsServerEstablishedConnections(s, sg.Name, servicegroupnameParts[1])
e.serviceGroupsServerEstablishedConnections.Collect(ch)

e.collectServiceGroupsServerEstablishedConnections(stats, sg.Name, member.ServerName)
e.serviceGroupsServerEstablishedConnections.Collect(ch)
e.collectServiceGroupsCurrentReusePool(s, sg.Name, servicegroupnameParts[1])
e.serviceGroupsCurrentReusePool.Collect(ch)

e.collectServiceGroupsCurrentReusePool(stats, sg.Name, member.ServerName)
e.serviceGroupsCurrentReusePool.Collect(ch)

e.collectServiceGroupsMaxClients(stats, sg.Name, member.ServerName)
e.serviceGroupsMaxClients.Collect(ch)
}
e.collectServiceGroupsMaxClients(s, sg.Name, servicegroupnameParts[1])
e.serviceGroupsMaxClients.Collect(ch)
}
}

Expand Down
102 changes: 39 additions & 63 deletions collector/service_groups.go
Expand Up @@ -153,115 +153,91 @@ var (
)
)

func (e *Exporter) collectServiceGroupsState(ns netscaler.NSAPIResponse, sgName string, servername string) {
func (e *Exporter) collectServiceGroupsState(sg netscaler.ServiceGroupMemberStats, sgName string, servername string) {
e.serviceGroupsState.Reset()

for _, sg := range ns.ServiceGroupMemberStats {
state := 0.0
state := 0.0

if sg.State == "UP" {
state = 1.0
}

e.serviceGroupsState.WithLabelValues(e.nsInstance, sgName, servername).Set(state)
if sg.State == "UP" {
state = 1.0
}

e.serviceGroupsState.WithLabelValues(e.nsInstance, sgName, servername).Set(state)
}

func (e *Exporter) collectServiceGroupsAvgTTFB(ns netscaler.NSAPIResponse, sgName string, servername string) {
func (e *Exporter) collectServiceGroupsAvgTTFB(sg netscaler.ServiceGroupMemberStats, sgName string, servername string) {
e.serviceGroupsAvgTTFB.Reset()

for _, sg := range ns.ServiceGroupMemberStats {
val, _ := strconv.ParseFloat(sg.AvgTimeToFirstByte, 64)
e.serviceGroupsAvgTTFB.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}
val, _ := strconv.ParseFloat(sg.AvgTimeToFirstByte, 64)
e.serviceGroupsAvgTTFB.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}

func (e *Exporter) collectServiceGroupsTotalRequests(ns netscaler.NSAPIResponse, sgName string, servername string) {
func (e *Exporter) collectServiceGroupsTotalRequests(sg netscaler.ServiceGroupMemberStats, sgName string, servername string) {
e.serviceGroupsTotalRequests.Reset()

for _, sg := range ns.ServiceGroupMemberStats {
val, _ := strconv.ParseFloat(sg.TotalRequests, 64)
e.serviceGroupsTotalRequests.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}
val, _ := strconv.ParseFloat(sg.TotalRequests, 64)
e.serviceGroupsTotalRequests.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}

func (e *Exporter) collectServiceGroupsTotalResponses(ns netscaler.NSAPIResponse, sgName string, servername string) {
func (e *Exporter) collectServiceGroupsTotalResponses(sg netscaler.ServiceGroupMemberStats, sgName string, servername string) {
e.serviceGroupsTotalResponses.Reset()

for _, sg := range ns.ServiceGroupMemberStats {
val, _ := strconv.ParseFloat(sg.TotalResponses, 64)
e.serviceGroupsTotalResponses.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}
val, _ := strconv.ParseFloat(sg.TotalResponses, 64)
e.serviceGroupsTotalResponses.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}

func (e *Exporter) collectServiceGroupsTotalRequestBytes(ns netscaler.NSAPIResponse, sgName string, servername string) {
func (e *Exporter) collectServiceGroupsTotalRequestBytes(sg netscaler.ServiceGroupMemberStats, sgName string, servername string) {
e.serviceGroupsTotalRequestBytes.Reset()

for _, sg := range ns.ServiceGroupMemberStats {
val, _ := strconv.ParseFloat(sg.TotalRequestBytes, 64)
e.serviceGroupsTotalRequestBytes.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}
val, _ := strconv.ParseFloat(sg.TotalRequestBytes, 64)
e.serviceGroupsTotalRequestBytes.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}

func (e *Exporter) collectServiceGroupsTotalResponseBytes(ns netscaler.NSAPIResponse, sgName string, servername string) {
func (e *Exporter) collectServiceGroupsTotalResponseBytes(sg netscaler.ServiceGroupMemberStats, sgName string, servername string) {
e.serviceGroupsTotalResponseBytes.Reset()

for _, sg := range ns.ServiceGroupMemberStats {
val, _ := strconv.ParseFloat(sg.TotalResponseBytes, 64)
e.serviceGroupsTotalResponseBytes.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}
val, _ := strconv.ParseFloat(sg.TotalResponseBytes, 64)
e.serviceGroupsTotalResponseBytes.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}

func (e *Exporter) collectServiceGroupsCurrentClientConnections(ns netscaler.NSAPIResponse, sgName string, servername string) {
func (e *Exporter) collectServiceGroupsCurrentClientConnections(sg netscaler.ServiceGroupMemberStats, sgName string, servername string) {
e.serviceGroupsCurrentClientConnections.Reset()

for _, sg := range ns.ServiceGroupMemberStats {
val, _ := strconv.ParseFloat(sg.CurrentClientConnections, 64)
e.serviceGroupsCurrentClientConnections.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}
val, _ := strconv.ParseFloat(sg.CurrentClientConnections, 64)
e.serviceGroupsCurrentClientConnections.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}

func (e *Exporter) collectServiceGroupsSurgeCount(ns netscaler.NSAPIResponse, sgName string, servername string) {
func (e *Exporter) collectServiceGroupsSurgeCount(sg netscaler.ServiceGroupMemberStats, sgName string, servername string) {
e.serviceGroupsSurgeCount.Reset()

for _, sg := range ns.ServiceGroupMemberStats {
val, _ := strconv.ParseFloat(sg.SurgeCount, 64)
e.serviceGroupsSurgeCount.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}
val, _ := strconv.ParseFloat(sg.SurgeCount, 64)
e.serviceGroupsSurgeCount.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}

func (e *Exporter) collectServiceGroupsCurrentServerConnections(ns netscaler.NSAPIResponse, sgName string, servername string) {
func (e *Exporter) collectServiceGroupsCurrentServerConnections(sg netscaler.ServiceGroupMemberStats, sgName string, servername string) {
e.serviceGroupsCurrentServerConnections.Reset()

for _, sg := range ns.ServiceGroupMemberStats {
val, _ := strconv.ParseFloat(sg.CurrentServerConnections, 64)
e.serviceGroupsCurrentServerConnections.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}
val, _ := strconv.ParseFloat(sg.CurrentServerConnections, 64)
e.serviceGroupsCurrentServerConnections.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}

func (e *Exporter) collectServiceGroupsServerEstablishedConnections(ns netscaler.NSAPIResponse, sgName string, servername string) {
func (e *Exporter) collectServiceGroupsServerEstablishedConnections(sg netscaler.ServiceGroupMemberStats, sgName string, servername string) {
e.serviceGroupsServerEstablishedConnections.Reset()

for _, sg := range ns.ServiceGroupMemberStats {
val, _ := strconv.ParseFloat(sg.ServerEstablishedConnections, 64)
e.serviceGroupsServerEstablishedConnections.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}
val, _ := strconv.ParseFloat(sg.ServerEstablishedConnections, 64)
e.serviceGroupsServerEstablishedConnections.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}

func (e *Exporter) collectServiceGroupsCurrentReusePool(ns netscaler.NSAPIResponse, sgName string, servername string) {
func (e *Exporter) collectServiceGroupsCurrentReusePool(sg netscaler.ServiceGroupMemberStats, sgName string, servername string) {
e.serviceGroupsCurrentReusePool.Reset()

for _, sg := range ns.ServiceGroupMemberStats {
val, _ := strconv.ParseFloat(sg.CurrentReusePool, 64)
e.serviceGroupsCurrentReusePool.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}
val, _ := strconv.ParseFloat(sg.CurrentReusePool, 64)
e.serviceGroupsCurrentReusePool.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}

func (e *Exporter) collectServiceGroupsMaxClients(ns netscaler.NSAPIResponse, sgName string, servername string) {
func (e *Exporter) collectServiceGroupsMaxClients(sg netscaler.ServiceGroupMemberStats, sgName string, servername string) {
e.serviceGroupsMaxClients.Reset()

for _, sg := range ns.ServiceGroupMemberStats {
val, _ := strconv.ParseFloat(sg.MaxClients, 64)
e.serviceGroupsMaxClients.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}
val, _ := strconv.ParseFloat(sg.MaxClients, 64)
e.serviceGroupsMaxClients.WithLabelValues(e.nsInstance, sgName, servername).Set(val)
}
2 changes: 1 addition & 1 deletion make.bat
Expand Up @@ -2,7 +2,7 @@
SETLOCAL

set APP=Citrix-NetScaler-Exporter
set VERSION=4.1.0
set VERSION=4.2.0
set BINARY-WINDOWS-X64=%APP%_%VERSION%_Windows_amd64.exe
set BINARY-LINUX=%APP%_%VERSION%_Linux_amd64

Expand Down
31 changes: 0 additions & 31 deletions netscaler/cfg_servicegroupmemberbindings.go

This file was deleted.

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
44 changes: 44 additions & 0 deletions netscaler/get_service_group_member_stats.go
@@ -0,0 +1,44 @@
package netscaler

import (
"encoding/json"
"fmt"

"github.com/pkg/errors"
)

// ServiceGroupMemberStats represents the data returned from the /stat/servicegroupmember Nitro API endpoint
type ServiceGroupMemberStats struct {
State string `json:"state"`
AvgTimeToFirstByte string `json:"avgsvrttfb"`
TotalRequests string `json:"totalrequests"`
TotalResponses string `json:"totalresponses"`
TotalRequestBytes string `json:"totalrequestbytes"`
TotalResponseBytes string `json:"totalresponsebytes"`
CurrentClientConnections string `json:"curclntconnections"`
SurgeCount string `json:"surgecount"`
CurrentServerConnections string `json:"cursrvrconnections"`
ServerEstablishedConnections string `json:"svrestablishedconn"`
CurrentReusePool string `json:"curreusepool"`
MaxClients string `json:"maxclients"`
PrimaryIPAddress string `json:"primaryipaddress"`
ServiceGroupName string `json:"servicegroupname"`
}

// GetServiceGroupMemberStats queries the Nitro API for servicegroup member stats
func GetServiceGroupMemberStats(c *NitroClient, querystring string) (NSAPIResponse, error) {
q := fmt.Sprintf("servicegroup/%s", querystring)
stats, err := c.GetStats(q, "statbindings=yes")
if err != nil {
return NSAPIResponse{}, err
}

var response = new(NSAPIResponse)

err = json.Unmarshal(stats, &response)
if err != nil {
return NSAPIResponse{}, errors.Wrap(err, "error unmarshalling response body")
}

return *response, nil
}
Expand Up @@ -8,7 +8,8 @@ import (

// ServiceGroups represents the data returned from the /config/servicegroup Nitro API endpoint
type ServiceGroups struct {
Name string `json:"servicegroupname"`
Name string `json:"servicegroupname"`
ServiceGroupMembers []ServiceGroupMemberStats `json:"servicegroupmember"`
}

// GetServiceGroups queries the Nitro API for service group config
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 0 additions & 1 deletion netscaler/nsapiresponse.go
Expand Up @@ -11,7 +11,6 @@ type NSAPIResponse struct {
VirtualServerStats []VirtualServerStats `json:"lbvserver"`
ServiceStats []ServiceStats `json:"service"`
ServiceGroups []ServiceGroups `json:"servicegroup"`
ServiceGroupMemberBindings []ServiceGroupMemberBindings `json:"servicegroup_servicegroupmember_binding"`
ServiceGroupMemberStats []ServiceGroupMemberStats `json:"servicegroupmember"`
GSLBServiceStats []GSLBServiceStats `json:"gslbservice"`
GSLBVirtualServerStats []GSLBVirtualServerStats `json:"gslbvserver"`
Expand Down

0 comments on commit 8b6c12f

Please sign in to comment.