Skip to main content

Overview

SQL Query Stress collects detailed performance statistics during load tests by leveraging SQL Server’s built-in statistics features (SET STATISTICS IO and SET STATISTICS TIME). This page documents all collected metrics, how they are gathered, and how to interpret them.

Collected Metrics

All metrics are stored in the QueryOutput class (defined in LoadEngine.cs:535-560) and collected per query execution.

Core Metrics

Time
TimeSpan
Description: Total elapsed time for the query execution as measured by the client using System.Diagnostics.Stopwatch.Unit: TimeSpan (milliseconds precision)Interpretation: Includes network latency, data retrieval time, and all client-side processing. This is wall-clock time from when the query starts until it completes.Source: Client-side stopwatch measurement
CpuTime
int
Description: Total CPU time consumed by SQL Server executing the query.Unit: MillisecondsInterpretation: Actual CPU processing time on the server. Does not include wait time for I/O, locks, or other resources. A query with high CpuTime relative to ElapsedTime is CPU-bound.Source: Parsed from SET STATISTICS TIME ON outputCollection: Enabled when CollectTimeStats is true
ElapsedTime
int
Description: Total elapsed time as reported by SQL Server.Unit: MillisecondsInterpretation: Server-side measurement of query duration. Includes CPU time plus all wait time (I/O, locks, parallelism waits, etc.). Does not include network transmission time.Source: Parsed from SET STATISTICS TIME ON outputCollection: Enabled when CollectTimeStats is true
LogicalReads
int
Description: Total number of logical reads (8KB pages) performed by the query across all tables.Unit: Pages (8KB each)Interpretation: Number of data pages read from the buffer cache (includes both cached and disk-read pages). Higher values indicate more data being scanned. Reducing logical reads through better indexing improves performance.Source: Summed from SET STATISTICS IO ON output for all tablesCollection: Enabled when CollectIoStats is true
Finished
boolean
Description: Indicates whether this was the last iteration for the thread.Interpretation: Used internally to track test progress and determine when threads complete.
ActiveThreads
int
Description: Number of threads still running at the time this result was recorded.Interpretation: Useful for understanding load characteristics over time. Shows how many concurrent connections were active.
E
Exception
Description: Exception object if the query execution failed.Interpretation: Contains detailed error information. Null if the query succeeded. Check for connection errors, timeout errors, or SQL syntax errors.

How Statistics Are Collected

1. Enabling Statistics Collection

When CollectIoStats or CollectTimeStats is enabled in the configuration, SQL Query Stress executes the following commands before running queries:
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
Implementation reference: LoadEngine.cs:139-144

2. InfoMessage Event Handling

SQL Server sends statistics as informational messages through the SqlConnection.InfoMessage event. SQL Query Stress attaches an event handler (GetInfoMessages method at LoadEngine.cs:343-365) to capture these messages.

3. Regex Pattern Matching

Statistics are extracted from InfoMessage text using compiled regular expressions:

Logical Reads Pattern

@"(?:Table (\'\'w{1,}\'|'#\w{1,}\'|'##\w{1,}\'). Scan count \d{1,}, logical reads )(\d{1,})"
Matches: Messages like:
Table 'Customers'. Scan count 1, logical reads 42, physical reads 0, ...
Extracted Value: The number after “logical reads” (42 in the example) Implementation: LoadEngine.cs:280

CPU and Elapsed Time Pattern

@"(?:SQL Server Execution Times:|SQL Server parse and compile time:)(?:\s{1,}CPU time = )(\d{1,})(?: ms,\s{1,}elapsed time = )(\d{1,})"
Matches: Messages like:
SQL Server Execution Times:
   CPU time = 156 ms,  elapsed time = 2034 ms.
Extracted Values:
  • First captured group: CPU time in milliseconds (156)
  • Second captured group: Elapsed time in milliseconds (2034)
Implementation: LoadEngine.cs:284-287

4. Accumulation

When multiple tables are accessed, logical reads are summed across all tables. CPU and elapsed time are accumulated across parse/compile and execution phases. Reference: LoadEngine.cs:347-363

Understanding Statistics IO Output

When SET STATISTICS IO ON is enabled, SQL Server provides detailed I/O information:
Table 'Orders'. Scan count 1, logical reads 42, physical reads 0, 
read-ahead reads 0, lob logical reads 0, lob physical reads 0, 
lob read-ahead reads 0.

What SQL Query Stress Tracks

MetricTrackedDescription
Logical Reads✅ YesPages read from buffer cache
Physical Reads❌ NoPages read from disk
Read-ahead Reads❌ NoPages read by read-ahead mechanism
Scan Count❌ NoNumber of seeks/scans performed
LOB Reads❌ NoLarge object read operations
SQL Query Stress focuses on logical reads as the primary I/O metric because it represents total data accessed regardless of cache state.

Understanding Statistics Time Output

When SET STATISTICS TIME ON is enabled, SQL Server provides timing information:
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 5 ms.

SQL Server Execution Times:
   CPU time = 156 ms,  elapsed time = 2034 ms.

Metric Interpretation

MetricDescriptionHigh Value Indicates
CPU TimePure computation timeCPU-bound operations, calculations, aggregations
Elapsed TimeTotal server-side timeI/O waits, lock waits, parallelism waits
Time (Client)Client-measured timeNetwork latency, result set transfer

Common Patterns

CPU-Bound Query:
CPU time = 1500 ms, elapsed time = 1550 ms
  • CPU ≈ Elapsed Time
  • Query spent most time in computation
  • Optimize with better algorithms, indexing, or reduce data volume
I/O-Bound Query:
CPU time = 50 ms, elapsed time = 3000 ms
  • Elapsed Time >> CPU Time
  • Query spent most time waiting for I/O
  • Optimize with better indexing, caching, or faster storage
Lock-Contention Query:
CPU time = 10 ms, elapsed time = 5000 ms
  • Very low CPU, very high elapsed time
  • Query spent most time waiting for locks
  • Review isolation levels, add WITH (NOLOCK) if appropriate, or optimize locking strategy

Aggregated Statistics

During a load test, the UI displays aggregated statistics across all query executions:

Calculated Metrics

Total Queries Executed:
NumThreads × NumIterations
Average Elapsed Time:
Sum(Time) / Total Queries
Average Logical Reads:
Sum(LogicalReads) / Total Queries
Queries Per Second:
Total Queries / Total Test Duration
Error Rate:
(Queries with E != null) / Total Queries × 100%

Configuration Impact on Statistics

ForceDataRetrieval Setting

When false (default):
  • Executes query with ExecuteNonQuery()
  • Results are generated but not transmitted to client
  • Lower client-side Time measurements
  • More representative of write operations
When true:
  • Reads all result sets completely using ExecuteReader()
  • Forces full data retrieval and network transmission
  • Higher client-side Time measurements
  • More representative of read operations with data consumption
Implementation: LoadEngine.cs:416-433

Statistics Collection Overhead

Enabling statistics collection adds minimal overhead:
  • CollectIoStats: ~1-5% overhead (parsing InfoMessages)
  • CollectTimeStats: ~1-5% overhead (parsing InfoMessages)
The overhead is negligible compared to query execution time and provides valuable diagnostic information.

Best Practices

For Performance Testing

  1. Enable both statistics types to get complete picture:
    "CollectIoStats": true,
    "CollectTimeStats": true
    
  2. Use ForceDataRetrieval when testing read workloads:
    "ForceDataRetrieval": true
    
  3. Monitor logical reads to validate index effectiveness
  4. Compare CPU vs Elapsed Time to identify bottleneck type

For Accurate Measurements

  1. Run warm-up iterations before collecting final statistics
  2. Use sufficient iterations to get statistically significant averages
  3. Monitor ActiveThreads to ensure consistent concurrency
  4. Check for exceptions (E field) that might skew results

Build docs developers (and LLMs) love