Skip to content

Commit

Permalink
Merge pull request #38 from m0ar/master
Browse files Browse the repository at this point in the history
Add worker metrics
  • Loading branch information
martinhaus authored Sep 28, 2021
2 parents 6460651 + e3b59b3 commit a303468
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 1 deletion.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -68,6 +68,8 @@ The original method of zone filtering by using env variables `ZONE_<name>` 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
Expand Down
89 changes: 89 additions & 0 deletions cloudflare.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -158,7 +179,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) {
Expand Down Expand Up @@ -340,6 +380,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 {
Expand Down
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,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 {
Expand Down
29 changes: 29 additions & 0 deletions prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,37 @@ var (
Help: "Number of Heath check events per region per origin",
}, []string{"zone", "health_status", "origin_ip", "region", "fqdn"},
)

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()
Expand Down

0 comments on commit a303468

Please sign in to comment.