parsing.go (220 lines of code) (raw):

package main import ( "encoding/json" "fmt" "io" ) func parseZoneIDs(apiRespBody io.Reader, zonesFilter []string) (map[string]string, error) { var zoneList zonesResp if err := json.NewDecoder(apiRespBody).Decode(&zoneList); err != nil { return nil, err } zones := map[string]string{} for _, zone := range zoneList.Result { if zone.Status != "pending" && (len(zonesFilter) == 0 || contains(zonesFilter, zone.Name)) { zones[zone.ID] = zone.Name } } return zones, nil } type extractFunc func(zoneResp, map[string]string, *TimeBucket) (int, error) func extractZoneNetworkErrorLogs(zone zoneResp, zoneNames map[string]string, scrapeBucket *TimeBucket) (int, error) { // NELs have different sampling rates. // // According to https://w3c.github.io/network-error-logging/#generate-a-network-error-report // "ok" is the only accepted code for a successful response, so only those would have a different // sampling rate compared to all others. // // Currently, Cloudflare uses: // - the default of 1 as a `failure_fraction` // - 0.01 as a `success_fraction` // // So for every single failure and 100 successful connections the browser is instructed to report a success. // Thus, we multiply successes by 100. // This *might* be a dynamic value, but it seems constant across zones and is not exposed with its value in the // API, so for now, this is hard-coded. const failureFraction float64 = 1 const successFraction float64 = 0.01 for _, nel := range zone.NetworkErrorLogs { var currentFraction float64 = failureFraction if nel.Dimensions.Type == "ok" { currentFraction = successFraction } networkErrorLogs.WithLabelValues( zoneNames[zone.ZoneTag], nel.Dimensions.ClientIPCountryCode, toString(nel.Dimensions.ClientIPVersion), nel.Dimensions.LastKnownGoodColoCode, nel.Dimensions.Protocol, nel.Dimensions.Phase, nel.Dimensions.Type, ). Add(float64(nel.Count)/currentFraction, scrapeBucket.getEndTime()) } return len(zone.NetworkErrorLogs), nil } func extractZoneHTTPCountry(zone zoneResp, zoneNames map[string]string, scrapeBucket *TimeBucket) (int, error) { for _, countryTraffic := range zone.TrafficCountry { httpCountryRequests.WithLabelValues(zoneNames[zone.ZoneTag], countryTraffic.Dimensions.ClientCountryName). Add(float64(countryTraffic.Count), scrapeBucket.getEndTime()) httpCountryBytes.WithLabelValues(zoneNames[zone.ZoneTag], countryTraffic.Dimensions.ClientCountryName). Add(float64(countryTraffic.Sum.EdgeResponseBytes), scrapeBucket.getEndTime()) } return len(zone.TrafficCountry), nil } func extractZoneHTTPColo(zone zoneResp, zoneNames map[string]string, scrapeBucket *TimeBucket) (int, error) { for _, coloTraffic := range zone.TrafficColo { httpColoRequests.WithLabelValues(zoneNames[zone.ZoneTag], coloTraffic.Dimensions.ColoCode). Add(float64(coloTraffic.Count), scrapeBucket.getEndTime()) httpColoBytes.WithLabelValues(zoneNames[zone.ZoneTag], coloTraffic.Dimensions.ColoCode). Add(float64(coloTraffic.Sum.EdgeResponseBytes), scrapeBucket.getEndTime()) } return len(zone.TrafficColo), nil } func extractZoneHTTPCached(zone zoneResp, zoneNames map[string]string, scrapeBucket *TimeBucket) (int, error) { for _, cachedTraffic := range zone.TrafficCached { httpCachedRequests.WithLabelValues(zoneNames[zone.ZoneTag], cachedTraffic.Dimensions.CacheStatus). Add(float64(cachedTraffic.Count), scrapeBucket.getEndTime()) httpCachedBytes.WithLabelValues(zoneNames[zone.ZoneTag], cachedTraffic.Dimensions.CacheStatus). Add(float64(cachedTraffic.Sum.EdgeResponseBytes), scrapeBucket.getEndTime()) } return len(zone.TrafficCached), nil } func extractZoneHTTPDetailed(zone zoneResp, zoneNames map[string]string, scrapeBucket *TimeBucket) (int, error) { for _, detailedTraffic := range zone.TrafficDetails { httpTLSRequests.WithLabelValues(zoneNames[zone.ZoneTag], detailedTraffic.Dimensions.ClientSSLProtocol). Add(float64(detailedTraffic.Count), scrapeBucket.getEndTime()) httpProtocolRequests.WithLabelValues(zoneNames[zone.ZoneTag], detailedTraffic.Dimensions.ClientRequestHTTPProtocol). Add(float64(detailedTraffic.Count), scrapeBucket.getEndTime()) httpResponses.WithLabelValues( zoneNames[zone.ZoneTag], toString(detailedTraffic.Dimensions.EdgeResponseStatus), toString(detailedTraffic.Dimensions.OriginResponseStatus), ). Add(float64(detailedTraffic.Count), scrapeBucket.getEndTime()) } return len(zone.TrafficDetails), nil } func extractZoneFirewallEvents(zone zoneResp, zoneNames map[string]string, scrapeBucket *TimeBucket) (int, error) { for _, firewallEventGroup := range zone.FirewallEventsAdaptiveGroups { firewallEvents.WithLabelValues( zoneNames[zone.ZoneTag], firewallEventGroup.Dimensions.Action, firewallEventGroup.Dimensions.Source, firewallEventGroup.Dimensions.RuleID, toString(firewallEventGroup.Dimensions.EdgeResponseStatus), toString(firewallEventGroup.Dimensions.OriginResponseStatus), ).Add(float64(firewallEventGroup.Count), scrapeBucket.getEndTime()) } return len(zone.FirewallEventsAdaptiveGroups), nil } func extractZoneIPReputation(zone zoneResp, zoneNames map[string]string, scrapeBucket *TimeBucket) (int, error) { for _, ipRep := range zone.IPReputation { reputation.WithLabelValues( zoneNames[zone.ZoneTag], ipRep.Dimensions.Source, ipRep.Dimensions.Repuation, ipRep.Dimensions.ClientCountryName, ).Add(float64(ipRep.Count), scrapeBucket.getEndTime()) } return len(zone.IPReputation), nil } func extractZoneHealthCheckEvents(zone zoneResp, zoneNames map[string]string, scrapeBucket *TimeBucket) (int, error) { for _, healthCheckEventsGroup := range zone.HealthCheckEventsGroups { healthCheckEvents.WithLabelValues( zoneNames[zone.ZoneTag], healthCheckEventsGroup.Dimensions.FailureReason, healthCheckEventsGroup.Dimensions.HealthCheckName, healthCheckEventsGroup.Dimensions.HealthStatus, toString(healthCheckEventsGroup.Dimensions.OriginResponseStatus), healthCheckEventsGroup.Dimensions.Region, healthCheckEventsGroup.Dimensions.Scope, ).Add(float64(healthCheckEventsGroup.Count), scrapeBucket.getEndTime()) } return len(zone.HealthCheckEventsGroups), nil } type cloudflareResp struct { Viewer struct { Zones []zoneResp `json:"zones"` } `json:"viewer"` } type zoneResp struct { TrafficCached []struct { Count uint64 `json:"count"` Dimensions struct { CacheStatus string `json:"cacheStatus"` SampleInterval uint64 `json:"sampleInterval"` } `json:"dimensions"` Sum struct { EdgeResponseBytes uint64 `json:"edgeResponseBytes"` } `json:"sum"` } `json:"trafficCached"` TrafficColo []struct { Count uint64 `json:"count"` Dimensions struct { ColoCode string `json:"coloCode"` SampleInterval uint64 `json:"sampleInterval"` } `json:"dimensions"` Sum struct { EdgeResponseBytes uint64 `json:"edgeResponseBytes"` } `json:"sum"` } `json:"trafficColo"` NetworkErrorLogs []struct { Count uint64 `json:"count"` Dimensions struct { ClientIPCountryCode string `json:"clientIPCountryCode"` ClientIPVersion int `json:"clientIPVersion"` LastKnownGoodColoCode string `json:"lastKnownGoodColoCode"` Protocol string `json:"protocol"` Phase string `json:"phase"` Type string `json:"type"` } `json:"dimensions"` } `json:"networkErrorLogs"` TrafficCountry []struct { Count uint64 `json:"count"` Dimensions struct { ClientCountryName string `json:"clientCountryName"` SampleInterval uint64 `json:"sampleInterval"` } `json:"dimensions"` Sum struct { EdgeResponseBytes uint64 `json:"edgeResponseBytes"` } `json:"sum"` } `json:"trafficCountry"` TrafficDetails []struct { Count uint64 `json:"count"` Dimensions struct { ClientRequestHTTPProtocol string `json:"clientRequestHTTPProtocol"` ClientSSLProtocol string `json:"clientSSLProtocol"` EdgeResponseStatus int `json:"edgeResponseStatus"` OriginResponseStatus int `json:"originResponseStatus"` SampleInterval uint64 `json:"sampleInterval"` } `json:"dimensions"` Sum struct { EdgeResponseBytes uint64 `json:"edgeResponseBytes"` } `json:"sum"` } `json:"trafficDetails"` FirewallEventsAdaptiveGroups []struct { Count uint64 `json:"count"` Dimensions struct { Action string `json:"action"` EdgeResponseStatus int `json:"edgeResponseStatus"` OriginResponseStatus int `json:"originResponseStatus"` SampleInterval int `json:"sampleInterval,omitempty"` RuleID string `json:"ruleId"` Source string `json:"source"` } `json:"dimensions"` } `json:"firewallEventsAdaptiveGroups,omitempty"` IPReputation []struct { Count uint64 `json:"count"` Dimensions struct { Repuation string `json:"clientIPClass"` ClientCountryName string `json:"clientCountryName"` SampleInterval int `json:"sampleInterval,omitempty"` Source string `json:"source"` } `json:"dimensions"` } `json:"reputation,omitempty"` HealthCheckEventsGroups []struct { Count uint64 `json:"count"` Dimensions struct { Datetime string `json:"datetime"` FailureReason string `json:"failureReason"` HealthCheckName string `json:"healthCheckName"` HealthStatus string `json:"healthStatus"` OriginResponseStatus int `json:"originResponseStatus"` Region string `json:"region"` Scope string `json:"scope"` } `json:"dimensions"` } `json:"healthCheckEventsGroups,omitempty"` ZoneTag string `json:"zoneTag"` } type zonesResp struct { Result []struct { ID string `json:"id"` Name string `json:"name"` Status string `json:"status"` } `json:"result"` } func toString(i int) string { return fmt.Sprintf("%d", i) }