Skip to content

Commit b2678a7

Browse files
author
Gamze Nur Kabakci
committed
feat: add ASN metrics
Add cloudflare_zone_requests_asn and cloudflare_zone_bandwidth_asn to track traffic by ISP/network operator
1 parent 3dc871d commit b2678a7

3 files changed

Lines changed: 149 additions & 0 deletions

File tree

cloudflare.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,33 @@ type cloudflareResponseLogpushZone struct {
8585
} `json:"viewer"`
8686
}
8787

88+
type cloudflareResponseASN struct {
89+
Viewer struct {
90+
Zones []zoneRespASN `json:"zones"`
91+
} `json:"viewer"`
92+
}
93+
94+
type zoneRespASN struct {
95+
ZoneTag string `json:"zoneTag"`
96+
97+
HttpRequestsASNGroups []struct {
98+
Count uint64 `json:"count"`
99+
Dimensions struct {
100+
Datetime string `json:"datetime"`
101+
ClientASN string `json:"clientAsn"`
102+
ClientASNDescription string `json:"clientASNDescription"`
103+
} `json:"dimensions"`
104+
Sum struct {
105+
EdgeResponseBytes uint64 `json:"edgeResponseBytes"`
106+
Visits uint64 `json:"visits"`
107+
} `json:"sum"`
108+
Avg struct {
109+
SampleInterval float64 `json:"sampleInterval"`
110+
} `json:"avg"`
111+
} `json:"httpRequestsAdaptiveGroups"`
112+
}
113+
114+
88115
type logpushResponse struct {
89116
LogpushHealthAdaptiveGroups []struct {
90117
Count uint64 `json:"count"`
@@ -1010,3 +1037,54 @@ func filterNonFreePlanZones(zones []cfzones.Zone) (filteredZones []cfzones.Zone)
10101037
}
10111038
return
10121039
}
1040+
1041+
func fetchASNTotals(zoneIDs []string) (*cloudflareResponseASN, error) {
1042+
request := graphql.NewRequest(`
1043+
query ($zoneIDs: [String!], $mintime: Time!, $maxtime: Time!, $limit: Int!) {
1044+
viewer {
1045+
zones(filter: { zoneTag_in: $zoneIDs }) {
1046+
zoneTag
1047+
httpRequestsAdaptiveGroups(
1048+
limit: $limit
1049+
filter: { datetime_geq: $mintime, datetime_lt: $maxtime }
1050+
orderBy: [count_DESC]
1051+
) {
1052+
count
1053+
avg {
1054+
sampleInterval
1055+
}
1056+
dimensions {
1057+
datetime
1058+
clientAsn
1059+
clientASNDescription
1060+
}
1061+
sum {
1062+
edgeResponseBytes
1063+
visits
1064+
}
1065+
}
1066+
}
1067+
}
1068+
}
1069+
`)
1070+
1071+
now, now1mAgo := GetTimeRange()
1072+
request.Var("limit", gqlQueryLimit)
1073+
request.Var("maxtime", now)
1074+
request.Var("mintime", now1mAgo)
1075+
request.Var("zoneIDs", zoneIDs)
1076+
1077+
gql.Mu.RLock()
1078+
defer gql.Mu.RUnlock()
1079+
1080+
ctx, cancel := context.WithTimeout(context.Background(), cftimeout)
1081+
defer cancel()
1082+
1083+
var resp cloudflareResponseASN
1084+
if err := gql.Client.Run(ctx, request, &resp); err != nil {
1085+
log.Errorf("failed to fetch ASN totals, err:%v", err)
1086+
return nil, err
1087+
}
1088+
1089+
return &resp, nil
1090+
}

main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ func fetchMetrics() {
146146

147147
wg.Add(1)
148148
go fetchLogpushAnalyticsForZone(filteredZones, &wg)
149+
150+
wg.Add(1)
151+
go fetchZoneASNAnalytics(filteredZones, &wg)
149152
} else if zoneCount > cfgraphqlreqlimit {
150153
for s := 0; s < zoneCount; s += cfgraphqlreqlimit {
151154
e := s + cfgraphqlreqlimit
@@ -163,6 +166,9 @@ func fetchMetrics() {
163166

164167
wg.Add(1)
165168
go fetchLogpushAnalyticsForZone(filteredZones[s:e], &wg)
169+
170+
wg.Add(1)
171+
go fetchZoneASNAnalytics(filteredZones[s:e], &wg)
166172
}
167173
}
168174

prometheus.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ const (
6363
tunnelHealthStatusMetricName MetricName = "cloudflare_tunnel_health_status"
6464
tunnelConnectorInfoMetricName MetricName = "cloudflare_tunnel_connector_info"
6565
tunnelConnectorActiveConnectionsMetricName MetricName = "cloudflare_tunnel_connector_active_connections"
66+
zoneRequestASNMetricName MetricName = "cloudflare_zone_requests_asn"
67+
zoneBandwidthASNMetricName MetricName = "cloudflare_zone_bandwidth_asn"
6668
)
6769

6870
type MetricsSet map[MetricName]struct{}
@@ -334,6 +336,16 @@ var (
334336
Name: tunnelConnectorActiveConnectionsMetricName.String(),
335337
Help: "Reports number of active connections for a Cloudflare Tunnel connector",
336338
}, []string{"account", "tunnel_id", "client_id"})
339+
340+
zoneRequestASN = prometheus.NewCounterVec(prometheus.CounterOpts{
341+
Name: zoneRequestASNMetricName.String(),
342+
Help: "Number of requests per ASN (Autonomous System Number)",
343+
}, []string{"zone", "account", "asn", "asn_description"})
344+
345+
zoneBandwidthASN = prometheus.NewCounterVec(prometheus.CounterOpts{
346+
Name: zoneBandwidthASNMetricName.String(),
347+
Help: "Bandwidth per ASN (Autonomous System Number) in bytes",
348+
}, []string{"zone", "account", "asn", "asn_description"})
337349
)
338350

339351
func buildAllMetricsSet() MetricsSet {
@@ -377,6 +389,8 @@ func buildAllMetricsSet() MetricsSet {
377389
allMetricsSet.Add(tunnelHealthStatusMetricName)
378390
allMetricsSet.Add(tunnelConnectorInfoMetricName)
379391
allMetricsSet.Add(tunnelConnectorActiveConnectionsMetricName)
392+
allMetricsSet.Add(zoneRequestASNMetricName)
393+
allMetricsSet.Add(zoneBandwidthASNMetricName)
380394
return allMetricsSet
381395
}
382396

@@ -523,6 +537,12 @@ func mustRegisterMetrics(deniedMetrics MetricsSet) {
523537
if !deniedMetrics.Has(tunnelConnectorActiveConnectionsMetricName) {
524538
prometheus.MustRegister(tunnelConnectorActiveConnections)
525539
}
540+
if !deniedMetrics.Has(zoneRequestASNMetricName) {
541+
prometheus.MustRegister(zoneRequestASN)
542+
}
543+
if !deniedMetrics.Has(zoneBandwidthASNMetricName) {
544+
prometheus.MustRegister(zoneBandwidthASN)
545+
}
526546
}
527547

528548
func fetchLoadblancerPoolsHealth(account cfaccounts.Account, wg *sync.WaitGroup) {
@@ -1038,3 +1058,48 @@ func getCloudflareTunnelStatusValue(status string) uint8 {
10381058
return 255
10391059
}
10401060
}
1061+
1062+
func fetchZoneASNAnalytics(zones []cfzones.Zone, wg *sync.WaitGroup) {
1063+
defer wg.Done()
1064+
1065+
// ASN metrics are not available in free tier
1066+
if viper.GetBool("free_tier") {
1067+
return
1068+
}
1069+
1070+
zoneIDs := extractZoneIDs(zones)
1071+
if len(zoneIDs) == 0 {
1072+
return
1073+
}
1074+
1075+
r, err := fetchASNTotals(zoneIDs)
1076+
if err != nil {
1077+
return
1078+
}
1079+
1080+
for _, z := range r.Viewer.Zones {
1081+
name, account := findZoneAccountName(zones, z.ZoneTag)
1082+
addASNGroups(&z, name, account)
1083+
}
1084+
}
1085+
1086+
func addASNGroups(z *zoneRespASN, name string, account string) {
1087+
if len(z.HttpRequestsASNGroups) == 0 {
1088+
return
1089+
}
1090+
1091+
for _, g := range z.HttpRequestsASNGroups {
1092+
asn := g.Dimensions.ClientASN
1093+
asnDesc := g.Dimensions.ClientASNDescription
1094+
if asnDesc == "" {
1095+
asnDesc = "unknown"
1096+
}
1097+
1098+
zoneRequestASN.With(prometheus.Labels{
1099+
"zone": name, "account": account, "asn": asn, "asn_description": asnDesc,
1100+
}).Add(float64(g.Count))
1101+
zoneBandwidthASN.With(prometheus.Labels{
1102+
"zone": name, "account": account, "asn": asn, "asn_description": asnDesc,
1103+
}).Add(float64(g.Sum.EdgeResponseBytes))
1104+
}
1105+
}

0 commit comments

Comments
 (0)