diff --git a/loadtest/scripts/collect-server-baseline.sh b/loadtest/scripts/collect-server-baseline.sh index 25ff404..5dd45a5 100644 --- a/loadtest/scripts/collect-server-baseline.sh +++ b/loadtest/scripts/collect-server-baseline.sh @@ -1,14 +1,20 @@ #!/usr/bin/env bash set -euo pipefail -if [[ $# -lt 2 || $# -gt 3 ]]; then - echo "usage: $0 OUTPUT_DIR DURATION_SECONDS [INTERVAL_SECONDS]" >&2 +if [[ $# -lt 2 || $# -gt 4 ]]; then + echo "usage: $0 OUTPUT_DIR DURATION_SECONDS [INTERVAL_SECONDS] [ENABLE_PY_SPY]" >&2 exit 2 fi output_dir=$1 duration_seconds=$2 interval_seconds=${3:-1} +enable_py_spy=${4:-true} + +if [[ "$enable_py_spy" != "true" && "$enable_py_spy" != "false" ]]; then + echo "ENABLE_PY_SPY must be true or false" >&2 + exit 2 +fi mkdir -p "$output_dir" @@ -43,8 +49,11 @@ echo "timestamp,active_sessions,queue_size,received_real,processed_real,dropped_ pidstat -h -r -u -p "$server_pid" "$interval_seconds" "$duration_seconds" > "$output_dir/pidstat.txt" & pidstat_pid=$! -/opt/inno-live-venv/bin/py-spy record --pid "$server_pid" --duration "$duration_seconds" --rate 50 --format speedscope --output "$output_dir/py-spy-speedscope.json" & -py_spy_pid=$! +py_spy_pid="" +if [[ "$enable_py_spy" == "true" ]]; then + /opt/inno-live-venv/bin/py-spy record --pid "$server_pid" --duration "$duration_seconds" --rate 50 --format speedscope --output "$output_dir/py-spy-speedscope.json" & + py_spy_pid=$! +fi samples=$((duration_seconds / interval_seconds)) for ((sample = 0; sample < samples; sample++)); do @@ -65,7 +74,9 @@ for ((sample = 0; sample < samples; sample++)); do done wait "$pidstat_pid" -wait "$py_spy_pid" +if [[ -n "$py_spy_pid" ]]; then + wait "$py_spy_pid" +fi curl --fail --silent http://127.0.0.1:8000/health > "$output_dir/health-final.json" curl --fail --silent http://127.0.0.1:8000/sessions > "$output_dir/sessions-final.json" curl --fail --silent http://127.0.0.1:8000/metrics > "$output_dir/metrics-final.prom" diff --git a/loadtest/windows/collect-server-metrics.ps1 b/loadtest/windows/collect-server-metrics.ps1 new file mode 100644 index 0000000..d0655c9 --- /dev/null +++ b/loadtest/windows/collect-server-metrics.ps1 @@ -0,0 +1,141 @@ +param( + [Parameter(Mandatory = $true)] + [string]$OutputDirectory, + + [int]$DurationSeconds = 360, + [int]$IntervalSeconds = 1, + [int]$WaitTimeoutSeconds = 600, + [ValidateSet("bypass", "fixed_delay", "real")] + [string]$Mode = "fixed_delay", + [string]$BaseUrl = "http://127.0.0.1:8000" +) + +$ErrorActionPreference = "Stop" + +function Get-MetricsText { + (Invoke-WebRequest -UseBasicParsing -Uri "$BaseUrl/metrics" -TimeoutSec 5).Content +} + +function Get-MetricValue { + param( + [string]$Text, + [string]$Name, + [string]$Labels = "", + [switch]$ZeroIfMissing + ) + + $escapedName = [regex]::Escape($Name) + $escapedLabels = [regex]::Escape($Labels) + $pattern = if ($Labels) { + "(?m)^${escapedName}\{${escapedLabels}\} ([0-9.eE+-]+)$" + } else { + "(?m)^${escapedName} ([0-9.eE+-]+)$" + } + + $match = [regex]::Match($Text, $pattern) + if ($match.Success) { + return [double]$match.Groups[1].Value + } + if ($ZeroIfMissing) { + return 0.0 + } + + $metric = if ($Labels) { "${Name}{${Labels}}" } else { $Name } + throw "Required Prometheus metric not found: $metric" +} + +function Assert-MetricFamily { + param( + [string]$Text, + [string]$Name + ) + + $escapedName = [regex]::Escape($Name) + if (-not [regex]::IsMatch($Text, "(?m)^# TYPE ${escapedName} ")) { + throw "Required Prometheus metric family not found: $Name" + } +} + +New-Item -ItemType Directory -Force -Path $OutputDirectory | Out-Null +$initialMetricsPath = Join-Path $OutputDirectory "metrics-initial.prom" +$finalMetricsPath = Join-Path $OutputDirectory "metrics-final.prom" +$samplesPath = Join-Path $OutputDirectory "app.csv" +$modeLabels = "mode=`"$Mode`"" + +$initialMetrics = Get-MetricsText +$requiredMetricFamilies = @( + "innolive_active_sessions", + "innolive_processing_queue_size", + "innolive_frame_received_total", + "innolive_frame_processed_total", + "innolive_frame_dropped_total", + "innolive_frame_failures_total", + "process_cpu_seconds_total", + "process_resident_memory_bytes" +) +foreach ($metricName in $requiredMetricFamilies) { + Assert-MetricFamily -Text $initialMetrics -Name $metricName +} +$initialMetrics | Set-Content -Encoding utf8 -Path $initialMetricsPath +(Invoke-WebRequest -UseBasicParsing -Uri "$BaseUrl/health" -TimeoutSec 5).Content | + Set-Content -Encoding utf8 -Path (Join-Path $OutputDirectory "health-initial.json") + +"timestamp,active_sessions,queue_size,received_$Mode,processed_$Mode,dropped_$Mode,failures_$Mode,process_cpu_seconds,process_rss_bytes" | + Set-Content -Encoding utf8 -Path $samplesPath + +Write-Host "Waiting for an active WebRTC session (timeout: ${WaitTimeoutSeconds}s)" +$deadline = (Get-Date).AddSeconds($WaitTimeoutSeconds) +do { + $metrics = Get-MetricsText + $active = Get-MetricValue -Text $metrics -Name "innolive_active_sessions" + if ($active -gt 0) { + break + } + if ((Get-Date) -ge $deadline) { + throw "Timed out waiting for an active WebRTC session" + } + Start-Sleep -Seconds 1 +} while ($true) + +Write-Host "Active session detected; collecting for ${DurationSeconds}s" +$metricDeadline = (Get-Date).AddSeconds(10) +do { + $metrics = Get-MetricsText + try { + $null = Get-MetricValue -Text $metrics -Name "innolive_frame_received_total" -Labels $modeLabels + $null = Get-MetricValue -Text $metrics -Name "innolive_frame_processed_total" -Labels $modeLabels + break + } catch { + if ((Get-Date) -ge $metricDeadline) { + throw + } + Start-Sleep -Seconds 1 + } +} while ($true) + +$end = (Get-Date).AddSeconds($DurationSeconds) +do { + $metrics = Get-MetricsText + $timestamp = (Get-Date).ToString("o") + $active = Get-MetricValue -Text $metrics -Name "innolive_active_sessions" + $queue = Get-MetricValue -Text $metrics -Name "innolive_processing_queue_size" + $received = Get-MetricValue -Text $metrics -Name "innolive_frame_received_total" -Labels $modeLabels + $processed = Get-MetricValue -Text $metrics -Name "innolive_frame_processed_total" -Labels $modeLabels + $dropped = Get-MetricValue -Text $metrics -Name "innolive_frame_dropped_total" -Labels $modeLabels -ZeroIfMissing + $failures = Get-MetricValue -Text $metrics -Name "innolive_frame_failures_total" -Labels $modeLabels -ZeroIfMissing + $cpu = Get-MetricValue -Text $metrics -Name "process_cpu_seconds_total" + $rss = Get-MetricValue -Text $metrics -Name "process_resident_memory_bytes" + + "$timestamp,$active,$queue,$received,$processed,$dropped,$failures,$cpu,$rss" | + Add-Content -Encoding utf8 -Path $samplesPath + Start-Sleep -Seconds $IntervalSeconds +} while ((Get-Date) -lt $end) + +$finalMetrics = Get-MetricsText +$finalMetrics | Set-Content -Encoding utf8 -Path $finalMetricsPath +(Invoke-WebRequest -UseBasicParsing -Uri "$BaseUrl/health" -TimeoutSec 5).Content | + Set-Content -Encoding utf8 -Path (Join-Path $OutputDirectory "health-final.json") +(Invoke-WebRequest -UseBasicParsing -Uri "$BaseUrl/sessions" -TimeoutSec 5).Content | + Set-Content -Encoding utf8 -Path (Join-Path $OutputDirectory "sessions-final.json") + +Write-Host "Server metrics saved to $OutputDirectory" diff --git a/test-docs/results/fixed-delay-20ms/1-session.md b/test-docs/results/fixed-delay-20ms/1-session.md new file mode 100644 index 0000000..21da3ab --- /dev/null +++ b/test-docs/results/fixed-delay-20ms/1-session.md @@ -0,0 +1,49 @@ +# Python fixed_delay 20ms — 1세션 + +## 대표 상세 실행 정보 + +- 클라이언트 원본: `baseline-fixed-delay-20ms-5m.json` / `.csv` +- 원본 검증값: [artifact-manifest.md](artifact-manifest.md) +- 시작: 2026-07-16 22:29:54 KST +- 종료: 2026-07-16 22:34:59 KST +- 전체 실행 시간: 305.48초 +- 입력: `720p30.ivf` (1280×720, VP8, 30FPS) +- 안정 구간: 최초 30초 이후 표본 +- 결과: **대표 실행 통합 기준 통과** + +## 전체 반복 실행 요약 + +| 실행 | 시작 | 평균 수신 FPS | 평균 프레임 차이 | RTT p95 최대 | 최종 연결 | 클라이언트 판정 | +| --- | --- | ---: | ---: | ---: | ---: | --- | +| `baseline-fixed-delay-20ms-5m` | 07-16 22:29:54 | 29.61 | 1.31% | 32.71ms | 1/1 | 통과 | +| `fixed-delay-20ms-stateful-1-after-disconnect-5m` | 07-17 00:08:03 | 29.75 | 0.77% | 18.95ms | 1/1 | 통과 | + +- 전체 성공률: 2/2 (100%) +- 성공 실행 평균 수신 FPS: 29.68 +- 성공 실행 평균 프레임 차이: 1.04% +- 실행별 RTT p95 최댓값 평균: 25.83ms + +## 클라이언트 처리량 + +| 클라이언트 | 평균 수신 FPS | 수신 FPS p5 | 0 제외 최소 FPS | 0 표본 | 송신 프레임 | 수신 프레임 | 프레임 차이 | 손실 RTP 패킷 | RTP 손실률 | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 29.61 | 28.20 | 24.20 | 0 | 8,999 | 8,881 | 1.31% | 0 | 0.000% | + +## 클라이언트 네트워크 및 연결 상태 + +| 클라이언트 | jitter 평균/p95/최대 | RTT 평균/p95/최대 | 최종 PeerConnection / ICE | +| ---: | ---: | ---: | --- | +| 1 | 3.41 / 5.84 / 20.55ms | 15.88 / 32.71 / 150.99ms | `connected` / `connected` | + +## 서버 수집 결과 + +연결 설정과 종료 정리를 포함해 324초 동안 서버 지표를 수집했습니다. 수신·처리 +프레임은 각각 8,882개, stale drop은 110개(1.24%), 처리 오류는 0건이었고 처리 큐 +최댓값은 0이었습니다. 전체 수집 구간의 CPU 사용량은 약 0.74코어, 종료 시 RSS는 +811.19MiB였으며 최종 세션 목록은 비어 있었습니다. 연결 설정과 종료가 포함된 전체 +구간 FPS는 최초 30초를 제외한 합격 판정에는 사용하지 않았습니다. + +## 판정 + +평균 수신 FPS가 27을 넘고 프레임 차이가 10% 미만이었습니다. 연결이 끝까지 +유지됐으며 서버 처리 오류와 큐 증가도 없었습니다. diff --git a/test-docs/results/fixed-delay-20ms/2-sessions.md b/test-docs/results/fixed-delay-20ms/2-sessions.md new file mode 100644 index 0000000..d2ec976 --- /dev/null +++ b/test-docs/results/fixed-delay-20ms/2-sessions.md @@ -0,0 +1,50 @@ +# Python fixed_delay 20ms — 2세션 + +## 대표 상세 실행 정보 + +- 클라이언트 원본: `fixed-delay-20ms-2-clients-5m.json` / `.csv` +- 원본 검증값: [artifact-manifest.md](artifact-manifest.md) +- 시작: 2026-07-16 22:39:35 KST +- 종료: 2026-07-16 22:44:46 KST +- 전체 실행 시간: 310.63초 +- 입력: `720p30.ivf` (1280×720, VP8, 30FPS) +- 안정 구간: 최초 30초 이후 표본 +- 결과: **통합 기준 통과** + +## 전체 반복 실행 요약 + +| 실행 | 시작 | 평균 수신 FPS | 최저 세션 평균 FPS | 평균/최대 프레임 차이 | RTT p95 최대 | 최종 연결 | 클라이언트 판정 | +| --- | --- | ---: | ---: | ---: | ---: | ---: | --- | +| `fixed-delay-20ms-2-clients-5m` | 07-16 22:39:35 | 29.57 | 29.56 | 1.48% / 1.53% | 34.93ms | 2/2 | 통과 | + +- 전체 성공률: 1/1 (100%) +- 성공 실행 평균 수신 FPS: 29.57 +- 성공 실행 평균 프레임 차이: 1.48% +- 반복 실행이 1건이므로 반복성은 아직 확정하지 않습니다. + +## 클라이언트 처리량 + +| 클라이언트 | 평균 수신 FPS | 수신 FPS p5 | 0 제외 최소 FPS | 0 표본 | 송신 프레임 | 수신 프레임 | 프레임 차이 | 손실 RTP 패킷 | RTP 손실률 | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 29.57 | 28.80 | 26.60 | 0 | 9,158 | 9,027 | 1.43% | 0 | 0.000% | +| 2 | 29.56 | 27.40 | 26.40 | 0 | 8,999 | 8,861 | 1.53% | 0 | 0.000% | + +## 클라이언트 네트워크 및 연결 상태 + +| 클라이언트 | jitter 평균/p95/최대 | RTT 평균/p95/최대 | 최종 PeerConnection / ICE | +| ---: | ---: | ---: | --- | +| 1 | 3.68 / 9.48 / 13.24ms | 14.01 / 34.93 / 81.63ms | `connected` / `connected` | +| 2 | 4.09 / 12.51 / 23.98ms | 11.69 / 26.09 / 165.37ms | `connected` / `connected` | + +## 서버 수집 결과 + +326초 동안 서버 지표를 수집했습니다. 서버 수신 17,890프레임, 처리 17,889프레임, +stale drop 256개(1.43%), 처리 오류 0건, 처리 큐 최댓값 0을 기록했습니다. 전체 수집 +구간의 CPU 사용량은 약 1.06코어였고 종료 시 RSS는 932.81MiB였습니다. 최종 세션 +목록은 비어 있었습니다. 연결 설정과 종료가 포함되므로 전체 구간 FPS는 참고값으로만 +사용합니다. + +## 판정 + +두 세션 모두 평균 수신 27FPS를 넘고 연결을 유지했으며 최대 프레임 차이는 1.53%로 +10% 기준을 충분히 만족했습니다. diff --git a/test-docs/results/fixed-delay-20ms/4-sessions.md b/test-docs/results/fixed-delay-20ms/4-sessions.md new file mode 100644 index 0000000..467e03d --- /dev/null +++ b/test-docs/results/fixed-delay-20ms/4-sessions.md @@ -0,0 +1,56 @@ +# Python fixed_delay 20ms — 4세션 + +## 대표 상세 실행 정보 + +- 클라이언트 원본: `fixed-delay-20ms-4-clients-5m.json` / `.csv` +- 원본 검증값: [artifact-manifest.md](artifact-manifest.md) +- 시작: 2026-07-16 22:49:14 KST +- 종료: 2026-07-16 22:54:35 KST +- 전체 실행 시간: 321.27초 +- 입력: `720p30.ivf` (1280×720, VP8, 30FPS) +- 안정 구간: 최초 30초 이후 표본 +- 결과: **대표 실행 통합 기준 통과** + +## 전체 반복 실행 요약 + +| 실행 | 시작 | 평균 수신 FPS | 최저 세션 평균 FPS | 평균/최대 프레임 차이 | RTT p95 최대 | 최종 연결 | 클라이언트 판정 | +| --- | --- | ---: | ---: | ---: | ---: | ---: | --- | +| `fixed-delay-20ms-4-clients-5m` | 07-16 22:49:14 | 29.06 | 28.91 | 3.18% / 3.66% | 96.76ms | 4/4 | 통과 | +| `fixed-delay-20ms-stateful-4-after-disconnect-5m` | 07-17 00:16:09 | 29.62 | 29.48 | 1.25% / 1.69% | 58.98ms | 4/4 | 통과 | + +- 전체 성공률: 2/2 (100%) +- 성공 실행 평균 수신 FPS: 29.34 +- 성공 실행 평균 프레임 차이: 2.22% +- 실행별 최대 프레임 차이 평균: 2.67% +- 실행별 RTT p95 최댓값 평균: 77.87ms + +## 클라이언트 처리량 + +| 클라이언트 | 평균 수신 FPS | 수신 FPS p5 | 0 제외 최소 FPS | 0 표본 | 송신 프레임 | 수신 프레임 | 프레임 차이 | 손실 RTP 패킷 | RTP 손실률 | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 29.18 | 28.20 | 26.40 | 0 | 9,477 | 9,212 | 2.80% | 0 | 0.000% | +| 2 | 29.14 | 27.60 | 25.60 | 0 | 9,318 | 9,045 | 2.93% | 0 | 0.000% | +| 3 | 29.00 | 26.80 | 24.80 | 0 | 9,159 | 8,852 | 3.35% | 0 | 0.000% | +| 4 | 28.91 | 26.60 | 22.60 | 0 | 8,999 | 8,670 | 3.66% | 0 | 0.000% | + +## 클라이언트 네트워크 및 연결 상태 + +| 클라이언트 | jitter 평균/p95/최대 | RTT 평균/p95/최대 | 최종 PeerConnection / ICE | +| ---: | ---: | ---: | --- | +| 1 | 4.46 / 10.54 / 17.32ms | 21.53 / 59.62 / 196.69ms | `connected` / `connected` | +| 2 | 4.03 / 8.41 / 15.97ms | 21.29 / 96.76 / 111.40ms | `connected` / `connected` | +| 3 | 4.60 / 9.51 / 16.89ms | 24.17 / 92.93 / 244.00ms | `connected` / `connected` | +| 4 | 4.39 / 8.54 / 12.47ms | 20.30 / 66.82 / 153.63ms | `connected` / `connected` | + +## 서버 수집 결과 + +362초 동안 서버 지표를 수집했습니다. 서버 수신·처리 프레임은 각각 35,783개, +stale drop은 1,141개(3.19%), 처리 오류는 0건이었고 처리 큐 최댓값은 0이었습니다. +전체 수집 구간의 CPU 사용량은 약 1.83코어, 종료 시 RSS는 1,129.31MiB였습니다. +최종 세션 목록은 비어 있었습니다. 연결 설정과 종료가 포함된 전체 구간 FPS는 안정 +구간 판정값이 아닌 참고값입니다. + +## 판정 + +네 세션 모두 평균 수신 27FPS를 넘었습니다. 최대 프레임 차이는 3.66%였고 모든 +PeerConnection과 ICE 상태가 `connected`로 유지됐습니다. diff --git a/test-docs/results/fixed-delay-20ms/5-sessions.md b/test-docs/results/fixed-delay-20ms/5-sessions.md new file mode 100644 index 0000000..914991e --- /dev/null +++ b/test-docs/results/fixed-delay-20ms/5-sessions.md @@ -0,0 +1,49 @@ +# Python fixed_delay 20ms — 5세션 + +## 시험 정보 + +- 클라이언트 원본: `fixed-delay-20ms-stateful-5-boundary-5m.json` / `.csv` +- 원본 검증값: [artifact-manifest.md](artifact-manifest.md) +- 시작: 2026-07-17 00:30:55 KST +- 종료: 2026-07-17 00:36:21 KST +- 전체 실행 시간: 326.49초 +- 입력: `720p30.ivf` (1280×720, VP8, 30FPS) +- 안정 구간: 최초 30초 이후 표본 +- 결과: **실패** + +## 전체 반복 실행 요약 + +| 실행 | 평균 수신 FPS | 최저 세션 평균 FPS | 평균/최대 프레임 차이 | RTT p95 최대 | 최종 연결 | 클라이언트 판정 | +| --- | ---: | ---: | ---: | ---: | ---: | --- | +| `fixed-delay-20ms-stateful-5-boundary-5m` | 23.49 | 20.46 | 20.84% / 30.03% | 1,404.51ms | 4/5 | 실패 | + +- 전체 성공률: 0/1 (0%) +- 성공 실행 평균: 성공 실행이 없어 계산하지 않음 +- 단일 실패 실행만 있으므로 서버의 5세션 고정 용량 경계로 확정하지 않음 + +## 클라이언트 처리량 + +| 클라이언트 | 평균 수신 FPS | 수신 FPS p5 | 0 제외 최소 FPS | 0 표본 | 송신 프레임 | 수신 프레임 | 프레임 차이 | 손실 RTP 패킷 | RTP 손실률 | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 24.62 | 7.40 | 5.60 | 0 | 9,635 | 7,920 | 17.80% | 2 | 0.011% | +| 2 | 20.46 | 0.00 | 4.00 | 14 | 9,476 | 6,630 | 30.03% | 0 | 0.000% | +| 3 | 23.93 | 6.60 | 4.60 | 0 | 9,318 | 7,461 | 19.93% | 5 | 0.027% | +| 4 | 24.39 | 7.60 | 5.80 | 0 | 9,159 | 7,479 | 18.34% | 5 | 0.028% | +| 5 | 24.06 | 6.20 | 5.00 | 1 | 8,999 | 7,371 | 18.09% | 3 | 0.017% | + +## 클라이언트 네트워크 및 연결 상태 + +| 클라이언트 | jitter 평균/p95/최대 | RTT 평균/p95/최대 | 최종 PeerConnection / ICE | +| ---: | ---: | ---: | --- | +| 1 | 5.96 / 16.13 / 21.44ms | 246.80 / 1,350.62 / 1,478.47ms | `connected` / `connected` | +| 2 | 6.31 / 11.78 / 17.42ms | 421.75 / 1,304.28 / 1,473.51ms | `failed` / `failed` | +| 3 | 5.47 / 15.27 / 23.32ms | 266.83 / 1,394.26 / 1,698.52ms | `connected` / `connected` | +| 4 | 6.05 / 16.26 / 28.17ms | 249.54 / 1,404.51 / 1,587.83ms | `connected` / `connected` | +| 5 | 5.74 / 16.25 / 19.25ms | 240.66 / 1,389.36 / 1,421.40ms | `connected` / `connected` | + +## 판정 + +모든 세션의 평균 FPS와 프레임 차이가 기준을 위반했고 RTT도 초 단위로 증가했습니다. +이후 같은 서버에서 6·7·8세션이 통과했으므로 이 결과는 서버의 단조로운 용량 한계가 +아니라 당시 공인 UDP/NAT 경로 이상 사례로 분류합니다. 실패 결과 자체는 전체 반복 +성공률과 네트워크 이상 이력에 포함합니다. diff --git a/test-docs/results/fixed-delay-20ms/6-sessions.md b/test-docs/results/fixed-delay-20ms/6-sessions.md new file mode 100644 index 0000000..2e6aef0 --- /dev/null +++ b/test-docs/results/fixed-delay-20ms/6-sessions.md @@ -0,0 +1,77 @@ +# Python fixed_delay 20ms — 6세션 + +## 대표 상세 실행 정보 + +- 클라이언트 원본: `fixed-delay-20ms-6-no-diagnostic-traffic.json` / `.csv` +- 원본 검증값: [artifact-manifest.md](artifact-manifest.md) +- 시작: 2026-07-17 02:02:52 KST +- 종료: 2026-07-17 02:08:24 KST +- 전체 실행 시간: 332.25초 +- 입력: `720p30.ivf` (1280×720, VP8, 30FPS) +- 안정 구간: 최초 30초 이후 표본 +- 별도 health/ping 트래픽: 사용하지 않음 +- 결과: **클라이언트 기준 통과, 서버 기준 미측정** + +## 전체 반복 실행 요약 + +| 실행 | 시작 | 평균 수신 FPS | 최저 세션 평균 FPS | 평균/최대 프레임 차이 | RTT p95 최대 | 최종 연결 | 클라이언트 판정 | +| --- | --- | ---: | ---: | ---: | ---: | ---: | --- | +| `fixed-delay-20ms-6-clients-5m` | 07-16 23:43:19 | 13.72 | 6.63 | 52.21% / 76.99% | 889.88ms | 2/6 | 실패 | +| `fixed-delay-20ms-6-clients-stateful-no-profiler` | 07-16 23:58:17 | 15.29 | 14.86 | 46.98% / 48.68% | 1,041.49ms | 0/6 | 실패 | +| `fixed-delay-20ms-stateful-6-repeat-1` | 07-17 00:44:36 | 15.93 | 8.55 | 45.72% / 70.01% | 2,169.56ms | 2/6 | 실패 | +| `fixed-delay-20ms-stateful-6-repeat-2` | 07-17 00:57:37 | 14.56 | 0.17 | 50.32% / 96.42% | 3,420.81ms | 2/6 | 실패 | +| `fixed-delay-20ms-fresh-server-6-compare` | 07-17 01:08:08 | 1.37 | 0.74 | 93.98% / 97.34% | 4,151.75ms | 0/6 | 실패 | +| `fixed-delay-20ms-6-network-diagnostic` | 07-17 01:22:39 | 29.29 | 28.83 | 1.89% / 2.42% | 61.48ms | 6/6 | 통과 | +| `fixed-delay-20ms-6-back-to-back-diagnostic` | 07-17 01:34:26 | 29.04 | 28.65 | 2.66% / 2.90% | 52.41ms | 6/6 | 통과 | +| `fixed-delay-20ms-6-back-to-back-diagnostic2` | 07-17 01:45:09 | 29.31 | 28.94 | 1.72% / 1.91% | 228.74ms | 6/6 | 통과 | +| `fixed-delay-20ms-6-back-to-back-diagnostic3` | 07-17 01:50:46 | 29.15 | 28.82 | 2.33% / 2.51% | 91.90ms | 6/6 | 통과 | +| `fixed-delay-20ms-6-no-diagnostic-traffic` | 07-17 02:02:52 | 29.39 | 28.98 | 1.53% / 1.82% | 58.03ms | 6/6 | 통과 | + +- 전체 성공률: 5/10 (50%) +- 성공 실행 평균 수신 FPS: 29.24 +- 성공 실행 평균 프레임 차이: 2.03% +- 성공 실행별 최대 프레임 차이 평균: 2.31% +- 성공 실행별 RTT p95 최댓값 평균: 98.51ms + +정상 수치만 보면 6세션 처리량은 충분하지만 공인망 End-to-End 반복성은 50%입니다. +실패 실행도 평균에서 삭제하지 않고 위 표와 성공률에 보존합니다. 다만 정상 성능 평균은 +서버가 처리 가능한 상태의 수치를 보기 위해 통과한 5건만 사용합니다. + +## 클라이언트 처리량 + +| 클라이언트 | 평균 수신 FPS | 수신 FPS p5 | 0 제외 최소 FPS | 0 표본 | 송신 프레임 | 수신 프레임 | 프레임 차이 | 손실 RTP 패킷 | RTP 손실률 | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 29.56 | 28.60 | 26.60 | 0 | 9,803 | 9,662 | 1.44% | 5 | 0.025% | +| 2 | 29.48 | 28.20 | 25.80 | 0 | 9,645 | 9,474 | 1.77% | 2 | 0.010% | +| 3 | 29.60 | 28.80 | 26.00 | 0 | 9,487 | 9,356 | 1.38% | 1 | 0.005% | +| 4 | 29.68 | 29.00 | 28.00 | 0 | 9,316 | 9,214 | 1.09% | 0 | 0.000% | +| 5 | 28.98 | 28.00 | 25.80 | 1 | 9,158 | 8,991 | 1.82% | 0 | 0.000% | +| 6 | 29.02 | 26.80 | 26.40 | 1 | 8,999 | 8,847 | 1.69% | 0 | 0.000% | + +## 클라이언트 네트워크 및 연결 상태 + +| 클라이언트 | jitter 평균/p95/최대 | RTT 평균/p95/최대 | 최종 PeerConnection / ICE | +| ---: | ---: | ---: | --- | +| 1 | 3.19 / 4.88 / 8.20ms | 15.17 / 31.85 / 134.00ms | `connected` / `connected` | +| 2 | 3.19 / 5.09 / 6.70ms | 16.36 / 58.03 / 173.84ms | `connected` / `connected` | +| 3 | 2.98 / 5.32 / 7.46ms | 7.28 / 30.20 / 61.22ms | `connected` / `connected` | +| 4 | 3.12 / 4.81 / 7.00ms | 8.83 / 27.82 / 76.25ms | `connected` / `connected` | +| 5 | 3.51 / 6.05 / 30.62ms | 13.92 / 39.60 / 143.37ms | `connected` / `connected` | +| 6 | 3.06 / 4.67 / 8.21ms | 13.16 / 42.19 / 63.17ms | `connected` / `connected` | + +## 서버 수집 결과 + +이 대표 무진단 실행에는 동일 시점의 서버 자원 수집기를 연결하지 않았습니다. 다른 +6세션 성공 실행에는 서버 카운터가 있지만 서로 다른 실행의 값을 섞지 않기 위해 이 +문서에는 포함하지 않았습니다. + +## 판정 및 반복성 + +여섯 세션이 클라이언트 기준을 모두 통과했습니다. 최대 프레임 차이는 1.82%, RTT p95 최댓값은 +58.03ms였고 최종 연결 상태는 모두 `connected`였습니다. 클라이언트 5와 6의 0FPS +표본 1개는 순차 연결 직후의 첫 속도 계산 표본이며 이후 누적 수신 프레임은 계속 +증가했습니다. + +이전에 발생한 비단조 6세션 실패는 상위 결과 문서에서 공인 UDP/NAT 경로 이상으로 +분리했으며 서버 용량 경계로 판정하지 않았습니다. 대표 실행에는 동시점 서버 지표가 +없으므로 서버 FPS·오류율·큐 기준의 전체 합격으로도 판정하지 않습니다. diff --git a/test-docs/results/fixed-delay-20ms/7-sessions.md b/test-docs/results/fixed-delay-20ms/7-sessions.md new file mode 100644 index 0000000..ba7c5ed --- /dev/null +++ b/test-docs/results/fixed-delay-20ms/7-sessions.md @@ -0,0 +1,61 @@ +# Python fixed_delay 20ms — 7세션 + +## 대표 상세 실행 정보 + +- 클라이언트 원본: `fixed-delay-20ms-7-no-diagnostic.json` / `.csv` +- 원본 검증값: [artifact-manifest.md](artifact-manifest.md) +- 시작: 2026-07-17 02:12:36 KST +- 종료: 2026-07-17 02:18:13 KST +- 전체 실행 시간: 337.31초 +- 입력: `720p30.ivf` (1280×720, VP8, 30FPS) +- 안정 구간: 최초 30초 이후 표본 +- 별도 health/ping 트래픽: 사용하지 않음 +- 결과: **클라이언트 기준 통과, 서버 기준 미측정** + +## 전체 반복 실행 요약 + +| 실행 | 시작 | 평균 수신 FPS | 최저 세션 평균 FPS | 평균/최대 프레임 차이 | RTT p95 최대 | 최종 연결 | 클라이언트 판정 | +| --- | --- | ---: | ---: | ---: | ---: | ---: | --- | +| `fixed-delay-20ms-7-no-diagnostic` | 07-17 02:12:36 | 29.10 | 28.77 | 2.36% / 3.08% | 26.95ms | 7/7 | 통과 | + +- 전체 성공률: 1/1 (100%) +- 성공 실행 평균 수신 FPS: 29.10 +- 성공 실행 평균 프레임 차이: 2.36% +- 반복 실행이 1건이므로 반복성은 아직 확정하지 않습니다. + +## 클라이언트 처리량 + +| 클라이언트 | 평균 수신 FPS | 수신 FPS p5 | 0 제외 최소 FPS | 0 표본 | 송신 프레임 | 수신 프레임 | 프레임 차이 | 손실 RTP 패킷 | RTP 손실률 | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 29.23 | 28.20 | 23.00 | 0 | 9,958 | 9,694 | 2.65% | 2 | 0.010% | +| 2 | 29.06 | 26.00 | 23.60 | 0 | 9,800 | 9,498 | 3.08% | 0 | 0.000% | +| 3 | 29.25 | 27.40 | 23.00 | 0 | 9,642 | 9,401 | 2.50% | 0 | 0.000% | +| 4 | 29.45 | 28.60 | 26.80 | 0 | 9,484 | 9,307 | 1.87% | 2 | 0.010% | +| 5 | 28.84 | 27.00 | 26.40 | 1 | 9,313 | 9,095 | 2.34% | 0 | 0.000% | +| 6 | 29.08 | 28.80 | 27.00 | 1 | 9,153 | 9,014 | 1.52% | 0 | 0.000% | +| 7 | 28.77 | 26.60 | 25.60 | 1 | 8,999 | 8,769 | 2.56% | 5 | 0.027% | + +## 클라이언트 네트워크 및 연결 상태 + +| 클라이언트 | jitter 평균/p95/최대 | RTT 평균/p95/최대 | 최종 PeerConnection / ICE | +| ---: | ---: | ---: | --- | +| 1 | 3.66 / 6.37 / 8.21ms | 7.81 / 16.89 / 70.02ms | `connected` / `connected` | +| 2 | 3.46 / 6.26 / 7.00ms | 7.71 / 16.51 / 109.45ms | `connected` / `connected` | +| 3 | 3.52 / 7.14 / 8.38ms | 8.97 / 26.31 / 57.74ms | `connected` / `connected` | +| 4 | 3.71 / 6.92 / 10.03ms | 10.46 / 22.00 / 73.36ms | `connected` / `connected` | +| 5 | 3.65 / 6.23 / 8.04ms | 6.43 / 10.96 / 25.89ms | `connected` / `connected` | +| 6 | 3.26 / 5.96 / 6.49ms | 7.42 / 21.93 / 36.70ms | `connected` / `connected` | +| 7 | 3.80 / 6.40 / 8.51ms | 10.37 / 26.95 / 59.91ms | `connected` / `connected` | + +## 서버 수집 결과 + +이 실행에는 동일 시점의 서버 자원 수집기를 연결하지 않았습니다. 바로 다음 8세션 +실행에서 전체 서버 지표를 수집했습니다. + +## 판정 + +모든 세션이 평균 수신 27FPS를 넘었습니다. 최대 프레임 차이는 3.08%, RTT p95 +최댓값은 26.95ms였으며 최종 PeerConnection과 ICE 상태는 모두 `connected`였습니다. +후순위 클라이언트의 0FPS 값은 순차 연결 직후의 초기 속도 표본이며 지속 정지가 +아닙니다. 같은 시점 서버 지표가 없으므로 서버 FPS·오류율·큐 기준은 측정 보류이며, +이 실행을 전체 합격이나 최대 안정 용량으로 확정하지 않습니다. diff --git a/test-docs/results/fixed-delay-20ms/8-sessions.md b/test-docs/results/fixed-delay-20ms/8-sessions.md new file mode 100644 index 0000000..8851e09 --- /dev/null +++ b/test-docs/results/fixed-delay-20ms/8-sessions.md @@ -0,0 +1,84 @@ +# Python fixed_delay 20ms — 8세션 + +## 대표 상세 실행 정보 + +- 클라이언트 원본: `fixed-delay-20ms-8-final-with-server-metrics.json` / `.csv` +- 서버 원본: [비공개 리소스 저장소의 불변 경로](https://github.com/team-framework/server-load-test-resources/tree/39c24c97218c28c74e01a5a938037506383d6f7c/results/2026-07-16-17/fixed-delay-20ms/server/fixed-delay-20ms-8-final) +- 원본 검증값: [artifact-manifest.md](artifact-manifest.md) +- 원본 공개 상태: 비공개 저장소, 접근 권한이 있는 리뷰어 검증 가능 +- 시작: 2026-07-17 02:29:13 KST +- 종료: 2026-07-17 02:34:56 KST +- 전체 실행 시간: 343.27초 +- 입력: `720p30.ivf` (1280×720, VP8, 30FPS) +- 안정 구간: 최초 30초 이후 표본 +- 별도 health/ping 트래픽: 사용하지 않음 +- 결과: **대표 실행 통합 기준 통과** + +## 전체 반복 실행 요약 + +| 실행 | 시작 | 평균 수신 FPS | 최저 세션 평균 FPS | 평균/최대 프레임 차이 | RTT p95 최대 | 최종 연결 | 판정 범위 | +| --- | --- | ---: | ---: | ---: | ---: | ---: | --- | +| `fixed-delay-20ms-8-clients-5m` | 07-16 23:00:19 | 28.05 | 27.68 | 5.68% / 6.33% | 218.81ms | 8/8 | 클라이언트 통과, 서버 미측정 | +| `fixed-delay-20ms-8-no-diagnostic` | 07-17 02:18:16 | 29.09 | 28.69 | 2.29% / 3.01% | 35.02ms | 8/8 | 클라이언트 통과, 서버 미측정 | +| `fixed-delay-20ms-8-final-with-server-metrics` | 07-17 02:29:13 | 28.49 | 28.18 | 4.25% / 4.98% | 41.47ms | 8/8 | 통합 기준 통과 | + +- 클라이언트 기준 통과율: 3/3 (100%) +- 동시점 서버 지표 포함 통합 기준 통과: 1/1 (나머지 2회 서버 기준 미측정) +- 성공 실행 평균 수신 FPS: 28.54 +- 성공 실행 평균 프레임 차이: 4.07% +- 성공 실행별 최대 프레임 차이 평균: 4.77% +- 성공 실행별 RTT p95 최댓값 평균: 98.43ms + +## 클라이언트 처리량 + +| 클라이언트 | 평균 수신 FPS | 수신 FPS p5 | 0 제외 최소 FPS | 0 표본 | 송신 프레임 | 수신 프레임 | 프레임 차이 | 손실 RTP 패킷 | RTP 손실률 | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1 | 28.74 | 27.60 | 25.40 | 0 | 10,138 | 9,718 | 4.14% | 0 | 0.000% | +| 2 | 28.44 | 25.80 | 23.46 | 0 | 9,979 | 9,482 | 4.98% | 1 | 0.005% | +| 3 | 28.70 | 27.20 | 24.40 | 0 | 9,809 | 9,391 | 4.26% | 1 | 0.005% | +| 4 | 28.63 | 27.40 | 24.60 | 0 | 9,650 | 9,210 | 4.56% | 0 | 0.000% | +| 5 | 28.18 | 26.40 | 25.20 | 1 | 9,477 | 9,040 | 4.61% | 0 | 0.000% | +| 6 | 28.45 | 27.60 | 25.40 | 1 | 9,318 | 8,974 | 3.69% | 0 | 0.000% | +| 7 | 28.39 | 26.80 | 25.20 | 1 | 9,159 | 8,806 | 3.85% | 0 | 0.000% | +| 8 | 28.38 | 27.80 | 27.60 | 1 | 8,999 | 8,649 | 3.89% | 0 | 0.000% | + +## 클라이언트 네트워크 및 연결 상태 + +| 클라이언트 | jitter 평균/p95/최대 | RTT 평균/p95/최대 | 최종 PeerConnection / ICE | +| ---: | ---: | ---: | --- | +| 1 | 3.60 / 6.52 / 8.94ms | 7.61 / 18.33 / 37.96ms | `connected` / `connected` | +| 2 | 3.90 / 7.01 / 10.20ms | 11.49 / 31.10 / 45.84ms | `connected` / `connected` | +| 3 | 3.52 / 6.62 / 9.08ms | 14.33 / 33.77 / 82.18ms | `connected` / `connected` | +| 4 | 3.68 / 6.23 / 8.69ms | 17.39 / 41.00 / 85.28ms | `connected` / `connected` | +| 5 | 3.55 / 6.87 / 8.13ms | 14.53 / 39.43 / 58.43ms | `connected` / `connected` | +| 6 | 3.37 / 5.51 / 7.33ms | 14.44 / 41.47 / 68.37ms | `connected` / `connected` | +| 7 | 3.44 / 5.99 / 9.62ms | 11.47 / 30.56 / 73.53ms | `connected` / `connected` | +| 8 | 3.76 / 6.39 / 10.42ms | 7.72 / 18.42 / 24.15ms | `connected` / `connected` | + +## 8세션 전체 활성 구간 서버 지표 + +| 지표 | 결과 | 판정 | +| --- | ---: | --- | +| 8세션 전체 활성 구간 | 303.4초 | 참고값 | +| 수신 프레임 | 69,549 | 참고값 | +| 처리 프레임 | 69,545 | 통과 | +| 전체 처리 FPS | 229.22 | 통과 | +| 세션당 처리 FPS | 28.65 | 통과 | +| stale drop | 3,102 / 69,549 (4.46%) | 통과 | +| 처리 오류 | 0 | 통과 | +| 평균 CPU | 4.13코어 | 참고값 | +| 평균 / 최대 RSS | 1.654 / 1.655GiB | 참고값 | +| 처리 큐 최댓값 | 0 | 통과 | +| 종료 후 활성 세션 / 큐 | 0 / 0 | 통과 | +| 종료 후 health | `ready` | 통과 | + +클라이언트 프레임 차이와 서버 stale drop은 일부 관측 구간이 겹치므로 각각 판정하며 +서로 더하지 않습니다. + +## 판정 + +클라이언트 기준은 3회 모두 통과했고, 서버 지표를 동시에 수집한 1회는 서버 기준도 +통과했습니다. 따라서 5분 fixed-delay 예비 시험에서 **8개 동시 세션 체크포인트를 +1회 통합 검증**했습니다. 나머지 2회에는 서버 지표가 없어 전체 기준 반복 통과로 +계산하지 않습니다. 8은 측정된 최대치가 아니며, 최대 안정 용량을 확정하려면 모든 +실행에서 동시점 서버 지표를 수집하고 경계 탐색과 30분 지속 시험을 진행해야 합니다. diff --git a/test-docs/results/fixed-delay-20ms/artifact-manifest.md b/test-docs/results/fixed-delay-20ms/artifact-manifest.md new file mode 100644 index 0000000..86bfd05 --- /dev/null +++ b/test-docs/results/fixed-delay-20ms/artifact-manifest.md @@ -0,0 +1,79 @@ +# fixed_delay 20ms 원본 매니페스트 + +이 문서는 2026-07-16~17 시험 결과 문서에 사용한 원본을 식별합니다. SHA-256은 +2026-07-17에 시험 PC의 원본에서 계산했고, 업로드된 Git blob이 로컬 원본과 바이트 +단위로 일치하는지 확인했습니다. + +기준 입력 영상과 아래 원본은 비공개 +[`team-framework/server-load-test-resources`](https://github.com/team-framework/server-load-test-resources/tree/39c24c97218c28c74e01a5a938037506383d6f7c) +저장소의 불변 커밋 `39c24c97218c28c74e01a5a938037506383d6f7c`에 있습니다. + +- 클라이언트 원본: [`results/2026-07-16-17/fixed-delay-20ms/client/`](https://github.com/team-framework/server-load-test-resources/tree/39c24c97218c28c74e01a5a938037506383d6f7c/results/2026-07-16-17/fixed-delay-20ms/client) +- 서버 원본: [`results/2026-07-16-17/fixed-delay-20ms/server/fixed-delay-20ms-8-final/`](https://github.com/team-framework/server-load-test-resources/tree/39c24c97218c28c74e01a5a938037506383d6f7c/results/2026-07-16-17/fixed-delay-20ms/server/fixed-delay-20ms-8-final) + +## 클라이언트 원본 + +| 파일 | 크기(byte) | SHA-256 | +| --- | ---: | --- | +| `baseline-fixed-delay-20ms-5m.csv` | 10,711 | `76D1DCCF4598A9CA7DCB73D07FB07F98F7970F366168AEA403A5A842A7509342` | +| `baseline-fixed-delay-20ms-5m.json` | 38,805 | `C32C6BCCD193861500A6E6A9CF63B6A846A12CCA942C20C84CB83D1C5B34BC8B` | +| `fixed-delay-20ms-2-clients-5m.csv` | 21,342 | `E780573089E4C51A3D4ADE90ED58A2DA7C3EA3359D7E60C175CAD717BB60B859` | +| `fixed-delay-20ms-2-clients-5m.json` | 77,628 | `2776BAD6E0080CB729F8E7B8BC0F48409C2A939DC20C06362590A493B265BE34` | +| `fixed-delay-20ms-4-clients-5m.csv` | 43,206 | `B5AC99AD69FC0D1426EAC96DD7064379E0B9028CCCC89D258ABEA2DC048E7072` | +| `fixed-delay-20ms-4-clients-5m.json` | 157,284 | `D774A67FD6F121ACDBDCF00F705F0FA3A0B797A3E4BB0B8EE3AB7170AEFDE66A` | +| `fixed-delay-20ms-6-back-to-back-diagnostic.csv` | 65,675 | `6BC8E0E0533017D6C8F16E692BE148E0180C409D474CD4EFF03FC9DBE04A3ED6` | +| `fixed-delay-20ms-6-back-to-back-diagnostic.json` | 240,258 | `D05E73AC4B0C3AF23D5BBF9165675F4A04B048C71E309AF3436FCCF357081C2D` | +| `fixed-delay-20ms-6-back-to-back-diagnostic2.csv` | 65,687 | `839DB6FA7605BA598F9D62A38C65F872ED4C27156FEC90606366B9C7BD1C8800` | +| `fixed-delay-20ms-6-back-to-back-diagnostic2.json` | 239,501 | `DDA3F1731BBCCF86AC24091389636801372C426757041A3BC7C2CFFF19B6F314` | +| `fixed-delay-20ms-6-back-to-back-diagnostic3.csv` | 65,692 | `CDB898509F23EFC92997D66D5DBEA5058AC0D62E2F41E2BB6FD8FF96D1DD7033` | +| `fixed-delay-20ms-6-back-to-back-diagnostic3.json` | 240,526 | `C5AA596ED07C50864EB3A91BF57CC481E93470C5C8DBE115C3A228F0C2B8F40D` | +| `fixed-delay-20ms-6-clients-5m.csv` | 67,844 | `B3019AE84072BCA5F948594832A827B50D1353EC2B21C2F01F4C874014337099` | +| `fixed-delay-20ms-6-clients-5m.json` | 246,360 | `F7B347A4BD55CD5A17C9E26BB7E3DB3610AB2A4559C975E3511CC9B28C5FF3D9` | +| `fixed-delay-20ms-6-clients-stateful-no-profiler.csv` | 68,036 | `E8EB9BD9F75DE50AEC504C8F397581CBC99569CC54E37A536F66DDD6B00088FA` | +| `fixed-delay-20ms-6-clients-stateful-no-profiler.json` | 244,692 | `FB41F1B4ECFB495C8D2B1F730DCD7FA1A038ED3C2CCCFC499EFD0C4942AF8108` | +| `fixed-delay-20ms-6-network-diagnostic.csv` | 65,700 | `98FDE82F700C279F0141718BC7A35E724E1193D4492AEE9AAC0C4D94D723E0B8` | +| `fixed-delay-20ms-6-network-diagnostic.json` | 241,617 | `A8C4DC7FB3842A08B6B8A42516EA49F20650B34B958F63F49BE3787CE031CD53` | +| `fixed-delay-20ms-6-no-diagnostic-traffic.csv` | 65,574 | `9D248FE9E0D1053A10D4DC1B6D0E3D3969CBB39BEAFC7A54FA9942E2D19CBBBA` | +| `fixed-delay-20ms-6-no-diagnostic-traffic.json` | 241,872 | `7D6D4133AB33C1872CD39E4F01ABBC0363E61E06985117F23AF1CD8A5DF709D1` | +| `fixed-delay-20ms-7-no-diagnostic.csv` | 77,060 | `6CA9A25353C4088B9A19AB2D8D36AB019D4F9FB11211253DA6BCCDF63C0C16D2` | +| `fixed-delay-20ms-7-no-diagnostic.json` | 282,340 | `61B5D2A1B38BCDE6C1C300C4A23F254A16510DA6BCB3582774170FEE6ADA54C4` | +| `fixed-delay-20ms-8-clients-5m.csv` | 89,054 | `F75AB8B11F57B854456E1A050D137E7B038AA05CB945782A0F8A62CD52080EEC` | +| `fixed-delay-20ms-8-clients-5m.json` | 324,723 | `BA84E3C620C2DBCFC269CD3766155C4981F1703C8533BD11354F08797AD34E15` | +| `fixed-delay-20ms-8-final-with-server-metrics.csv` | 88,825 | `AA829003C110DB194A46007016CB4FA36CA2E2FCDFCE3ACAA7AD493D905CDDC6` | +| `fixed-delay-20ms-8-final-with-server-metrics.json` | 324,801 | `90E35141C36E14B107D9B4CFAF3B822B8950B24F264AB8C1BB6ADEE93A948F95` | +| `fixed-delay-20ms-8-no-diagnostic.csv` | 88,723 | `00107854D0F82D92E3E8D48D43BDB9365A9D6D04B73C06B1FE761EE954A580FC` | +| `fixed-delay-20ms-8-no-diagnostic.json` | 329,057 | `2843F35E661D0F336733E783CFF83C629087EAFBC42F21855EF597C1595F3899` | +| `fixed-delay-20ms-fresh-server-6-compare.csv` | 69,569 | `F1B1D4E7B2C04485DE87F4F3D9CB25A6DED290643DFEC9722DFDF47C23AB506A` | +| `fixed-delay-20ms-fresh-server-6-compare.json` | 246,939 | `98980D7D6C3E8C56D27BA9111BC18E56D9ED89CDA9E9DF1DFDD4C124AA349837` | +| `fixed-delay-20ms-stateful-1-after-disconnect-5m.csv` | 10,682 | `713EB17AE04ADDA748E8CE1847A2BA2AECDA7A511AD0426FC42060B98A179716` | +| `fixed-delay-20ms-stateful-1-after-disconnect-5m.json` | 38,792 | `C34F95A0B461D0DC2D3581D6670F3D19C887DD8A74816D383B390A18EEB6EEF3` | +| `fixed-delay-20ms-stateful-4-after-disconnect-5m.csv` | 43,120 | `4E726DFF25BD1F2C519555E153523FA019D8B9C053E5005557AA743055D95EEE` | +| `fixed-delay-20ms-stateful-4-after-disconnect-5m.json` | 157,296 | `32FC0D7991968AA1D47271B4292166AED57DC31BD24A8176FD1240058A800694` | +| `fixed-delay-20ms-stateful-5-boundary-5m.csv` | 54,639 | `53193F627EC0294A144EC53D416BCC11933B243D9BB4E57C730F7AFAAAF78909` | +| `fixed-delay-20ms-stateful-5-boundary-5m.json` | 199,383 | `87A4C61EC11E81C3641B1863B5F011A94A023BA622B35A2070215313AFF65B2E` | +| `fixed-delay-20ms-stateful-6-repeat-1.csv` | 67,490 | `23E3E522CFDBE696C74E588360C0AD407520D48828258C5782639FD28A8DB16B` | +| `fixed-delay-20ms-stateful-6-repeat-1.json` | 247,764 | `828A2193430ECE01CFAC7B30C636C6DF4CF2BC78C78B87EF1CF8D50E4B5755E2` | +| `fixed-delay-20ms-stateful-6-repeat-2.csv` | 71,477 | `A234C9ECAD9EB42FD9B4620F88C26CA46D9EA04039529A169DF0216798A4321B` | +| `fixed-delay-20ms-stateful-6-repeat-2.json` | 261,784 | `23C820C7DB67DFB116997FAE38B926EF502ED6C366815E0B8EC3F18C3E05DE38` | + +## 8세션 서버 원본 + +로컬 경로: `loadtest/server-results/fixed-delay-20ms-8-final/` + +| 파일 | 크기(byte) | SHA-256 | +| --- | ---: | --- | +| `app.csv` | 28,095 | `1BA1CA7B57C7D7C3DA183ECFD4FA9AB7DD4EB2FFF5FF21080B8FEB9504D66EDD` | +| `health-final.json` | 69 | `5936C07B4C0D45415F51B830B3E607724DD20A0A0823207861BFA8C4206A0825` | +| `health-initial.json` | 69 | `5936C07B4C0D45415F51B830B3E607724DD20A0A0823207861BFA8C4206A0825` | +| `metrics-final.prom` | 6,355 | `07B2F92BF1889F8F61A7925AED27D7872E50493C2B7F60C12A77AD6DD2C0CE1D` | +| `metrics-initial.prom` | 6,352 | `EEC576FBA9F17C87F8D9E349E6ABA0E9D95E081DAAE2A57D6717CA01F5E5056D` | +| `sessions-final.json` | 20 | `6254B38AB0F4A29C5B05C0319B3CBF2EA4F0A655F2D14B52E3D3842D9ADCD877` | + +## 검증 방법 + +저장소 접근 권한이 있는 리뷰어는 위 불변 커밋에서 파일을 받은 뒤 다음 명령으로 +매니페스트와 비교할 수 있습니다. + +```powershell +Get-FileHash -Algorithm SHA256 -LiteralPath ".\파일명" +``` diff --git a/test-docs/test-backlog.md b/test-docs/test-backlog.md index 4e6507a..ccb7ada 100644 --- a/test-docs/test-backlog.md +++ b/test-docs/test-backlog.md @@ -9,9 +9,11 @@ - [x] 720p 30FPS VP8/IVF 기준 영상 1종 준비 - [x] Windows/WSL2 Ubuntu 24.04 서버 환경 구성 - [x] RTX 3060 Ti CUDA PyTorch 실제 연산 확인 -- [x] Python 전체 테스트 통과: 278개 +- [x] Python 전체 테스트 통과: 279개 - [x] WSL mirrored networking 적용 - [x] 맥북과 Linux 서버 간 최초 WebRTC 미디어 연결 +- [x] Python `fixed_delay 20ms` 1·2·4·6·7·8세션 5분 시험 +- [x] Windows 호스트용 Prometheus 서버 메트릭 수집기 추가 맥북 공인 IP로 범위를 제한한 Windows 방화벽 규칙을 적용했고, `bypass` 모드에서 30초 원격 미디어 smoke test에 성공했습니다. ICE는 `completed`, PeerConnection은 @@ -31,6 +33,8 @@ 합격 기준은 720p 30FPS 입력의 90%인 27FPS입니다. +- [x] bypass 단일 세션 5분 Python/aiortc 기준선 측정 +- [x] bypass CPU/RSS 및 `py-spy` 프로파일 수집 - [x] `quantize=16` 적용 후 실제 GPU 예열 시험 - [x] 예열 후 단일 세션 5분 기준선 시험 - [x] 첫 30초를 제외한 수신·서버 처리 FPS 평균 및 p95 계산 @@ -41,19 +45,53 @@ 2026-07-13 기준선은 수신 25.51FPS, 서버 처리 25.52FPS, 송수신 프레임 차이 14.98%, stale drop 17.47%로 실패했습니다. 따라서 다중 세션 시험은 보류합니다. +2026-07-16 bypass 기준선은 수신 29.66FPS, 송수신 프레임 차이 1.15%로 +통과했습니다. 기본 WebRTC 경로는 단일 720p30 스트림을 처리할 수 있으므로 real +모드의 detection·blur 경로를 우선 최적화합니다. + 단일 세션에서 평균 27FPS 또는 드롭률 10% 기준을 만족하지 못하면 다중 세션 시험을 중단하고 decode·AI·blur·encode 단계별 병목 계측을 먼저 구현합니다. -## 우선순위 3: 계단식·지속·스파이크 부하 +## 우선순위 3: real 단일 세션 최적화 + +언어 선택 기준선 확정을 우선하므로 현재는 보류합니다. + +- [ ] detection을 전처리·추론·후처리로 세분화 +- [ ] detection 호출과 CPU/GPU 동기화 최적화 +- [ ] blur를 마스크 생성·Gaussian 연산·CPU 반환으로 세분화 +- [ ] blur 연산 최적화 +- [ ] 최적화 후 예열 및 real 단일 세션 5분 재시험 +- [ ] 평균 수신·서버 처리 27FPS와 드롭 10% 기준 재판정 +- [ ] identity exclusion 활성 조건에서 identity 계측 실제 샘플 검증 + +## 우선순위 4: Python/Go 언어 비교 기준선 + +- [x] Python fixed_delay 20ms 단일 세션 5분 기준선 +- [x] Python fixed_delay 20ms 다중 세션 반복 및 8세션 체크포인트 예비 검증 +- [x] fixed-delay JSON/CSV와 서버 수집 원본을 비공개 리소스 저장소에 업로드하고 + `artifact-manifest.md`에 불변 커밋 링크 추가 +- [ ] 6·7세션과 8세션 반복 실행에서 동시점 서버 FPS·오류율·큐·정리 지표 재수집 +- [ ] Python bypass `1 → 2 → 4 → 8` 세션 5분 기준선 +- [ ] Python bypass 조건을 반복해 편차 기록 +- [ ] 동일 REST·WebSocket·WebRTC 계약의 Go/Pion 서버 구현 +- [ ] Go bypass 단일 세션 5분 기준선 +- [ ] Go fixed_delay 20ms 단일 세션 5분 기준선 +- [ ] 양쪽의 FPS·드롭·CPU·RSS·연결 성공률 비교 +- [ ] 동일 AI 모델·정책을 적용할 수 있으면 Go real 기준선 비교 -단일 세션 기준선을 통과한 뒤 진행합니다. +AI 의미가 다르면 결과를 순수 언어 비교가 아닌 전체 구현 비교로 표시합니다. -- [ ] `1 → 2 → 4 → 8` 세션을 각 5분 유지 +## 우선순위 5: 계단식·지속·스파이크 부하 + +각 구현이 해당 모드의 단일 세션 기준선을 통과한 뒤 진행합니다. + +- [x] Python fixed_delay에서 `1 → 2 → 4 → 8` 세션을 각 5분 유지 +- [ ] Python bypass와 Go 구현에서 `1 → 2 → 4 → 8` 세션을 각 5분 유지 - [ ] 최초 기준 위반 단계 주변을 1세션 단위로 재시험 - [ ] 최대 안정 세션의 70~80%로 1~2시간 지속 시험 - [ ] `0 → 최대 후보 → 0 → 최대 후보` 스파이크·재연결 시험 -## 우선순위 4: End-to-End 지연 측정 +## 우선순위 6: End-to-End 지연 측정 현재 서버 내부 프레임 처리 시간만 측정합니다. 송신부터 처리 영상 수신까지 측정하려면 다음 중 하나가 필요합니다. @@ -63,7 +101,7 @@ 첫 구현은 대표 클라이언트 1~3개가 타임코드 영상을 판독하는 방식으로 진행합니다. -## 우선순위 5: 단계별 처리 지연 +## 완료된 기반 작업: 단계별 처리 지연 병목 분석을 위해 전체 프레임 처리 시간을 다음 단계로 분리해야 합니다. @@ -75,14 +113,14 @@ - [x] encode 입력 프레임 생성 계측 - [x] 계측 적용 서버를 재시작하고 예열 후 단일 세션 5분 재프로파일 - [x] 단계별 평균과 p95를 비교해 최우선 최적화 대상 선정 -- [ ] detection 호출·전처리·추론·후처리를 세분화하고 최적화 -- [ ] blur 마스크 생성·Gaussian 연산·CPU 반환을 세분화하고 최적화 -- [ ] 최적화 후 단일 세션 5분 기준선 재시험 + +identity 계측 코드는 구현됐지만 재프로파일 당시 identity exclusion이 비활성이라 실제 +identity 샘플은 아직 검증하지 않았습니다. 현재 `encode` 단계는 BGR 배열을 AV 프레임으로 만드는 시간입니다. aiortc 내부 VP8 코덱 압축 시간은 포함하지 않으며, 필요하면 aiortc encoder 계층에 별도 훅을 추가합니다. -## 우선순위 6: 모니터링 스택 검증 +## 우선순위 7: 모니터링 스택 검증 Docker Desktop 또는 실제 Linux Docker 환경에서 다음 스택을 기동하고 검증합니다. @@ -93,13 +131,13 @@ docker compose -f docker-compose.test.yml up -d - Prometheus: - Grafana: (`admin` / `admin`) -## 우선순위 7: 시스템·GPU 자원 메트릭 +## 우선순위 8: 시스템·GPU 자원 메트릭 - CPU/RAM: Node Exporter - 컨테이너: cAdvisor - GPU/VRAM: DCGM Exporter 또는 `nvidia-smi` 주기 수집기 -## 우선순위 8: 고정 테스트 영상 확대 +## 우선순위 9: 고정 테스트 영상 확대 현재 기준 영상 1종은 준비됐습니다. 본 시험에는 다음 3종을 고정해야 합니다. @@ -107,7 +145,7 @@ docker compose -f docker-compose.test.yml up -d - 얼굴 4명, 움직임 보통 - 얼굴 다수, 움직임 많음 -## 우선순위 9: 네트워크 장애 자동화 +## 우선순위 10: 네트워크 장애 자동화 `tc netem` 시나리오와 자동 복구 스크립트가 필요합니다. @@ -116,8 +154,3 @@ docker compose -f docker-compose.test.yml up -d - 손실 1/3/5% - 3초 단절 - 테스트 종료 시 자동 복구 - -## 우선순위 10: Go 서버 비교 구현 - -현재 저장소는 Python 서버입니다. Python 기준선 시험을 완료한 뒤 동일 API와 AI 의미를 -갖는 Go 서버를 구현해야 최종 언어 비교가 가능합니다. diff --git a/test-docs/test-plan.md b/test-docs/test-plan.md index af22f8e..98df7f9 100644 --- a/test-docs/test-plan.md +++ b/test-docs/test-plan.md @@ -504,21 +504,32 @@ A와 C 모두 Go가 크게 빠름 --- -# 8. 권장 성능 합격 기준 +# 8. 확정 성능 합격 기준 -수치는 현재 단일 세션 측정 결과에 맞게 조정해야 하지만, 초기 기준은 다음처럼 잡을 수 있다. +2026-07-13 단일 세션 기준선부터 다음 기준을 공통으로 사용한다. 720p 30FPS 입력의 +90%인 27FPS를 최소 처리량으로 두고, 이와 일치하도록 프레임 손실·드롭 허용치를 +10%로 고정한다. | 항목 | 예시 기준 | | --- | --- | | 연결 성공률 | 99% 이상 | -| 처리 FPS | 목표 FPS의 95% 이상 | -| 프레임 드롭률 | 1% 미만 | -| 서버 오류율 | 0.1% 미만 | +| 클라이언트 수신 FPS | 평균 27FPS 이상 | +| 서버 처리 FPS | 평균 27FPS 이상 | +| 프레임 손실·드롭 | 10% 이하 | +| 서버 오류율 | 0.1% 이하 | | End-to-End 지연 | 제품 목표 이하 | | 메모리 증가 | 지속 테스트 중 안정화 | | 처리 큐 | 지속적으로 증가하지 않음 | | 비식별화 정확도 | 기존 Python보다 감소하지 않음 | +E2E p50/p95/p99는 타임코드 또는 RTP 메타데이터 측정 구현 전까지 판정에서 보류한다. + +`1 → 2 → 4 → 8`은 최대 세션 수를 미리 8로 제한하는 값이 아니라 한계 구간을 빠르게 +찾기 위한 계단식 체크포인트다. 같은 실행에서 클라이언트와 서버 기준을 모두 측정해 +8세션이 통과하면 “8세션 체크포인트 통합 통과”로 기록한다. 반복 실행에서도 통합 기준이 +재현되고 30분 지속 기준을 만족하기 전에는 “최소 안정 8세션”으로 확정하지 않는다. +최대치가 필요한 경우 12·16세션으로 확대한 뒤 최초 실패 구간을 1세션 단위로 좁힌다. + InnoLive가 “초저지연”을 핵심 가치로 내세우고 있으므로, 단순히 서버가 죽지 않는지를 기준으로 하면 안 된다. 지연과 프레임 드롭, 비식별화 정상 동작을 모두 만족해야 수용 가능한 세션으로 계산해야 한다. 최종 수용량은 다음처럼 정의하는 게 좋다. @@ -634,4 +645,4 @@ Python 분석: py-spy / cProfile Go 분석: pprof ``` -이 방식이면 **“현재 서버가 몇 명을 처리하는가”와 “Go로 전환했을 때 실제로 몇 배 개선되는가”를 동일한 실험에서 함께 증명**할 수 있다. \ No newline at end of file +이 방식이면 **“현재 서버가 몇 명을 처리하는가”와 “Go로 전환했을 때 실제로 몇 배 개선되는가”를 동일한 실험에서 함께 증명**할 수 있다. diff --git a/test-docs/test-result.md b/test-docs/test-result.md index 09a8921..128fc81 100644 --- a/test-docs/test-result.md +++ b/test-docs/test-result.md @@ -41,6 +41,126 @@ 매 프레임 `half` 폐기 경고를 출력했으므로 `quantize=16` 적용 후 5분 기준선을 다시 측정합니다. +## Python bypass 단일 세션 5분 기준선 + +2026-07-16에 AI를 제외한 Python/aiortc 미디어 경로를 측정했습니다. 첫 실행의 +클라이언트 결과와 동일 조건 재실행의 서버 자원 결과를 사용했습니다. 각 실행은 5분이고 +클라이언트 FPS 통계와 서버 CPU/RSS 통계에서 첫 30초를 제외했습니다. + +| 항목 | 결과 | 합격 여부 | +| --- | ---: | --- | +| 평균 송신 FPS | 30.00 | 참고값 | +| 평균 수신 FPS | 29.66 | 통과 | +| 수신 FPS p95 | 30.40 | 참고값 | +| 송수신 프레임 차이 | 1.15% | 통과 | +| RTP 패킷 손실 | 0% | 통과 | +| RTT 평균 / p95 | 18.39 / 111.45ms | 참고값 | +| jitter 평균 / p95 | 3.37 / 10.44ms | 참고값 | +| 서버 재실행 전체 수신 / 처리 | 8,935 / 8,935 | 통과 | +| 서버 stale drop | 52 (약 0.58%) | 통과 | +| 서버 처리 오류 | 0 | 통과 | +| 평균/최대 CPU | 78.77% / 96% | 참고값 | +| 평균/최대 RSS | 852.60 / 852.98MiB | 참고값 | +| 종료 후 활성 세션/큐 | 0 / 0 | 통과 | + +판정은 **bypass 단일 세션 통과**입니다. 같은 입력의 real 재프로파일은 25.66FPS와 +14.48% 프레임 차이를 보였으므로 기본 WebRTC·네트워크 경로보다 AI detection·blur가 +단일 세션 목표 미달의 주원인입니다. bypass `py-spy`의 주요 인코딩 worker 표본에서는 +`encode`가 52.3%로 나타났으며 향후 Go/Pion bypass CPU 비교에 사용합니다. + +## Python fixed_delay 20ms 다중 세션 예비 용량 + +2026-07-16~17에 동일한 Python/aiortc 코드·설정·하드웨어와 `720p30.ivf`를 사용해 +`fixed_delay 20ms`를 1·2·4·5·6·7·8세션으로 측정했습니다. 시험 도중 서버를 +재시작한 구간이 있으므로 모든 실행이 하나의 서버 프로세스에서 이어진 것은 아닙니다. +각 실행은 5분이며 첫 +30초 이후의 클라이언트 표본을 집계했습니다. 반복 실행을 대표 성공 1건으로 축약하지 +않고 모두 기록합니다. 아래 정상 평균은 모든 세션이 FPS·프레임 차이·연결 기준을 +만족한 실행만 집계한 값이며, 선택 편향을 피하기 위해 전체 성공률을 함께 표시합니다. + +| 동시 세션 | 클라이언트 통과/전체 | 성공 실행 평균 수신 FPS | 성공 실행 평균 프레임 차이 | 실행별 최대 차이 평균 | 실행별 RTT p95 최댓값 평균 | 동시점 서버 검증 | 판정 | +| ---: | ---: | ---: | ---: | ---: | ---: | --- | --- | +| [1](results/fixed-delay-20ms/1-session.md) | 2/2 | 29.68 | 1.04% | 1.04% | 25.83ms | 1개 수집 구간 | 클라이언트 통과, 서버 근거 있음 | +| [2](results/fixed-delay-20ms/2-sessions.md) | 1/1 | 29.57 | 1.48% | 1.53% | 34.93ms | 1개 수집 구간 | 통합 기준 통과, 반복 필요 | +| [4](results/fixed-delay-20ms/4-sessions.md) | 2/2 | 29.34 | 2.22% | 2.67% | 77.87ms | 1개 수집 구간 | 클라이언트 통과, 서버 근거 있음 | +| [5](results/fixed-delay-20ms/5-sessions.md) | 0/1 | - | - | - | - | 없음 | 네트워크 이상 실패 1건 | +| [6](results/fixed-delay-20ms/6-sessions.md) | 5/10 | 29.24 | 2.03% | 2.31% | 98.51ms | 대표 실행 없음 | 클라이언트 정상 처리 가능, 서버 기준 미측정 | +| [7](results/fixed-delay-20ms/7-sessions.md) | 1/1 | 29.10 | 2.36% | 3.08% | 26.95ms | 없음 | 클라이언트 통과, 서버 기준 미측정 | +| [8](results/fixed-delay-20ms/8-sessions.md) | 3/3 | 28.54 | 4.07% | 4.77% | 98.43ms | 1/3 실행 | 통합 기준 1회 통과, 클라이언트 3/3 통과 | + +동시 세션 수 링크에는 해당 세션 수로 수행한 모든 실행의 이름과 판정, 정상 실행 +평균, 대표 실행의 클라이언트별 FPS·누적 프레임·RTP 손실, jitter·RTT 분포, 최종 +연결 상태 및 수집 가능한 서버 지표를 기록했습니다. 성공 실행 평균은 실패를 숨기는 +전체 평균이 아니며 반드시 `클라이언트 통과/전체`와 함께 해석합니다. + +판정은 두 수준으로 구분합니다. + +- **클라이언트 통과**: 모든 클라이언트가 평균 수신 27FPS 이상, 프레임 차이 10% + 이하, 최종 연결 성공을 만족합니다. +- **통합 기준 통과**: 클라이언트 통과와 같은 실행에서 서버 처리 FPS 27 이상, + stale drop 10% 이하, 처리 오류율 0.1% 이하, 큐 비증가 및 종료 정리까지 확인합니다. + +동시점 서버 지표가 없는 실행은 클라이언트 통과로만 표시하며 전체 합격을 주장하지 +않습니다. 처리 오류율은 `처리 오류 / 서버 수신 프레임 × 100`으로 계산하며, 오류가 +한 건이라도 있으면 실패하는 것이 아니라 0.1% 초과일 때 실패합니다. + +집계 순서는 다음과 같습니다. 먼저 최초 30초 이후 각 클라이언트의 평균을 계산하고, +이를 산술 평균해 실행 평균을 만듭니다. 정상 평균은 통과 실행들의 실행 평균을 다시 +동일 가중치로 산술 평균합니다. 한 클라이언트라도 평균 수신 27FPS 미만, 프레임 차이 +10% 초과 또는 최종 연결 실패가 있으면 클라이언트 실패로 분류합니다. 서버 지표가 있는 +실행은 서버 FPS, stale drop, 처리 오류율, 큐와 종료 정리를 별도로 판정합니다. 실패 +실행은 정상 평균에서는 제외하지만 전체 실행 수와 성공률에서는 제외하지 않습니다. + +최종 8세션 실행에서는 Windows 호스트 수집기로 서버 메트릭도 함께 기록했습니다. +8세션이 모두 활성인 303.4초 동안 69,545프레임을 처리해 전체 229.22FPS, 세션당 +28.65FPS를 기록했습니다. stale drop은 3,102 / 69,549(4.46%), 처리 오류는 +0건이었습니다. 평균 CPU는 약 4.13코어, 최대 RSS는 약 1.66GiB였고 처리 큐는 +시험 내내 0이었습니다. 종료 후 활성 세션과 큐도 모두 0이었습니다. + +따라서 네트워크가 정상인 구간의 5분 예비 시험에서 8세션은 클라이언트 기준 3/3을 +통과했고, 그중 서버 지표를 함께 수집한 1회는 통합 기준도 통과했습니다. 이 결과는 +**Python fixed_delay 8세션 체크포인트의 예비 처리 가능성**을 뒷받침하지만, 서버 +기준의 반복 통과나 End-to-End 최대 안정 세션을 확정하지는 않습니다. 공인망 전체 +반복성은 특히 6세션에서 5/10입니다. 최종 최대 안정 세션을 주장하려면 통제된 LAN +또는 TURN 경로에서 동시점 서버 지표를 매번 수집하고, 더 높은 부하의 경계를 찾은 뒤 +후보 세션 수를 30분 이상 유지해야 합니다. + +### 비단조 실패 실행과 네트워크 판정 + +같은 시험 과정에서 5·6세션이 끊기고 ICE 로그에 `Consent to send expired`가 기록된 +실행들이 있었습니다. 그러나 8세션 성공 뒤 더 낮은 세션 수가 실패했고, 서버 재시작 +직후에도 실패했으며, profiler를 끈 실행도 실패했습니다. 이후 같은 서버에서 6세션이 +연속으로 성공했고 7세션 종료 2.884초 뒤 시작한 8세션도 통과했습니다. health/ping +진단 트래픽을 끈 6·7·8세션도 모두 통과했습니다. + +실행 경계는 결과 이름에 보존했습니다. `stateful-*` 실행들은 기존 프로세스 상태를 +유지했고, `fresh-server-6-compare`는 서버 재시작 직후 수행했습니다. 이후 성공 실행은 +같은 코드와 설정을 사용했지만 모든 실행이 하나의 프로세스에서 연속 수행된 것은 +아닙니다. 따라서 재시작 여부만으로 성공과 실패를 설명할 수 없다는 범위까지만 +해석합니다. + +이 비단조 실패는 서버의 고정 용량 경계, 누적 상태, profiler 부하 또는 cooldown으로 +설명되지 않습니다. 현재 가장 가능성 높은 원인은 공인 인터넷 구간의 일시적인 WebRTC +UDP/NAT 경로 이상입니다. 실패 원본도 세션별 상세 문서와 전체 성공률에 포함합니다. +다만 정상 성능 수치의 평균에는 성공 실행만 포함하고, 언어별 서버 처리량 비교에서는 +통제되지 않은 네트워크 실패를 별도 표시합니다. 최종 용량 비교는 가능하면 같은 LAN +또는 통제된 TURN 경로에서 반복합니다. + +### 원본 보관 및 재현성 + +- 기준 입력 영상과 fixed-delay 클라이언트 JSON/CSV 40개, 8세션 서버 수집 원본 + 6개는 비공개 + [`team-framework/server-load-test-resources`](https://github.com/team-framework/server-load-test-resources/tree/39c24c97218c28c74e01a5a938037506383d6f7c) + 저장소의 불변 커밋 `39c24c97218c28c74e01a5a938037506383d6f7c`에 보관합니다. +- 시험 원본 경로는 + [`results/2026-07-16-17/fixed-delay-20ms/`](https://github.com/team-framework/server-load-test-resources/tree/39c24c97218c28c74e01a5a938037506383d6f7c/results/2026-07-16-17/fixed-delay-20ms) + 이며 저장소 접근 권한이 있는 리뷰어가 내려받아 재계산할 수 있습니다. +- 현재 보유한 원본의 파일 크기와 SHA-256은 + [artifact-manifest.md](results/fixed-delay-20ms/artifact-manifest.md)에 기록했습니다. + 업로드된 파일과 로컬 원본의 동일성을 확인할 수 있습니다. +- 문서의 수치는 해당 해시의 원본으로 계산했습니다. 저장소가 비공개이므로 접근 권한이 + 없는 제3자의 독립 검증은 제한되지만, PR 리뷰어에게 권한이 있다면 재현 가능합니다. + ## Python 단일 세션 5분 기준선 첫 30초는 안정화 구간으로 제외하고 이후 4분 30초를 집계합니다. @@ -117,8 +237,8 @@ CPU/GPU 동기화 비용의 영향이 큰 상태입니다. | 항목 | Python | Go | 차이 | | --- | --- | --- | --- | -| 최대 연결 가능 세션 | | | | -| 최대 안정 세션 | | | | +| fixed_delay 예비 체크포인트 | 클라이언트 8세션 3/3, 통합 기준 1/1 | | | +| 최대 안정 세션 | 미확정 (30분 지속 시험 전) | | | | 운영 권장 세션 | | | | | 평균 처리 FPS | | | | | 프레임 드롭률 | | | | diff --git a/test-docs/test-runbook.md b/test-docs/test-runbook.md index e1cd1fb..f113567 100644 --- a/test-docs/test-runbook.md +++ b/test-docs/test-runbook.md @@ -31,6 +31,82 @@ docker compose -f docker-compose.test.yml up -d - Prometheus: - Grafana: (`admin` / `admin`) +## Baseline measurement rules + +- Use `720p30.ivf` and one client for the single-session baseline. +- Run each measured condition for five minutes. +- Exclude the first 30 seconds and aggregate the remaining 270 seconds. +- For `real`, run a separate warm-up client first and exclude it from results. +- Accept only when average client receive FPS and server process FPS are at + least 27, frame loss/drop is at most 10%, connection success is at least + 99%, and server processing errors are at most 0.1%. +- E2E p50/p95/p99 remain deferred until timecode or RTP metadata measurement + is implemented. + +Collect server-side CPU, RSS, GPU, Prometheus snapshots, and a `py-spy` profile: + +```bash +bash loadtest/scripts/collect-server-baseline.sh \ + loadtest/server-results/real-single-5m 300 1 +``` + +Pass `false` as the fourth argument when a profile is not needed: + +```bash +bash loadtest/scripts/collect-server-baseline.sh \ + loadtest/server-results/fixed-delay-8 360 1 false +``` + +The current collector waits up to 600 seconds for a session and writes +per-second frame columns for `real` mode. Its initial/final Prometheus snapshots +still contain every mode, so use their counter delta for `bypass` and +`fixed_delay`. Start the client within the wait window. + +When the server is reachable from Windows at `127.0.0.1:8000`, collect the +same Prometheus counters plus process CPU time and RSS from PowerShell: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass ` + -File ".\loadtest\windows\collect-server-metrics.ps1" ` + -OutputDirectory ".\loadtest\server-results\fixed-delay-8" ` + -DurationSeconds 360 -IntervalSeconds 1 ` + -Mode fixed_delay +``` + +The Windows collector supports `bypass`, `fixed_delay`, and `real`. It waits +up to 600 seconds for the first active session, then saves per-second counters, +initial/final Prometheus snapshots, health, and final session cleanup state. +It also waits up to 10 seconds for the selected mode's received/processed frame +series. Missing required metrics stop the collection instead of being recorded +as zero; only absent drop/failure counter series are treated as zero. + +Use `client pass` only for FPS, frame-gap, and connection checks derived from the +Pion result. Use `combined pass` only when the same run also captures server FPS, +stale drop, failure rate, queue behavior, and cleanup. A client-only pass must not +be reported as a full capacity pass. + +Judge client send/receive frame gap and server stale drop independently. They +observe overlapping loss in different parts of the pipeline and must not be +added together. + +After each run, verify `GET /sessions` returns no sessions and `/health` reports +AI queue depths of zero. + +## External Windows/WSL testing + +- Do not use `127.0.0.1` from the load-generator Mac; use the Windows server's + current public or LAN IP. +- Set `WEBRTC_ANNOUNCED_IP` to the same reachable server address. +- Allow TCP 8000 and UDP 50000-60000 only from the Mac's current public IP. +- Recheck both public IPs after a router reconnect, VPN/WARP change, or reboot. +- WSL2 mirrored networking also requires the supplied Windows/Hyper-V firewall + script to be run from an elevated PowerShell. +- A non-monotonic result such as eight sessions passing while five or six fail, + especially with `Consent to send expired`, is a network-path incident rather + than a server capacity boundary until it reproduces on a controlled LAN or + TURN path. Preserve it separately and repeat the same load without restarting + the server. + ## Control-plane load The k6 script measures REST session lifecycle load only. It does not generate