Files

14 KiB
Raw Permalink Blame History

7교시 바이브 코딩 · tbench 만들기

정답본 · 수업이 끝난 뒤(8교시 뒤) 학생 저장소에 올립니다. 그 전에는 올리지 않습니다. 요청서는 VIBE-REQ-TBENCH.md, 문제는 VIBE-Q-TBENCH.md. 68일차부터 바이브 코딩 프로그램은 수업 준비 때 미리 만들어 보지 않습니다. 아래 TODO · SPEC · IMP-PLAN · 요청문과 확인 기준은 예시이고, 걸린 시간은 8교시 강사 바이브 코딩에서 재어 기록합니다. 10/26 추가: 강사 요청으로 R1~R14를 모두 넣은 본을 tbench/에 직접 만들어 봤습니다 (7절). 요청문으로 CLI 에이전트에 시킨 것이 아니라 Claude Code가 SPEC · IMP-PLAN을 보고 쓴 것이라, 8교시 걸린 시간 표와는 따로 봅니다.

1. Doc/TODO.md 예

# tbench TODO

## 만들고 싶은 것
어느 폴더에서나 `tbench`로 웹훅을 여러 번 불러, 보통 · 느린 쪽 시간과 실패를 보여 주고,
바꾸기 전 · 후를 같은 조건으로 비교하는 터미널 도구. 나중에는 기준을 주면 통과 · 실패만 알려 줌.

## 요청의 말 → 할 일
| # | 요청의 말 (짧게) | 할 일 (내 말) | 꼭 · 나중 |
| --- | --- | --- | --- |
| ① | 여러 번 · 몇 번 실패 · 보통 · 느린 쪽 | N번 부르고 한 번마다 상태 · 시간, 끝에 p50 · p95 · max · 실패 | 꼭 |
| ② | 기다리기 싫음 · 모양 | 오는 대로 표에 한 줄씩, 스파크라인 | 꼭 |
| ③ | 숫자로 비교 · 다른 질문 비교 망신 | 결과 저장, 두 결과 비교, 조건이 다르면 경고 | 꼭 |
| ④ | 한꺼번에 | 동시에 k개 | 나중 |
| ⑤ | 만들어 둔 요청 고르기 | requests 폴더에서 고르기 (turl은 형식 확인 뒤) | 나중 |
| ⑥ | 첫 번만 느림 | 첫 호출 표시, 빼고도 p50, 미리 불러 두기 | 꼭 |
| ⑦ | 왜 실패했는지 | 연결 · 시간 초과 · 서버 오류를 따로 셈 | 꼭 |
| ⑧ | 단계별 시간 평균 | 응답의 timings를 모아 단계 평균 | 나중 |
| ⑨ | 화면 없이 통과 · 실패 | 기준을 넘으면 실패, 종료 코드 | 나중 |
| ⑩ | 줄줄이 기록 | 한 번마다 한 줄 파일 | 나중 |
| ⑪ | 설정 저장 · 이름으로 | 설정 파일, 이름으로 실행 | 나중 |
| ⑫ | 잰 것 목록 | 저장한 결과 목록 | 나중 |
| 끝 | 키가 찍히면 안 됨 | 저장 · 기록에서 키 가리기 | 꼭 |
| 끝 | 진짜 헬프데스크 두드리지 않기 | 시험은 가짜 서버로 | 꼭 |
| 첫 문단 | 어느 폴더에서나 | uv tool install | 꼭 |

## 요청에 없지만 걱정되는 것
- 시간을 어디서부터 어디까지 재나, 끝없이 기다리면?
- p95를 어떻게 계산하나 (사람마다 다르면 비교가 안 됨)
- 저장 폴더 · 설정 파일을 어디에 (어느 폴더에서 실행해도 같은 곳)

## 오늘 50분 안에 못 할 것 같은 것
④ ⑤ ⑧ ⑨ ⑩ ⑪ ⑫ — IMP-PLAN의 다음 단계로

2. Doc/SPEC.md 예 (요구 표는 VIBE-Q와 같음, 확인 기준을 구체적으로)

요구 확인 기준
R1 가짜 서버(지연 0.1초, 3번째마다 500)로 n=6 → 실패 2, 시간 6개. stats.py 테스트: 1~10초 → p50 5 · p95 10 (nearest-rank, 자리 = 올림(p × n))
R2 worker로 부름, 결과마다 표 한 줄 · 스파크라인 · 요약 갱신 (가짜 서버 지연 0.5초 × 6에서 3초 동안 한 줄씩 늘어남)
R3 저장 JSON: {url, method, body_sha256, n, concurrency, warmup, started, seconds:[...], statuses:[...], errors:[...]}. compare에서 url · body_sha256 · n이 다르면 WARNING: different ... 줄
R4 가짜 서버 지연 0.3초 × 6번: --concurrency 1 약 1.8초, 3 약 0.6초 안팎
R5 requests/*.json 목록을 TUI OptionList로, 고르면 그 본문으로 실행
R6 가짜 서버 첫 응답만 2초 → 1번 줄 note first call, "p50 excl. first" 따로. --warmup 1 → 시간 목록 n개(warmup 제외)
R7 없는 포트 → connect 1, 지연 3초 + --timeout 1 → timeout 1, 500 → http_5xx 1, 404 → http_4xx 1
R8 --header "Authorization: Bearer sk-test" → 저장 · 기록 파일 grep -c sk-test 0, 대신 "Authorization": "***"
R9 응답 JSON의 timings(숫자 칸만)를 칸마다 평균. 내 헬프데스크 → classify_ms … total_ms 평균 한 줄. timings가 없으면 표 없음
R10 --max-p95 2.5 --max-failures 0: 넘으면 마지막 줄 FAIL p95 3.1 > 2.5, 종료 코드 1. 아니면 PASS, 0
R11 --log runs.jsonl: 줄마다 {"t", "i", "status", "seconds", "error_kind"}, 줄 수 = N(+ warmup은 "warmup": true)
R12 ~/.tbench/profiles.toml, tbench profile save/list, tbench run <이름>: 다른 폴더에서도 같은 결과 파일 조건
R13 기본 저장 폴더 ~/.tbench/results/, tbench history → 최신이 위, 날짜 · URL · N · p50 · p95 · 실패
R14 uv tool install --editable . 뒤 cd /tmp && tbench --help 성공, uv tool list에 tbench

숨은 요구 · 범위 밖은 VIBE-Q 표와 같습니다.

3. Doc/IMP-PLAN.md 예

# tbench IMP-PLAN

## 엔진 1단계 · 부르고 재기 (R1 · R6 · R7 · R8)  ← 7교시
- [ ] src/tbench/runner.py: call_once(url, method, body, headers, timeout) → {i, status, seconds, error_kind}
- [ ] src/tbench/stats.py: percentile(nearest-rank), summarize(results) → p50 · p95 · max · failures · by_kind · p50_excl_first
- [ ] tests/conftest.py: 가짜 서버 (지연 · N번째마다 500 · 첫 응답만 느림 · 404)
- [ ] cli.py: tbench <url> --body-file --n --method --timeout --header --warmup --plain
- 확인: uv run pytest -q · uv run tbench <url> -n 5 --plain

## TUI 1단계 · 오는 대로 보이기 (R2)  ← 7교시
- [ ] app.py: Sparkline · 요약 Static · DataTable, worker 가 runner 를 돌리며 결과마다 call_from_thread 로 갱신
- 확인: uv run tbench <url> -n 20 (가짜 서버 지연 0.5초)

## 설치 (R14)  ← 7교시 끝
- [ ] pyproject [project.scripts] tbench = "tbench.cli:main", uv tool install --editable .

## 엔진 2단계 · 남기고 비교 (R3 · R13 · R11)
- [ ] save(result, path=None) 기본 ~/.tbench/results/<시각>.json, compare(a, b), history()
- [ ] --log runs.jsonl
## 엔진 3단계 · 동시 · 기준 · 응답 분석 · 설정 (R4 · R10 · R9 · R12)
## TUI 2단계 · 고르기 · 비교 · 기록 화면 (R5 · R3 · R13)

4. 단계별 요청문 예

엔진 1단계 · 부르고 재기

Doc/SPEC.md 와 Doc/IMP-PLAN.md 를 읽고 "엔진 1단계"만 만들어 줘. uv 프로젝트(src/tbench), 명령 이름 tbench, 코드와 화면 글자는 영어.
tbench <url> [--body-file f.json] [-n 10] [--method POST] [--timeout 30] [--header "K: V" ...] [--warmup 0] --plain
- 한 번마다 "#i status seconds note", 끝에 p50 · p95 · max · failures(종류별: connect · timeout · http_5xx · http_4xx), p50 excl. first.
  시간은 보내기 직전 ~ 본문을 다 받을 때까지. 실패도 한 번으로 셈. 1번 줄 note 는 "first call". warmup 은 재지 않고 버림.
- 백분위: 정렬 후 nearest-rank (ceil(p*n) 번째). stats.py 로 분리하고 pytest 로 1..10 → p50 5, p95 10.
- 엔진은 결과를 하나씩 돌려주는 제너레이터로 (나중에 TUI가 받음). 화면 코드는 아직 쓰지 않는다.
- 테스트는 pytest 안에서 http.server 로 가짜 서버(지연, N번째마다 500, 첫 응답만 느림, 404)를 띄워서. 진짜 n8n 을 부르지 않는다.
- Authorization 등 키 머리글은 출력 · 저장에서 "***".
끝나면 IMP-PLAN 의 엔진 1단계 체크를 표시하고 확인 명령을 알려 줘.

TUI 1단계 · 오는 대로 보이기

IMP-PLAN 의 "TUI 1단계"만 해 줘. tbench <url> ... (--plain 없이) 는 Textual 앱:
위 Sparkline(시간 목록), 요약 줄(완료/전체, 실패, p50, p95, max, p50 excl. first), 아래 DataTable(#, status, seconds, note).
엔진 제너레이터를 worker(thread)에서 돌리고 결과마다 call_from_thread 로 표 · 스파크라인 · 요약을 갱신. 엔진 코드는 바꾸지 않는다.
키: q 끝. 확인: 가짜 서버 지연 0.5초 × 6 에서 한 줄씩 늘어나는지.

설치

pyproject.toml 에 [project.scripts] tbench = "tbench.cli:main" 이 있는지 보고, 없으면 넣어 줘.
그리고 내가 실행할 명령만 알려 줘: uv tool install --editable . , uv tool list, cd /tmp && tbench --help

엔진 2단계 · 남기고 비교

IMP-PLAN 의 "엔진 2단계"만: --save [path] (없으면 ~/.tbench/results/<YYYYMMDD-HHMMSS>.json) 에
{url, method, body_sha256, n, concurrency, warmup, started, seconds, statuses, errors} 저장 (키 머리글은 "***").
tbench compare a.json b.json: p50 · p95 · max · failures 와 변화(%) 표, url · body_sha256 · n 이 다르면 WARNING 줄.
tbench history: ~/.tbench/results 의 파일을 최신부터 날짜 · url · n · p50 · p95 · failures 표. --log runs.jsonl: 한 번마다 한 줄.
테스트: 다른 본문 두 결과 → WARNING, 저장 파일에 sk- 없음.

엔진 3단계 · 동시 · 기준 · 응답 분석 · 설정

IMP-PLAN 의 "엔진 3단계"만: --concurrency k (스레드 풀, 결과는 끝난 순서로 내보내되 번호 유지),
--max-p95 초 · --max-failures 수 → 마지막 줄 PASS/FAIL, 종료 코드 0/1,
응답 JSON 에 timings 가 있으면 숫자 칸마다 평균 (요약 아래 한 줄),
~/.tbench/profiles.toml: tbench profile save <name> <url> [옵션], tbench profile list, tbench run <name> [-n ...].
가짜 서버 테스트: 지연 0.3초 × 6 에서 k=3 이 k=1 보다 빠름, 기준을 넘으면 종료 코드 1.

5. 7교시에 볼 것

볼 것 막혔을 때 한마디
TODO · SPEC 없이 에이전트부터 "요청서로 Doc/TODO.md부터. 확인 방법이 없으면 됐는지 모릅니다"
IMP-PLAN이 한 단계짜리 "엔진과 화면을 나누고, 엔진도 1 · 2 · 3으로. 단계마다 commit"
첫 요청에 14개를 다 넣음 "IMP-PLAN의 엔진 1단계만"
다 끝난 뒤에야 화면이 나옴 "worker로 부르고 결과마다 표에 더하게"
p95가 max와 늘 같음 "n이 작으면 그럴 수 있어요. 계산 방법을 테스트로 고정"
테스트가 진짜 n8n을 부름 "pytest 안에서 가짜 서버"
비교가 다른 요청끼리 "저장할 때 url · 본문 해시를 같이"
tbench를 못 찾음 uv tool update-shell 뒤 새 터미널, uv tool list
설치한 tbench가 예전 코드 --editable로 다시, 또는 uv tool install --force .

자주 빠뜨리는 것 (수업 준비 때 예상)

빠뜨린 것 생기는 일
백분위 계산 방법 사람마다 · 실행마다 p95가 달라 비교가 흔들림
실패도 한 번으로 셈 · 실패 종류 연결이 안 되면 프로그램이 멈추거나, 실패가 시간 목록에서 빠져 p95가 좋아 보임
저장 파일에 조건(url · 본문 해시 · N) 다른 질문으로 잰 것끼리 비교 (요청서의 "망신")
엔진과 화면 나누기 --plain · compare · 종료 코드를 화면 없이 못 씀, 테스트가 어려움
가짜 서버 테스트할 때마다 진짜 헬프데스크 · OpenAI 호출
[project.scripts] uv tool install은 되는데 tbench 명령이 없음
저장 · 설정 위치를 집 폴더에 다른 폴더에서 실행하면 history · profile이 비어 보임

6. 강사 바이브 코딩 (8교시, 15분 · 시간 기록)

할 것 걸린 시간 메모
엔진 1단계 + TUI 1단계 + 설치를 위 요청문으로 (8교시에 잼)
R10 --max-p95 종료 코드 (74일차 배포 전 검사의 씨앗) (8교시에 잼)

7. 수업 준비 때 직접 만들어 본 본 (10/26, tbench/)

확인 결과
uv run pytest -q (가짜 서버) 12 passed (18.8초): 백분위 · 실패 2/6 · 첫 호출 · warmup · 실패 종류 4가지 · 키 가리기 · compare WARNING · history · log · 동시 · 종료 코드 · timings · profile · TUI 한 줄씩 · --pick
uv tool install --editable . → cd /tmp && tbench --help 됨, uv tool list에 tbench v0.1.0
진짜 헬프데스크 my-helpdesk + requests/faq-vpn-nocache.json -n 5 --plain --save --max-p95 4 3.947 · 1.579 · 1.532 · 1.824 · 1.429초, 1번 줄 first call. p50 1.579 · p95 3.947 · max 3.947 · p50 excl. first 1.532, PASS
같은 때 flow.py bench my-helpdesk requests/faq-vpn-nocache.json -n 5 p50 1.38 · p95 1.83 · max 1.83초 (바로 앞 tbench가 데워 둠)

(강사 Mac의 n8n에 올라가 있던 my-helpdesk는 규칙 분류 · 코드 검사가 들어간 판이라 llm_calls 1, timings에 classify_ms가 없음.)

만들며 알게 된 것 (학생이 막힐 만한 곳):

알게 된 것 정한 방법
n이 작으면 p95 = max (위 실측: 5번이면 p95는 5번째 = 첫 호출 3.9초) nearest-rank 그대로 두고, 첫 호출 제외 p50과 --warmup으로 따로 봄 (⑥)
profile save에 키를 넣으면 "남기는 파일에 키가 찍히면 안 돼요"와 부딪힘 키 머리글은 $MY_KEY처럼 환경 변수로만 저장, 글자 키는 ***로 바꾸고 안내 (숨은 요구)
응답마다 timings 칸이 다름 (규칙 길은 classify_ms 없음) 칸마다 그 칸이 있는 응답끼리 평균
동시(--concurrency)면 결과가 끝난 순서로 옴 번호(i)는 보낸 순서로 유지, 저장은 번호순
본문 해시를 JSON을 다시 쓴 글자로 내면 같은 파일도 해시가 달라질 수 있음 파일 글자 그대로 보내고 그 글자로 해시
TUI 테스트 Textual run_test() + 가짜 서버 지연 0.5초: 1.3초 뒤 1~5줄, 끝나면 6줄