<!-- task=m-performance-hotspot-optimization/13_kotlin_ws_diagnostics plan=0 tag=TEST -->

# Plan - TEST

## 이 파일을 읽는 구현 에이전트에게

`CODE_REVIEW-local-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수입니다. 구현 후 active 파일은 그대로 두고 리뷰 준비를 보고하세요. 사용자만 결정할 수 있는 범위 변경, 외부 환경, secret, 사용자 소유 장비가 막으면 리뷰 stub의 `사용자 리뷰 요청` 섹션에 근거를 기록하고 멈추세요. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 마세요. 명령 재실행이나 산출물 수집으로 닫을 수 있는 증거 공백은 사용자 리뷰 요청이 아닙니다.

## 배경

Kotlin WS는 기존 full baseline에서 roundtrip concurrency 16/64/256 p99가 40ms대이고, 1MB payload heap peak가 WS 약 668.3MB로 TCP보다 큽니다. 현재 코드에는 OkHttp `ByteString.toByteArray()` copy, server `ByteBuffer` copy, `WsClient.receiveQueue`, `Communicator.inboundQueue`, write `ByteString` 생성 비용이 모두 섞여 있어 바로 최적화하면 원인 판단이 흐려집니다. 이 subtask는 제품 API 변경 없이 진단 계측과 focused stress 출력만 추가합니다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록합니다. 직접 사용자 프롬프트는 금지이며, code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유합니다.

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/milestones/performance-hotspot-optimization.md`
- `agent-test/local/rules.md`
- `agent-test/local/proto-socket-performance-baseline.md`
- `agent-test/local/kotlin-smoke.md`
- `agent-test/runs/20260603-035352-proto-socket-stress-quick.md`
- `agent-test/runs/20260603-062724-proto-socket-stress-full.md`
- `agent-ops/rules/project/domain/kotlin/rules.md`
- `kotlin/build.gradle.kts`
- `kotlin/settings.gradle.kts`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsServer.kt`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/Communicator.kt`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/BaseClient.kt`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/TcpClient.kt`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/TcpServer.kt`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/HeartbeatTimer.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/WsTest.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/TlsWsTest.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/CommunicatorTest.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/TcpTest.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/TestHelpers.kt`
- `kotlin/crosstest/stress.kt`

### 테스트 환경 규칙

`test_env=local`을 사용한다. `agent-test/local/rules.md`를 읽었고, 성능/latency 작업이므로 `agent-test/local/proto-socket-performance-baseline.md`, Kotlin 변경이므로 `agent-test/local/kotlin-smoke.md`를 적용한다. 필수 명령은 Kotlin 변경 후 `cd kotlin && ./gradlew test`, 성능 관련 변경 후 `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --quick`이다. focused 원인 분리는 보조 검증으로 `run_stress.sh --quick --lang kotlin --transport ws --profile roundtrip,payload`를 사용할 수 있다.

### 테스트 커버리지 공백

기존 `WsTest`/`TlsWsTest`는 WS send/receive, request-response, broadcast, disconnect를 검증한다. 기존 `CommunicatorTest.testWsInboundStagingOverflowDisconnects`와 gateway tests는 staging overflow와 gateway submit 순서를 검증한다. 현재 테스트는 WS frame copy 시간, staged/backlog peak, write ByteString 생성 비용, payload별 memory peak 원인 분리를 검증하지 않는다. 이 plan에서 internal metrics와 focused stress 출력 검증을 추가한다.

### 심볼 참조

renamed/removed symbol 없음.

### 분할 판단

split 정책을 먼저 평가했다. 공유 task group은 `agent-task/m-performance-hotspot-optimization/`이다. `13_kotlin_ws_diagnostics`는 독립 진단/계측 작업이다. `14+13_kotlin_ws_transport_hardening`은 13번 complete.log 이후 구현한다. `15+14_kotlin_ws_reference_guard`는 14번 complete.log 이후 성능 guard와 evidence 정리를 수행한다. 13번은 선행 의존성이 없다.

### 범위 결정 근거

이 단계는 Kotlin 내부 진단과 stress 출력만 다룬다. `proto/**`, Dart/Go/Python/TypeScript 구현, public API, package version은 제외한다. 성능 최적화 코드는 14번 계획에서 처리한다.

### 빌드 등급

`local-G07`: Kotlin 단일 도메인이고 검증 명령이 명확하지만, 성능 계측과 concurrency/backpressure 관찰을 포함해 중간 난도가 있다.

## 구현 체크리스트

- [ ] Kotlin WS 진단 metrics를 internal-only로 추가하고 public API를 늘리지 않는다.
- [ ] stress harness가 Kotlin WS roundtrip/payload rows에서 frame copy, staged/backlog, memory peak 관찰값을 기록하도록 보강한다.
- [ ] WS/TLS/Communicator 단위 테스트가 기존 순서, overflow, disconnect 계약을 유지하는지 확인한다.
- [ ] `cd kotlin && ./gradlew test`와 focused stress 명령을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [TEST-1] Kotlin WS Internal Metrics

문제: [WsClient.kt](/config/workspace/proto-socket/kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt:41)는 `receiveQueue`만 있고 staging 상태를 관찰할 방법이 없다. [WsClient.kt](/config/workspace/proto-socket/kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt:186)는 OkHttp callback에서 `ByteString.toByteArray()` 비용을 즉시 치르지만 stress row에는 이 비용이 분리되지 않는다.

해결 방법: `internal data class WsMetricsSnapshot`과 `internal class WsMetrics`를 추가하고 `WsClient`가 received/enqueued/dropped/maxStaged/copyNanos를 누적하게 한다. `receiveBytes`는 enqueue 성공/실패를 metrics에 기록하고, OkHttp `onMessage`와 server `onMessage(ByteBuffer)`는 copy 시간을 측정해 `receiveBytes(bytes, copyNanos)`로 전달한다.

Before:

```kotlin
// WsClient.kt:58
internal fun receiveBytes(bytes: ByteArray) {
    if (!communicator.isAlive()) return
    val result = receiveQueue.trySend(bytes)
```

After:

```kotlin
internal fun receiveBytes(bytes: ByteArray, copyNanos: Long = 0L) {
    if (!communicator.isAlive()) return
    metrics.recordReceived(copyNanos)
    val result = receiveQueue.trySend(bytes)
```

수정 파일 및 체크리스트:

- [ ] `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt`: internal metrics type, snapshot accessor, receive/copy counters.
- [ ] `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsServer.kt`: `ByteBuffer` copy time 전달.
- [ ] `kotlin/src/test/kotlin/com/tokilabs/proto_socket/CommunicatorTest.kt`: staging overflow test가 metrics dropped/max staged를 검증.

테스트 작성: 작성한다. `testWsInboundStagingOverflowDisconnects`에 metrics assertion을 추가하고, 필요하면 새 `testWsMetricsTracksStagingAndDrops`를 둔다.

중간 검증:

```bash
cd kotlin && ./gradlew test
```

### [TEST-2] Stress Row Diagnostics

문제: [stress.kt](/config/workspace/proto-socket/kotlin/crosstest/stress.kt:170)는 queueBacklog/gatewayBacklog를 항상 0으로 출력해 WS staging 비용을 row에서 볼 수 없다. [stress.kt](/config/workspace/proto-socket/kotlin/crosstest/stress.kt:647)는 payload memory peak만 기록하고 copy/staging 비용을 분리하지 않는다.

해결 방법: `StressClientHandle`에 optional WS metrics provider를 추가하고, Kotlin WS rows에서 queueBacklog에 max staged, gatewayBacklog에 dropped count 또는 residual staged를 기록한다. stderr에는 copyMs/frame count를 stable key-value로 남긴다. TCP rows는 기존 0을 유지한다.

수정 파일 및 체크리스트:

- [ ] `kotlin/crosstest/stress.kt`: `StressClientHandle` 확장, `dial("ws")`에서 metrics provider 연결.
- [ ] `kotlin/crosstest/stress.kt`: `emitRow` 호출부에 transport별 queue/backlog 값을 전달하도록 변경.
- [ ] `kotlin/crosstest/stress.kt`: roundtrip/payload WS stderr에 `wsCopyMs`, `wsMaxStaged`, `wsDropped` 기록.

테스트 작성: 별도 unit test보다 focused stress를 실행해 output schema와 row 값을 검증한다. parser 호환을 깨면 안 되므로 필드 개수는 기존 ROW 계약을 유지한다.

중간 검증:

```bash
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --lang kotlin --transport ws --profile roundtrip,payload
```

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt` | TEST-1 |
| `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsServer.kt` | TEST-1 |
| `kotlin/src/test/kotlin/com/tokilabs/proto_socket/CommunicatorTest.kt` | TEST-1 |
| `kotlin/crosstest/stress.kt` | TEST-2 |

## 최종 검증

```bash
cd kotlin && ./gradlew test
```

```bash
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --lang kotlin --transport ws --profile roundtrip,payload
```

기대 결과: Gradle test는 `BUILD SUCCESSFUL`, focused stress는 Kotlin WS roundtrip/payload rows를 PASS로 기록하고 새 diagnostic 값이 schema를 깨지 않는다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
