17.4 Diagnostics: Trace Parser, Async & Sandbox Frameworks
Key Takeaways
- Microsoft Dynamics Trace Parser delivers deep call-tree telemetry, enabling developers to isolate X++ execution bottlenecks, SQL statement durations, and excessive RPC round-trips.
- The Trace Cockpit in Dynamics 365 Finance and Operations captures Event Tracing for Windows (ETW) performance traces directly from the browser without requiring server-level access.
- A high RPC round-trip count in Trace Parser typically indicates client-server chattiness caused by uncached form display methods or row-by-row query iteration.
- The runAsync framework runs compute-heavy X++ methods on background threads; arguments and return values must be serialized into X++ containers because objects cannot cross thread boundaries.
- The Sandbox framework (SysDictClass::invokeObjectMethodSandbox) executes code in an isolated secondary worker session, preventing UI freezes and shielding the primary user session from unhandled exceptions.
17.4 Diagnostics: Trace Parser, Async & Sandbox Frameworks
Quick Answer: Performance diagnostics in Dynamics 365 Finance and Operations centers on capturing ETW traces using the browser-based Trace Cockpit and analyzing them in Microsoft Dynamics Trace Parser. Trace Parser isolates X++ execution time from SQL statement duration, identifies missing index recommendations, and diagnoses client-server chattiness (excessive RPC round-trips). To eliminate interactive UI freezes, long-running operations are offloaded using the
runAsyncframework (which requires static methods and parameter passing via primitive X++ containers) or isolated using the Sandbox framework (SysDictClass::invokeObjectMethodSandbox), which executes code in a separate worker session to achieve complete fault isolation.
1. Capturing Performance Traces with Trace Cockpit
Developers and administrators can capture detailed Event Tracing for Windows (ETW) logs directly within the Dynamics 365 web client without requiring Remote Desktop or administrative Azure portal access.
Trace Capture & Analysis Workflow
┌─────────────────────────────────────────────────────────────┐
│ Dynamics 365 Web Client │
│ (System administration > Inquiries > Trace > Trace cockpit)│
├─────────────────────────────────────────────────────────────┤
│ 1. Open Trace Cockpit │
│ 2. Set Trace Name & Enable 'Capture SQL Statement Events' │
│ 3. Click 'Start Trace' │
│ 4. Reproduce exact user scenario in new browser tab │
│ 5. Return to Trace Cockpit and click 'Stop Trace' │
│ 6. Click 'Download Trace' (.etl.zip package) │
└──────────────────────────────┬──────────────────────────────┘
│ Download .etl.zip
▼
┌─────────────────────────────────────────────────────────────┐
│ Microsoft Dynamics Trace Parser │
│ • Import Trace (.etl) │
│ • Analyze Call Tree, X++ vs. SQL time, RPC counts │
│ • Inspect Missing Indexes & Slow Statements │
└─────────────────────────────────────────────────────────────┘
Trace Capture Best Practices
- Keep Scenarios Narrow: Start the trace immediately before clicking the problematic button and stop it immediately after the operation completes. Capturing general navigation or idle time inflates trace sizes with noise.
- Include SQL Events: Always ensure the Capture SQL Statement Events toggle is enabled to record exact T-SQL text, execution plans, and duration metrics.
- Target User Session: Traces can be bound to the current user session or configured to trace background batch processes by specifying the target worker thread.
2. Microsoft Dynamics Trace Parser Deep Dive
Microsoft Dynamics Trace Parser is the primary diagnostic desktop tool for analyzing captured .etl trace files.
The Call Tree & Execution Metrics
Trace Parser structures trace events into a hierarchical Call Tree that visualizes the exact execution stack from UI event down to database statement:
- Inclusive Time (Total Time): The total elapsed time spent inside a method, including all child methods and database calls it invoked.
- Exclusive Time (Self Time): The time spent purely inside that specific method's own instructions, excluding any calls to child methods.
- Call Count: The total number of times a method was executed. High call counts (e.g., 50,000 executions of a helper method) immediately signal loop inefficiency.
Trace Parser Diagnostic Breakdown
Scenario A: Database Bottleneck (I/O Bound)
┌─────────────────────────────────────────────────────────────┐
│ Total Elapsed Time: 120 seconds │
│ ├─ X++ Execution Time: 8 seconds ( 6.7%) │
│ └─ SQL Statement Duration: 112 seconds (93.3%) <── BOTTLENECK
│ ↳ Inspect: Missing indexes, table scans, lock waits │
└─────────────────────────────────────────────────────────────┘
Scenario B: AOS Logic Bottleneck (CPU Bound)
┌─────────────────────────────────────────────────────────────┐
│ Total Elapsed Time: 120 seconds │
│ ├─ X++ Execution Time: 114 seconds (95.0%) <── BOTTLENECK
│ └─ SQL Statement Duration: 6 seconds ( 5.0%) │
│ ↳ Inspect: Algorithmic complexity, container loops │
└─────────────────────────────────────────────────────────────┘
Diagnosing Client-Server Chattiness (RPC Round-Trips)
In the Trace Parser Summary tab, examine the RPC Round-Trip Count:
- If opening a form or scrolling a grid produces thousands of RPC calls, the system is suffering from client-server chattiness.
- The root cause is almost always an uncached display method, unjoined table datasources, or redundant calls to
reread()andresearch().
Missing Index Suggestions & Top SQL Statements
The SQL Statements tab in Trace Parser ranks all queries by cumulative duration:
- Missing Indexes: Trace Parser highlights queries where SQL Server suggested an index during execution plan evaluation, displaying the recommended inequality columns and included columns.
- Row-by-Row Detection: If an
update_recordsetdegraded into row-by-row fallback, Trace Parser will show thousands of individualUPDATEstatements matching the row count rather than a single set-based statement.
3. Asynchronous Processing Patterns: The runAsync Framework
Executing long-running operations synchronously in interactive user sessions causes browser "Page Unresponsive" dialogs and HTTP gateway timeouts. The runAsync framework executes intensive tasks asynchronously on a background worker thread.
runAsync Architecture & Thread Separation
Interactive User Session (Thread A) Background Worker Thread (Thread B)
┌────────────────────────────────────┐ ┌────────────────────────────────────┐
│ User clicks 'Recalculate'
│ 1. Pack inputs into container │ │ │
│ params = [AccountNum, Date]; │ │ │
│ 2. runAsync(class, method, params, │ │ │
│ successCb, failCb); │─────>│ 3. Unpack container │
│ │ │ 4. Execute heavy calculation │
│ UI remains fully responsive! │ │ 5. Pack results into container │
│ User continues working... │<─────│ 6. Dispatch result container │
│ │ └────────────────────────────────────┘
│ 7. asyncSuccessCallback(result) │
│ (Updates UI / Displays message) │
└────────────────────────────────────┘
Rules for Implementing runAsync
- Static Method Requirement: The asynchronous method to run must be a
public staticmethod. - Container Serialization: Because Thread A and Thread B have completely separate memory stacks, object pointers and class instances cannot cross thread boundaries. All input parameters and output results must be serialized into an X++
container. - Callback Methods: The developer defines static callback methods to handle completion or failure.
Complete runAsync Implementation Example
/// <summary>
/// Demonstrates asynchronous processing using the runAsync framework.
/// </summary>
public final class FinancialCalculationAsyncService
{
/// <summary>
/// Initiates the background calculation from a form or action button.
/// </summary>
public static void startCalculation(CustAccount _accountNum, TransDate _cutOffDate)
{
// Pack parameters into a container
container asyncParams = [_accountNum, _cutOffDate];
// Invoke runAsync with worker method, parameters, and callbacks
runAsync(
classNum(FinancialCalculationAsyncService),
staticMethodStr(FinancialCalculationAsyncService, calculateWorker),
asyncParams,
classNum(FinancialCalculationAsyncService),
staticMethodStr(FinancialCalculationAsyncService, calculationSuccessCallback),
classNum(FinancialCalculationAsyncService),
staticMethodStr(FinancialCalculationAsyncService, calculationFailureCallback)
);
}
/// <summary>
/// Asynchronous worker method executing on background thread.
/// </summary>
public static container calculateWorker(container _params)
{
CustAccount accountNum;
TransDate cutOffDate;
[accountNum, cutOffDate] = _params;
// Execute heavy business calculation
AmountMST calculatedTotal = CustTrans::calcTotalOutstanding(accountNum, cutOffDate);
// Return result packed in a container
return [accountNum, calculatedTotal];
}
/// <summary>
/// Success callback executed upon completion.
/// </summary>
public static void calculationSuccessCallback(container _result)
{
CustAccount accountNum;
AmountMST total;
[accountNum, total] = _result;
Info(strFmt("@ApplicationPlatform:AsyncCalculationSuccess", accountNum, total));
}
/// <summary>
/// Failure callback executed if an unhandled exception occurs in the worker.
/// </summary>
public static void calculationFailureCallback(container _exceptionInfo)
{
Error("@ApplicationPlatform:AsyncCalculationFailed");
}
}
4. The Sandbox Framework (SysDictClass::invokeObjectMethodSandbox)
While runAsync offloads work asynchronously to keep the UI interactive, the Sandbox Framework is designed for execution isolation and fault containment.
Architectural Purpose of the Sandbox
When integrating with third-party components, executing untrusted document parsers, or evaluating complex pricing matrices, a runtime crash (such as a stack overflow, fatal memory exception, or unhandled CLR abort) would normally terminate the entire user session, destroying all unsaved form data.
Executing logic through SysDictClass::invokeObjectMethodSandbox spins up a secondary, isolated worker session:
- Fault Isolation: If the sandboxed code throws an unhandled exception or crashes, only the secondary sandbox session is terminated. The user's primary interactive session remains healthy and intact.
- Clean Memory Scope: All temporary allocations and memory buffers created during the sandboxed call are automatically reclaimed when the secondary session closes.
Sandbox Implementation Example
/// <summary>
/// Executes complex CAD parsing inside a protected sandbox worker session.
/// </summary>
public static container parseCadDocumentSandbox(CADModelParser _parserInstance, Filename _filePath)
{
container inputParams = [_filePath];
container resultContainer;
SysDictClass dictClass = new SysDictClass(classNum(CADModelParser));
// Executes the method inside an isolated secondary worker session
resultContainer = dictClass.invokeObjectMethodSandbox(
_parserInstance,
methodStr(CADModelParser, executeParse),
inputParams
);
return resultContainer;
}
5. Diagnostic & Asynchronous Framework Decision Matrix
| Diagnostic / Processing Pattern | Execution Mode | Scope & Primary Use Case | Fault / Thread Isolation |
|---|---|---|---|
| Trace Cockpit & Trace Parser | Diagnostic Post-Mortem | Performance profiling, SQL vs. X++ breakdown, missing index analysis, RPC round-trip measurement | Telemetry only; zero runtime code isolation. |
runAsync Framework | Asynchronous (Background Thread) | Offloading intensive calculations from interactive UI threads while providing success/failure callbacks | Background thread isolation; parameters must serialize via containers. |
| SysOperation Framework | Async / Scheduled Batch | Enterprise business processes supporting dialogs, batch scheduling, runtime parameter contracts, and retry policies | Batch thread isolation; full framework support for queries and data contracts. |
Sandbox Framework (invokeObjectMethodSandbox) | Synchronous (Secondary Session) | Fault containment for risky, memory-intensive, or third-party logic where crashes must not harm primary user session | Complete session-level fault isolation; unhandled crashes do not drop main session. |
| Standard Batch Framework | Asynchronous Batch Queue | Long-running multi-hour scheduled workloads (invoicing, ledger closing, MRP runs) distributed across batch server group | Separate batch process; independent session and transaction lifecycle. |
6. Realistic Enterprise Scenario: Diagnosing & Remediating a Frozen Warehouse Scanning Form
Business Problem
In a 24/7 logistics hub, warehouse operators use custom mobile tablets running an interactive warehouse dispatch scanning form (WHSDispatchScanForm). During peak shift changeovers, scanning a pallet barcode causes the form UI to freeze for 35 to 45 seconds. Operators repeatedly click the scan button, generating additional thread contention and locking terminal sessions. The operations manager escalates the incident as a critical operational blocker.
Architecture & Implementation Walkthrough
- Capturing Telemetry via Trace Cockpit: A developer navigates to System administration > Inquiries > Trace > Trace cockpit, starts a trace, replicates scanning a single pallet on the mobile form, and immediately stops and downloads the
.etl.ziptrace. - Isolating Root Causes in Trace Parser:
- RPC Chattiness: The Summary tab shows an astounding 3,400 RPC round-trips during the 40-second freeze.
- Uncached Display Method: Expanding the Call Tree reveals that
WHSDispatchScanFormincludes a grid displaying pallet items with a display methodcalcTotalPalletWeight(). The method recalculates physical weight by queryingInventDimandInventTranson every redraw, firing 3,400 times as records render. - Synchronous Carrier Rate Call: The Call Tree further reveals an 11-second synchronous call to an external legacy C++ DLL (
CarrierRateEngine.dll) executing inside the UI thread to calculate real-time freight surcharges.
- Applying Remediation:
- Display Method Caching: The developer decorates
calcTotalPalletWeight()with[SysClientCacheDataMethodAttribute(true)]. This eliminates 3,380 RPC round-trips, satisfying subsequent row views from browser memory. - Offloading Calculation via
runAsync: The freight calculation logic is refactored into apublic staticworker method, dispatched asynchronously viarunAsyncusing container parameters ([palletId, warehouseCode]). The form remains fully responsive and immediately displays a loading spinner. - Isolating C++ DLL Crashes via Sandbox: Because the third-party carrier DLL occasionally throws memory segmentation faults on corrupt barcode payloads, the execution is wrapped inside
SysDictClass::invokeObjectMethodSandbox. If the DLL crashes, only the secondary sandbox session terminates, and a user-friendly error message is displayed without crashing the warehouse operator's active form.
- Display Method Caching: The developer decorates
Measurable Outcomes
- Form scan response time dropped from 42 seconds to 320 milliseconds (a 99.2% improvement).
- RPC round-trips per scan dropped from 3,400 down to 4.
- Terminal app crashes dropped to zero, eliminating warehouse dispatch bottlenecks.
7. Real-World MB-500 Exam Traps
[!WARNING] Exam Trap 1: Attempting to Pass Living Objects Across
runAsyncThreads A favorite MB-500 trick question presents code attempting to pass a table buffer (CustTable) or a class instance directly as an argument intorunAsync. In .NET Core / Dynamics 365, asynchronous worker threads execute on completely isolated thread stacks. Living object references and pointers cannot cross thread boundaries. All input parameters and return values must be packed and unpacked using primitive X++containerobjects.
[!WARNING] Exam Trap 2: Using Instance Methods for
runAsyncWorkers The target worker method and both callback methods passed torunAsyncmust bepublic static. Attempting to pass an instance method or private method causes a runtime reflection exception when the AOS thread pool attempts invocation.
[!WARNING] Exam Trap 3: Confusing the Sandbox Framework with
runAsyncWhile both frameworks execute code away from standard execution paths, their objectives differ fundamentally.runAsyncis designed for asynchronous responsiveness on background worker threads (non-blocking UI). The Sandbox framework (invokeObjectMethodSandbox) is designed for fault isolation and session containment (shielding the primary user session from fatal crashes in risky or unmanaged code). Sandbox execution is typically synchronous within an isolated secondary session.
[!WARNING] Exam Trap 4: Misinterpreting Inclusive vs. Exclusive Time in Trace Parser When analyzing a slow trace, candidates often jump to the method with the highest Inclusive Time. However, top-level methods (like
FormRun.init()orClass.run()) almost always have the highest Inclusive Time because they enclose all child calls. To pinpoint the actual line of slow code, developers must sort by Exclusive Time (Self Time) to see where CPU time is actually being spent, or inspect the SQL Statements tab for long-running database queries.
[!WARNING] Exam Trap 5: Forgetting to Enable SQL Statement Events in Trace Cockpit If an ETW trace is captured without checking the Capture SQL Statement Events toggle in the Trace Cockpit, the trace will capture X++ call stacks and RPC events, but the SQL Statements tab in Trace Parser will be completely empty. For database diagnostics, capturing SQL events is mandatory.
A performance engineer is investigating severe latency when users open the All Sales Orders list page. In Microsoft Dynamics Trace Parser, the engineer observes that opening the form generates over 2,500 RPC round-trips and executes hundreds of repetitive SQL statements taking 1-2 milliseconds each against the InventDim table. What diagnosis does this Trace Parser call tree telemetry indicate?
A developer needs to execute a complex pricing calculation in the background using the runAsync framework to keep the user interface responsive. Which requirement must the developer adhere to when implementing the asynchronous worker method?
An enterprise solution integrates with an external legacy COM/DLL component that occasionally crashes due to memory access violations when parsing malformed CAD design files. The development lead requires that if the parsing operation fails or crashes, the user's interactive Dynamics 365 session must remain unaffected and unsaved form data must not be lost. Which framework should the developer use to isolate this execution?
During an end-of-month batch billing run, a custom billing batch job takes four hours to complete. When reviewing the trace in Microsoft Dynamics Trace Parser, the developer notes that total elapsed time is 240 minutes, but cumulative SQL Statement Duration accounts for only 8 minutes, while X++ Execution Time accounts for 232 minutes. What does this telemetry reveal about the performance bottleneck?