Skip to content

Commit 9233e89

Browse files
authored
Add support for cloudflare_worker_operation_count (#17)
* Add support for cloudflare_worker_operation_count * Update README.md
1 parent 98d8160 commit 9233e89

2 files changed

Lines changed: 58 additions & 7 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ Note: `ZONE_<name>` configuration is not supported as flag.
143143
# HELP cloudflare_queue_operations_lag_time Average lag time between write and read/delete (milliseconds)
144144
# HELP cloudflare_queue_operations_retry_count Average retry count for queue message operations
145145
# HELP cloudflare_worker_operation_duration_seconds Average duration of a worker operation in seconds, sliced by metric_type (worker-defined event type) and label (worker-defined slice)
146+
# HELP cloudflare_worker_operation_count Worker operation count in the last 5 minutes, sliced by metric_type (worker-defined event type) and label (worker-defined slice). Windowed sum, not a cumulative counter.
146147
```
147148

148149
## Workers Analytics Engine (WAE) integration
@@ -165,7 +166,9 @@ Discovery is **disabled by default**. Set `WAE_DATASET_PREFIX` (e.g. `my_worker_
165166

166167
Additional `blobN` / `doubleN` fields are ignored by the exporter; they're for your own diagnostic queries against WAE.
167168

168-
3. Deploy. On the next scrape cycle the exporter will discover the dataset and expose `cloudflare_worker_operation_duration_seconds` series labeled with your `script_name`, `metric_type` (from `index1`), and `label` (from `blob1`).
169+
3. Deploy. On the next scrape cycle the exporter will discover the dataset and expose two metrics, both labeled with your `script_name`, `metric_type` (from `index1`), and `label` (from `blob1`):
170+
- `cloudflare_worker_operation_duration_seconds` — duration quantiles, derived from `double1`.
171+
- `cloudflare_worker_operation_count` — the windowed event count (`sum(_sample_interval)` over the 5-minute window). This is a gauge, not a cumulative counter, so consume it directly — do **not** apply `rate()`.
169172

170173
### Notes
171174

prometheus.go

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ const (
7777
queueOperationLagTimeMetricName MetricName = "cloudflare_queue_operations_lag_time"
7878
queueOperationRetryCountMetricName MetricName = "cloudflare_queue_operations_retry_count"
7979
workerOperationDurationMetricName MetricName = "cloudflare_worker_operation_duration_seconds"
80+
workerOperationCountMetricName MetricName = "cloudflare_worker_operation_count"
8081
)
8182

8283
type MetricsSet map[MetricName]struct{}
@@ -414,6 +415,11 @@ var (
414415
Name: workerOperationDurationMetricName.String(),
415416
Help: "Worker operation duration quantiles in seconds, sliced by metric_type (worker-defined event type), label (worker-defined slice), and quantile (P50, P75, P99, P999)",
416417
}, []string{"script_name", "account", "metric_type", "label", "quantile"})
418+
419+
workerOperationCount = prometheus.NewGaugeVec(prometheus.GaugeOpts{
420+
Name: workerOperationCountMetricName.String(),
421+
Help: "Worker operation count in the last 5 minutes, sliced by metric_type (worker-defined event type) and label (worker-defined slice). Windowed sum, not a cumulative counter.",
422+
}, []string{"script_name", "account", "metric_type", "label"})
417423
)
418424

419425
func buildAllMetricsSet() MetricsSet {
@@ -473,6 +479,7 @@ func buildAllMetricsSet() MetricsSet {
473479
allMetricsSet.Add(queueOperationLagTimeMetricName)
474480
allMetricsSet.Add(queueOperationRetryCountMetricName)
475481
allMetricsSet.Add(workerOperationDurationMetricName)
482+
allMetricsSet.Add(workerOperationCountMetricName)
476483
return allMetricsSet
477484
}
478485

@@ -658,6 +665,9 @@ func mustRegisterMetrics(deniedMetrics MetricsSet) {
658665
if !deniedMetrics.Has(workerOperationDurationMetricName) {
659666
prometheus.MustRegister(workerOperationDuration)
660667
}
668+
if !deniedMetrics.Has(workerOperationCountMetricName) {
669+
prometheus.MustRegister(workerOperationCount)
670+
}
661671
}
662672

663673
func fetchLoadblancerPoolsHealth(account cfaccounts.Account, wg *sync.WaitGroup) {
@@ -1359,6 +1369,28 @@ func addCloudflareTunnelStatus(account cfaccounts.Account) {
13591369
}
13601370
}
13611371

1372+
// waeFloat coerces a value from a decoded WAE JSON row into a float64. The SQL
1373+
// API returns Float64 columns (e.g. quantiles) as JSON numbers, but renders
1374+
// 64-bit integer results such as sum(_sample_interval) as quoted strings to
1375+
// preserve precision. Both are expected and handled quietly; anything else is
1376+
// logged because it means the data is wrong (bad numeric string, or a column
1377+
// missing/renamed), not just a different-but-valid representation.
1378+
func waeFloat(column string, v interface{}) float64 {
1379+
switch n := v.(type) {
1380+
case float64:
1381+
return n
1382+
case string:
1383+
f, err := strconv.ParseFloat(n, 64)
1384+
if err != nil {
1385+
log.Errorf("WAE column %q: could not parse %q as float: %v", column, n, err)
1386+
}
1387+
return f
1388+
default:
1389+
log.Warnf("WAE column %q has unexpected type %T", column, v)
1390+
return 0
1391+
}
1392+
}
1393+
13621394
// fetchWorkerWAEAnalytics discovers all WAE datasets in the account that match
13631395
// the configured prefix and queries each one for worker metrics. The dataset
13641396
// name's suffix (with underscores converted to dashes) is emitted as the
@@ -1399,6 +1431,7 @@ func fetchWorkerWAEAnalytics(account cfaccounts.Account, wg *sync.WaitGroup, den
13991431
// see traffic (or scripts that no longer exist) disappear instead of going
14001432
// stale at their last value.
14011433
workerOperationDuration.DeletePartialMatch(prometheus.Labels{"account": accountName})
1434+
workerOperationCount.DeletePartialMatch(prometheus.Labels{"account": accountName})
14021435

14031436
for _, dataset := range datasets {
14041437
scriptName := strings.ReplaceAll(strings.TrimPrefix(dataset, prefix), "_", "-")
@@ -1407,6 +1440,7 @@ func fetchWorkerWAEAnalytics(account cfaccounts.Account, wg *sync.WaitGroup, den
14071440
SELECT
14081441
index1 AS metric_type,
14091442
blob1 AS label,
1443+
sum(_sample_interval) AS count,
14101444
quantileWeighted(0.50, double1, _sample_interval) AS p50_ms,
14111445
quantileWeighted(0.75, double1, _sample_interval) AS p75_ms,
14121446
quantileWeighted(0.99, double1, _sample_interval) AS p99_ms,
@@ -1422,7 +1456,9 @@ func fetchWorkerWAEAnalytics(account cfaccounts.Account, wg *sync.WaitGroup, den
14221456
continue
14231457
}
14241458

1425-
if deniedMetricsSet.Has(workerOperationDurationMetricName) {
1459+
durationDenied := deniedMetricsSet.Has(workerOperationDurationMetricName)
1460+
countDenied := deniedMetricsSet.Has(workerOperationCountMetricName)
1461+
if durationDenied && countDenied {
14261462
continue
14271463
}
14281464

@@ -1443,15 +1479,27 @@ func fetchWorkerWAEAnalytics(account cfaccounts.Account, wg *sync.WaitGroup, den
14431479
continue
14441480
}
14451481

1446-
for _, q := range quantiles {
1447-
val, _ := row[q.column].(float64)
1448-
workerOperationDuration.With(prometheus.Labels{
1482+
if !durationDenied {
1483+
for _, q := range quantiles {
1484+
val, _ := row[q.column].(float64)
1485+
workerOperationDuration.With(prometheus.Labels{
1486+
"script_name": scriptName,
1487+
"account": accountName,
1488+
"metric_type": metricType,
1489+
"label": label,
1490+
"quantile": q.label,
1491+
}).Set(val / 1000.0)
1492+
}
1493+
}
1494+
1495+
if !countDenied {
1496+
count := waeFloat("count", row["count"])
1497+
workerOperationCount.With(prometheus.Labels{
14491498
"script_name": scriptName,
14501499
"account": accountName,
14511500
"metric_type": metricType,
14521501
"label": label,
1453-
"quantile": q.label,
1454-
}).Set(val / 1000.0)
1502+
}).Set(count)
14551503
}
14561504
}
14571505
}

0 commit comments

Comments
 (0)