|
6 | 6 | package listeners |
7 | 7 |
|
8 | 8 | import ( |
| 9 | + "slices" |
9 | 10 | "strings" |
10 | 11 |
|
11 | 12 | yaml "go.yaml.in/yaml/v2" |
@@ -72,24 +73,85 @@ func NamespaceRoot(namespace string) string { |
72 | 73 | return namespace |
73 | 74 | } |
74 | 75 |
|
75 | | -// InstanceNamespaces returns the configured `namespace` of each instance in |
76 | | -// cfg.Instances that sets one explicitly. Instances with no namespace |
77 | | -// configured are skipped: with nothing to compare, assuming a match would |
78 | | -// risk suppressing discovery unnecessarily. Exported so the config manager |
79 | | -// can use the same logic to populate StaticConfigIndex with namespace roots |
80 | | -// from scheduled static (non-template) generic-scraper configs. |
81 | | -func InstanceNamespaces(cfg integration.Config) []string { |
82 | | - var namespaces []string |
| 76 | +// GenericIntegrationNamespaceRoots returns, for each instance in cfg, the |
| 77 | +// metric-namespace root (see NamespaceRoot) it would submit metrics under: |
| 78 | +// - if the instance sets an explicit `namespace:`, that field's root, or |
| 79 | +// - otherwise, the root of each explicit metric rename target in the |
| 80 | +// instance's `metrics`/`extra_metrics` field (see |
| 81 | +// instanceMetricRenameTargets). |
| 82 | +// |
| 83 | +// The metrics-rename fallback only matters when namespace is unset: a |
| 84 | +// generic openmetrics/prometheus check submits `namespace.metric_name`, but |
| 85 | +// when namespace is empty the metric name is submitted completely |
| 86 | +// unprefixed (verified in datadog_checks_base's AgentCheck._format_namespace) |
| 87 | +// — so a rename target that's already a fully-qualified dotted name (e.g. |
| 88 | +// `envoy_cluster_http2_streams_active: envoy.cluster.http2.streams_active`) |
| 89 | +// collides with the native integration's own metric, and there's no |
| 90 | +// `namespace:` value to catch it. When namespace *is* set, it's prepended on |
| 91 | +// top of the rename target regardless, so the rename can't itself collide — |
| 92 | +// hence checking metrics only in the no-namespace case. See |
| 93 | +// https://datadoghq.atlassian.net/browse/DSCVR-626?focusedCommentId=3493570 |
| 94 | +// |
| 95 | +// Instances with neither an explicit namespace nor a qualifying rename |
| 96 | +// contribute nothing: with no signal to compare, assuming a match would risk |
| 97 | +// suppressing discovery unnecessarily. Exported so the config manager can use |
| 98 | +// the same logic to populate StaticConfigIndex with namespace roots from |
| 99 | +// scheduled static (non-template) generic-scraper configs. |
| 100 | +func GenericIntegrationNamespaceRoots(cfg integration.Config) []string { |
| 101 | + var roots []string |
83 | 102 | for _, inst := range cfg.Instances { |
84 | 103 | var common integration.CommonInstanceConfig |
85 | 104 | if err := yaml.Unmarshal(inst, &common); err != nil { |
86 | 105 | continue |
87 | 106 | } |
88 | 107 | if common.Namespace != "" { |
89 | | - namespaces = append(namespaces, common.Namespace) |
| 108 | + roots = append(roots, NamespaceRoot(common.Namespace)) |
| 109 | + continue |
| 110 | + } |
| 111 | + for _, target := range instanceMetricRenameTargets(inst) { |
| 112 | + roots = append(roots, NamespaceRoot(target)) |
| 113 | + } |
| 114 | + } |
| 115 | + return roots |
| 116 | +} |
| 117 | + |
| 118 | +// instanceMetricRenameTargets returns the explicit rename target of each |
| 119 | +// entry in inst's `metrics`/`extra_metrics` field that renames a raw metric |
| 120 | +// to a different name, mirroring the shapes accepted by |
| 121 | +// MetricTransformer.normalize_metric_config (openmetrics v2) and the legacy |
| 122 | +// metrics_mapper loops (openmetrics v1, prometheus) in datadog_checks_base: |
| 123 | +// each list entry is either |
| 124 | +// - a plain string: pass-through, not a rename, skipped; |
| 125 | +// - a single-key map to a string: the string is the rename target; or |
| 126 | +// - a single-key map to a nested map with a `name` key: that key's value is |
| 127 | +// the rename target (no `name` key means the raw metric name is kept, |
| 128 | +// i.e. still not a rename, skipped). |
| 129 | +func instanceMetricRenameTargets(inst integration.Data) []string { |
| 130 | + var raw struct { |
| 131 | + Metrics []interface{} `yaml:"metrics"` |
| 132 | + ExtraMetrics []interface{} `yaml:"extra_metrics"` |
| 133 | + } |
| 134 | + if err := yaml.Unmarshal(inst, &raw); err != nil { |
| 135 | + return nil |
| 136 | + } |
| 137 | + var targets []string |
| 138 | + for _, entry := range slices.Concat(raw.Metrics, raw.ExtraMetrics) { |
| 139 | + m, ok := entry.(map[interface{}]interface{}) |
| 140 | + if !ok { |
| 141 | + continue // plain string (or any other scalar): pass-through, no rename |
| 142 | + } |
| 143 | + for _, value := range m { |
| 144 | + switch v := value.(type) { |
| 145 | + case string: |
| 146 | + targets = append(targets, v) |
| 147 | + case map[interface{}]interface{}: |
| 148 | + if name, ok := v["name"].(string); ok { |
| 149 | + targets = append(targets, name) |
| 150 | + } |
| 151 | + } |
90 | 152 | } |
91 | 153 | } |
92 | | - return namespaces |
| 154 | + return targets |
93 | 155 | } |
94 | 156 |
|
95 | 157 | // filterTemplatesDiscovery drops configuration-discovery templates that are |
@@ -122,8 +184,8 @@ func filterTemplatesDiscovery(staticIdx *StaticConfigIndex, configs map[string]i |
122 | 184 | } |
123 | 185 | nonDiscoveryNames[cfg.Name] = struct{}{} |
124 | 186 | if IsGenericIntegrationCheckName(cfg.Name) { |
125 | | - for _, ns := range InstanceNamespaces(cfg) { |
126 | | - siblingGenericNamespaceRoots[NamespaceRoot(ns)] = struct{}{} |
| 187 | + for _, root := range GenericIntegrationNamespaceRoots(cfg) { |
| 188 | + siblingGenericNamespaceRoots[root] = struct{}{} |
127 | 189 | } |
128 | 190 | } |
129 | 191 | } |
|
0 commit comments