Skip to content

Commit 7821c0d

Browse files
authored
Merge pull request #189 from gncaglar/add-asn-metrics
feat: add ASN metrics
2 parents f3d930d + 53c5fd9 commit 7821c0d

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 cloudflareResponseEdgeErrorsByPath struct {
89116
Viewer struct {
90117
Zones []zoneRespEdgeErrorsByPath `json:"zones"`
@@ -1104,3 +1131,54 @@ func filterNonFreePlanZones(zones []cfzones.Zone) (filteredZones []cfzones.Zone)
11041131
}
11051132
return
11061133
}
1134+
1135+
func fetchASNTotals(zoneIDs []string) (*cloudflareResponseASN, error) {
1136+
request := graphql.NewRequest(`
1137+
query ($zoneIDs: [String!], $mintime: Time!, $maxtime: Time!, $limit: Int!) {
1138+
viewer {
1139+
zones(filter: { zoneTag_in: $zoneIDs }) {
1140+
zoneTag
1141+
httpRequestsAdaptiveGroups(
1142+
limit: $limit
1143+
filter: { datetime_geq: $mintime, datetime_lt: $maxtime }
1144+
orderBy: [count_DESC]
1145+
) {
1146+
count
1147+
avg {
1148+
sampleInterval
1149+
}
1150+
dimensions {
1151+
datetime
1152+
clientAsn
1153+
clientASNDescription
1154+
}
1155+
sum {
1156+
edgeResponseBytes
1157+
visits
1158+
}
1159+
}
1160+
}
1161+
}
1162+
}
1163+
`)
1164+
1165+
now, now1mAgo := GetTimeRange()
1166+
request.Var("limit", gqlQueryLimit)
1167+
request.Var("maxtime", now)
1168+
request.Var("mintime", now1mAgo)
1169+
request.Var("zoneIDs", zoneIDs)
1170+
1171+
gql.Mu.RLock()
1172+
defer gql.Mu.RUnlock()
1173+
1174+
ctx, cancel := context.WithTimeout(context.Background(), cftimeout)
1175+
defer cancel()
1176+
1177+
var resp cloudflareResponseASN
1178+
if err := gql.Client.Run(ctx, request, &resp); err != nil {
1179+
log.Errorf("failed to fetch ASN totals, err:%v", err)
1180+
return nil, err
1181+
}
1182+
1183+
return &resp, nil
1184+
}

main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ func fetchMetrics() {
158158
go fetchLogpushAnalyticsForZone(filteredZones, &wg)
159159

160160
wg.Add(1)
161+
go fetchZoneASNAnalytics(filteredZones, &wg)
162+
163+
wg.Add(1)
161164
go fetchEdgeErrorsByPathAnalytics(filteredZones, &wg)
162165
} else if zoneCount > cfgraphqlreqlimit {
163166
for s := 0; s < zoneCount; s += cfgraphqlreqlimit {
@@ -178,6 +181,9 @@ func fetchMetrics() {
178181
go fetchLogpushAnalyticsForZone(filteredZones[s:e], &wg)
179182

180183
wg.Add(1)
184+
go fetchZoneASNAnalytics(filteredZones[s:e], &wg)
185+
186+
wg.Add(1)
181187
go fetchEdgeErrorsByPathAnalytics(filteredZones[s:e], &wg)
182188
}
183189
}

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
zoneEdgeErrorsByPathMetricName MetricName = "cloudflare_zone_edge_errors_by_path"
6769
)
6870

@@ -336,6 +338,16 @@ var (
336338
Help: "Reports number of active connections for a Cloudflare Tunnel connector",
337339
}, []string{"account", "tunnel_id", "client_id"})
338340

341+
zoneRequestASN = prometheus.NewCounterVec(prometheus.CounterOpts{
342+
Name: zoneRequestASNMetricName.String(),
343+
Help: "Number of requests per ASN (Autonomous System Number)",
344+
}, []string{"zone", "account", "asn", "asn_description"})
345+
346+
zoneBandwidthASN = prometheus.NewCounterVec(prometheus.CounterOpts{
347+
Name: zoneBandwidthASNMetricName.String(),
348+
Help: "Bandwidth per ASN (Autonomous System Number) in bytes",
349+
}, []string{"zone", "account", "asn", "asn_description"})
350+
339351
zoneEdgeErrorsByPath = prometheus.NewCounterVec(prometheus.CounterOpts{
340352
Name: zoneEdgeErrorsByPathMetricName.String(),
341353
Help: "Number of edge errors (4xx and 5xx) by request path",
@@ -386,6 +398,8 @@ func buildAllMetricsSet() MetricsSet {
386398
allMetricsSet.Add(tunnelHealthStatusMetricName)
387399
allMetricsSet.Add(tunnelConnectorInfoMetricName)
388400
allMetricsSet.Add(tunnelConnectorActiveConnectionsMetricName)
401+
allMetricsSet.Add(zoneRequestASNMetricName)
402+
allMetricsSet.Add(zoneBandwidthASNMetricName)
389403
allMetricsSet.Add(zoneEdgeErrorsByPathMetricName)
390404
return allMetricsSet
391405
}
@@ -533,6 +547,12 @@ func mustRegisterMetrics(deniedMetrics MetricsSet) {
533547
if !deniedMetrics.Has(tunnelConnectorActiveConnectionsMetricName) {
534548
prometheus.MustRegister(tunnelConnectorActiveConnections)
535549
}
550+
if !deniedMetrics.Has(zoneRequestASNMetricName) {
551+
prometheus.MustRegister(zoneRequestASN)
552+
}
553+
if !deniedMetrics.Has(zoneBandwidthASNMetricName) {
554+
prometheus.MustRegister(zoneBandwidthASN)
555+
}
536556
if !deniedMetrics.Has(zoneEdgeErrorsByPathMetricName) {
537557
prometheus.MustRegister(zoneEdgeErrorsByPath)
538558
}
@@ -1099,3 +1119,48 @@ func getCloudflareTunnelStatusValue(status string) uint8 {
10991119
return 255
11001120
}
11011121
}
1122+
1123+
func fetchZoneASNAnalytics(zones []cfzones.Zone, wg *sync.WaitGroup) {
1124+
defer wg.Done()
1125+
1126+
// ASN metrics are not available in free tier
1127+
if viper.GetBool("free_tier") {
1128+
return
1129+
}
1130+
1131+
zoneIDs := extractZoneIDs(zones)
1132+
if len(zoneIDs) == 0 {
1133+
return
1134+
}
1135+
1136+
r, err := fetchASNTotals(zoneIDs)
1137+
if err != nil {
1138+
return
1139+
}
1140+
1141+
for _, z := range r.Viewer.Zones {
1142+
name, account := findZoneAccountName(zones, z.ZoneTag)
1143+
addASNGroups(&z, name, account)
1144+
}
1145+
}
1146+
1147+
func addASNGroups(z *zoneRespASN, name string, account string) {
1148+
if len(z.HttpRequestsASNGroups) == 0 {
1149+
return
1150+
}
1151+
1152+
for _, g := range z.HttpRequestsASNGroups {
1153+
asn := g.Dimensions.ClientASN
1154+
asnDesc := g.Dimensions.ClientASNDescription
1155+
if asnDesc == "" {
1156+
asnDesc = "unknown"
1157+
}
1158+
1159+
zoneRequestASN.With(prometheus.Labels{
1160+
"zone": name, "account": account, "asn": asn, "asn_description": asnDesc,
1161+
}).Add(float64(g.Count))
1162+
zoneBandwidthASN.With(prometheus.Labels{
1163+
"zone": name, "account": account, "asn": asn, "asn_description": asnDesc,
1164+
}).Add(float64(g.Sum.EdgeResponseBytes))
1165+
}
1166+
}

0 commit comments

Comments
 (0)