[DSM] Initial transaction tracking implementation#7949
Conversation
This comment has been minimized.
This comment has been minimized.
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (7949) and master.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Metric | Master (Mean ± 95% CI) | Current (Mean ± 95% CI) | Change | Status |
|---|---|---|---|---|
| .NET Framework 4.8 - Baseline | ||||
| duration | 71.12 ± (71.14 - 71.48) ms | 77.50 ± (77.46 - 77.82) ms | +9.0% | ❌⬆️ |
| .NET Framework 4.8 - Bailout | ||||
| duration | 75.81 ± (75.66 - 76.02) ms | 81.86 ± (81.65 - 82.08) ms | +8.0% | ❌⬆️ |
| .NET Framework 4.8 - CallTarget+Inlining+NGEN | ||||
| duration | 1060.34 ± (1060.56 - 1066.04) ms | 1132.00 ± (1132.04 - 1138.14) ms | +6.8% | ❌⬆️ |
HttpMessageHandler
| Metric | Master (Mean ± 95% CI) | Current (Mean ± 95% CI) | Change | Status |
|---|---|---|---|---|
| .NET Framework 4.8 - Bailout | ||||
| duration | 194.44 ± (194.43 - 194.70) ms | 211.03 ± (211.12 - 212.11) ms | +8.5% | ❌⬆️ |
Full Metrics Comparison
FakeDbCommand
| Metric | Master (Mean ± 95% CI) | Current (Mean ± 95% CI) | Change | Status |
|---|---|---|---|---|
| .NET Framework 4.8 - Baseline | ||||
| duration | 71.12 ± (71.14 - 71.48) ms | 77.50 ± (77.46 - 77.82) ms | +9.0% | ❌⬆️ |
| .NET Framework 4.8 - Bailout | ||||
| duration | 75.81 ± (75.66 - 76.02) ms | 81.86 ± (81.65 - 82.08) ms | +8.0% | ❌⬆️ |
| .NET Framework 4.8 - CallTarget+Inlining+NGEN | ||||
| duration | 1060.34 ± (1060.56 - 1066.04) ms | 1132.00 ± (1132.04 - 1138.14) ms | +6.8% | ❌⬆️ |
| .NET Core 3.1 - Baseline | ||||
| process.internal_duration_ms | 22.06 ± (22.02 - 22.10) ms | 24.03 ± (23.97 - 24.10) ms | +8.9% | ✅⬆️ |
| process.time_to_main_ms | 83.08 ± (82.88 - 83.27) ms | 92.58 ± (92.34 - 92.82) ms | +11.4% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 10.90 ± (10.90 - 10.91) MB | 10.92 ± (10.92 - 10.92) MB | +0.2% | ✅⬆️ |
| runtime.dotnet.threads.count | 12 ± (12 - 12) | 12 ± (12 - 12) | +0.0% | ✅ |
| .NET Core 3.1 - Bailout | ||||
| process.internal_duration_ms | 22.09 ± (22.04 - 22.13) ms | 23.77 ± (23.70 - 23.84) ms | +7.6% | ✅⬆️ |
| process.time_to_main_ms | 84.51 ± (84.31 - 84.70) ms | 93.91 ± (93.69 - 94.14) ms | +11.1% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 10.94 ± (10.93 - 10.94) MB | 10.96 ± (10.95 - 10.96) MB | +0.2% | ✅⬆️ |
| runtime.dotnet.threads.count | 13 ± (13 - 13) | 13 ± (13 - 13) | +0.0% | ✅ |
| .NET Core 3.1 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 223.97 ± (222.65 - 225.29) ms | 242.06 ± (240.98 - 243.14) ms | +8.1% | ✅⬆️ |
| process.time_to_main_ms | 515.23 ± (514.03 - 516.42) ms | 562.92 ± (561.46 - 564.37) ms | +9.3% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 48.37 ± (48.34 - 48.40) MB | 48.43 ± (48.40 - 48.46) MB | +0.1% | ✅⬆️ |
| runtime.dotnet.threads.count | 28 ± (28 - 28) | 28 ± (28 - 28) | +0.0% | ✅⬆️ |
| .NET 6 - Baseline | ||||
| process.internal_duration_ms | 21.24 ± (21.14 - 21.33) ms | 22.45 ± (22.34 - 22.57) ms | +5.7% | ✅⬆️ |
| process.time_to_main_ms | 73.11 ± (72.66 - 73.55) ms | 79.92 ± (79.48 - 80.37) ms | +9.3% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 10.60 ± (10.59 - 10.60) MB | 10.64 ± (10.64 - 10.64) MB | +0.4% | ✅⬆️ |
| runtime.dotnet.threads.count | 10 ± (10 - 10) | 10 ± (10 - 10) | +0.0% | ✅ |
| .NET 6 - Bailout | ||||
| process.internal_duration_ms | 20.95 ± (20.90 - 20.99) ms | 22.09 ± (22.04 - 22.14) ms | +5.4% | ✅⬆️ |
| process.time_to_main_ms | 72.57 ± (72.42 - 72.71) ms | 80.09 ± (79.87 - 80.31) ms | +10.4% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 10.71 ± (10.70 - 10.71) MB | 10.75 ± (10.75 - 10.76) MB | +0.4% | ✅⬆️ |
| runtime.dotnet.threads.count | 11 ± (11 - 11) | 11 ± (11 - 11) | +0.0% | ✅ |
| .NET 6 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 389.45 ± (387.22 - 391.68) ms | 383.62 ± (381.62 - 385.61) ms | -1.5% | ✅ |
| process.time_to_main_ms | 516.46 ± (515.52 - 517.40) ms | 554.99 ± (553.77 - 556.22) ms | +7.5% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 49.97 ± (49.94 - 50.00) MB | 49.72 ± (49.68 - 49.75) MB | -0.5% | ✅ |
| runtime.dotnet.threads.count | 28 ± (28 - 28) | 28 ± (28 - 28) | +0.4% | ✅⬆️ |
| .NET 8 - Baseline | ||||
| process.internal_duration_ms | 19.07 ± (19.04 - 19.10) ms | 20.52 ± (20.47 - 20.57) ms | +7.6% | ✅⬆️ |
| process.time_to_main_ms | 70.93 ± (70.78 - 71.09) ms | 78.06 ± (77.88 - 78.24) ms | +10.0% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 7.68 ± (7.67 - 7.68) MB | 7.69 ± (7.68 - 7.70) MB | +0.2% | ✅⬆️ |
| runtime.dotnet.threads.count | 10 ± (10 - 10) | 10 ± (10 - 10) | +0.0% | ✅ |
| .NET 8 - Bailout | ||||
| process.internal_duration_ms | 19.18 ± (19.15 - 19.22) ms | 20.55 ± (20.49 - 20.60) ms | +7.1% | ✅⬆️ |
| process.time_to_main_ms | 72.54 ± (72.37 - 72.71) ms | 79.74 ± (79.54 - 79.94) ms | +9.9% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 7.71 ± (7.70 - 7.71) MB | 7.74 ± (7.73 - 7.74) MB | +0.4% | ✅⬆️ |
| runtime.dotnet.threads.count | 11 ± (11 - 11) | 11 ± (11 - 11) | +0.0% | ✅ |
| .NET 8 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 305.63 ± (303.62 - 307.64) ms | 306.78 ± (304.19 - 309.37) ms | +0.4% | ✅⬆️ |
| process.time_to_main_ms | 477.25 ± (476.43 - 478.06) ms | 520.24 ± (519.26 - 521.22) ms | +9.0% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 36.98 ± (36.95 - 37.00) MB | 37.17 ± (37.14 - 37.20) MB | +0.5% | ✅⬆️ |
| runtime.dotnet.threads.count | 27 ± (27 - 27) | 27 ± (27 - 27) | -1.0% | ✅ |
HttpMessageHandler
| Metric | Master (Mean ± 95% CI) | Current (Mean ± 95% CI) | Change | Status |
|---|---|---|---|---|
| .NET Framework 4.8 - Baseline | ||||
| duration | 192.50 ± (197.62 - 201.07) ms | 204.30 ± (201.26 - 203.45) ms | +6.1% | ✅⬆️ |
| .NET Framework 4.8 - Bailout | ||||
| duration | 194.44 ± (194.43 - 194.70) ms | 211.03 ± (211.12 - 212.11) ms | +8.5% | ❌⬆️ |
| .NET Framework 4.8 - CallTarget+Inlining+NGEN | ||||
| duration | 1136.42 ± (1136.91 - 1143.73) ms | 1207.55 ± (1200.15 - 1209.53) ms | +6.3% | ✅⬆️ |
| .NET Core 3.1 - Baseline | ||||
| process.internal_duration_ms | 185.05 ± (184.78 - 185.32) ms | 201.94 ± (201.47 - 202.40) ms | +9.1% | ✅⬆️ |
| process.time_to_main_ms | 79.69 ± (79.53 - 79.85) ms | 87.53 ± (87.24 - 87.83) ms | +9.8% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 3 ± (3 - 3) | 3 ± (3 - 3) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 16.24 ± (16.21 - 16.27) MB | 16.06 ± (16.05 - 16.08) MB | -1.1% | ✅ |
| runtime.dotnet.threads.count | 20 ± (19 - 20) | 20 ± (20 - 20) | +0.1% | ✅⬆️ |
| .NET Core 3.1 - Bailout | ||||
| process.internal_duration_ms | 184.69 ± (184.51 - 184.88) ms | 198.72 ± (197.79 - 199.66) ms | +7.6% | ✅⬆️ |
| process.time_to_main_ms | 81.00 ± (80.90 - 81.10) ms | 87.60 ± (87.11 - 88.08) ms | +8.1% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 3 ± (3 - 3) | 3 ± (3 - 3) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 15.98 ± (15.83 - 16.13) MB | 16.08 ± (16.06 - 16.10) MB | +0.6% | ✅⬆️ |
| runtime.dotnet.threads.count | 20 ± (20 - 20) | 21 ± (21 - 21) | +4.6% | ✅⬆️ |
| .NET Core 3.1 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 391.85 ± (390.62 - 393.09) ms | 410.06 ± (408.49 - 411.62) ms | +4.6% | ✅⬆️ |
| process.time_to_main_ms | 509.06 ± (507.91 - 510.21) ms | 538.48 ± (536.21 - 540.76) ms | +5.8% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 3 ± (3 - 3) | 3 ± (3 - 3) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 58.65 ± (58.44 - 58.86) MB | 59.32 ± (59.28 - 59.36) MB | +1.1% | ✅⬆️ |
| runtime.dotnet.threads.count | 30 ± (30 - 30) | 30 ± (30 - 30) | +0.1% | ✅⬆️ |
| .NET 6 - Baseline | ||||
| process.internal_duration_ms | 189.31 ± (189.08 - 189.54) ms | 200.74 ± (199.91 - 201.58) ms | +6.0% | ✅⬆️ |
| process.time_to_main_ms | 69.48 ± (69.34 - 69.62) ms | 73.15 ± (72.82 - 73.48) ms | +5.3% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 15.80 ± (15.62 - 15.98) MB | 16.29 ± (16.27 - 16.31) MB | +3.1% | ✅⬆️ |
| runtime.dotnet.threads.count | 18 ± (18 - 18) | 19 ± (19 - 19) | +7.7% | ✅⬆️ |
| .NET 6 - Bailout | ||||
| process.internal_duration_ms | 188.32 ± (188.12 - 188.53) ms | 200.95 ± (200.21 - 201.68) ms | +6.7% | ✅⬆️ |
| process.time_to_main_ms | 70.45 ± (70.38 - 70.52) ms | 74.40 ± (74.10 - 74.70) ms | +5.6% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 15.49 ± (15.32 - 15.66) MB | 16.35 ± (16.33 - 16.37) MB | +5.6% | ✅⬆️ |
| runtime.dotnet.threads.count | 18 ± (18 - 19) | 20 ± (20 - 20) | +9.6% | ✅⬆️ |
| .NET 6 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 595.27 ± (592.34 - 598.19) ms | 598.66 ± (596.38 - 600.94) ms | +0.6% | ✅⬆️ |
| process.time_to_main_ms | 504.66 ± (503.90 - 505.41) ms | 532.17 ± (530.32 - 534.03) ms | +5.5% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 61.53 ± (61.44 - 61.62) MB | 61.60 ± (61.51 - 61.69) MB | +0.1% | ✅⬆️ |
| runtime.dotnet.threads.count | 30 ± (30 - 30) | 30 ± (30 - 30) | +0.5% | ✅⬆️ |
| .NET 8 - Baseline | ||||
| process.internal_duration_ms | 187.32 ± (187.14 - 187.51) ms | 199.52 ± (198.81 - 200.23) ms | +6.5% | ✅⬆️ |
| process.time_to_main_ms | 68.93 ± (68.81 - 69.06) ms | 73.29 ± (73.00 - 73.57) ms | +6.3% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 11.71 ± (11.61 - 11.80) MB | 11.66 ± (11.65 - 11.68) MB | -0.4% | ✅ |
| runtime.dotnet.threads.count | 18 ± (17 - 18) | 19 ± (18 - 19) | +5.7% | ✅⬆️ |
| .NET 8 - Bailout | ||||
| process.internal_duration_ms | 186.36 ± (186.22 - 186.51) ms | 197.27 ± (196.64 - 197.90) ms | +5.9% | ✅⬆️ |
| process.time_to_main_ms | 69.84 ± (69.76 - 69.92) ms | 74.10 ± (73.84 - 74.36) ms | +6.1% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 11.75 ± (11.66 - 11.83) MB | 11.71 ± (11.70 - 11.73) MB | -0.3% | ✅ |
| runtime.dotnet.threads.count | 19 ± (18 - 19) | 19 ± (19 - 20) | +5.2% | ✅⬆️ |
| .NET 8 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 519.95 ± (517.52 - 522.37) ms | 514.20 ± (510.84 - 517.57) ms | -1.1% | ✅ |
| process.time_to_main_ms | 463.30 ± (462.67 - 463.93) ms | 496.18 ± (494.64 - 497.72) ms | +7.1% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 50.69 ± (50.66 - 50.71) MB | 50.62 ± (50.58 - 50.66) MB | -0.1% | ✅ |
| runtime.dotnet.threads.count | 30 ± (30 - 30) | 30 ± (30 - 30) | +0.2% | ✅⬆️ |
Comparison explanation
Execution-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:
- Welch test with statistical test for significance of 5%
- Only results indicating a difference greater than 5% and 5 ms are considered.
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 charts
FakeDbCommand (.NET Framework 4.8)
gantt
title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (7949) - mean (78ms) : 75, 80
master - mean (71ms) : 69, 74
section Bailout
This PR (7949) - mean (82ms) : crit, 79, 85
master - mean (76ms) : 73, 78
section CallTarget+Inlining+NGEN
This PR (7949) - mean (1,135ms) : crit, 1090, 1180
master - mean (1,063ms) : 1023, 1104
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 (7949) - mean (124ms) : 120, 128
master - mean (112ms) : 108, 116
section Bailout
This PR (7949) - mean (125ms) : crit, 122, 129
master - mean (113ms) : 109, 117
section CallTarget+Inlining+NGEN
This PR (7949) - mean (844ms) : crit, 813, 874
master - mean (776ms) : 754, 798
FakeDbCommand (.NET 6)
gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (7949) - mean (110ms) : 97, 122
master - mean (101ms) : 93, 109
section Bailout
This PR (7949) - mean (109ms) : crit, 106, 113
master - mean (100ms) : 97, 102
section CallTarget+Inlining+NGEN
This PR (7949) - mean (966ms) : 937, 994
master - mean (933ms) : 897, 969
FakeDbCommand (.NET 8)
gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (7949) - mean (107ms) : 103, 111
master - mean (98ms) : 95, 100
section Bailout
This PR (7949) - mean (108ms) : crit, 106, 111
master - mean (99ms) : 96, 102
section CallTarget+Inlining+NGEN
This PR (7949) - mean (858ms) : 819, 897
master - mean (813ms) : 777, 850
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 (7949) - mean (202ms) : 185, 219
master - mean (199ms) : 174, 225
section Bailout
This PR (7949) - mean (212ms) : crit, 205, 218
master - mean (195ms) : 193, 196
section CallTarget+Inlining+NGEN
This PR (7949) - mean (1,205ms) : 1137, 1273
master - mean (1,140ms) : 1092, 1189
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 (7949) - mean (299ms) : 287, 311
master - mean (273ms) : 269, 277
section Bailout
This PR (7949) - mean (295ms) : crit, 273, 317
master - mean (274ms) : 271, 277
section CallTarget+Inlining+NGEN
This PR (7949) - mean (984ms) : crit, 929, 1039
master - mean (928ms) : 907, 949
HttpMessageHandler (.NET 6)
gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (7949) - mean (282ms) : 263, 301
master - mean (267ms) : 263, 271
section Bailout
This PR (7949) - mean (284ms) : crit, 267, 300
master - mean (267ms) : 264, 269
section CallTarget+Inlining+NGEN
This PR (7949) - mean (1,162ms) : 1113, 1212
master - mean (1,131ms) : 1088, 1174
HttpMessageHandler (.NET 8)
gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (7949) - mean (283ms) : 266, 299
master - mean (266ms) : 262, 269
section Bailout
This PR (7949) - mean (282ms) : crit, 269, 295
master - mean (265ms) : 263, 267
section CallTarget+Inlining+NGEN
This PR (7949) - mean (1,046ms) : 984, 1109
master - mean (1,015ms) : 980, 1049
BenchmarksBenchmark execution time: 2026-04-09 18:28:40 Comparing candidate commit 340c71a in PR branch Found 32 performance improvements and 37 performance regressions! Performance is the same for 211 metrics, 8 unstable metrics.
|
de4b6c3 to
ef669de
Compare
|
@codex review |
|
To use Codex here, create a Codex account and connect to github. |
| IsInDefaultState = tracerSettings.IsDataStreamsMonitoringInDefaultState; | ||
| _registry = new DataStreamsExtractorRegistry(tracerSettings.DataStreamsTransactionExtractors); | ||
|
|
||
| Log.Debug(@"Data Streams extractors loaded: {AsJson}", _registry.AsJson()); |
There was a problem hiding this comment.
Is the @ needed?
| { | ||
| var registry = new DataStreamsExtractorRegistry("[{\"name\": \"transaction-origin\", \"type\": \"HTTP_OUT_HEADERS\", \"value\": \"transaction-id\"}]"); | ||
| registry.AsJson().Should().Be("{\"HttpOutHeaders\":[{\"name\":\"transaction-origin\",\"type\":\"HTTP_OUT_HEADERS\",\"value\":\"transaction-id\",\"ExtractorType\":1}]}"); | ||
| } |
There was a problem hiding this comment.
[nit] Could add a test for GetExtractorsByType that it can correctly store multiple values for the same type
| var span = new Span(new SpanContext(traceId: 123, spanId: 456), DateTimeOffset.UtcNow); | ||
|
|
||
| var act = () => span.TrackTransaction(dsm, "tx-abc", "some-checkpoint"); | ||
| act.Should().NotThrow(); |
There was a problem hiding this comment.
Probably worth checking that writer.DataStreamsTransactions.Size() remains 0 here?
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b5235f8902
ℹ️ 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".
| return; | ||
| } | ||
|
|
||
| var deserialized = JsonHelper.DeserializeObject<List<DataStreamsTransactionExtractor>>(extractorsJson); |
There was a problem hiding this comment.
Handle invalid extractor JSON without throwing
DD_DATA_STREAMS_TRANSACTION_EXTRACTORS is user-provided text, but this constructor deserializes it without a guard. JsonHelper.DeserializeObject throws on malformed JSON, and this path runs during DataStreamsManager initialization, so a bad env var can abort tracer startup instead of being ignored as invalid config.
Useful? React with 👍 / 👎.
| { | ||
| _idBytes = Encoding.UTF8.GetBytes(id); | ||
| _timestamp = timestamp; | ||
| _checkpointId = Cache.GetOrAdd(checkpoint, Interlocked.Increment(ref _counter)); |
There was a problem hiding this comment.
Allocate checkpoint IDs only on first key insertion
This GetOrAdd call eagerly increments _counter even when checkpoint already exists, so the counter grows on every transaction rather than on new checkpoint names. Because checkpoint IDs are serialized to a single byte, a delayed new checkpoint can quickly wrap/collide with existing IDs, making TransactionCheckpointIds ambiguous and corrupting transaction decoding.
Useful? React with 👍 / 👎.
| buffer[offset + 8] = (byte)_timestamp; | ||
|
|
||
| // id size, up to 256 bytes | ||
| buffer[offset + 9] = (byte)_idBytes.Length; |
There was a problem hiding this comment.
Validate transaction ID length before encoding
The wire format stores transaction ID length in one byte, but this code writes _idBytes.Length modulo 256 and still copies the full byte array. If a header-derived transaction ID is longer than 255 bytes, the encoded length no longer matches the payload, so downstream parsing becomes misaligned for this and subsequent records.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
I think this is still applicable, i.e. as a question of correctness? 🤔
| if (_transactionBuffer.TryEnqueue(transaction)) | ||
| { |
There was a problem hiding this comment.
Drain transaction queue in FlushAsync
Transactions are queued in _transactionBuffer, but FlushAsync only drains _buffer and _backlogBuffer. That means explicit flushes (and shutdown flushes when the processing loop is stopping or misses the semaphore window) can complete while queued transactions remain unsent, causing silent transaction loss.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
I think this needs to be addressed
|
All comments are now addressed. |
bouwkast
left a comment
There was a problem hiding this comment.
I think we need integration tests for this
DataStreamsMonitoringKafkaTests.cs
And it looks like since we want this added to HttpClient I think we'd get some tests there, I don't see any DSM stuff in it, unsure if we'd need a new sample or not
Added tests for http and kafka |
HttpClient requires an explicit assembly reference on .NET Framework. Mirrors the same pattern used in Samples.HttpMessageHandler.
bouwkast
left a comment
There was a problem hiding this comment.
Still in progress, mainly have the test code to go through which I will pick up first thing tomorrow
| IsInDefaultState = tracerSettings.IsDataStreamsMonitoringInDefaultState; | ||
| _registry = new DataStreamsExtractorRegistry(tracerSettings.DataStreamsTransactionExtractors); | ||
|
|
||
| Log.Debug(@"Data Streams extractors loaded: {AsJson}", _registry.AsJson()); |
There was a problem hiding this comment.
| Log.Debug(@"Data Streams extractors loaded: {AsJson}", _registry.AsJson()); | |
| if (Log.IsEnabled(LogEventLevel.Debug)) | |
| { | |
| Log.Debug(@"Data Streams extractors loaded: {AsJson}", _registry.AsJson()); | |
| } |
I think as written .AsJson() is called every single time this would be hit, unsure how often, but best to wrap I would think
There was a problem hiding this comment.
💯 definitely should do this (also remove the @ 😄)
|
|
||
| public void TrackTransaction(byte[] transactionIdBytes, string checkpointName) | ||
| { | ||
| if (!IsEnabled) |
There was a problem hiding this comment.
| if (!IsEnabled) | |
| if (!IsTransactionTrackingEnabled) |
Is this supposed to be this?
|
|
||
| public void TrackTransaction(string transactionId, string checkpointName) | ||
| { | ||
| if (!IsEnabled) |
There was a problem hiding this comment.
| if (!IsEnabled) | |
| if (!IsTransactionTrackingEnabled) |
Is this supposed to be this?
There was a problem hiding this comment.
Yeah, this looks like a bug to me?
| { | ||
| foreach (var headerValue in headerValues) | ||
| { | ||
| dataStreamsManager.TrackTransaction(headerValue, extractor.Name); |
There was a problem hiding this comment.
Just wanted to confirm: Should we be tracking a transaction if scope is null?
There is a check below on line 65 if (scope is not null)
I think the transaction stuff isn't dependent on the scope, but just wanted to make sure that is accurate
There was a problem hiding this comment.
(If so it may be helpful to have a comment somewhere here stating why)
I also haven't checked when the scope could / would be null here
There was a problem hiding this comment.
It's not dependent, but we need to account for active spans for linking purpose.
| /// <param name="manager">The <see cref="DataStreamsManager"/> to use</param> | ||
| /// <param name="transactionId">The transaction identifier</param> | ||
| /// <param name="checkpointName">The checkpoint name at which the transaction is being tracked</param> | ||
| internal static void TrackTransaction(this Span span, DataStreamsManager? manager, string transactionId, string checkpointName) |
There was a problem hiding this comment.
I don't see any usages of this, is this still needed?
There was a problem hiding this comment.
Good catch, it should be used when there's an active span - we want to add transaction ids as span attributes.
|
|
||
| public byte[] GetDataAndReset() | ||
| { | ||
| // trim zeros |
There was a problem hiding this comment.
Could we check if _size is 0 right away and if so just return an empty []? Otherwise we always do _data = new byte[_initialByteSize]; even when we don't need to (I think)
|
|
||
| foreach (var pair in Cache) | ||
| { | ||
| var keyBytes = Encoding.UTF8.GetBytes(pair.Key); |
There was a problem hiding this comment.
| var keyBytes = Encoding.UTF8.GetBytes(pair.Key); | |
| var keyBytes = Truncate(Encoding.UTF8.GetBytes(pair.Key)); |
Do we need to truncate this as well? I see we truncate the _idBytes = Truncate(idBytes); above
There was a problem hiding this comment.
It's OK for the checkpoint name to be longer than 255 characters. This limitation is in place only for transaction ids.
| { | ||
| var registry = new DataStreamsExtractorRegistry("[{\"name\": \"n\", \"type\": \"HTTP_OUT_HEADERS\", \"value\": \"v\"}]"); | ||
| var extractor = registry.GetExtractorsByType(DataStreamsTransactionExtractor.Type.HttpOutHeaders)!.Single(); | ||
| extractor.ExtractorType.Should().Be(extractor.ExtractorType); |
There was a problem hiding this comment.
Isn't this just validating that it is the same type as what it is?
There was a problem hiding this comment.
Good catch, I used it for debugging, but forgot to cleanup
andrewlock
left a comment
There was a problem hiding this comment.
Haven't finished yet, but some things to be going on with
| IsInDefaultState = tracerSettings.IsDataStreamsMonitoringInDefaultState; | ||
| _registry = new DataStreamsExtractorRegistry(tracerSettings.DataStreamsTransactionExtractors); | ||
|
|
||
| Log.Debug(@"Data Streams extractors loaded: {AsJson}", _registry.AsJson()); |
There was a problem hiding this comment.
💯 definitely should do this (also remove the @ 😄)
| return; | ||
| } | ||
|
|
||
| if (deserialized == null) |
There was a problem hiding this comment.
| if (deserialized == null) | |
| if (deserialized == null || deserialized.Length == 0) |
| List<DataStreamsTransactionExtractor>? deserialized; | ||
| try | ||
| { | ||
| deserialized = JsonHelper.DeserializeObject<List<DataStreamsTransactionExtractor>>(extractorsJson); |
There was a problem hiding this comment.
This seems like a very complicated and error prone format we're getting customers to need to use, have we considered a simpler public configuration API for this 😟
There was a problem hiding this comment.
This format is flexible, since we don't know what other extractors we may need later. The intention was to use Remote Config in the future. For now we help customers onboard.
|
|
||
| private Type? _cachedType; | ||
|
|
||
| public enum Type |
There was a problem hiding this comment.
Please don't call it Type, it's too common an API and will clash with System namespace
| public enum Type | |
| public enum ExtractorType |
| { | ||
| if (_cachedType is null) | ||
| { | ||
| _cachedType = TypeMap.TryGetValue(StringType, out var t) ? t : Type.Unknown; |
There was a problem hiding this comment.
nit: just use a switch expression and ditch the TypeMap. I'm not really sure you need the cachedType tbh:
| _cachedType = TypeMap.TryGetValue(StringType, out var t) ? t : Type.Unknown; | |
| _cachedType = StringType switch | |
| { | |
| "HTTP_OUT_HEADERS" => Type.HttpOutHeaders, | |
| "HTTP_IN_HEADERS" => Type.HttpInHeaders, | |
| "KAFKA_CONSUME_HEADERS" => Type.KafkaConsumeHeaders, | |
| "KAFKA_PRODUCE_HEADERS" => Type.KafkaProduceHeaders, | |
| _ => Type.Unknown, | |
| }; |
andrewlock
left a comment
There was a problem hiding this comment.
Looking good, just a last few bits I think (I haven't checked the tests thoroughly though!)
| if (hasTransactions) | ||
| { | ||
| var currentTs = DateTimeOffset.UtcNow.ToUnixTimeNanoseconds(); | ||
| var bucketStartTime = currentTs - (currentTs % bucketDurationNs); |
There was a problem hiding this comment.
I don't know if it matters, but this seems like it has the potential to be essentially "wrong" if there are delays processing? Should this be passed into the Serialize method and tracked externally, when the data is actually recorded/snapshotted?
There was a problem hiding this comment.
We carry the timestamp with each transaction, so the actual bucket is not important. It is used only as a way of splitting the data into chunks.
| } | ||
| } | ||
|
|
||
| public static IReadOnlyList<DataStreamsTransactionExtractor> ParseList(string json) |
There was a problem hiding this comment.
nit: return the concrete type when you know it(it's better for perf)
| public static IReadOnlyList<DataStreamsTransactionExtractor> ParseList(string json) | |
| public static List<DataStreamsTransactionExtractor> ParseList(string json) |
| defaultValue: new DefaultResult<IReadOnlyList<DataStreamsTransactionExtractor>>([], "[]"), | ||
| converter: json => ParsingResult<IReadOnlyList<DataStreamsTransactionExtractor>>.Success(DataStreamsTransactionExtractor.ParseList(json)), |
There was a problem hiding this comment.
nit: use the concrete type when we know it (better for perf)
| defaultValue: new DefaultResult<IReadOnlyList<DataStreamsTransactionExtractor>>([], "[]"), | |
| converter: json => ParsingResult<IReadOnlyList<DataStreamsTransactionExtractor>>.Success(DataStreamsTransactionExtractor.ParseList(json)), | |
| defaultValue: new DefaultResult<List<DataStreamsTransactionExtractor>>([], "[]"), | |
| converter: json => ParsingResult<List<DataStreamsTransactionExtractor>>.Success(DataStreamsTransactionExtractor.ParseList(json)), |
| /// <summary> | ||
| /// Gets a raw value for DSM extractors | ||
| /// </summary> | ||
| internal IReadOnlyList<DataStreamsTransactionExtractor> DataStreamsTransactionExtractors { get; } |
There was a problem hiding this comment.
| internal IReadOnlyList<DataStreamsTransactionExtractor> DataStreamsTransactionExtractors { get; } | |
| internal List<DataStreamsTransactionExtractor> DataStreamsTransactionExtractors { get; } |
| buffer[offset + 8] = (byte)_timestamp; | ||
|
|
||
| // id size, up to 256 bytes | ||
| buffer[offset + 9] = (byte)_idBytes.Length; |
There was a problem hiding this comment.
I think this is still applicable, i.e. as a question of correctness? 🤔
| public bool IsTransactionTrackingEnabled | ||
| { | ||
| get => Volatile.Read(ref _isInDefaultState); | ||
| get => !_isInDefaultState && IsEnabled; | ||
| } |
There was a problem hiding this comment.
nit:
| public bool IsTransactionTrackingEnabled | |
| { | |
| get => Volatile.Read(ref _isInDefaultState); | |
| get => !_isInDefaultState && IsEnabled; | |
| } | |
| public bool IsTransactionTrackingEnabled => !_isInDefaultState && IsEnabled; |
|
|
||
| public void TrackTransaction(string transactionId, string checkpointName) | ||
| { | ||
| if (!IsEnabled) |
There was a problem hiding this comment.
Yeah, this looks like a bug to me?
What Does This Do
Initial transaction tracking implementation.
Motivation
Multiple customers mentioned the need to track individual message across multiple service and environments and log messages which haven't reached the end of the pre-defined pipeline.
See this document for details.
Payload format description is here.
Additional Notes
Transaction tracking is currently in closed beta, so the configuration flags will be documented after public availability.
Technical details
Adds DSM transaction tracking by introducing a DataStreamsTransactionContainer that accumulates serialized transaction records (checkpoint ID, nanosecond timestamp, and UTF-8 transaction ID bytes) within the existing
DataStreamsAggregator flush cycle, triggering an early flush when the buffer exceeds 512 KB.
Transaction extraction is driven by a configurable DataStreamsExtractorRegistry (loaded from DD_DATA_STREAMS_TRANSACTION_EXTRACTORS
JSON) that maps named extractors to header keys for four integration types: