5
5
# Script to update metadata.csv based on temporal's code and current
6
6
# `METRIC_MAP` as defined on metrics.py
7
7
# Must be run in an environment that has the integration installed,
8
- # and passed the go code from temporal's codebase where metrics are defined via stdin, e.g.:
9
- # cat ${path_to_temporal}/common/metrics/metric_defs.go | hatch run py3.8-1.19:python ./scripts/generate_metadata.py
8
+ # and passed the tag of the temporal version from stdin, e.g.:
9
+ # hatch run py3.8-1.19:python ./scripts/generate_metadata.py --tag=v1.19.0
10
10
11
11
import csv
12
12
import re
13
13
import sys
14
+ import requests
15
+ import argparse
16
+ from urllib .parse import urljoin
14
17
15
18
from datadog_checks .temporal .metrics import METRIC_MAP
16
19
17
- temporal_metric_matcher = re .compile (r'New(?P<type>\w+)Def\("(?P<name>\w+)"\)' )
18
20
19
21
20
22
def main ():
23
+ """
24
+ Generates and updates metadata.csv for the Temporal integration by:
25
+ 1. Reading existing metadata from metadata.csv
26
+ 2. Fetching and processing Temporal's metric definitions from the repository URL (e.g., https://raw.githubusercontent.com/temporalio/temporal/refs/tags/v1.19.0/common/metrics/metric_defs.go)
27
+ 3. Merging the data to create updated metadata
28
+ 4. Writing the results to metadata.csv
29
+
30
+ Metric Converting Rule:
31
+ - Counters: Adds .count suffix (e.g., service.pending_requests.count)
32
+ - Gauges: Preserves original name with gauge type
33
+ - Histograms: Creates three metrics per histogram:
34
+ * .bucket: For histogram buckets
35
+ * .count: For total count
36
+ * .sum: For sum of values (with appropriate units)
37
+ - Timers: Similar to histograms but with millisecond units
38
+ - Native Dynamic Metrics: Added manually & preserved if already present in existing metadata
39
+
40
+ Metadata Preservation:
41
+ - Maintains existing metadata for metrics that were present in previous versions
42
+ - Preserves custom configurations (units, descriptions, etc.) from existing metadata
43
+ - Handles metrics that might have been dropped in newer Temporal versions
44
+ - Ensures backward compatibility while incorporating new metric definitions
45
+ """
46
+ parser = argparse .ArgumentParser (description = 'Generate metadata.csv for Temporal integration' )
47
+ parser .add_argument ('--tag' , required = True , help = 'Temporal version tag (e.g., v1.19.0)' )
48
+ args = parser .parse_args ()
49
+
21
50
# First, read the existing metadata.csv to keep existing metadata from metrics for later
22
51
with open ('metadata.csv' , newline = '' ) as metadata_file :
23
52
reader = csv .DictReader (metadata_file )
24
53
metadata_fields = reader .fieldnames
25
54
previous_metadata = {row ['metric_name' ]: row for row in reader }
26
55
27
- # Then, read metrics from temporal's source code, fed through stdin
28
- # This file lives in /common/metrics/metric_defs.go inside temporal's repo
29
- parsed_metrics = (parse_temporal_metric (line ) for line in sys .stdin .readlines ())
30
- temporal_metric_types = {metric ['name' ]: metric ['type' ] for metric in parsed_metrics if metric }
56
+ # Fetch metrics from temporal's source code
57
+ try :
58
+ temporal_metrics = fetch_temporal_metrics (args .tag )
59
+ except requests .RequestException as e :
60
+ print (f"Error fetching metrics from Temporal repository: { e } " )
61
+ sys .exit (1 )
62
+ except ValueError as e :
63
+ print (f"Error: { e } " )
64
+ sys .exit (1 )
65
+
66
+ # Extract metric definitions from the fetched code
67
+ temporal_metric_types = extract_metric_defs (temporal_metrics )
31
68
32
69
# Sanity check: Check whether there are metrics in the temporal code that are not present
33
70
# in the `METRIC_MAP` and warn about them:
@@ -53,13 +90,30 @@ def append_metric_metadata(metric_name, metric_type='count', unit_name=None):
53
90
metric_meta ['unit_name' ] = unit_name
54
91
metadata .append (metric_meta )
55
92
93
+ # Build the metadata for the metrics that both lives in temporal's code and in the METRIC_MAP
56
94
for temporal_name , name in METRIC_MAP .items ():
95
+ if isinstance (name , dict ) and name .get ('type' ) == 'native_dynamic' :
96
+ # Native dynamic metrics have its type defined at run time, and usually added manually, see https://github.com/DataDog/integrations-core/pull/18050
97
+ existing , exist = check_existing_metric (name .get ('name' ), previous_metadata )
98
+ if exist :
99
+ print (f"INFO: dynamic metric `{ name } ` is reserved because it's present in the current metadata.csv file" )
100
+ metadata .extend (existing )
101
+ else :
102
+ print (f"WARNING: skipping metric `{ name } ` because native dynamic type and is not present in the current metadata.csv file" )
103
+ continue
104
+
57
105
try :
58
106
temporal_type = temporal_metric_types [temporal_name ]
59
107
except KeyError :
60
- print (f"WARNING: skipping metric `{ temporal_name } /{ name } ` as it's not present in input data" )
108
+ # If metrics does not exist in this Temporal version, try to search metric in the current metadata file and preserve it if it's already exist
109
+ existing , exist = check_existing_metric (name , previous_metadata )
110
+ if exist :
111
+ metadata .extend (existing )
112
+ else :
113
+ print (f"WARNING: skipping metric `{ temporal_name } /{ name } ` because it's not present in both temporal metric definitions and the current metatada.csv file" )
61
114
continue
62
115
116
+ # Update the metrics name based on the temporal type
63
117
if temporal_type == 'counter' :
64
118
append_metric_metadata (f'{ name } .count' )
65
119
elif temporal_type == 'gauge' :
@@ -84,12 +138,88 @@ def append_metric_metadata(metric_name, metric_type='count', unit_name=None):
84
138
writer .writeheader ()
85
139
writer .writerows (metadata )
86
140
87
-
88
- def parse_temporal_metric (line ):
89
- match = temporal_metric_matcher .search (line )
90
- if match :
91
- return {k : v .lower () for k , v in match .groupdict ().items ()}
92
-
141
+ def extract_metric_defs (go_code : str ) -> dict :
142
+ """
143
+ Extract metric definitions from Go code that are function calls starting with 'New' and containing 'Def'.
144
+
145
+ Args:
146
+ go_code (str): The Go source code content
147
+
148
+ Returns:
149
+ dict: Dictionary with metric name as key and type as value
150
+ """
151
+ results = {}
152
+
153
+ # Regular expression to match variable declarations with New*Def function calls
154
+ # This pattern looks for:
155
+ # 1. Variable name
156
+ # 2. = New*Def(
157
+ # 3. Metric name in quotes
158
+ pattern = r'(\w+)\s*=\s*(New\w*Def)\s*\(\s*"([^"]+)"'
159
+
160
+ # Find all matches in the code
161
+ matches = re .finditer (pattern , go_code )
162
+
163
+ for match in matches :
164
+ func_name = match .group (2 )
165
+ metric_name = match .group (3 )
166
+
167
+ # Extract type from function name (everything between New and Def)
168
+ type_name = func_name [3 :- 3 ] # Remove "New" prefix and "Def" suffix
169
+
170
+ results [metric_name .lower ()] = type_name .lower ()
171
+
172
+ return results
173
+
174
+ added_keys = set ()
175
+
176
+ def check_existing_metric (name : str , previous_metadata : dict ) -> tuple [list , bool ]:
177
+ """
178
+ Check if a metric exists in the previous metadata and add it to the current metadata if found.
179
+
180
+ Args:
181
+ name: The name of the metric to check, example of a metric name: service.pending_requests
182
+ previous_metadata: Dictionary containing the previous metadata
183
+
184
+ Returns:
185
+ tuple: (metadata list with any existing metrics added, boolean indicating if metric exists)
186
+ """
187
+ pattern = re .compile (rf"^temporal\.server\.{ re .escape (name )} (?:\.[a-z]+)*$" )
188
+ exist = False
189
+ result = []
190
+ for key in previous_metadata :
191
+ if pattern .match (key ) and key not in added_keys :
192
+ # A metric were supported in the previous temporal version, but dropped in the current temporal version
193
+ result .append (previous_metadata .get (key ))
194
+ print (f"INFO: { key } is reserved because it exists in the current metatadata.csv file" )
195
+ exist = True
196
+ added_keys .add (key )
197
+ return result , exist
198
+
199
+ def fetch_temporal_metrics (tag : str ) -> str :
200
+ """
201
+ Fetch the metrics definitions file from Temporal repository for a specific tag.
202
+
203
+ Args:
204
+ tag (str): The Temporal version tag (e.g., 'v1.19.0')
205
+
206
+ Returns:
207
+ str: The content of the metrics definitions file
208
+
209
+ Raises:
210
+ requests.RequestException: If the request fails
211
+ ValueError: If the tag format is invalid
212
+ """
213
+ if not tag .startswith ('v' ):
214
+ raise ValueError ("Tag must start with 'v' (e.g., 'v1.19.0')" )
215
+
216
+ base_url = "https://raw.githubusercontent.com/temporalio/temporal/refs/tags"
217
+ metrics_path = "common/metrics/metric_defs.go"
218
+ url = urljoin (f"{ base_url } /{ tag } /" , metrics_path )
219
+
220
+ response = requests .get (url )
221
+ response .raise_for_status ()
222
+ return response .text
93
223
94
224
if __name__ == '__main__' :
95
225
main ()
0 commit comments