Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f64a748
Add periodic memory leak reporting
bmehta001 Sep 18, 2026
c64c35a
Validate leak workflow changes on pull requests
bmehta001 Sep 18, 2026
c12d725
Repair hosted leak analysis execution
bmehta001 Sep 18, 2026
fe74bd7
Stabilize hosted leak scenarios
bmehta001 Sep 18, 2026
ba167fd
Make the sample portable under leak analysis
bmehta001 Sep 18, 2026
c1888e5
Stop loading the leaking Windows Network List Manager
bmehta001 Sep 18, 2026
f693154
Release memory allocated by unit tests
bmehta001 Sep 18, 2026
77d6581
Verify WinRT network detection lifecycle
bmehta001 Sep 18, 2026
e948a2c
Remove obsolete Network List Manager state
bmehta001 Sep 18, 2026
6c979a2
Run leak analysis only after relevant changes
bmehta001 Sep 18, 2026
a266054
Address Copilot leak-review findings
bmehta001 Sep 18, 2026
3192701
Synchronize cached network detector state
bmehta001 Sep 18, 2026
88c7338
Cover WinRT network cost mapping
bmehta001 Sep 18, 2026
355b250
Synchronize WinRT callback teardown
bmehta001 Sep 18, 2026
68f5276
Decouple WinRT callbacks from detector lifetime
bmehta001 Sep 18, 2026
ffc696e
Exclude instrumented SQLite timing assertion
bmehta001 Sep 18, 2026
6da670a
Initialize the WinRT listener apartment
bmehta001 Sep 18, 2026
ce3e63c
Guard network detector tests by feature
bmehta001 Sep 18, 2026
fa09281
Fix network shutdown and leak regression gaps
bmehta001 Sep 21, 2026
3dc04b1
Prevent network shutdown queue starvation
bmehta001 Sep 21, 2026
a30f5fd
Keep queued network refreshes observable
bmehta001 Sep 21, 2026
1b3fc58
Serialize network detector lifecycle changes
bmehta001 Sep 21, 2026
d6bcef3
Avoid reentrant network listener self-join
bmehta001 Sep 22, 2026
33a24e0
Merge main into periodic leak analysis
bmehta001 Sep 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/memory-leak-baseline.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Platform,Scenario,UniqueLeaks,TotalLeaks,LeakBytes,UniquePossibleLeaks,TotalPossibleLeaks,PossibleLeakBytes,UniqueReachable,TotalReachable,ReachableBytes
Windows,unit-tests,10,113,4256,14,15,7206,467,644,227717
Windows,functional-tests,7,382,14072,7,3994,1038248411,1722,2823,725167
Windows,sample-cpp-mini,0,0,0,0,0,0,973,1795,411008
Linux,unit-tests,10,127,3730,5,7,3452,8,8,77045
Linux,functional-tests,3,156,4694,1,1,4104,360,643,279619
Linux,sample-cpp-mini,1,2,32,0,0,0,15,23,81130
153 changes: 153 additions & 0 deletions .github/scripts/run-drmemory.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$DrMemoryPath,

[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$LogDirectory,

[Parameter(Mandatory = $true)]
[ValidatePattern('^[A-Za-z0-9_.-]+$')]
[string]$Scenario,

[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$TargetPath,

[string[]]$TargetArguments = @(),

[string]$BaselinePath
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

function Get-LeakCount {
param(
[Parameter(Mandatory = $true)]
[string]$Results,

[Parameter(Mandatory = $true)]
[string]$Category
)

$escapedCategory = [regex]::Escape($Category)
$pattern = "(?m)^\s*(?:~~Dr\.M~~\s+)?([\d,]+) unique,\s+([\d,]+) total,\s+([\d,]+) byte\(s\) of $escapedCategory\r?$"
$match = [regex]::Match($Results, $pattern)
if (-not $match.Success) {
throw "Dr. Memory results do not contain the '$Category' summary."
}

return @{
Unique = [int64]($match.Groups[1].Value -replace ",", "")
Total = [int64]($match.Groups[2].Value -replace ",", "")
Bytes = [int64]($match.Groups[3].Value -replace ",", "")
}
}

$resolvedDrMemoryPath = (Resolve-Path -LiteralPath $DrMemoryPath).Path
$resolvedTargetPath = (Resolve-Path -LiteralPath $TargetPath).Path
$resolvedLogDirectory = [System.IO.Path]::GetFullPath($LogDirectory)
$scenarioDirectory = Join-Path $resolvedLogDirectory $Scenario
New-Item -ItemType Directory -Path $scenarioDirectory -Force | Out-Null

Write-Host "Running Dr. Memory leak analysis for $Scenario"
& $resolvedDrMemoryPath `
-batch `
-leaks_only `
-logdir $scenarioDirectory `
-- `
$resolvedTargetPath `
@TargetArguments
$targetExitCode = $LASTEXITCODE
if ($targetExitCode -ne 0) {
throw "Dr. Memory or $Scenario exited with code $targetExitCode."
}

$resultFiles = @(Get-ChildItem -LiteralPath $scenarioDirectory -Filter results.txt -File -Recurse)
$resultFiles = @($resultFiles | Where-Object {
Select-String -LiteralPath $_.FullName -Pattern '^(?:NO )?ERRORS FOUND:\r?$' -Quiet
})
if ($resultFiles.Count -ne 1) {
throw "Expected one completed Dr. Memory results.txt for $Scenario, found $($resultFiles.Count)."
}

$results = Get-Content -LiteralPath $resultFiles[0].FullName -Raw
$leaks = Get-LeakCount -Results $results -Category "leak(s)"
$possibleLeaks = Get-LeakCount -Results $results -Category "possible leak(s)"
$reachable = Get-LeakCount -Results $results -Category "still-reachable allocation(s)"

$summary = [pscustomobject]@{
Platform = if ($env:RUNNER_OS) { $env:RUNNER_OS } else { [System.Environment]::OSVersion.Platform }
Scenario = $Scenario
UniqueLeaks = $leaks.Unique
TotalLeaks = $leaks.Total
LeakBytes = $leaks.Bytes
UniquePossibleLeaks = $possibleLeaks.Unique
TotalPossibleLeaks = $possibleLeaks.Total
PossibleLeakBytes = $possibleLeaks.Bytes
UniqueReachable = $reachable.Unique
TotalReachable = $reachable.Total
ReachableBytes = $reachable.Bytes
}

$summaryPath = Join-Path $resolvedLogDirectory "summary.csv"
$summaries = if (Test-Path -LiteralPath $summaryPath) {
@(Import-Csv -LiteralPath $summaryPath) + @($summary)
}
else {
@($summary)
}
$summaries | Export-Csv -LiteralPath $summaryPath -NoTypeInformation

$baselineStatus = "Not compared"
if ($BaselinePath) {
$resolvedBaselinePath = (Resolve-Path -LiteralPath $BaselinePath).Path
$baselineRows = @(Import-Csv -LiteralPath $resolvedBaselinePath | Where-Object {
$_.Platform -eq $summary.Platform -and $_.Scenario -eq $summary.Scenario
})
if ($baselineRows.Count -ne 1) {
throw "Expected one baseline for $($summary.Platform)/$Scenario, found $($baselineRows.Count)."
}

$regressions = @()
foreach ($metric in @(
"UniqueLeaks",
"TotalLeaks",
"LeakBytes",
"UniquePossibleLeaks",
"TotalPossibleLeaks",
"PossibleLeakBytes",
"UniqueReachable",
"TotalReachable",
"ReachableBytes"
)) {
$currentValue = [int64]$summary.$metric
$baselineValue = [int64]$baselineRows[0].$metric
if ($currentValue -gt $baselineValue) {
$regressions += "$metric increased from $baselineValue to $currentValue"
}
}

if ($regressions.Count -eq 0) {
$baselineStatus = "At or below baseline"
}
else {
$baselineStatus = "$($regressions.Count) increase(s)"
foreach ($regression in $regressions) {
Write-Host "::warning title=Dr. Memory regression ($($summary.Platform)/$Scenario)::$regression"
}
}
}

$markdown = @"
| Scenario | Unique leaks | Total leaks | Leak bytes | Unique possible | Total possible | Possible bytes | Unique reachable | Total reachable | Reachable bytes | Baseline |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---|
| $Scenario | $($leaks.Unique) | $($leaks.Total) | $($leaks.Bytes) | $($possibleLeaks.Unique) | $($possibleLeaks.Total) | $($possibleLeaks.Bytes) | $($reachable.Unique) | $($reachable.Total) | $($reachable.Bytes) | $baselineStatus |
"@
Write-Host $markdown
if ($env:GITHUB_STEP_SUMMARY) {
Add-Content -LiteralPath $env:GITHUB_STEP_SUMMARY -Value $markdown
}
227 changes: 227 additions & 0 deletions .github/workflows/memory-leak-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
name: Memory leak analysis

on:
Comment thread
bmehta001 marked this conversation as resolved.
workflow_dispatch:
schedule:
- cron: 0 8 * * 1
push:
branches:
- main
paths:
- .github/scripts/run-drmemory.ps1
- .github/memory-leak-baseline.csv
- .github/workflows/memory-leak-analysis.yml
- CMakeLists.txt
- CMakePresets.json
- Solutions/**
- cmake/**
- examples/cpp/SampleCppMini/**
- lib/**
- sqlite/**
- tests/**
- third_party/Solutions/zlib/**
- third_party/googletest
- tools/gen-version.cmd
- zlib/**
pull_request:
branches:
- main
paths:
- .github/scripts/run-drmemory.ps1
- .github/memory-leak-baseline.csv
- .github/workflows/memory-leak-analysis.yml

permissions:
contents: read

concurrency:
group: memory-leak-analysis-${{ github.ref }}
cancel-in-progress: false

env:
DRMEMORY_VERSION: 2.6.20434
DRMEMORY_TAG: cronbuild-2.6.20434

jobs:
windows:
name: Dr. Memory on Windows
runs-on: windows-2022
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Initialize googletest
run: git submodule update --init --depth=1 third_party/googletest

- name: Setup MSBuild
uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0
with:
vs-version: '[17,)'

- name: Build leak-analysis targets
shell: cmd
run: >-
tools\gen-version.cmd &&
msbuild Solutions\MSTelemetrySDK.sln
/target:sqlite:Rebuild,zlib:Rebuild,Tests\gmock:Rebuild,Tests\gtest:Rebuild,Tests\UnitTests:Rebuild,Tests\FuncTests:Rebuild,Samples\cpp\SampleCppMini:Rebuild
/p:BuildProjectReferences=true
/p:Configuration=Debug
/p:Platform=x64
/maxcpucount:2

- name: Download Dr. Memory
shell: pwsh
env:
DRMEMORY_SHA256: ED9C0E3F1BDB7F8DB1ADC13531493FB1C451E15F375638592C01D8837825A73A
run: |
$archive = Join-Path $env:RUNNER_TEMP "DrMemory-Windows-$env:DRMEMORY_VERSION.zip"
$url = "https://github.com/DynamoRIO/drmemory/releases/download/$env:DRMEMORY_TAG/DrMemory-Windows-$env:DRMEMORY_VERSION.zip"
Invoke-WebRequest -Uri $url -OutFile $archive
$actualHash = (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash
if ($actualHash -ne $env:DRMEMORY_SHA256) {
throw "Dr. Memory archive hash mismatch: expected $env:DRMEMORY_SHA256, got $actualHash."
}
Expand-Archive -LiteralPath $archive -DestinationPath $env:RUNNER_TEMP

- name: Analyze unit tests
shell: pwsh
run: >-
./.github/scripts/run-drmemory.ps1
-DrMemoryPath "$env:RUNNER_TEMP/DrMemory-Windows-$env:DRMEMORY_VERSION/bin64/drmemory.exe"
-LogDirectory drmemory-results
-Scenario unit-tests
-TargetPath Solutions/out/Debug/x64/UnitTests/UnitTests.exe
-BaselinePath .github/memory-leak-baseline.csv
-TargetArguments "--gtest_filter=-OfflineStorageTests_SQLite.StoreThousandEventsTakesLessThanASecond"

- name: Analyze functional tests
shell: pwsh
run: >-
./.github/scripts/run-drmemory.ps1
-DrMemoryPath "$env:RUNNER_TEMP/DrMemory-Windows-$env:DRMEMORY_VERSION/bin64/drmemory.exe"
-LogDirectory drmemory-results
-Scenario functional-tests
-TargetPath Solutions/out/Debug/x64/FuncTests/FuncTests.exe
-BaselinePath .github/memory-leak-baseline.csv
-TargetArguments "--gtest_filter=-BasicFuncTests.killSwitchWorks"

- name: Analyze basic sample
shell: pwsh
run: >-
./.github/scripts/run-drmemory.ps1
-DrMemoryPath "$env:RUNNER_TEMP/DrMemory-Windows-$env:DRMEMORY_VERSION/bin64/drmemory.exe"
-LogDirectory drmemory-results
-Scenario sample-cpp-mini
-BaselinePath .github/memory-leak-baseline.csv
-TargetPath Solutions/out/Debug/x64/SampleCppMini/SampleCppMini.exe

- name: Verify Network List Manager is not loaded
shell: pwsh
run: |
$moduleLogs = @()
foreach ($scenario in @("unit-tests", "functional-tests", "sample-cpp-mini")) {
$scenarioLogs = @(Get-ChildItem "drmemory-results/$scenario" -Filter global.*.log -File -Recurse)
if ($scenarioLogs.Count -eq 0) {
throw "Dr. Memory did not produce a module log for $scenario."
}
$moduleLogs += $scenarioLogs
}
$matches = $moduleLogs | Select-String -Pattern 'module load event:\s+"netprofm\.dll"'
if ($matches) {
$matches | ForEach-Object { Write-Error "$($_.Path):$($_.LineNumber): $($_.Line)" }
throw "Network detection loaded netprofm.dll."
}

- name: Upload Windows reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: drmemory-windows
path: drmemory-results
if-no-files-found: error
retention-days: 90

linux:
name: Dr. Memory on Linux
runs-on: ubuntu-22.04
timeout-minutes: 120
env:
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Initialize googletest
run: git submodule update --init --depth=1 third_party/googletest

- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y libcurl4-openssl-dev

- name: Build leak-analysis targets
run: |
cmake --preset matsdk-debug \
-DMATSDK_BUILD_UNIT_TESTS=ON \
-DMATSDK_BUILD_FUNC_TESTS=ON
cmake --build --preset matsdk-debug --parallel 2

- name: Build basic sample
run: |
cmake --install out --prefix "$PWD/out/install"
cmake -S examples/cpp/SampleCppMini -B out/sample-cpp-mini \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_DISABLE_FIND_PACKAGE_MSTelemetry=TRUE \
-DMATSDK_INSTALL_DIR="$PWD/out/install"
cmake --build out/sample-cpp-mini --parallel 2

- name: Download Dr. Memory
env:
DRMEMORY_SHA256: 79B7718C0040A68B4FCECD9BA1C422174350A5B3A40A8417047A9219E9C2F258
run: |
archive="$RUNNER_TEMP/DrMemory-Linux-$DRMEMORY_VERSION.tar.gz"
url="https://github.com/DynamoRIO/drmemory/releases/download/$DRMEMORY_TAG/DrMemory-Linux-$DRMEMORY_VERSION.tar.gz"
curl --fail --location --retry 3 --output "$archive" "$url"
echo "$DRMEMORY_SHA256 $archive" | sha256sum --check --strict
tar -xzf "$archive" -C "$RUNNER_TEMP"

- name: Analyze unit tests
shell: pwsh
run: >-
./.github/scripts/run-drmemory.ps1
-DrMemoryPath "$env:RUNNER_TEMP/DrMemory-Linux-$env:DRMEMORY_VERSION/bin64/drmemory"
-LogDirectory drmemory-results
-Scenario unit-tests
-BaselinePath .github/memory-leak-baseline.csv
-TargetPath out/tests/unittests/UnitTests

- name: Analyze functional tests
shell: pwsh
run: >-
./.github/scripts/run-drmemory.ps1
-DrMemoryPath "$env:RUNNER_TEMP/DrMemory-Linux-$env:DRMEMORY_VERSION/bin64/drmemory"
-LogDirectory drmemory-results
-Scenario functional-tests
-TargetPath out/tests/functests/FuncTests
-BaselinePath .github/memory-leak-baseline.csv
-TargetArguments "--gtest_filter=-BasicFuncTests.killSwitchWorks"

- name: Analyze basic sample
shell: pwsh
run: >-
./.github/scripts/run-drmemory.ps1
-DrMemoryPath "$env:RUNNER_TEMP/DrMemory-Linux-$env:DRMEMORY_VERSION/bin64/drmemory"
-LogDirectory drmemory-results
-Scenario sample-cpp-mini
-BaselinePath .github/memory-leak-baseline.csv
-TargetPath out/sample-cpp-mini/SampleCppMini

- name: Upload Linux reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: drmemory-linux
path: drmemory-results
if-no-files-found: error
retention-days: 90
2 changes: 1 addition & 1 deletion docs/building-custom-SKU.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Build recipe must contain the following preprocessor definitions:
| HAVE_MAT_WIN_LOG | off | Will log statements to disk on windows if trace enabled and HAVE_MAT_LOGGING defined |
| HAVE_MAT_EVT_TRACEID | off | Enable event tracking by adding trace-id to http request header on Windows. This is for debugging purpose, and not recommended to be enabled in production. The collector doesn't parse/read this header. As of now, this is meant to be used through the capi, where the http-send handler should remove this header from the event data before sending it to collector. |
| HAVE_MAT_STORAGE | on | Enable SQLite persistent offline storage |
| HAVE_MAT_NETDETECT | on | _Win32 Desktop only_: Use NLM COM object for network cost detection on Windows 8+ |
| HAVE_MAT_NETDETECT | on | _Win32 Desktop only_: Use Windows Runtime APIs for network cost detection on Windows 8+ |
| HAVE_MAT_SHORT_NS | off | Use short "MAT::" namespace instead of "Microsoft::Applications::Events::" to reduce the .DLL size |
| HAVE_CS4 | off | Build with Common Schema 4.0 support. Current default is `off`, i.e. building with Common Schema 3.0 support |
| HAVE_CS4_FULL | off | Enable additional Common Schema 4.0 protocol features needed by server / services SDK |
Expand Down
Loading
Loading