-
Notifications
You must be signed in to change notification settings - Fork 105
Expand file tree
/
Copy pathmetrics.go
More file actions
941 lines (782 loc) · 29.2 KB
/
Copy pathmetrics.go
File metadata and controls
941 lines (782 loc) · 29.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2024-present Datadog, Inc.
package cmd
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/DataDog/datadog-api-client-go/v2/api/datadog"
"github.com/DataDog/datadog-api-client-go/v2/api/datadogV1"
"github.com/DataDog/datadog-api-client-go/v2/api/datadogV2"
"github.com/DataDog/pup/pkg/formatter"
"github.com/DataDog/pup/pkg/util"
"github.com/spf13/cobra"
)
var metricsCmd = &cobra.Command{
Use: "metrics",
Short: "Query and manage metrics",
Long: `Query time-series metrics, list available metrics, and manage metric metadata.
Metrics are the foundation of monitoring in Datadog. This command provides
comprehensive access to query metrics data, list available metrics, manage
metadata, and submit custom metrics.
CAPABILITIES:
• Query time-series metrics data with flexible time ranges
• List all available metrics with optional filtering
• Get and update metric metadata (description, unit, type)
• Submit custom metrics to Datadog
• List metric tags and tag configurations
METRIC TYPES:
• gauge: Point-in-time value (e.g., CPU usage, memory)
• count: Cumulative count (e.g., request count, errors)
• rate: Rate of change per second (e.g., requests per second)
• distribution: Statistical distribution (e.g., latency percentiles)
TIME RANGES:
Supports flexible time range specifications:
• Relative: 1h, 30m, 7d, 1w (hours, minutes, days, weeks)
• Absolute: Unix timestamps or ISO 8601 format
• Special: now (current time)
EXAMPLES:
# Query metrics
pup metrics query --query="avg:system.cpu.user{*}" --from="1h" --to="now"
pup metrics query --query="sum:app.requests{env:prod} by {service}" --from="4h"
# List metrics
pup metrics list
pup metrics list --filter="system.*"
# Get metric metadata
pup metrics metadata get system.cpu.user
pup metrics metadata get system.cpu.user --output=table
# Update metric metadata
pup metrics metadata update system.cpu.user \
--description="CPU user time" \
--unit="percent" \
--type="gauge"
# Submit custom metrics
pup metrics submit --name="custom.metric" --value=123 --tags="env:prod,team:backend"
pup metrics submit --name="custom.gauge" --value=99.5 --type="gauge" --timestamp=now
# List metric tags
pup metrics tags list system.cpu.user
pup metrics tags list system.cpu.user --from="1h"
AUTHENTICATION:
Requires either OAuth2 authentication (pup auth login) or API keys
(DD_API_KEY and DD_APP_KEY environment variables).`,
}
// Query command
var metricsQueryCmd = &cobra.Command{
Use: "query",
Short: "Query time-series metrics data (v2 API)",
Long: `Query time-series metrics data with flexible aggregation and filtering.
This command queries metrics data from Datadog using the metrics query language.
You can specify aggregation functions, filters, grouping, and time ranges.
QUERY SYNTAX:
<aggregation>:<metric_name>{<filter>} [by {<group>}]
Examples:
• avg:system.cpu.user{*}
• sum:app.requests{env:prod} by {service}
• max:system.disk.used{host:web-*}
• avg:system.load.1{availability-zone:us-east-1a} by {host}
AGGREGATIONS:
• avg: Average value
• sum: Sum of all values
• min: Minimum value
• max: Maximum value
• count: Count of data points
TIME RANGES:
• Relative: 1h, 30m, 7d, 1w, 1M (hours, minutes, days, weeks, months)
• Absolute: Unix timestamp (seconds)
• Special: now (current time)
EXAMPLES:
# Query CPU usage for the last hour
pup metrics query --query="avg:system.cpu.user{*}" --from="1h" --to="now"
# Query request count by service for last 4 hours
pup metrics query --query="sum:app.requests{env:prod} by {service}" --from="4h"
# Query memory usage for specific hosts
pup metrics query --query="avg:system.mem.used{host:web-*}" --from="2h"
# Query with absolute timestamps
pup metrics query --query="avg:system.load.1{*}" --from="1704067200" --to="1704153600"
OUTPUT:
Returns time-series data including:
• series: Array of time-series data points
• from_date: Query start time (Unix timestamp seconds)
• to_date: Query end time (Unix timestamp seconds)
• query: The query string used
• res_type: Response type
• resp_version: Response version`,
RunE: runMetricsQuery,
}
// Search command (v1 API)
var metricsSearchCmd = &cobra.Command{
Use: "search",
Short: "Search metrics (v1 API)",
Long: `Search metrics using the v1 QueryMetrics API with classic query syntax.
This command uses the v1 metrics query endpoint which accepts the traditional
Datadog query string format directly. Use this when you want straightforward
metric queries without v2 timeseries formula semantics.
QUERY SYNTAX:
<aggregation>:<metric_name>{<filter>} [by {<group>}]
EXAMPLES:
# Query CPU usage for the last hour
pup metrics search --query="avg:system.cpu.user{*}" --from="1h"
# Query request count by service
pup metrics search --query="sum:app.requests{env:prod} by {service}" --from="4h"
# Query with absolute time range
pup metrics search --query="avg:system.load.1{*}" --from="1704067200" --to="1704153600"`,
RunE: runMetricsSearch,
}
// List command
var metricsListCmd = &cobra.Command{
Use: "list",
Short: "List all available metrics",
Long: `List all available metrics in your Datadog account.
This command retrieves the list of all metrics that have been submitted to
Datadog. You can optionally filter the list using a metric name pattern or
filter by Datadog tags.
FILTERING BY NAME:
Use the --filter flag to search for metrics matching a name pattern:
• system.* - All system metrics
• *.cpu.* - All CPU-related metrics
• custom.* - All custom metrics
• myapp.* - All metrics starting with myapp
• *request* - All metrics containing "request"
Pattern matching supports wildcards (* and ?) and is case-sensitive.
Filtering is performed client-side after fetching all metrics.
FILTERING BY TAGS:
Use the --tag-filter flag to filter by Datadog tags (comma-separated):
• env:prod - Metrics tagged with env:prod
• env:prod,service:api - Metrics tagged with both env:prod AND service:api
Tag filtering is performed server-side by the Datadog API and returns
metrics that match ALL specified tags.
EXAMPLES:
# List all metrics
pup metrics list
# Filter by metric name pattern
pup metrics list --filter="system.*"
pup metrics list --filter="*.cpu.*"
pup metrics list --filter="*request*"
# Filter by tags
pup metrics list --tag-filter="env:prod"
pup metrics list --tag-filter="env:prod,service:api"
# Combine both filters (name pattern + tags)
pup metrics list --filter="system.*" --tag-filter="env:prod"
OUTPUT:
Returns an array of metric names. The response may be paginated for
large metric sets.`,
RunE: runMetricsList,
}
// Metadata command group
var metricsMetadataCmd = &cobra.Command{
Use: "metadata",
Short: "Manage metric metadata",
Long: `Get and update metric metadata including description, unit, and type.
Metric metadata provides context about what a metric represents, its unit
of measurement, and its type. This information helps teams understand and
correctly interpret metrics.
METADATA FIELDS:
• description: Human-readable description of the metric
• unit: Unit of measurement (e.g., byte, percent, request)
• type: Metric type (gauge, count, rate, distribution)
• per_unit: Per-unit for rate metrics (e.g., second)
• short_name: Short name for display
EXAMPLES:
# Get metric metadata
pup metrics metadata get system.cpu.user
# Update metric metadata
pup metrics metadata update system.cpu.user \
--description="Percentage of CPU time spent in user space" \
--unit="percent" \
--type="gauge"
# Update multiple fields
pup metrics metadata update custom.response.time \
--description="API response time" \
--unit="millisecond" \
--type="gauge" \
--short-name="Response Time"`,
}
var metricsMetadataGetCmd = &cobra.Command{
Use: "get [metric-name]",
Short: "Get metric metadata",
Long: `Get metadata for a specific metric.
Retrieves all metadata associated with a metric including description,
unit, type, integration information, and more.
ARGUMENTS:
metric-name The name of the metric (e.g., system.cpu.user)
EXAMPLES:
# Get metadata for a system metric
pup metrics metadata get system.cpu.user
# Get metadata for a custom metric
pup metrics metadata get custom.api.latency
# Get metadata with table output
pup metrics metadata get system.cpu.user --output=table
OUTPUT:
Returns metric metadata including:
• description: Metric description
• unit: Unit of measurement
• type: Metric type
• per_unit: Per-unit for rate metrics
• short_name: Display name
• integration: Integration name (if applicable)
• statsd_interval: StatsD flush interval`,
Args: cobra.ExactArgs(1),
RunE: runMetricsMetadataGet,
}
var metricsMetadataUpdateCmd = &cobra.Command{
Use: "update [metric-name]",
Short: "Update metric metadata",
Long: `Update metadata for a specific metric.
Updates one or more metadata fields for a metric. Only specified fields
will be updated; other fields remain unchanged.
ARGUMENTS:
metric-name The name of the metric to update
FLAGS:
--description Metric description
--unit Unit of measurement
--type Metric type (gauge, count, rate, distribution)
--per-unit Per-unit for rate metrics
--short-name Short display name
EXAMPLES:
# Update description only
pup metrics metadata update custom.api.latency \
--description="API endpoint response latency"
# Update multiple fields
pup metrics metadata update custom.request.rate \
--description="Request rate per second" \
--unit="request" \
--type="rate" \
--per-unit="second"
# Update unit and type
pup metrics metadata update custom.memory.used \
--unit="byte" \
--type="gauge"
OUTPUT:
Returns success message with updated metadata.`,
Args: cobra.ExactArgs(1),
RunE: runMetricsMetadataUpdate,
}
// Submit command
var metricsSubmitCmd = &cobra.Command{
Use: "submit",
Short: "Submit custom metrics to Datadog",
Long: `Submit custom metric data points to Datadog.
This command allows you to submit custom metrics from the command line.
Useful for testing, scripting, and one-off metric submissions.
METRIC TYPES:
• gauge: Current value (default)
• count: Cumulative count
• rate: Rate per second
REQUIRED FLAGS:
--name Metric name (e.g., custom.my.metric)
--value Metric value (numeric)
OPTIONAL FLAGS:
--type Metric type (gauge, count, rate) [default: gauge]
--timestamp Unix timestamp or "now" [default: now]
--tags Comma-separated tags (e.g., env:prod,team:api)
--host Host name to associate with metric
--interval Interval for rate/count metrics (seconds)
EXAMPLES:
# Submit a gauge metric
pup metrics submit --name="custom.temperature" --value=72.5
# Submit with tags
pup metrics submit \
--name="custom.api.requests" \
--value=1250 \
--tags="env:prod,service:api,region:us-east-1"
# Submit a count metric
pup metrics submit \
--name="custom.events.processed" \
--value=100 \
--type="count"
# Submit with specific timestamp
pup metrics submit \
--name="custom.batch.size" \
--value=5000 \
--timestamp="1704067200"
# Submit with host
pup metrics submit \
--name="custom.worker.queue.size" \
--value=42 \
--host="worker-01.example.com" \
--tags="env:prod"
OUTPUT:
Returns success message with submission details.
NOTES:
• Metrics are submitted to the v2 metrics intake API
• Values can be integers or floating-point numbers
• Tags must follow the format key:value
• Metric names should use lowercase with dots/underscores`,
RunE: runMetricsSubmit,
}
// Tags command group
var metricsTagsCmd = &cobra.Command{
Use: "tags",
Short: "Manage metric tags",
Long: `List and manage metric tag configurations.
Metric tags provide dimensions for filtering and grouping metrics.
This command allows you to explore available tags for metrics.
EXAMPLES:
# List tags for a metric
pup metrics tags list system.cpu.user
# List tags for a specific time period
pup metrics tags list system.cpu.user --from="1h"
# List tags for custom metric
pup metrics tags list custom.api.latency --from="24h"`,
}
var metricsTagsListCmd = &cobra.Command{
Use: "list [metric-name]",
Short: "List tags for a metric",
Long: `List all tag keys and values for a specific metric.
Retrieves all unique tag combinations that have been submitted with
a metric over the specified time period.
ARGUMENTS:
metric-name The name of the metric
FLAGS:
--from Start time (relative or absolute) [default: 1h]
--to End time (relative or absolute) [default: now]
EXAMPLES:
# List tags for the last hour
pup metrics tags list system.cpu.user
# List tags for the last 24 hours
pup metrics tags list system.cpu.user --from="24h"
# List tags for custom metric
pup metrics tags list custom.api.requests --from="7d"
OUTPUT:
Returns array of tag strings in key:value format.`,
Args: cobra.ExactArgs(1),
RunE: runMetricsTagsList,
}
// Command flags
var (
// Query flags
queryString string
fromTime string
toTime string
// List flags
filterPattern string
tagFilter string
// Metadata update flags
metadataDescription string
metadataUnit string
metadataType string
metadataPerUnit string
metadataShortName string
// Submit flags
submitName string
submitValue float64
submitType string
submitTimestamp string
submitTags string
submitHost string
submitInterval int64
)
func init() {
// Query command flags
metricsQueryCmd.Flags().StringVar(&queryString, "query", "", "Metric query string (required)")
metricsQueryCmd.Flags().StringVar(&fromTime, "from", "1h", "Start time (e.g., 1h, 30m, 7d, now, unix timestamp)")
metricsQueryCmd.Flags().StringVar(&toTime, "to", "now", "End time (e.g., now, unix timestamp)")
if err := metricsQueryCmd.MarkFlagRequired("query"); err != nil {
panic(fmt.Errorf("failed to mark flag as required: %w", err))
}
// Search command flags
metricsSearchCmd.Flags().StringVar(&queryString, "query", "", "Metric query string (required)")
metricsSearchCmd.Flags().StringVar(&fromTime, "from", "1h", "Start time (e.g., 1h, 30m, 7d, now, unix timestamp)")
metricsSearchCmd.Flags().StringVar(&toTime, "to", "now", "End time (e.g., now, unix timestamp)")
if err := metricsSearchCmd.MarkFlagRequired("query"); err != nil {
panic(fmt.Errorf("failed to mark flag as required: %w", err))
}
// List command flags
metricsListCmd.Flags().StringVar(&filterPattern, "filter", "", "Filter metrics by name pattern (e.g., system.*, *.cpu.*)")
metricsListCmd.Flags().StringVar(&tagFilter, "tag-filter", "", "Filter metrics by tags (e.g., env:prod,service:api)")
// Metadata update flags
metricsMetadataUpdateCmd.Flags().StringVar(&metadataDescription, "description", "", "Metric description")
metricsMetadataUpdateCmd.Flags().StringVar(&metadataUnit, "unit", "", "Metric unit")
metricsMetadataUpdateCmd.Flags().StringVar(&metadataType, "type", "", "Metric type (gauge, count, rate, distribution)")
metricsMetadataUpdateCmd.Flags().StringVar(&metadataPerUnit, "per-unit", "", "Per-unit for rate metrics")
metricsMetadataUpdateCmd.Flags().StringVar(&metadataShortName, "short-name", "", "Short display name")
// Submit command flags
metricsSubmitCmd.Flags().StringVar(&submitName, "name", "", "Metric name (required)")
metricsSubmitCmd.Flags().Float64Var(&submitValue, "value", 0, "Metric value (required)")
metricsSubmitCmd.Flags().StringVar(&submitType, "type", "gauge", "Metric type (gauge, count, rate)")
metricsSubmitCmd.Flags().StringVar(&submitTimestamp, "timestamp", "now", "Timestamp (now or unix timestamp)")
metricsSubmitCmd.Flags().StringVar(&submitTags, "tags", "", "Comma-separated tags (e.g., env:prod,team:api)")
metricsSubmitCmd.Flags().StringVar(&submitHost, "host", "", "Host name")
metricsSubmitCmd.Flags().Int64Var(&submitInterval, "interval", 0, "Interval in seconds for rate/count metrics")
if err := metricsSubmitCmd.MarkFlagRequired("name"); err != nil {
panic(fmt.Errorf("failed to mark flag as required: %w", err))
}
if err := metricsSubmitCmd.MarkFlagRequired("value"); err != nil {
panic(fmt.Errorf("failed to mark flag as required: %w", err))
}
// Tags command flags
metricsTagsListCmd.Flags().StringVar(&fromTime, "from", "1h", "Start time")
metricsTagsListCmd.Flags().StringVar(&toTime, "to", "now", "End time")
// Add subcommands to metadata
metricsMetadataCmd.AddCommand(metricsMetadataGetCmd)
metricsMetadataCmd.AddCommand(metricsMetadataUpdateCmd)
// Add subcommands to tags
metricsTagsCmd.AddCommand(metricsTagsListCmd)
// Add subcommands to metrics
metricsCmd.AddCommand(metricsQueryCmd)
metricsCmd.AddCommand(metricsSearchCmd)
metricsCmd.AddCommand(metricsListCmd)
metricsCmd.AddCommand(metricsMetadataCmd)
metricsCmd.AddCommand(metricsSubmitCmd)
metricsCmd.AddCommand(metricsTagsCmd)
}
// runMetricsQuery executes the metrics query command
func runMetricsQuery(cmd *cobra.Command, args []string) error {
client, err := getClient()
if err != nil {
return err
}
// Parse time ranges as second-aligned millisecond timestamps
fromMs, err := util.ParseTimeToUnixMilli(fromTime)
if err != nil {
return fmt.Errorf("invalid --from time: %w", err)
}
toMs, err := util.ParseTimeToUnixMilli(toTime)
if err != nil {
return fmt.Errorf("invalid --to time: %w", err)
}
// Use v2 API for timeseries query
api := datadogV2.NewMetricsApi(client.V2())
body := datadogV2.TimeseriesFormulaQueryRequest{
Data: datadogV2.TimeseriesFormulaRequest{
Attributes: datadogV2.TimeseriesFormulaRequestAttributes{
Formulas: []datadogV2.QueryFormula{
{Formula: "a"},
},
Queries: []datadogV2.TimeseriesQuery{{
MetricsTimeseriesQuery: &datadogV2.MetricsTimeseriesQuery{
DataSource: datadogV2.METRICSDATASOURCE_METRICS,
Query: queryString,
Name: datadog.PtrString("a"),
},
}},
From: fromMs,
To: toMs,
},
Type: datadogV2.TIMESERIESFORMULAREQUESTTYPE_TIMESERIES_REQUEST,
},
}
resp, r, err := api.QueryTimeseriesData(client.Context(), body)
if err != nil {
if r != nil {
apiBody := extractAPIErrorBody(err)
if apiBody != "" {
return fmt.Errorf("failed to query metrics: %w\nStatus: %d\nAPI Response: %s\n\nRequest Details:\n- Query: %s\n- From: %d\n- To: %d\n\nTroubleshooting:\n- Verify your query syntax is correct (e.g., avg:metric.name{filter})\n- Check that the time range is valid\n- Ensure the metric exists and has data in the specified time range\n- Confirm you have proper permissions to access the metric",
err, r.StatusCode, apiBody,
queryString,
fromMs,
toMs)
}
return fmt.Errorf("failed to query metrics: %w (status: %d)", err, r.StatusCode)
}
return fmt.Errorf("failed to query metrics: %w", err)
}
var meta *formatter.Metadata
if isAgentMode() {
meta = &formatter.Metadata{Command: "metrics query"}
}
return formatAndPrint(resp, meta)
}
// runMetricsSearch executes the metrics search command using the v1 API
func runMetricsSearch(cmd *cobra.Command, args []string) error {
client, err := getClient()
if err != nil {
return err
}
// Parse time ranges
from, err := util.ParseTimeParam(fromTime)
if err != nil {
return fmt.Errorf("invalid --from time: %w", err)
}
to, err := util.ParseTimeParam(toTime)
if err != nil {
return fmt.Errorf("invalid --to time: %w", err)
}
api := datadogV1.NewMetricsApi(client.V1())
resp, r, err := api.QueryMetrics(client.Context(), from.Unix(), to.Unix(), queryString)
if err != nil {
if r != nil {
apiBody := extractAPIErrorBody(err)
if apiBody != "" {
return fmt.Errorf("failed to search metrics: %w\nStatus: %d\nAPI Response: %s",
err, r.StatusCode, apiBody)
}
return fmt.Errorf("failed to search metrics: %w (status: %d)", err, r.StatusCode)
}
return fmt.Errorf("failed to search metrics: %w", err)
}
return formatAndPrint(resp, nil)
}
// matchMetricName checks if a metric name matches a wildcard pattern.
// Supports * (match any characters) and ? (match single character).
func matchMetricName(pattern, name string) bool {
// If no pattern, match all
if pattern == "" {
return true
}
// Simple case: exact match
if pattern == name {
return true
}
// Convert glob pattern to regex-like matching
pIdx, nIdx := 0, 0
pLen, nLen := len(pattern), len(name)
// Track position for backtracking after *
var starIdx, matchIdx int = -1, 0
for nIdx < nLen {
if pIdx < pLen {
switch pattern[pIdx] {
case '?':
// ? matches any single character
pIdx++
nIdx++
continue
case '*':
// * matches zero or more characters
starIdx = pIdx
matchIdx = nIdx
pIdx++
continue
default:
// Regular character must match exactly
if pattern[pIdx] == name[nIdx] {
pIdx++
nIdx++
continue
}
}
}
// If we have a star, try backtracking
if starIdx != -1 {
pIdx = starIdx + 1
matchIdx++
nIdx = matchIdx
continue
}
return false
}
// Consume remaining * in pattern
for pIdx < pLen && pattern[pIdx] == '*' {
pIdx++
}
return pIdx == pLen
}
// runMetricsList executes the metrics list command
func runMetricsList(cmd *cobra.Command, args []string) error {
client, err := getClient()
if err != nil {
return err
}
api := datadogV1.NewMetricsApi(client.V1())
// From time defaults to 1 hour ago
from := time.Now().Add(-1 * time.Hour).Unix()
// Build API options - only use tag filter for --tag-filter flag
opts := datadogV1.NewListActiveMetricsOptionalParameters()
if tagFilter != "" {
opts = opts.WithTagFilter(tagFilter)
}
resp, r, err := api.ListActiveMetrics(client.Context(), from, *opts)
if err != nil {
if r != nil {
apiBody := extractAPIErrorBody(err)
if apiBody != "" {
filters := []string{}
if filterPattern != "" {
filters = append(filters, fmt.Sprintf("Name pattern: %s", filterPattern))
}
if tagFilter != "" {
filters = append(filters, fmt.Sprintf("Tag filter: %s", tagFilter))
}
filterInfo := strings.Join(filters, "\n- ")
if filterInfo == "" {
filterInfo = "None"
}
return fmt.Errorf("failed to list metrics: %w\nStatus: %d\nAPI Response: %s\n\nRequest Details:\n- Filters:\n %s\n- From: %s (Unix: %d)\n\nTroubleshooting:\n- For --filter, use metric name patterns (e.g., system.*, *.cpu.*)\n- For --tag-filter, use Datadog tags (e.g., env:prod,service:api)\n- Verify you have permissions to list metrics",
err, r.StatusCode, apiBody,
filterInfo,
time.Unix(from, 0).Format(time.RFC3339), from)
}
return fmt.Errorf("failed to list metrics: %w (status: %d)", err, r.StatusCode)
}
return fmt.Errorf("failed to list metrics: %w", err)
}
// Apply client-side name filtering if pattern is specified
if filterPattern != "" {
metrics, ok := resp.GetMetricsOk()
if ok && metrics != nil {
filtered := make([]string, 0)
for _, metric := range *metrics {
if matchMetricName(filterPattern, metric) {
filtered = append(filtered, metric)
}
}
resp.Metrics = filtered
}
}
return formatAndPrint(resp, nil)
}
// runMetricsMetadataGet executes the metadata get command
func runMetricsMetadataGet(cmd *cobra.Command, args []string) error {
client, err := getClient()
if err != nil {
return err
}
metricName := args[0]
api := datadogV1.NewMetricsApi(client.V1())
resp, r, err := api.GetMetricMetadata(client.Context(), metricName)
if err != nil {
if r != nil {
apiBody := extractAPIErrorBody(err)
if apiBody != "" {
return fmt.Errorf("failed to get metric metadata: %w\nStatus: %d\nAPI Response: %s\n\nMetric: %s\n\nTroubleshooting:\n- Verify the metric name is correct\n- Ensure the metric exists in your account\n- Check that you have permissions to view metadata",
err, r.StatusCode, apiBody, metricName)
}
return fmt.Errorf("failed to get metric metadata: %w (status: %d)", err, r.StatusCode)
}
return fmt.Errorf("failed to get metric metadata: %w", err)
}
return formatAndPrint(resp, nil)
}
// runMetricsMetadataUpdate executes the metadata update command
func runMetricsMetadataUpdate(cmd *cobra.Command, args []string) error {
client, err := getClient()
if err != nil {
return err
}
metricName := args[0]
api := datadogV1.NewMetricsApi(client.V1())
// Build metadata update body
body := datadogV1.MetricMetadata{}
if metadataDescription != "" {
body.SetDescription(metadataDescription)
}
if metadataUnit != "" {
body.SetUnit(metadataUnit)
}
if metadataType != "" {
body.SetType(metadataType)
}
if metadataPerUnit != "" {
body.SetPerUnit(metadataPerUnit)
}
if metadataShortName != "" {
body.SetShortName(metadataShortName)
}
// Check if at least one field is specified
if !body.HasDescription() && !body.HasUnit() && !body.HasType() && !body.HasPerUnit() && !body.HasShortName() {
return fmt.Errorf("at least one metadata field must be specified (--description, --unit, --type, --per-unit, --short-name)")
}
resp, r, err := api.UpdateMetricMetadata(client.Context(), metricName, body)
if err != nil {
if r != nil {
apiBody := extractAPIErrorBody(err)
if apiBody != "" {
return fmt.Errorf("failed to update metric metadata: %w\nStatus: %d\nAPI Response: %s\n\nMetric: %s\n\nTroubleshooting:\n- Verify the metric name is correct\n- Check that the metadata values are valid (unit, type, etc.)\n- Ensure you have permissions to update metadata",
err, r.StatusCode, apiBody, metricName)
}
return fmt.Errorf("failed to update metric metadata: %w (status: %d)", err, r.StatusCode)
}
return fmt.Errorf("failed to update metric metadata: %w", err)
}
return formatAndPrint(resp, nil)
}
// runMetricsSubmit executes the metrics submit command
func runMetricsSubmit(cmd *cobra.Command, args []string) error {
// The metrics intake API requires an API key (DD_API_KEY).
// OAuth2 bearer tokens are not supported for metric submission.
if cfg.APIKey == "" {
return fmt.Errorf(
"metrics submit requires a Datadog API key.\n\n" +
"Set the DD_API_KEY environment variable:\n" +
" export DD_API_KEY=\"your-api-key\"\n\n" +
"You can find your API key at https://app.datadoghq.com/organization-settings/api-keys\n\n" +
"Note: OAuth2 authentication (pup auth login) is not supported for metric submission.",
)
}
client, err := apiKeyClientFactory(cfg)
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}
// Parse timestamp
var timestamp int64
if submitTimestamp == "now" {
timestamp = time.Now().Unix()
} else {
ts, err := strconv.ParseInt(submitTimestamp, 10, 64)
if err != nil {
return fmt.Errorf("invalid timestamp: %w", err)
}
timestamp = ts
}
// Parse tags
var tags []string
if submitTags != "" {
tags = strings.Split(submitTags, ",")
// Trim whitespace from tags
for i := range tags {
tags[i] = strings.TrimSpace(tags[i])
}
}
// Determine metric type
var metricType datadogV2.MetricIntakeType
switch strings.ToLower(submitType) {
case "gauge":
metricType = datadogV2.METRICINTAKETYPE_GAUGE
case "count":
metricType = datadogV2.METRICINTAKETYPE_COUNT
case "rate":
metricType = datadogV2.METRICINTAKETYPE_RATE
default:
return fmt.Errorf("invalid metric type: %s (must be gauge, count, or rate)", submitType)
}
// Build metric payload
point := datadogV2.MetricPoint{
Timestamp: ×tamp,
Value: &submitValue,
}
// Convert MetricIntakeType to string for resource type
metricTypeStr := string(metricType)
resource := datadogV2.MetricResource{
Name: &submitName,
Type: &metricTypeStr,
}
series := datadogV2.MetricSeries{
Metric: submitName,
Type: &metricType,
Points: []datadogV2.MetricPoint{point},
Resources: []datadogV2.MetricResource{resource},
}
if len(tags) > 0 {
series.Tags = tags
}
if submitInterval > 0 {
series.Interval = &submitInterval
}
body := datadogV2.MetricPayload{
Series: []datadogV2.MetricSeries{series},
}
// Submit using v2 API
api := datadogV2.NewMetricsApi(client.V2())
resp, r, err := api.SubmitMetrics(client.Context(), body, *datadogV2.NewSubmitMetricsOptionalParameters())
if err != nil {
if r != nil {
apiBody := extractAPIErrorBody(err)
if apiBody != "" {
return fmt.Errorf("failed to submit metrics: %w\nStatus: %d\nAPI Response: %s\n\nRequest Details:\n- Metric: %s\n- Value: %f\n- Type: %s\n- Timestamp: %d\n- Tags: %v\n\nTroubleshooting:\n- Verify the metric name follows naming conventions (lowercase, dots/underscores)\n- Check that the metric type is valid (gauge, count, rate)\n- Ensure your API key has permission to submit metrics\n- Verify tags are in key:value format",
err, r.StatusCode, apiBody,
submitName, submitValue, submitType, timestamp, tags)
}
return fmt.Errorf("failed to submit metrics: %w (status: %d)", err, r.StatusCode)
}
return fmt.Errorf("failed to submit metrics: %w", err)
}
return formatAndPrint(resp, nil)
}
// runMetricsTagsList executes the tags list command
func runMetricsTagsList(cmd *cobra.Command, args []string) error {
// NOTE: ListTagsByMetricName is not available in datadog-api-client-go v2.30.0
return fmt.Errorf("listing tags by metric name is not supported in the current API client version")
}