@@ -60,58 +60,65 @@ def compute_derivative_rows(self, rows, metrics, key, execution_indicators=None)
6060 'Some statement metrics are not available from the table: %s' , ',' .join (m for m in dropped_metrics )
6161 )
6262
63+ # All rows within a call share the same schema, so metric_columns only
64+ # needs to be computed once from the first (row, prev) pair.
65+ metric_columns = None
66+ indicator_cols = None
67+
6368 for row_key , row in merged_rows .items ():
6469 prev = self ._previous_statements .get (row_key )
6570 if prev is None :
6671 continue
6772
68- metric_columns = metrics & row .keys () & prev .keys ()
69-
70- # Take the diff of all metric values between the current row and the previous run's row.
71- # There are a couple of edge cases to be aware of:
72- #
73- # 1. Table truncation or stats reset: Because the table values are always increasing, a negative value
74- # suggests truncation or a stats reset. In this case, the row difference is discarded and the row should.
75- # be tracked from this run forward.
76- #
77- # 2. No changes since the previous run: There is no need to store metrics of 0, since that is implied by
78- # the absence of metrics. On any given check run, most rows will have no difference so this optimization
79- # avoids having to send a lot of unnecessary metrics.
80- #
81- # 3. Execution indicators: If execution_indicators is specified, only consider a query as changed if at
82- # least one of the execution indicator metrics has changed. This helps filter out cases where an old or
83- # less frequently executed normalized query was evicted due to the stats table being full, and then
84- # re-inserted to the stats table with a small call count and slight duration change. In this case,
85- # the new normalized query entry should be treated as the baseline for future diffs.
86-
87- diffed_row = {k : row [k ] - prev [k ] if k in metric_columns else row [k ] for k in row .keys ()}
88-
89- # Check for negative values, but only in the columns used for metrics
90- if any (diffed_row [k ] < 0 for k in metric_columns ):
91- # A "break" might be expected here instead of "continue," but there are cases where a subset of rows
92- # are removed. To avoid situations where all results are discarded every check run, we err on the side
93- # of potentially including truncated rows that exceed previous run counts.
73+ if metric_columns is None :
74+ metric_columns = metrics & row .keys () & prev .keys ()
75+ if execution_indicators :
76+ indicator_cols = execution_indicators & metric_columns
77+
78+ # Check diffs before allocating an output dict: skip rows with
79+ # negative diffs (stats reset), zero change, or no execution indicator change.
80+ has_negative = False
81+ has_change = False
82+ for k in metric_columns :
83+ diff = row [k ] - prev [k ]
84+ if diff < 0 :
85+ has_negative = True
86+ break
87+ if diff != 0 :
88+ has_change = True
89+
90+ if has_negative or not has_change :
9491 continue
9592
96- # If execution_indicators is specified, check if any of the execution indicator metrics have changed
97- if execution_indicators :
98- indicator_columns = execution_indicators & metric_columns
99- if not any (diffed_row [k ] > 0 for k in indicator_columns ):
93+ if execution_indicators and indicator_cols :
94+ has_indicator_change = False
95+ for k in indicator_cols :
96+ if row [k ] - prev [k ] > 0 :
97+ has_indicator_change = True
98+ break
99+ if not has_indicator_change :
100100 continue
101101
102- # No changes to the query; no metric needed
103- if all (diffed_row [k ] == 0 for k in metric_columns ):
104- continue
102+ result .append ({k : row [k ] - prev [k ] if k in metric_columns else row [k ] for k in row })
105103
106- result .append (diffed_row )
104+ # Update cache in-place: remove stale keys, update existing entries,
105+ # and only allocate new dicts for rows seen for the first time.
106+ new_keys = merged_rows .keys ()
107+ stale_keys = self ._previous_statements .keys () - new_keys
108+ for k in stale_keys :
109+ del self ._previous_statements [k ]
107110
108- # Only cache the metric columns needed for derivative computation.
109- # Non-metric columns (query text, metadata, etc.) are not needed for the diff calculation
110- # and would waste memory. The returned diffed_row already uses current row values for
111- # non-metric columns.
112- self ._previous_statements = {
113- row_key : {col : row [col ] for col in metrics if col in row } for row_key , row in merged_rows .items ()
114- }
111+ for row_key , row in merged_rows .items ():
112+ prev = self ._previous_statements .get (row_key )
113+ if prev is not None :
114+ # Sync columns to handle schema changes between calls.
115+ for col in metrics :
116+ if col in row :
117+ prev [col ] = row [col ]
118+ elif col in prev :
119+ del prev [col ]
120+ else :
121+ self ._previous_statements [row_key ] = {col : row [col ] for col in metrics if col in row }
115122
116123 return result
117124
0 commit comments