|
1 | 1 | package main |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "context" |
4 | 5 | "fmt" |
5 | 6 | "strconv" |
6 | 7 | "strings" |
@@ -75,6 +76,7 @@ const ( |
75 | 76 | queueOperationBytesMetricName MetricName = "cloudflare_queue_operations_bytes" |
76 | 77 | queueOperationLagTimeMetricName MetricName = "cloudflare_queue_operations_lag_time" |
77 | 78 | queueOperationRetryCountMetricName MetricName = "cloudflare_queue_operations_retry_count" |
| 79 | + workerOperationDurationMetricName MetricName = "cloudflare_worker_operation_duration_seconds" |
78 | 80 | ) |
79 | 81 |
|
80 | 82 | type MetricsSet map[MetricName]struct{} |
@@ -407,6 +409,11 @@ var ( |
407 | 409 | Help: "DNS Firewall query count by query type and response code", |
408 | 410 | }, []string{"account_id", "account_name", "dns_firewall_id", "query_type", "response_code"}, |
409 | 411 | ) |
| 412 | + |
| 413 | + workerOperationDuration = prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
| 414 | + Name: workerOperationDurationMetricName.String(), |
| 415 | + Help: "Average duration of a worker operation in seconds, sliced by metric_type (worker-defined event type) and label (worker-defined slice)", |
| 416 | + }, []string{"script_name", "account", "metric_type", "label"}) |
410 | 417 | ) |
411 | 418 |
|
412 | 419 | func buildAllMetricsSet() MetricsSet { |
@@ -465,6 +472,7 @@ func buildAllMetricsSet() MetricsSet { |
465 | 472 | allMetricsSet.Add(queueOperationBytesMetricName) |
466 | 473 | allMetricsSet.Add(queueOperationLagTimeMetricName) |
467 | 474 | allMetricsSet.Add(queueOperationRetryCountMetricName) |
| 475 | + allMetricsSet.Add(workerOperationDurationMetricName) |
468 | 476 | return allMetricsSet |
469 | 477 | } |
470 | 478 |
|
@@ -647,6 +655,9 @@ func mustRegisterMetrics(deniedMetrics MetricsSet) { |
647 | 655 | if !deniedMetrics.Has(queueOperationRetryCountMetricName) { |
648 | 656 | prometheus.MustRegister(queueOperationRetryCount) |
649 | 657 | } |
| 658 | + if !deniedMetrics.Has(workerOperationDurationMetricName) { |
| 659 | + prometheus.MustRegister(workerOperationDuration) |
| 660 | + } |
650 | 661 | } |
651 | 662 |
|
652 | 663 | func fetchLoadblancerPoolsHealth(account cfaccounts.Account, wg *sync.WaitGroup) { |
@@ -1348,6 +1359,89 @@ func addCloudflareTunnelStatus(account cfaccounts.Account) { |
1348 | 1359 | } |
1349 | 1360 | } |
1350 | 1361 |
|
| 1362 | +// fetchWorkerWAEAnalytics discovers all WAE datasets in the account that match |
| 1363 | +// the configured prefix and queries each one for worker metrics. The dataset |
| 1364 | +// name's suffix (with underscores converted to dashes) is emitted as the |
| 1365 | +// script_name label so the series are joinable with cloudflare_worker_* metrics. |
| 1366 | +func fetchWorkerWAEAnalytics(account cfaccounts.Account, wg *sync.WaitGroup, deniedMetricsSet MetricsSet) { |
| 1367 | + wg.Add(1) |
| 1368 | + defer wg.Done() |
| 1369 | + |
| 1370 | + prefix := viper.GetString("wae_dataset_prefix") |
| 1371 | + if prefix == "" { |
| 1372 | + return |
| 1373 | + } |
| 1374 | + |
| 1375 | + ctx, cancel := context.WithTimeout(context.Background(), cftimeout) |
| 1376 | + defer cancel() |
| 1377 | + |
| 1378 | + listResp, err := waeClient.Query(ctx, account.ID, "SHOW TABLES") |
| 1379 | + if err != nil { |
| 1380 | + log.Error("failed to list WAE datasets for account ", account.ID, ": ", err) |
| 1381 | + return |
| 1382 | + } |
| 1383 | + |
| 1384 | + var datasets []string |
| 1385 | + for _, row := range listResp.Data { |
| 1386 | + name, _ := row["dataset"].(string) |
| 1387 | + if strings.HasPrefix(name, prefix) { |
| 1388 | + datasets = append(datasets, name) |
| 1389 | + } |
| 1390 | + } |
| 1391 | + if len(datasets) == 0 { |
| 1392 | + log.Debugf("no WAE datasets matched prefix %q in account %s", prefix, account.ID) |
| 1393 | + return |
| 1394 | + } |
| 1395 | + |
| 1396 | + accountName := strings.ToLower(strings.ReplaceAll(account.Name, " ", "-")) |
| 1397 | + |
| 1398 | + // Drop this account's previous label combinations so labels that no longer |
| 1399 | + // see traffic (or scripts that no longer exist) disappear instead of going |
| 1400 | + // stale at their last value. |
| 1401 | + workerOperationDuration.DeletePartialMatch(prometheus.Labels{"account": accountName}) |
| 1402 | + |
| 1403 | + for _, dataset := range datasets { |
| 1404 | + scriptName := strings.ReplaceAll(strings.TrimPrefix(dataset, prefix), "_", "-") |
| 1405 | + |
| 1406 | + query := fmt.Sprintf(` |
| 1407 | + SELECT |
| 1408 | + index1 AS metric_type, |
| 1409 | + blob1 AS label, |
| 1410 | + SUM(_sample_interval * double1) / SUM(_sample_interval) AS avg_duration_ms, |
| 1411 | + SUM(_sample_interval) AS total_requests |
| 1412 | + FROM %s |
| 1413 | + WHERE timestamp > NOW() - INTERVAL '5' MINUTE |
| 1414 | + GROUP BY index1, blob1 |
| 1415 | + FORMAT JSON`, dataset) |
| 1416 | + |
| 1417 | + resp, err := waeClient.Query(ctx, account.ID, query) |
| 1418 | + if err != nil { |
| 1419 | + log.Errorf("failed to fetch WAE analytics from %s (account %s): %v", dataset, account.ID, err) |
| 1420 | + continue |
| 1421 | + } |
| 1422 | + |
| 1423 | + if deniedMetricsSet.Has(workerOperationDurationMetricName) { |
| 1424 | + continue |
| 1425 | + } |
| 1426 | + |
| 1427 | + for _, row := range resp.Data { |
| 1428 | + metricType, _ := row["metric_type"].(string) |
| 1429 | + label, _ := row["label"].(string) |
| 1430 | + avgDurationMs, _ := row["avg_duration_ms"].(float64) |
| 1431 | + if metricType == "" { |
| 1432 | + continue |
| 1433 | + } |
| 1434 | + |
| 1435 | + workerOperationDuration.With(prometheus.Labels{ |
| 1436 | + "script_name": scriptName, |
| 1437 | + "account": accountName, |
| 1438 | + "metric_type": metricType, |
| 1439 | + "label": label, |
| 1440 | + }).Set(avgDurationMs / 1000.0) |
| 1441 | + } |
| 1442 | + } |
| 1443 | +} |
| 1444 | + |
1351 | 1445 | // The status of the tunnel. |
1352 | 1446 | // Valid values are: |
1353 | 1447 | // - inactive (tunnel has never been run) |
|
0 commit comments