Skip to content

Commit 8f0a141

Browse files
chanceaclarkclaude
andcommitted
refactor: use SQL API instead of GraphQL for WAE stencil metrics
The Cloudflare GraphQL API does not support custom WAE datasets. Switch to the Analytics Engine SQL API endpoint which supports querying custom datasets directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2cf61be commit 8f0a141

3 files changed

Lines changed: 65 additions & 66 deletions

File tree

cloudflare.go

Lines changed: 51 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package main
22

33
import (
44
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
59
"strings"
610
"time"
711

@@ -324,24 +328,16 @@ type kvAccountResp struct {
324328
} `json:"kvOperationsAdaptiveGroups"`
325329
}
326330

327-
type cloudflareResponseWAE struct {
328-
Viewer struct {
329-
Accounts []waeAccountResp `json:"accounts"`
330-
} `json:"viewer"`
331+
type waeStencilRow struct {
332+
Endpoint string `json:"endpoint"`
333+
Environment string `json:"environment"`
334+
CacheHit string `json:"cache_hit"`
335+
AvgDurationMs float64 `json:"avg_duration_ms"`
336+
RequestCount uint64 `json:"request_count"`
331337
}
332338

333-
type waeAccountResp struct {
334-
MakeswiftStencilMetrics []struct {
335-
Dimensions struct {
336-
Blob1 string `json:"blob1"`
337-
Blob4 string `json:"blob4"`
338-
Blob5 string `json:"blob5"`
339-
} `json:"dimensions"`
340-
Avg struct {
341-
Double1 float64 `json:"double1"`
342-
} `json:"avg"`
343-
Count uint64 `json:"count"`
344-
} `json:"makeswiftStencilMetricsAdaptiveGroups"`
339+
type waeSQLResponse struct {
340+
Data []waeStencilRow `json:"data"`
345341
}
346342

347343
type cloudflareResponseSubrequests struct {
@@ -1239,45 +1235,51 @@ func fetchKVOperations(accountID string) (*cloudflareResponseKV, error) {
12391235
return &resp, nil
12401236
}
12411237

1242-
func fetchWAEStencilMetrics(accountID string) (*cloudflareResponseWAE, error) {
1243-
request := graphql.NewRequest(`
1244-
query ($accountID: String!, $mintime: Time!, $maxtime: Time!, $limit: Int!) {
1245-
viewer {
1246-
accounts(filter: {accountTag: $accountID}) {
1247-
makeswiftStencilMetricsAdaptiveGroups(limit: $limit, filter: {datetime_geq: $mintime, datetime_lt: $maxtime}) {
1248-
dimensions {
1249-
blob1
1250-
blob4
1251-
blob5
1252-
}
1253-
avg {
1254-
double1
1255-
}
1256-
count
1257-
}
1258-
}
1259-
}
1260-
}`)
1261-
1262-
now, now1mAgo := GetTimeRange()
1263-
request.Var("limit", gqlQueryLimit)
1264-
request.Var("maxtime", now)
1265-
request.Var("mintime", now1mAgo)
1266-
request.Var("accountID", accountID)
1267-
1268-
gql.Mu.RLock()
1269-
defer gql.Mu.RUnlock()
1238+
func fetchWAEStencilMetrics(accountID string) ([]waeStencilRow, error) {
1239+
now, nowAgo := GetTimeRange()
1240+
1241+
query := fmt.Sprintf(`
1242+
SELECT
1243+
blob1 AS endpoint,
1244+
blob4 AS environment,
1245+
blob5 AS cache_hit,
1246+
AVG(double1) AS avg_duration_ms,
1247+
COUNT() AS request_count
1248+
FROM makeswift_stencil_metrics
1249+
WHERE timestamp >= '%s' AND timestamp < '%s'
1250+
GROUP BY endpoint, environment, cache_hit
1251+
FORMAT JSON`,
1252+
nowAgo.Format(time.RFC3339),
1253+
now.Format(time.RFC3339),
1254+
)
1255+
1256+
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/accounts/%s/analytics_engine/sql", accountID)
12701257

12711258
ctx, cancel := context.WithTimeout(context.Background(), cftimeout)
12721259
defer cancel()
12731260

1274-
var resp cloudflareResponseWAE
1275-
if err := gql.Client.Run(ctx, request, &resp); err != nil {
1276-
log.Errorf("error fetching WAE stencil metrics, err:%v", err)
1277-
return nil, err
1261+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(query))
1262+
if err != nil {
1263+
return nil, fmt.Errorf("error creating WAE SQL request: %w", err)
12781264
}
12791265

1280-
return &resp, nil
1266+
resp, err := cfHTTPClient.Do(req)
1267+
if err != nil {
1268+
return nil, fmt.Errorf("error fetching WAE stencil metrics: %w", err)
1269+
}
1270+
defer resp.Body.Close()
1271+
1272+
if resp.StatusCode != http.StatusOK {
1273+
body, _ := io.ReadAll(resp.Body)
1274+
return nil, fmt.Errorf("WAE SQL API returned status %d: %s", resp.StatusCode, string(body))
1275+
}
1276+
1277+
var result waeSQLResponse
1278+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
1279+
return nil, fmt.Errorf("error decoding WAE SQL response: %w", err)
1280+
}
1281+
1282+
return result.Data, nil
12811283
}
12821284

12831285
func fetchWorkerSubrequests(accountID string) (*cloudflareResponseSubrequests, error) {

main.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import (
2121
)
2222

2323
var (
24-
cfclient *cf.Client
25-
cftimeout time.Duration
26-
gql *GraphQL
27-
log = logrus.New()
24+
cfclient *cf.Client
25+
cftimeout time.Duration
26+
gql *GraphQL
27+
cfHTTPClient *http.Client
28+
log = logrus.New()
2829

2930
// kvTrackedNamespaces is the set of KV namespace IDs that get their own
3031
// namespace_id label. All other namespaces are aggregated under "other".
@@ -330,6 +331,7 @@ func main() {
330331
Timeout: cftimeout,
331332
Transport: middlewares,
332333
}
334+
cfHTTPClient = gqlHTTPClient
333335
gql = NewGraphQLClient(gqlHTTPClient)
334336
} else if len(viper.GetString("cf_api_email")) > 0 && len(viper.GetString("cf_api_key")) > 0 {
335337
cfclient = cf.NewClient(
@@ -343,6 +345,7 @@ func main() {
343345
Timeout: cftimeout,
344346
Transport: middlewares,
345347
}
348+
cfHTTPClient = gqlHTTPClient
346349
gql = NewGraphQLClient(gqlHTTPClient)
347350
} else {
348351
log.Fatal("Please provide CF_API_KEY+CF_API_EMAIL or CF_API_TOKEN")

prometheus.go

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -773,26 +773,20 @@ func fetchWAEStencilAnalytics(account cfaccounts.Account, wg *sync.WaitGroup, de
773773
wg.Add(1)
774774
defer wg.Done()
775775

776-
r, err := fetchWAEStencilMetrics(account.ID)
776+
rows, err := fetchWAEStencilMetrics(account.ID)
777777
if err != nil {
778778
log.Error("failed to fetch WAE stencil metrics for account ", account.ID, ": ", err)
779779
return
780780
}
781781

782782
accountName := strings.ToLower(strings.ReplaceAll(account.Name, " ", "-"))
783783

784-
for _, a := range r.Viewer.Accounts {
785-
for _, m := range a.MakeswiftStencilMetrics {
786-
endpoint := m.Dimensions.Blob1
787-
environment := m.Dimensions.Blob4
788-
cacheHit := m.Dimensions.Blob5
789-
790-
if !deniedMetricsSet.Has(waeStencilRequestCountMetricName) {
791-
waeStencilRequestCount.With(prometheus.Labels{"endpoint": endpoint, "environment": environment, "cache_hit": cacheHit, "account": accountName}).Set(float64(m.Count))
792-
}
793-
if !deniedMetricsSet.Has(waeStencilAvgDurationMetricName) {
794-
waeStencilAvgDuration.With(prometheus.Labels{"endpoint": endpoint, "environment": environment, "cache_hit": cacheHit, "account": accountName}).Set(m.Avg.Double1)
795-
}
784+
for _, row := range rows {
785+
if !deniedMetricsSet.Has(waeStencilRequestCountMetricName) {
786+
waeStencilRequestCount.With(prometheus.Labels{"endpoint": row.Endpoint, "environment": row.Environment, "cache_hit": row.CacheHit, "account": accountName}).Set(float64(row.RequestCount))
787+
}
788+
if !deniedMetricsSet.Has(waeStencilAvgDurationMetricName) {
789+
waeStencilAvgDuration.With(prometheus.Labels{"endpoint": row.Endpoint, "environment": row.Environment, "cache_hit": row.CacheHit, "account": accountName}).Set(row.AvgDurationMs)
796790
}
797791
}
798792
}

0 commit comments

Comments
 (0)