Skip to content

Commit

Permalink
Add support for LoadBalancing metrics (#60)
Browse files Browse the repository at this point in the history
* git: Add gitignore file

* feat: Export LoadBalancer analytics metrics

This commit enables support for exporting LoadBalancer analytics
metrics. LoadBalancer's pool health is reported:
* 1 -> health
* 0 -> unhealthy
as long as the number of requests each pool receives from each CFL
network location[1]

1. https://developers.cloudflare.com/load-balancing/reference/load-balancing-analytics/#graphql-analytics

* feat: Export browser map page views count

Export browser_map_page_views_count requests metric
  • Loading branch information
axilleastr authored Apr 12, 2022
1 parent 1df59e2 commit 8e97133
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 0 deletions.
25 changes: 25 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Local environment
.envrc

# Bin directory
bin/

# Dependency directories (remove the comment below to include it)
vendor/

# editors
*.swp

2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,12 @@ Note: `ZONE_<name>` configuration is not supported as flag.
# HELP cloudflare_zone_requests_ssl_encrypted Number of encrypted requests for zone
# HELP cloudflare_zone_requests_status Number of request for zone per HTTP status
# HELP cloudflare_zone_requests_status_country_host Count of requests for zone per edge HTTP status per country per host
# HELP cloudflare_zone_requests_browser_map_page_views_count Number of successful requests for HTML pages per zone
# HELP cloudflare_zone_requests_total Number of requests for zone
# HELP cloudflare_zone_threats_country Threats per zone per country
# HELP cloudflare_zone_threats_total Threats per zone
# HELP cloudflare_zone_uniques_total Uniques per zone
# HELP cloudflare_zone_pool_health_status Reports the health of a pool, 1 for healthy, 0 for unhealthy.
```

## Helm chart repository
Expand Down
72 changes: 72 additions & 0 deletions cloudflare.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ type cloudflareResponseColo struct {
} `json:"viewer"`
}

type cloudflareResponseLb struct {
Viewer struct {
Zones []lbResp `json:"zones"`
} `json:"viewer"`
}

type accountResp struct {
WorkersInvocationsAdaptive []struct {
Dimensions struct {
Expand Down Expand Up @@ -172,6 +178,22 @@ type zoneResp struct {
ZoneTag string `json:"zoneTag"`
}

type lbResp struct {
LoadBalancingRequestsAdaptiveGroups []struct {
Count uint64 `json:"count"`
Dimensions struct {
ColoCode string `json:"coloCode"`
LbName string `json:"lbName"`
Region string `json:"region"`
SelectedOriginName string `json:"selectedOriginName"`
SelectedPoolHealthy int `json:"selectedPoolHealthy"`
SelectedPoolName string `json:"selectedPoolName"`
SteeringPolicy string `json:"steeringPolicy"`
} `json:"dimensions"`
} `json:"loadBalancingRequestsAdaptiveGroups"`
ZoneTag string `json:"zoneTag"`
}

func fetchZones() []cloudflare.Zone {
var api *cloudflare.API
var err error
Expand Down Expand Up @@ -453,6 +475,56 @@ func fetchWorkerTotals(accountID string) (*cloudflareResponseAccts, error) {
return &resp, nil
}

func fetchLoadBalancerTotals(zoneIDs []string) (*cloudflareResponseLb, error) {
now := time.Now().Add(-time.Duration(cfgScrapeDelay) * time.Second).UTC()
s := 60 * time.Second
now = now.Truncate(s)
now1mAgo := now.Add(-60 * time.Second)

request := graphql.NewRequest(`
query ($zoneIDs: [String!], $mintime: Time!, $maxtime: Time!, $limit: Int!) {
viewer {
zones(filter: { zoneTag_in: $zoneIDs }) {
zoneTag
loadBalancingRequestsAdaptiveGroups(
filter: { datetime_geq: $mintime, datetime_lt: $maxtime},
limit: $limit) {
count
dimensions {
coloCode
region
lbName
selectedPoolName
selectedOriginName
selectedPoolHealthy
steeringPolicy
}
}
}
}
}
`)
if len(cfgCfAPIToken) > 0 {
request.Header.Set("Authorization", "Bearer "+cfgCfAPIToken)
} else {
request.Header.Set("X-AUTH-EMAIL", cfgCfAPIEmail)
request.Header.Set("X-AUTH-KEY", cfgCfAPIKey)
}
request.Var("limit", 9999)
request.Var("maxtime", now)
request.Var("mintime", now1mAgo)
request.Var("zoneIDs", zoneIDs)

ctx := context.Background()
graphqlClient := graphql.NewClient(cfGraphQLEndpoint)
var resp cloudflareResponseLb
if err := graphqlClient.Run(ctx, request, &resp); err != nil {
log.Error(err)
return nil, err
}
return &resp, nil
}

func findZoneName(zones []cloudflare.Zone, ID string) string {
for _, z := range zones {
if z.ID == ID {
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func fetchMetrics() {

go fetchZoneAnalytics(targetZones, &wg)
go fetchZoneColocationAnalytics(targetZones, &wg)
go fetchLoadBalancerAnalytics(targetZones, &wg)
}

wg.Wait()
Expand Down
56 changes: 56 additions & 0 deletions prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ var (
}, []string{"zone", "status"},
)

zoneRequestBrowserMap = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "cloudflare_zone_requests_browser_map_page_views_count",
Help: "Number of successful requests for HTML pages per zone",
}, []string{"zone", "family"},
)

zoneRequestOriginStatusCountryHost = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "cloudflare_zone_requests_origin_status_country_host",
Help: "Count of not cached requests for zone per origin HTTP status per country per host",
Expand Down Expand Up @@ -167,6 +173,14 @@ var (
Help: "Duration quantiles by script name (GB*s)",
}, []string{"script_name", "quantile"},
)

poolHealthStatus = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "cloudflare_zone_pool_health_status",
Help: "Reports the health of a pool, 1 for healthy, 0 for unhealthy.",
},
[]string{"zone", "colo_code", "load_balancer_name", "origin_name", "steering_policy", "pool_name", "region"},
)
)

func fetchWorkerAnalytics(account cloudflare.Account, wg *sync.WaitGroup) {
Expand Down Expand Up @@ -274,6 +288,10 @@ func addHTTPGroups(z *zoneResp, name string) {
zoneRequestHTTPStatus.With(prometheus.Labels{"zone": name, "status": strconv.Itoa(status.EdgeResponseStatus)}).Add(float64(status.Requests))
}

for _, browser := range zt.Sum.BrowserMap {
zoneRequestBrowserMap.With(prometheus.Labels{"zone": name, "family": browser.UaBrowserFamily}).Add(float64(browser.PageViews))
}

zoneBandwidthTotal.With(prometheus.Labels{"zone": name}).Add(float64(zt.Sum.Bytes))
zoneBandwidthCached.With(prometheus.Labels{"zone": name}).Add(float64(zt.Sum.CachedBytes))
zoneBandwidthSSLEncrypted.With(prometheus.Labels{"zone": name}).Add(float64(zt.Sum.EncryptedBytes))
Expand Down Expand Up @@ -348,3 +366,41 @@ func addHTTPAdaptiveGroups(z *zoneResp, name string) {
}

}

func fetchLoadBalancerAnalytics(zones []cloudflare.Zone, wg *sync.WaitGroup) {
wg.Add(1)
defer wg.Done()

// None of the below referenced metrics are available in the free tier
if cfgFreeTier {
return
}

zoneIDs := extractZoneIDs(zones)

l, err := fetchLoadBalancerTotals(zoneIDs)
if err != nil {
return
}
for _, lb := range l.Viewer.Zones {
name := findZoneName(zones, lb.ZoneTag)
addLoadBalancingRequestsAdaptiveGroups(&lb, name)
}
}

func addLoadBalancingRequestsAdaptiveGroups(z *lbResp, name string) {

for _, g := range z.LoadBalancingRequestsAdaptiveGroups {
poolHealthStatus.With(
prometheus.Labels{
"zone": name,
"colo_code": g.Dimensions.ColoCode,
"load_balancer_name": g.Dimensions.LbName,
"origin_name": g.Dimensions.SelectedOriginName,
"steering_policy": g.Dimensions.SteeringPolicy,
"pool_name": g.Dimensions.SelectedPoolName,
"region": g.Dimensions.Region,
}).Set(float64(g.Dimensions.SelectedPoolHealthy))
}

}

0 comments on commit 8e97133

Please sign in to comment.