Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 7 additions & 11 deletions .github/workflows/ci-bdnbenchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ jobs:
name: BDNBenchmark
needs: [changes]
runs-on: ${{ matrix.os }}
concurrency:
group: bdn-benchmark-${{ matrix.gh-pages-branch }}-${{ matrix.os }}-${{ matrix.framework }}-${{ matrix.configuration }}-${{ matrix.test }}
cancel-in-progress: false
strategy:
fail-fast: false
matrix:
Expand All @@ -51,37 +54,30 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4

- name: Install dependencies
run: dotnet restore

- name: Run BDN.benchmark Perf Test
run: ./test/BDNPerfTests/run_bdnperftest.ps1 ${{ matrix.test }} ${{ matrix.framework }}
shell: pwsh
continue-on-error: false

- name: Random pause between tasks so multiple inserts don't run at the same time
run: |
$delay = Get-Random -Minimum 1 -Maximum 600
Write-Host "Sleeping for $delay seconds..."
Start-Sleep -Seconds $delay
shell: pwsh

- name: Upload test results to artifacts
uses: actions/upload-artifact@v4
with:
name: Results-${{ matrix.os }}-${{ matrix.framework }}-${{ matrix.configuration }}-${{ matrix.test }}
path: |
./test/BDNPerfTests/results
path: ./test/BDNPerfTests/results
if: ${{ always() }}

- name: Upload Error Log to artifacts
uses: actions/upload-artifact@v4
with:
name: ErrorLog-${{ matrix.os }}-${{ matrix.framework }}-${{ matrix.configuration }}-${{ matrix.test }}
path: |
./test/BDNPerfTests/errorlog
path: ./test/BDNPerfTests/errorlog
if: ${{ always() }}

# Run `github-action-benchmark` action for the Continuous Benchmarking Charts (https://microsoft.github.io/garnet/charts/)
Expand Down
148 changes: 144 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ jobs:
- name: Upload test results
uses: actions/upload-artifact@v4
with:
name: dotnet-garnet-results-${{ matrix.os }}-${{ matrix.framework }}-${{ matrix.configuration }}-${{ matrix.test }}
name: dotnet-standalone-results-${{ matrix.os }}-${{ matrix.framework }}-${{ matrix.configuration }}-${{ matrix.test }}
path: GarnetTestResults-${{ matrix.os }}-${{ matrix.framework }}-${{ matrix.configuration }}-${{ matrix.test }}
if: ${{ always() }}

Expand All @@ -159,7 +159,7 @@ jobs:
os: [ ubuntu-latest, windows-latest ]
framework: [ 'net8.0' , 'net10.0']
configuration: [ 'Debug', 'Release' ]
test: [ 'Garnet.test.cluster', 'Garnet.test.cluster.migrate', 'Garnet.test.cluster.replication', 'Garnet.test.cluster.replication.tls', 'Garnet.test.cluster.replication.asyncreplay', 'Garnet.test.cluster.replication.disklesssync', 'Garnet.test.cluster.vectorsets', 'Garnet.test.cluster.multilog' ]
test: [ 'Garnet.test.cluster', 'Garnet.test.cluster.migrate', 'Garnet.test.cluster.replication', 'Garnet.test.cluster.replication.tls', 'Garnet.test.cluster.replication.disklesssync', 'Garnet.test.cluster.vectorsets', 'Garnet.test.cluster.multilog' ]
if: needs.changes.outputs.garnet == 'true'
steps:
- name: Check out code
Expand All @@ -180,7 +180,7 @@ jobs:
- name: Upload test results
uses: actions/upload-artifact@v4
with:
name: dotnet-garnet-results-${{ matrix.os }}-${{ matrix.framework }}-${{ matrix.configuration }}-${{ matrix.test }}
name: dotnet-cluster-results-${{ matrix.os }}-${{ matrix.framework }}-${{ matrix.configuration }}-${{ matrix.test }}
path: GarnetTestResults-${{ matrix.os }}-${{ matrix.framework }}-${{ matrix.configuration }}-${{ matrix.test }}
if: ${{ always() }}

Expand Down Expand Up @@ -276,7 +276,7 @@ jobs:
echo "TSAVORITE_TEST_DIR=$dir" >> $env:GITHUB_ENV

- name: Run tests ${{ matrix.test }}
run: dotnet test ${{ env.TSAVORITE_TEST_DIR }} -f ${{ matrix.framework }} --configuration ${{ matrix.configuration }} --logger "console;verbosity=detailed" --logger trx --results-directory "TsavoriteTestResults-${{ matrix.os }}-${{ matrix.framework }}-${{ matrix.configuration }}-${{ matrix.test }}"
run: dotnet test ${{ env.TSAVORITE_TEST_DIR }} -f ${{ matrix.framework }} --configuration ${{ matrix.configuration }} --logger "console;verbosity=detailed" --logger trx --results-directory "TsavoriteTestResults-${{ matrix.os }}-${{ matrix.framework }}-${{ matrix.configuration }}-${{ matrix.test }}" -- NUnit.DisplayName=FullName
timeout-minutes: 45
- name: Upload test results
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -305,6 +305,146 @@ jobs:
- name: Build website
run: yarn build

# Job to generate combined test summaries per category
test-summary:
name: Test Summary
runs-on: ubuntu-latest
needs: [changes, test-garnet-standalone, test-garnet-cluster, test-tsavorite]
if: always() && (needs.test-garnet-standalone.result != 'skipped' || needs.test-garnet-cluster.result != 'skipped' || needs.test-tsavorite.result != 'skipped')
steps:
- name: Download standalone test results
if: needs.test-garnet-standalone.result != 'skipped'
uses: actions/download-artifact@v4
with:
pattern: dotnet-standalone-results-*
path: results/standalone
merge-multiple: true
- name: Download cluster test results
if: needs.test-garnet-cluster.result != 'skipped'
uses: actions/download-artifact@v4
with:
pattern: dotnet-cluster-results-*
path: results/cluster
merge-multiple: true
- name: Download Tsavorite test results
if: needs.test-tsavorite.result != 'skipped'
uses: actions/download-artifact@v4
with:
pattern: dotnet-tsavorite-results-*
path: results/tsavorite
merge-multiple: true
- name: Generate summaries
shell: pwsh
run: |
function Write-CategorySummary($category, $dir) {
$trxFiles = Get-ChildItem -Path $dir -Filter "*.trx" -Recurse -ErrorAction SilentlyContinue
if (-not $trxFiles -or $trxFiles.Count -eq 0) { return }
$totalTests = 0; $passed = 0; $failed = 0; $skipped = 0
$durations = @()
$failedTests = @()
foreach ($trx in $trxFiles) {
$xml = [System.Xml.XmlDocument]::new()
try { $xml.Load($trx.FullName) }
catch {
Write-Warning "Skipping malformed TRX file: $($trx.FullName) - $_"
continue
}
$ns = @{ t = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010" }
$results = Select-Xml -Xml $xml -XPath "//t:UnitTestResult" -Namespace $ns
foreach ($r in $results) {
$totalTests++
$outcome = $r.Node.outcome
if ($outcome -eq "Passed") { $passed++ }
elseif ($outcome -eq "Failed") {
$failed++
$dur = $r.Node.duration
$durSec = 0
if ($dur) { try { $durSec = [TimeSpan]::Parse($dur).TotalSeconds } catch { } }
$failedTests += @{ Name = $r.Node.testName; Seconds = $durSec }
}
else { $skipped++ }
$dur = $r.Node.duration
if ($dur) {
try { $ts = [TimeSpan]::Parse($dur); $durations += @{ Name = $r.Node.testName; Seconds = $ts.TotalSeconds } }
catch { }
}
}
}
# Compute total duration as sum of all individual test durations
$totalSec = ($durations | ForEach-Object { $_.Seconds } | Measure-Object -Sum).Sum
if ($totalSec -gt 0) {
$totalDurStr = "{0}m {1:D2}s" -f [int][math]::Floor($totalSec / 60), [int]($totalSec % 60)
} else { $totalDurStr = "N/A" }
$buckets = @(
@{ Label = "< 1s"; Min = 0; Max = 1 },
@{ Label = "1s - 5s"; Min = 1; Max = 5 },
@{ Label = "5s - 15s"; Min = 5; Max = 15 },
@{ Label = "15s - 30s"; Min = 15; Max = 30 },
@{ Label = "30s - 60s"; Min = 30; Max = 60 },
@{ Label = "> 60s"; Min = 60; Max = [double]::MaxValue }
)
$counts = @(0,0,0,0,0,0)
foreach ($d in $durations) {
for ($i = 0; $i -lt $buckets.Count; $i++) {
if ($d.Seconds -ge $buckets[$i].Min -and $d.Seconds -lt $buckets[$i].Max) { $counts[$i]++; break }
}
}
$maxCount = ($counts | Measure-Object -Maximum).Maximum
if ($maxCount -eq 0) { $maxCount = 1 }
$sb = [System.Text.StringBuilder]::new()
[void]$sb.AppendLine("## 🧪 $category")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("⏱️ **Total: $totalDurStr** · $totalTests tests (✅ $passed · ❌ $failed · ⏭️ $skipped)")
[void]$sb.AppendLine("")
# Failed tests section (only if there are failures)
if ($failedTests.Count -gt 0) {
[void]$sb.AppendLine("### ❌ Failed Tests")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("| Test | Duration |")
[void]$sb.AppendLine("|------|----------|")
foreach ($t in $failedTests) {
$durStr = "{0:F1}s" -f $t.Seconds
[void]$sb.AppendLine("| $($t.Name) | $durStr |")
}
[void]$sb.AppendLine("")
}
[void]$sb.AppendLine("### Distribution")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("| Duration | Count | |")
[void]$sb.AppendLine("|----------|------:|--|")
for ($i = 0; $i -lt $buckets.Count; $i++) {
$barLen = [int][math]::Round(($counts[$i] / $maxCount) * 20)
if ($barLen -eq 0 -and $counts[$i] -gt 0) { $barLen = 1 }
$bar = "█" * $barLen
[void]$sb.AppendLine("| $($buckets[$i].Label) | $($counts[$i]) | $bar |")
}
$top10 = $durations | Sort-Object { $_.Seconds } -Descending | Select-Object -First 10
if ($top10.Count -gt 0) {
[void]$sb.AppendLine("")
[void]$sb.AppendLine("### 🐢 Top 10 Slowest")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("| Test | Duration |")
[void]$sb.AppendLine("|------|----------|")
foreach ($t in $top10) {
$durStr = "{0:F1}s" -f $t.Seconds
[void]$sb.AppendLine("| $($t.Name) | $durStr |")
}
}
[void]$sb.AppendLine("")
$sb.ToString() >> $env:GITHUB_STEP_SUMMARY
}

# Generate per-category summaries
if (Test-Path "results/standalone") {
Write-CategorySummary "Garnet Standalone" "results/standalone"
}
if (Test-Path "results/cluster") {
Write-CategorySummary "Garnet Cluster" "results/cluster"
}
if (Test-Path "results/tsavorite") {
Write-CategorySummary "Tsavorite" "results/tsavorite"
}

pipeline-success:
name: Garnet CI (Complete)
runs-on: ubuntu-latest
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
strategy:
fail-fast: false
matrix:
test: [ 'Tsavorite.test', 'Tsavorite.test.recordops', 'Tsavorite.test.session', 'Tsavorite.test.session.context', 'Tsavorite.test.hlog', 'Tsavorite.test.recovery' ]
test: [ 'Tsavorite.test', 'Tsavorite.test.recordops', 'Tsavorite.test.session', 'Tsavorite.test.session.context', 'Tsavorite.test.hlog', 'Tsavorite.test.recovery', 'Tsavorite.test.stress' ]
os: [ ubuntu-latest, windows-latest ]
framework: [ 'net8.0', 'net10.0' ]
configuration: [ 'Debug', 'Release' ]
Expand Down Expand Up @@ -124,6 +124,7 @@ jobs:
'Tsavorite.test.session.context' = 'libs/storage/Tsavorite/cs/test/test.session.context'
'Tsavorite.test.hlog' = 'libs/storage/Tsavorite/cs/test/test.hlog'
'Tsavorite.test.recovery' = 'libs/storage/Tsavorite/cs/test/test.recovery'
'Tsavorite.test.stress' = 'libs/storage/Tsavorite/cs/test/test.stress'
}
$dir = $tsavoriteDirMap['${{ matrix.test }}']
echo "TSAVORITE_TEST_DIR=$dir" >> $env:GITHUB_ENV
Expand Down
2 changes: 1 addition & 1 deletion benchmark/BDN.benchmark/Cluster/ClusterContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ unsafe class ClusterContext
EmbeddedRespServer server;
RespServerSession session;
readonly BenchUtils benchUtils = new();
readonly int port = 7000;
readonly int port = 7000; // This is not in the shared ClusterTestContext so does not conflict

public static ReadOnlySpan<byte> keyTag => "{0}"u8;
public Request[] singleGetSet;
Expand Down
12 changes: 10 additions & 2 deletions libs/cluster/Server/Replication/CheckpointStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,16 @@ private void DeleteOutdatedCheckpoints()
// This is safe because curr is the oldest entry still referenced by active readers.
if (curr != null)
{
var hlogSize = storeWrapper.store.GetLogFileSize(curr.metadata.storeHlogToken);
storeWrapper.store.Log.ShiftBeginAddress(hlogSize.hybridLogFileStartAddress, truncateLog: true);
try
{
var hlogSize = storeWrapper.store.GetLogFileSize(curr.metadata.storeHlogToken);
storeWrapper.store.Log.ShiftBeginAddress(hlogSize.hybridLogFileStartAddress, truncateLog: true);
}
catch (Exception ex)
{
logger?.LogWarning(ex, "Skipping hlog truncation for checkpoint {token} because checkpoint metadata could not be read",
curr.metadata.storeHlogToken);
}
}

// Update head after delete
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,13 @@ private HybridLogRecoveryInfo ConvertMetadata(byte[] checkpointMetadata)
// Try to parse new format where cookie is embedded inside the HybridLogRecoveryInfo
try
{
using (StreamReader s = new(new MemoryStream(checkpointMetadata)))
{
recoveryInfo.Initialize(s);
}
using var s = new StreamReader(new MemoryStream(checkpointMetadata));
recoveryInfo.Initialize(s);
}
catch (Exception ex)
{
logger?.LogError(ex, "Best effort read of checkpoint metadata failed");
throw ex.InnerException;
throw;
}

return recoveryInfo;
Expand Down
4 changes: 3 additions & 1 deletion libs/host/Configuration/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,9 @@ internal sealed class Options : ICloneable
public int IndexResizeThreshold { get; set; }

// ValueOverflowThreshold must be at least 64 bytes and strictly less than PageSize (both after rounding down to the previous power of 2).
// Validated at server-options consumption time; see GarnetServerOptions.ValueOverflowThresholdBytes.
// Validated at server-options consumption time; see GarnetServerOptions.ValueOverflowThresholdBytes. Note that we do not have a KeyOverflowThreshold
// because it would complicate the minimum pagesize check that uses ValueOverflowThreshold check at startup; keys are usually small so calculating a
// minimum page size using a large(ish) Key threshold would have spurious errors. We'll defer that rare case to runtime checks.
[MemorySizeValidation(isRequired: false)]
[Option("value-overflow-threshold", Required = false, HelpText = "Max size of a value stored inline in the main-log page (larger values overflow to the heap). Accepts a memory size (e.g. 4k, 1m). Minimum 64 bytes; must be less than PageSize.")]
public string ValueOverflowThreshold { get; set; }
Expand Down
Loading
Loading