From 4b21a61c7657148d7dc7aacb1c658c562188e67f Mon Sep 17 00:00:00 2001 From: pvdvreede Date: Mon, 28 Jun 2021 03:53:17 +0000 Subject: [PATCH 1/2] Add metrics for workers. --- cloudflare.go | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 5 +++ prometheus.go | 29 +++++++++++++++++ 3 files changed, 123 insertions(+) diff --git a/cloudflare.go b/cloudflare.go index 3350558..21f12ff 100644 --- a/cloudflare.go +++ b/cloudflare.go @@ -19,12 +19,33 @@ type cloudflareResponse struct { } `json:"viewer"` } +type cloudflareResponseAccts struct { + Viewer struct { + Accounts []accountResp `json:"accounts"` + } `json:"viewer"` +} + type cloudflareResponseColo struct { Viewer struct { Zones []zoneRespColo `json:"zones"` } `json:"viewer"` } +type accountResp struct { + WorkersInvocationsAdaptive []struct { + Dimensions struct { + ScriptName string `json:"scriptName"` + Status string `json:"status"` + } + + Sum struct { + Requests uint64 `json:"requests"` + Errors uint64 `json:"errors"` + Duration float64 `json:"duration"` + } `json:"sum"` + } `json:"workersInvocationsAdaptive"` +} + type zoneRespColo struct { ColoGroups []struct { Dimensions struct { @@ -156,7 +177,26 @@ func fetchZones() []cloudflare.Zone { } return z +} + +func fetchAccounts() []cloudflare.Account { + var api *cloudflare.API + var err error + if len(cfgCfAPIToken) > 0 { + api, err = cloudflare.NewWithAPIToken(cfgCfAPIToken) + } else { + api, err = cloudflare.New(cfgCfAPIKey, cfgCfAPIEmail) + } + if err != nil { + log.Fatal(err) + } + + a, _, err := api.Accounts(cloudflare.PaginationOptions{PerPage: 100}) + if err != nil { + log.Fatal(err) + } + return a } func fetchZoneTotals(zoneIDs []string) (*cloudflareResponse, error) { @@ -337,6 +377,55 @@ func fetchColoTotals(zoneIDs []string) (*cloudflareResponseColo, error) { return &resp, nil } +func fetchWorkerTotals(accountID string) (*cloudflareResponseAccts, error) { + now := time.Now().Add(-180 * time.Second).UTC() + s := 60 * time.Second + now = now.Truncate(s) + now1mAgo := now.Add(-60 * time.Second) + + request := graphql.NewRequest(` + query ($accountID: String!, $mintime: Time!, $maxtime: Time!, $limit: Int!) { + viewer { + accounts(filter: {accountTag: $accountID} ) { + workersInvocationsAdaptive(limit: $limit, filter: { datetime_geq: $mintime, datetime_lt: $maxtime}) { + dimensions { + scriptName + status + datetime + } + + sum { + requests + errors + duration + } + } + } + } + } +`) + 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("accountID", accountID) + + ctx := context.Background() + graphqlClient := graphql.NewClient(cfGraphQLEndpoint) + var resp cloudflareResponseAccts + 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 { diff --git a/main.go b/main.go index 781dfde..38dce09 100644 --- a/main.go +++ b/main.go @@ -61,9 +61,14 @@ func filterZones(all []cloudflare.Zone, target []string) []cloudflare.Zone { func fetchMetrics() { var wg sync.WaitGroup zones := fetchZones() + accounts := fetchAccounts() filteredZones := filterZones(zones, getTargetZones()) + for _, a := range accounts { + go fetchWorkerAnalytics(a, &wg) + } + // Make requests in groups of 10 to avoid rate limit // 10 is the maximum amount of zones you can request at once for len(filteredZones) > 0 { diff --git a/prometheus.go b/prometheus.go index 03d2e50..71fc977 100644 --- a/prometheus.go +++ b/prometheus.go @@ -173,8 +173,37 @@ var ( Help: "Number of Heath check events per region per origin", }, []string{"zone", "health_status", "origin_ip", "region"}, ) + + workerRequests = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "cloudflare_worker_requests_count", + Help: "Number of requests sent to worker by script name", + }, []string{"script_name"}, + ) + + workerErrors = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "cloudflare_worker_errors_count", + Help: "Number of errors by script name", + }, []string{"script_name"}, + ) ) +func fetchWorkerAnalytics(account cloudflare.Account, wg *sync.WaitGroup) { + wg.Add(1) + defer wg.Done() + + r, err := fetchWorkerTotals(account.ID) + if err != nil { + return + } + + for _, a := range r.Viewer.Accounts { + for _, w := range a.WorkersInvocationsAdaptive { + workerRequests.With(prometheus.Labels{"script_name": w.Dimensions.ScriptName}).Add(float64(w.Sum.Requests)) + workerErrors.With(prometheus.Labels{"script_name": w.Dimensions.ScriptName}).Add(float64(w.Sum.Errors)) + } + } +} + func fetchZoneColocationAnalytics(zones []cloudflare.Zone, wg *sync.WaitGroup) { wg.Add(1) defer wg.Done() From e3b59b382421f52312be2d7ba69c53859ff9ab3d Mon Sep 17 00:00:00 2001 From: m0ar Date: Tue, 28 Sep 2021 13:07:59 +0200 Subject: [PATCH 2/2] Update README with worker metrics --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 83fb028..783e3d5 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ We help companies build, run, deploy and scale software and infrastructure by em ## Description -Prometheus exporter exposing Cloudflare Analytics dashboard data on per-zone basis. +Prometheus exporter exposing Cloudflare Analytics dashboard data on a per-zone basis, as well as Worker metrics. The exporter is also able to scrape Zone metrics by Colocations (https://www.cloudflare.com/network/). ## Grafana Dashboard @@ -67,6 +67,8 @@ The original method of zone filtering by using env variables `ZONE_` is no ## List of available metrics ``` +# HELP cloudflare_worker_errors_count Number of errors by script name +# HELP cloudflare_worker_requests_count Number of requests sent to worker by script name # HELP cloudflare_zone_bandwidth_cached Cached bandwidth per zone in bytes # HELP cloudflare_zone_bandwidth_content_type Bandwidth per zone per content type # HELP cloudflare_zone_bandwidth_country Bandwidth per country per zone