Skip to content

Commit 39271fe

Browse files
committed
update temporal integration generate_metadata.py script
1 parent 3b64930 commit 39271fe

File tree

1 file changed

+144
-14
lines changed

1 file changed

+144
-14
lines changed

temporal/scripts/generate_metadata.py

Lines changed: 144 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,66 @@
55
# Script to update metadata.csv based on temporal's code and current
66
# `METRIC_MAP` as defined on metrics.py
77
# 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
1010

1111
import csv
1212
import re
1313
import sys
14+
import requests
15+
import argparse
16+
from urllib.parse import urljoin
1417

1518
from datadog_checks.temporal.metrics import METRIC_MAP
1619

17-
temporal_metric_matcher = re.compile(r'New(?P<type>\w+)Def\("(?P<name>\w+)"\)')
1820

1921

2022
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+
2150
# First, read the existing metadata.csv to keep existing metadata from metrics for later
2251
with open('metadata.csv', newline='') as metadata_file:
2352
reader = csv.DictReader(metadata_file)
2453
metadata_fields = reader.fieldnames
2554
previous_metadata = {row['metric_name']: row for row in reader}
2655

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)
3168

3269
# Sanity check: Check whether there are metrics in the temporal code that are not present
3370
# in the `METRIC_MAP` and warn about them:
@@ -53,13 +90,30 @@ def append_metric_metadata(metric_name, metric_type='count', unit_name=None):
5390
metric_meta['unit_name'] = unit_name
5491
metadata.append(metric_meta)
5592

93+
# Build the metadata for the metrics that both lives in temporal's code and in the METRIC_MAP
5694
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+
57105
try:
58106
temporal_type = temporal_metric_types[temporal_name]
59107
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")
61114
continue
62115

116+
# Update the metrics name based on the temporal type
63117
if temporal_type == 'counter':
64118
append_metric_metadata(f'{name}.count')
65119
elif temporal_type == 'gauge':
@@ -84,12 +138,88 @@ def append_metric_metadata(metric_name, metric_type='count', unit_name=None):
84138
writer.writeheader()
85139
writer.writerows(metadata)
86140

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
93223

94224
if __name__ == '__main__':
95225
main()

0 commit comments

Comments
 (0)