Skip to content

Commit 1b01be5

Browse files
Fix object name field in rpc query_completion events and activity_id fields for error events (#20302)
* include object_name, not procedure name for rpc events * add changelog * fix changelog * object id and spills also not real fields * Include duration and query start for attention events * activity id and activity id xfer * add activity_id_xfer to base string fields * changelogs * Update 20302.fixed
1 parent ae43e8d commit 1b01be5

File tree

7 files changed

+205
-23
lines changed

7 files changed

+205
-23
lines changed

sqlserver/changelog.d/20302.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add object_name to rpc_completed XE events, add activity ID and activity ID Xfer to error events

sqlserver/datadog_checks/sqlserver/xe_collection/base.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ class XESessionBase(DBMAsyncJob):
9595
"client_app_name",
9696
"username",
9797
"activity_id",
98+
"activity_id_xfer",
9899
]
99100

100101
BASE_SQL_FIELDS = [
@@ -749,23 +750,21 @@ def _create_rqt_event(self, event, raw_sql_fields, query_details):
749750
"primary_sql_field": primary_field,
750751
}
751752

752-
# Only include duration and query_start for non-error events
753-
is_error_event = self.session_name == "datadog_query_errors"
754-
if not is_error_event:
753+
# Only exclude duration and query_start for error_reported events, not attention events
754+
is_error_reported = event.get("event_name") == "error_reported"
755+
if not is_error_reported:
755756
sqlserver_fields.update(
756757
{
757758
"duration_ms": event.get("duration_ms"),
758759
"query_start": query_details.get("query_start"),
759760
}
760761
)
761-
else:
762-
# Include error_number and message for error events
763-
sqlserver_fields.update(
764-
{
765-
"error_number": event.get("error_number"),
766-
"message": event.get("message"),
767-
}
768-
)
762+
763+
# Include error_number and message if they're present in the event
764+
if event.get("error_number") is not None:
765+
sqlserver_fields["error_number"] = event.get("error_number")
766+
if event.get("message"):
767+
sqlserver_fields["message"] = event.get("message")
769768

770769
# Add additional SQL fields to the sqlserver section
771770
# but only if they're not the primary field and not empty

sqlserver/datadog_checks/sqlserver/xe_collection/error_events.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,14 @@ def _process_error_reported_event(self, event, event_data):
9393
# Extract action elements
9494
for action in event.findall('./action'):
9595
action_name = action.get('name')
96-
if action_name:
96+
if not action_name:
97+
continue
98+
99+
if action_name == 'attach_activity_id':
100+
event_data['activity_id'] = extract_value(action)
101+
elif action_name == 'attach_activity_id_xfer':
102+
event_data['activity_id_xfer'] = extract_value(action)
103+
else:
97104
event_data[action_name] = extract_value(action)
98105

99106
return True
@@ -122,7 +129,11 @@ def _process_attention_event(self, event, event_data):
122129
if not action_name:
123130
continue
124131

125-
if action_name == 'session_id' or action_name == 'request_id':
132+
if action_name == 'attach_activity_id':
133+
event_data['activity_id'] = extract_value(action)
134+
elif action_name == 'attach_activity_id_xfer':
135+
event_data['activity_id_xfer'] = extract_value(action)
136+
elif action_name == 'session_id' or action_name == 'request_id':
126137
# These are numeric values in the actions
127138
value = extract_int_value(action)
128139
if value is not None:
@@ -137,11 +148,12 @@ def _normalize_event_impl(self, event):
137148
# First use the base normalization with type-specific fields
138149
normalized = self._normalize_event(event)
139150

140-
# For error events, remove query_start and duration_ms fields since they're not applicable
141-
if 'query_start' in normalized:
142-
del normalized['query_start']
143-
if 'duration_ms' in normalized:
144-
del normalized['duration_ms']
151+
# For error_reported events only, remove query_start and duration_ms fields since they're not applicable
152+
if normalized.get('xe_type') == 'error_reported':
153+
if 'query_start' in normalized:
154+
del normalized['query_start']
155+
if 'duration_ms' in normalized:
156+
del normalized['duration_ms']
145157

146158
return normalized
147159

sqlserver/datadog_checks/sqlserver/xe_collection/query_completion_events.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,12 @@ class QueryCompletionEventsHandler(XESessionBase):
4242
"physical_reads": 0,
4343
"logical_reads": 0,
4444
"writes": 0,
45-
"spills": 0,
4645
"row_count": 0,
47-
"object_id": 0,
48-
"line_number": 0,
4946
}
5047

5148
RPC_SPECIFIC_STRING_FIELDS = [
5249
"result",
53-
"procedure_name",
50+
"object_name",
5451
"data_stream",
5552
"connection_reset_option",
5653
]
@@ -146,9 +143,10 @@ def _process_action_elements(self, event, event_data):
146143
for action in event.findall('./action'):
147144
action_name = action.get('name')
148145
if action_name:
149-
# Add activity_id support
150146
if action_name == 'attach_activity_id':
151147
event_data['activity_id'] = extract_value(action)
148+
elif action_name == 'attach_activity_id_xfer':
149+
event_data['activity_id_xfer'] = extract_value(action)
152150
else:
153151
event_data[action_name] = extract_value(action)
154152

sqlserver/tests/test_xe_collection.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ def rpc_completed_expected_values():
190190
'client_hostname': 'EC2AMAZ-ML3E0PH',
191191
'client_app_name': 'SQLAgent - Job Manager',
192192
'username': 'NT AUTHORITY\\NETWORK SERVICE',
193+
'object_name': 'sp_executesql',
193194
}
194195

195196

@@ -208,6 +209,8 @@ def error_expected_values():
208209
'client_app_name': 'go-mssqldb',
209210
'username': 'shopper_4',
210211
'message': "'REPEAT' is not a recognized built-in function name.",
212+
'activity_id': 'F961B15C-752A-487E-AC4F-F2A9BAB11DB7-1',
213+
'activity_id_xfer': 'AFCCDE6F-EACD-47F3-9B62-CC02D517191B-0',
211214
}
212215

213216

@@ -253,6 +256,8 @@ def attention_expected_values():
253256
'client_hostname': 'COMP-MX2YQD7P2P',
254257
'client_app_name': 'azdata',
255258
'username': 'datadog',
259+
'activity_id': 'F961B15C-752A-487E-AC4F-F2A9BAB11DB7-1',
260+
'activity_id_xfer': 'AFCCDE6F-EACD-47F3-9B62-CC02D517191B-0',
256261
}
257262

258263

@@ -621,6 +626,8 @@ def test_process_events_rpc_completed(
621626
assert 'sp_executesql' in event['statement']
622627
assert 'sql_text' in event
623628
assert 'EXECUTE [msdb].[dbo].[sp_agent_log_job_history]' in event['sql_text']
629+
assert 'object_name' in event
630+
assert event['object_name'] == 'sp_executesql'
624631

625632
def test_process_events_error_reported(self, error_events_handler, sample_error_event_xml, error_expected_values):
626633
"""Test processing of error reported events"""
@@ -824,6 +831,34 @@ def test_normalize_error_event(self, error_events_handler):
824831
assert 'duration_ms' not in normalized
825832
assert 'query_start' not in normalized
826833

834+
def test_normalize_attention_event(self, error_events_handler):
835+
"""Test attention event normalization"""
836+
# Test attention event with fields
837+
event = {
838+
'event_name': 'attention',
839+
'timestamp': '2023-01-01T12:00:00.123Z',
840+
'duration_ms': 328.677,
841+
'session_id': 123,
842+
'request_id': 456,
843+
'database_name': 'TestDB',
844+
'sql_text': 'SELECT * FROM Customers WHERE CustomerId = 123',
845+
}
846+
847+
normalized = error_events_handler._normalize_event_impl(event)
848+
849+
# Verify normalized fields
850+
assert normalized['xe_type'] == 'attention'
851+
assert normalized['event_fire_timestamp'] == '2023-01-01T12:00:00.123Z'
852+
assert normalized['session_id'] == 123
853+
assert normalized['request_id'] == 456
854+
assert normalized['database_name'] == 'TestDB'
855+
assert normalized['sql_text'] == 'SELECT * FROM Customers WHERE CustomerId = 123'
856+
857+
# Verify duration_ms and query_start are preserved for attention events
858+
assert 'duration_ms' in normalized
859+
assert normalized['duration_ms'] == 328.677
860+
assert 'query_start' in normalized # Query start should be calculated from timestamp and duration
861+
827862
@patch('datadog_checks.sqlserver.xe_collection.base.datadog_agent')
828863
def test_create_event_payload(self, mock_agent, query_completion_handler):
829864
"""Test creation of event payload"""
@@ -926,6 +961,62 @@ def test_create_rqt_event(self, mock_agent, query_completion_handler):
926961
assert rqt_event['sqlserver']['query_start'] == '2023-01-01T11:59:50.123Z'
927962
assert rqt_event['sqlserver']['primary_sql_field'] == 'batch_text'
928963

964+
@patch('datadog_checks.sqlserver.xe_collection.base.datadog_agent')
965+
def test_create_rqt_event_attention(self, mock_agent, error_events_handler):
966+
"""Test creation of Raw Query Text event for attention event"""
967+
mock_agent.get_version.return_value = '7.30.0'
968+
969+
# Create attention event with SQL fields - from the error_events_handler
970+
event = {
971+
'event_name': 'attention',
972+
'timestamp': '2023-01-01T12:00:00.123Z',
973+
'duration_ms': 328.677,
974+
'session_id': 123,
975+
'database_name': 'TestDB',
976+
'sql_text': 'SELECT * FROM Customers WHERE CustomerId = ?',
977+
'query_signature': 'abc123',
978+
'primary_sql_field': 'sql_text',
979+
'dd_tables': ['Customers'],
980+
'dd_commands': ['SELECT'],
981+
}
982+
983+
# Create raw SQL fields
984+
raw_sql_fields = {
985+
'sql_text': 'SELECT * FROM Customers WHERE CustomerId = 123',
986+
'raw_query_signature': 'def456',
987+
}
988+
989+
# Query details with formatted timestamps
990+
query_details = {
991+
'event_fire_timestamp': '2023-01-01T12:00:00.123Z',
992+
'query_start': '2023-01-01T11:59:59.795Z', # 328.677ms before timestamp
993+
'duration_ms': 328.677,
994+
}
995+
996+
# Create RQT event
997+
rqt_event = error_events_handler._create_rqt_event(event, raw_sql_fields, query_details)
998+
999+
# Validate common payload fields
1000+
validate_common_payload_fields(rqt_event, expected_source='datadog_query_errors', expected_type='rqt')
1001+
1002+
# Verify DB fields
1003+
assert rqt_event['db']['instance'] == 'TestDB'
1004+
assert rqt_event['db']['query_signature'] == 'abc123'
1005+
assert rqt_event['db']['raw_query_signature'] == 'def456'
1006+
assert rqt_event['db']['statement'] == 'SELECT * FROM Customers WHERE CustomerId = 123'
1007+
1008+
# Verify sqlserver fields
1009+
assert rqt_event['sqlserver']['session_id'] == 123
1010+
assert rqt_event['sqlserver']['xe_type'] == 'attention'
1011+
assert rqt_event['sqlserver']['event_fire_timestamp'] == '2023-01-01T12:00:00.123Z'
1012+
1013+
# Key check: verify that duration_ms and query_start are present for attention events
1014+
# even though they come from the error_events_handler
1015+
assert 'duration_ms' in rqt_event['sqlserver']
1016+
assert rqt_event['sqlserver']['duration_ms'] == 328.677
1017+
assert 'query_start' in rqt_event['sqlserver']
1018+
assert rqt_event['sqlserver']['query_start'] == '2023-01-01T11:59:59.795Z'
1019+
9291020
def test_create_rqt_event_disabled(self, mock_check, mock_config):
9301021
"""Test RQT event creation when disabled"""
9311022
# Disable raw query collection
@@ -972,6 +1063,63 @@ def test_create_rqt_event_missing_signature(self, query_completion_handler):
9721063
# Should return None when missing signature
9731064
assert query_completion_handler._create_rqt_event(event, raw_sql_fields, query_details) is None
9741065

1066+
@patch('datadog_checks.sqlserver.xe_collection.base.datadog_agent')
1067+
def test_create_rqt_event_error_reported(self, mock_agent, error_events_handler):
1068+
"""Test creation of Raw Query Text event for error_reported event"""
1069+
mock_agent.get_version.return_value = '7.30.0'
1070+
1071+
# Create error_reported event with SQL fields
1072+
event = {
1073+
'event_name': 'error_reported',
1074+
'timestamp': '2023-01-01T12:00:00.123Z',
1075+
'error_number': 8134,
1076+
'severity': 15,
1077+
'session_id': 123,
1078+
'database_name': 'TestDB',
1079+
'sql_text': 'SELECT 1/0',
1080+
'message': 'Division by zero error',
1081+
'query_signature': 'abc123',
1082+
'primary_sql_field': 'sql_text',
1083+
}
1084+
1085+
# Create raw SQL fields
1086+
raw_sql_fields = {
1087+
'sql_text': 'SELECT 1/0',
1088+
'raw_query_signature': 'def456',
1089+
}
1090+
1091+
# Query details would not have duration_ms or query_start for error_reported events
1092+
query_details = {
1093+
'event_fire_timestamp': '2023-01-01T12:00:00.123Z',
1094+
}
1095+
1096+
# Create RQT event
1097+
rqt_event = error_events_handler._create_rqt_event(event, raw_sql_fields, query_details)
1098+
1099+
# Validate common payload fields
1100+
validate_common_payload_fields(rqt_event, expected_source='datadog_query_errors', expected_type='rqt')
1101+
1102+
# Verify DB fields
1103+
assert rqt_event['db']['instance'] == 'TestDB'
1104+
assert rqt_event['db']['query_signature'] == 'abc123'
1105+
assert rqt_event['db']['raw_query_signature'] == 'def456'
1106+
assert rqt_event['db']['statement'] == 'SELECT 1/0'
1107+
1108+
# Verify sqlserver fields
1109+
assert rqt_event['sqlserver']['session_id'] == 123
1110+
assert rqt_event['sqlserver']['xe_type'] == 'error_reported'
1111+
assert rqt_event['sqlserver']['event_fire_timestamp'] == '2023-01-01T12:00:00.123Z'
1112+
1113+
# Key check: verify that error_number and message are included for error_reported events
1114+
assert 'error_number' in rqt_event['sqlserver']
1115+
assert rqt_event['sqlserver']['error_number'] == 8134
1116+
assert 'message' in rqt_event['sqlserver']
1117+
assert rqt_event['sqlserver']['message'] == 'Division by zero error'
1118+
1119+
# Verify that duration_ms and query_start are NOT present for error_reported events
1120+
assert 'duration_ms' not in rqt_event['sqlserver']
1121+
assert 'query_start' not in rqt_event['sqlserver']
1122+
9751123

9761124
@pytest.mark.integration
9771125
@pytest.mark.usefixtures('dd_environment')

sqlserver/tests/xml_xe_events/attention.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,16 @@
9090
ORDER BY event_timestamp;
9191
</value>
9292
</action>
93+
<action name="attach_activity_id_xfer" package="package0">
94+
<type name="activity_id_xfer" package="package0" />
95+
<value>
96+
AFCCDE6F-EACD-47F3-9B62-CC02D517191B-0
97+
</value>
98+
</action>
99+
<action name="attach_activity_id" package="package0">
100+
<type name="activity_id" package="package0" />
101+
<value>
102+
F961B15C-752A-487E-AC4F-F2A9BAB11DB7-1
103+
</value>
104+
</action>
93105
</event>

sqlserver/tests/xml_xe_events/error_reported.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,16 @@
9595
/*dddbs='orders-app',ddps='orders-app',ddh='awbergs-sqlserver2019-test.c7ug0vvtkhqv.us-east-1.rds.amazonaws.com',dddb='dbmorders',ddprs='orders-sqlserver'*/ SELECT discount_percent, store_name, description, discount_in_currency, dbm_item_id, REPEAT('a', 1000) from discount where id BETWEEN 6117 AND 6127 GROUP by dbm_item_id, store_name, description, discount_in_currency, discount_percent /* date='12%2F31',key='val' */
9696
</value>
9797
</action>
98+
<action name="attach_activity_id_xfer" package="package0">
99+
<type name="activity_id_xfer" package="package0" />
100+
<value>
101+
AFCCDE6F-EACD-47F3-9B62-CC02D517191B-0
102+
</value>
103+
</action>
104+
<action name="attach_activity_id" package="package0">
105+
<type name="activity_id" package="package0" />
106+
<value>
107+
F961B15C-752A-487E-AC4F-F2A9BAB11DB7-1
108+
</value>
109+
</action>
98110
</event>

0 commit comments

Comments
 (0)