[EL] DEBUG-4880 Align probe expression compilation with runtime types#7992
[EL] DEBUG-4880 Align probe expression compilation with runtime types#7992dudikeleti merged 2 commits intomasterfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
This comment has been minimized.
This comment has been minimized.
BenchmarksBenchmark execution time: 2026-01-06 11:32:04 Comparing candidate commit 4354a65 in PR branch Found 10 performance improvements and 7 performance regressions! Performance is the same for 158 metrics, 11 unstable metrics. scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleMoreComplexBody net6.0
scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody netcoreapp3.1
scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorSimpleBody netcoreapp3.1
scenario:Benchmarks.Trace.AspNetCoreBenchmark.SendRequest net6.0
scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces net6.0
scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces netcoreapp3.1
scenario:Benchmarks.Trace.DbCommandBenchmark.ExecuteNonQuery net472
scenario:Benchmarks.Trace.HttpClientBenchmark.SendAsync netcoreapp3.1
scenario:Benchmarks.Trace.ILoggerBenchmark.EnrichedLog net6.0
scenario:Benchmarks.Trace.ILoggerBenchmark.EnrichedLog netcoreapp3.1
scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatAspectBenchmark netcoreapp3.1
scenario:Benchmarks.Trace.Log4netBenchmark.EnrichedLog netcoreapp3.1
scenario:Benchmarks.Trace.SerilogBenchmark.EnrichedLog netcoreapp3.1
scenario:Benchmarks.Trace.SpanBenchmark.StartFinishScope netcoreapp3.1
scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan net6.0
|
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (7992) and master. ✅ No regressions detected - check the details below Full Metrics ComparisonFakeDbCommand
HttpMessageHandler
Comparison explanationExecution-time benchmarks measure the whole time it takes to execute a program, and are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are highlighted in **red**. The following thresholds were used for comparing the execution times:
Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard. Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph). Duration chartsFakeDbCommand (.NET Framework 4.8)gantt
title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (7992) - mean (69ms) : 67, 70
master - mean (68ms) : 67, 70
section Bailout
This PR (7992) - mean (72ms) : 71, 73
master - mean (72ms) : 71, 73
section CallTarget+Inlining+NGEN
This PR (7992) - mean (1,010ms) : 963, 1057
master - mean (1,008ms) : 968, 1048
FakeDbCommand (.NET Core 3.1)gantt
title Execution time (ms) FakeDbCommand (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (7992) - mean (106ms) : 103, 108
master - mean (106ms) : 104, 108
section Bailout
This PR (7992) - mean (107ms) : 106, 108
master - mean (107ms) : 106, 108
section CallTarget+Inlining+NGEN
This PR (7992) - mean (736ms) : 680, 791
master - mean (731ms) : 669, 793
FakeDbCommand (.NET 6)gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (7992) - mean (93ms) : 92, 95
master - mean (93ms) : 91, 96
section Bailout
This PR (7992) - mean (94ms) : 94, 95
master - mean (94ms) : 93, 95
section CallTarget+Inlining+NGEN
This PR (7992) - mean (712ms) : 672, 751
master - mean (713ms) : 682, 744
FakeDbCommand (.NET 8)gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (7992) - mean (92ms) : 90, 94
master - mean (92ms) : 90, 94
section Bailout
This PR (7992) - mean (93ms) : 92, 93
master - mean (93ms) : 92, 94
section CallTarget+Inlining+NGEN
This PR (7992) - mean (634ms) : 618, 649
master - mean (630ms) : 618, 643
HttpMessageHandler (.NET Framework 4.8)gantt
title Execution time (ms) HttpMessageHandler (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (7992) - mean (198ms) : 192, 204
master - mean (193ms) : 189, 198
section Bailout
This PR (7992) - mean (201ms) : 196, 206
master - mean (197ms) : 194, 199
section CallTarget+Inlining+NGEN
This PR (7992) - mean (1,127ms) : 1067, 1186
master - mean (1,112ms) : 1061, 1164
HttpMessageHandler (.NET Core 3.1)gantt
title Execution time (ms) HttpMessageHandler (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (7992) - mean (277ms) : 273, 281
master - mean (277ms) : 272, 282
section Bailout
This PR (7992) - mean (277ms) : 273, 281
master - mean (277ms) : 273, 282
section CallTarget+Inlining+NGEN
This PR (7992) - mean (918ms) : 863, 974
master - mean (918ms) : 871, 966
HttpMessageHandler (.NET 6)gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (7992) - mean (271ms) : 267, 275
master - mean (270ms) : 264, 275
section Bailout
This PR (7992) - mean (270ms) : 266, 273
master - mean (270ms) : 266, 274
section CallTarget+Inlining+NGEN
This PR (7992) - mean (925ms) : 885, 965
master - mean (926ms) : 889, 963
HttpMessageHandler (.NET 8)gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (7992) - mean (269ms) : 265, 274
master - mean (270ms) : 265, 275
section Bailout
This PR (7992) - mean (269ms) : 266, 273
master - mean (270ms) : 266, 273
section CallTarget+Inlining+NGEN
This PR (7992) - mean (824ms) : 803, 846
master - mean (829ms) : 799, 860
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
7274bda to
4354a65
Compare
## Summary of changes - Reworked `ProbeExpressionEvaluator` to cache compiled expressions **per runtime type combination** (invocation target, return value, and scope members), instead of a single `Lazy<>` instance shared across all invocations. - Introduced `ExpressionCacheKey` to uniquely identify a compilation context using **runtime types** (`Value.GetType()` when available) and member runtime types to support polymorphic calls. - Replaced single cached fields with `ConcurrentDictionary<ExpressionCacheKey, …>` caches for: - Templates (`CompiledExpression<string>[]?`) - Condition (`CompiledExpression<bool>?`) - Metric (`CompiledExpression<double>?`) - Span decorations (compiled decorations array) - Preserved evaluation semantics and error aggregation, while ensuring compiled delegates match the actual runtime types. ## Reason for change The previous implementation compiled expressions once (on first invocation) and reused them for all subsequent calls. This fails in polymorphic scenarios where: - The declared type differs from the runtime type (e.g., generics/base class/interface method invoked on a derived instance). - Parameters/locals are declared as a base type (or `object`) but carry different concrete runtime types across invocations. In these cases, delegates compiled against the first observed type can fail later (typically invalid casts / expression binding mismatches), causing evaluation errors and incorrect probe results. ## Implementation details - Compute a **runtime-aware cache key** on each `Evaluate()` call: - `thisType`: `InvocationTarget.Value?.GetType()` (fallback to declared type, then `typeof(object)`) - `returnType`: `Return.Value?.GetType()` (fallback to declared type) - `members`: runtime types captured from `ScopeMember.Value?.GetType()` (fallback to declared `ScopeMember.Type`) - Store compiled artifacts in thread-safe caches: - `ConcurrentDictionary<ExpressionCacheKey, ...>` - On cache miss: compile, then `TryAdd` - Added `ExpressionCacheKey` struct: - Precomputes hash over `thisType`, `returnType`, and member runtime types. - Implements equality by comparing `ThisType`, `ReturnType`, and the captured member type sequence (hash used as a fast-path). - Added debug logging for cache misses (type + hash + cache size) to help diagnose compilation churn and verify caching behavior. ## Test coverage - Existing `DebuggerExpressionLanguageTests` remain supported via `CompiledTemplates` / `CompiledCondition` accessors (now returning the first cached entry). - Snapshot Exploration Test ## Dependencies - Depends on: #7991, #7992, #7993 - Merge order: #7991 → #7992 → #7993 → (this PR) ## Other details - Caches may grow with many unique type combinations; however this is required for correctness under polymorphic workloads. If needed later, we can add eviction/size caps. - This PR is part of an effort to make the Snapshot Exploration Test run successfully end-to-end.
Summary of changes
Align probe expression compilation with runtime types for
thisand args/locals to prevent type-mismatch runtime failures and keep expression-cache behavior consistent.Reason for change
Expression compilation previously relied on declared types (
ScopeMember.Type) forthisand for args/locals. In practice, the expression cache key is based on runtime types, and values can differ from their declared types (e.g., declaredInt32but actual valueInt64, or declared base type but runtime derived type). This could lead to cached expressions being compiled with one set of types but executed with another, causing runtimeInvalidCastExceptionduring lambda execution.Implementation details
Value.GetType()(when available) and fall back to the declared type otherwise.Expression.Convertwhen readingScopeMember.Value.this:thisTypeonce at the entry point (ParseExpression) preferring runtime type (InvocationTarget.Value?.GetType()), then pass it down viathisTypeOverride.thistype is used consistently throughout parsing/compilation.Test coverage
Snapshot Exploration Test
Other details
This PR is part of an effort to make the Snapshot Exploration Test run successfully end-to-end.