Repository navigation
API KO
Controller의 대시보드와 운영 API는 로그인 세션으로 보호합니다. 아래 인증에서 공개 모니터링 경로와 내부 서비스 인증을 구분합니다.
Controller는 웹 접근과 인증 시도를 <data-dir>/logs/access.jsonl, auth.jsonl에 보존합니다. 비밀번호·쿠키·인증 헤더·쿼리는 기록하지 않습니다. 필드·회전·조회 방법은 웹 접근·인증 로그를 참고하십시오.
| Method | Path | 설명 |
|---|---|---|
POST |
/api/v1/auth/login |
JSON 계정으로 로그인하고 세션 쿠키 발급 |
GET |
/api/v1/auth/session |
현재 사용자·세션 만료 시각 |
POST |
/api/v1/auth/logout |
현재 세션과 SSE 연결 종료 |
GET |
/metrics |
Controller와 실험 metric의 Prometheus exposition |
GET |
/api/v1/health |
Controller 상태와 Peer 시계 측정에 쓰는 현재 UTC 시각 |
GET |
/api/v1/ui-config |
대시보드 메뉴에 사용하는 Prometheus·Grafana 게시 포트 |
GET |
/api/v1/prometheus/controller-targets |
Controller 공개 metrics 주소의 Prometheus HTTP service-discovery group |
GET |
/api/v1/prometheus/agent-targets |
metrics URL을 알린 online Agent의 Prometheus HTTP service-discovery group |
GET |
/api/v1/snapshot |
노드 peerScores를 포함한 대시보드 전체 snapshot |
GET |
/api/v1/agents |
Agent 상태 |
POST |
/api/v1/agents/refresh |
등록 Agent·Peer 상태 조회; ?discover=true로 누락된 Swarm Agent도 탐색; 실패 정보 반환 |
PUT |
/api/v1/agents/{agentID}/capacity |
{capacity: 100}으로 Agent별 예외값 지정, {capacity: null}로 CLI 기본값 복원 |
GET |
/api/v1/nodes |
inspection으로 수집한 peerScores를 포함한 Peer 상태 |
GET |
/api/v1/network |
peerScores가 포함된 Peer, 연결 edge와 전파 지표 |
GET |
/api/v1/bootstrap?runId={runId} |
필수 run ID에 속한 준비 상태의 bootstrap peer만 반환 |
GET |
/api/v1/discovery?runId={runId}&topic={topic}&requesterNodeId={nodeId} |
동일 run·정확한 topic에서 ready·online 상태인 PubSub transport 후보 |
GET |
/api/v1/events |
최근 trace event |
GET |
/api/v1/stream |
peerScores를 포함한 실시간 snapshot SSE |
GET |
/api/v1/experiments |
실험 상태와 activeJobs, completedJobs, failedJobs, canceledJobs counter |
GET / POST
|
/api/v1/scenarios |
시나리오 요약 목록 조회 또는 검증한 {name, yaml} 저장 |
POST |
/api/v1/scenarios/validate |
원본 YAML을 저장·실행 없이 검증. 로그인 필요 |
GET / PUT / DELETE
|
/api/v1/scenarios/{id} |
저장 시나리오 하나를 불러오기, 갱신 또는 삭제 |
GET / POST
|
/api/v1/library-groups |
시나리오·결과가 공유하는 그룹 조회 또는 그룹 생성 |
PUT |
/api/v1/library-groups/members |
선택한 시나리오·결과 묶음을 한 그룹 또는 Ungrouped로 이동 |
PATCH / DELETE
|
/api/v1/library-groups/{id} |
시나리오·결과를 보존하면서 그룹 이름 변경 또는 그룹 삭제 |
GET |
/api/v1/result-storage |
로컬 여유 공간·시작 기준과 캐시된 NAS 보관 상태. 요청 중 NAS를 조회하지 않음 저장 상태 변경 시에만 로컬 결과 목록을 갱신할 수 있는 resultsRevision 문자열을 포함합니다. 숫자로 변환하지 않고 토큰으로 비교하십시오. |
GET |
/api/v1/results |
이전 Controller 실행에서 저장한 실험을 포함하는 결과 목록 |
GET / PUT
|
/api/v1/results/{id}/note |
실행별 공유 메모 조회·저장. revision으로 동시 수정 충돌 감지 |
GET / PUT
|
/api/v1/result-batches/{batchId}/note |
개별 Run 메모와 독립적인 그룹 메모 조회·저장. revision으로 동시 수정 충돌 감지 |
DELETE |
/api/v1/results/{id} |
비활성 저장 결과 삭제. 진행 중 배치·다운로드 보호 |
DELETE |
/api/v1/result-batches/{batchId} |
그룹의 모든 저장 run과 평균 분석 삭제. 전체 구성원의 활성 상태·다운로드를 먼저 검사 |
POST |
/api/v1/result-batches/{batchId}/resume |
실패 또는 시작 후 interrupted 회차를 제외하고 미시작 회차만 재개. 배치·회차 ID 유지. 첫 queued run과 202, 진행 중·재개 불가 시 409, 없는 배치면 404. 로그인 세션과 X-KPL-Request: dashboard 필요. |
POST |
/api/v1/result-batches/{batchId}/retry |
이전 Peer를 정리한 뒤 저장된 미완료 회차를 첫 phase부터 재실행. 완료 회차·배치 ID·회차 번호·seed 유지, 새 run ID 사용. 실패·중단·취소, 단일 실험과 마지막 회차 실패 포함. 첫 queued run과 202, 진행 중·재개 불가 시 409, 없는 배치면 404. 로그인 세션과 X-KPL-Request: dashboard 필요. |
POST |
/api/v1/result-batches/{batchId}/append |
완료 그룹에 {additionalRuns, expectedRepetitions}로 회차 추가. 첫 새 queued run과 202 반환 |
GET |
/api/v1/experiments/{id}/analysis |
저장 이벤트·관측치를 분석한 그래프 데이터와 집계 JSON |
GET / POST
|
/api/v1/analysis-jobs/{id} |
백그라운드 분석 상태 조회 / 접수. 중복 요청 재사용, ?refresh=1로 새 snapshot 분석 |
GET / HEAD
|
/api/v1/analysis-jobs/{id}/result?jobId={jobId} |
서버에 보관된 완료 분석 JSON 다운로드. 작업 미완료·다른 attempt는 409
|
GET / HEAD
|
/api/v1/analysis-jobs/{id}/summary?jobId={jobId} |
비교용 경량 분석 JSON. 메시지별 경로와 원본 시계열을 제외하고 집계·분포·적합 결과를 반환 |
GET / POST
|
/api/v1/batch-analysis-jobs/{batchId} |
동일 반복 실험의 통합 평균 작업 상태 조회 / 시작. ?refresh=1은 새 로그 경계로 갱신 |
GET / HEAD
|
/api/v1/batch-analysis-jobs/{batchId}/result?jobId={jobId} |
저장된 통합 평균과 개요용 run별 입력 다운로드 |
POST |
/api/v1/downloads |
선택한 시나리오·Run의 ZIP 다운로드 준비 |
GET |
/api/v1/downloads/{ticket} |
인증 후 만료 시간이 있는 일회용 티켓을 소모하고 ZIP 전송 |
GET / HEAD
|
/api/v1/experiments/{id}/download |
시나리오·메타데이터·이벤트·선택적 관측 파일·파생 지표를 ZIP으로 다운로드하거나 응답 본문 없이 크기 계산 |
POST |
/api/v1/experiments |
YAML 1회 실행 또는 JSON {scenario, repetitions}로 1~100회 순차 실행 |
POST |
/api/v1/experiments/{id}/stop |
X-KPL-Execution으로 지정한 실행을 취소한 뒤 제한 시간 내 job 종료와 generation-fenced Peer cleanup 수행. 누락·과거 실행 ID는 409
|
/api/v1/bootstrap의 runId query parameter는 필수입니다. registry는 해당 run에서 준비 상태이고 유효한 identity와 address 정보가 있는 boot 노드만 반환하므로 동시에 실행되는 실험끼리 bootstrap peer를 발견하지 않습니다. /api/v1/discovery에는 표시된 query 세 개가 모두 필요하며 요청자 자신을 제외하고 실제 전달·mesh 결과가 아닌 설정상 topic 참가자를 반환합니다. /api/v1/prometheus/agent-targets는 기본 Swarm Prometheus 설정이 사용하는 읽기 전용 운영 endpoint이며 offline Agent와 유효한 metrics URL이 없는 Agent는 제외합니다.
Bootstrap 응답은 {nodeId, peerId, addresses} 항목 배열이며 비어 있으면 null입니다. Discovery와 달리 bootstrap은 Agent online 상태로 필터링하지 않습니다. Discovery는 후보가 없을 때 빈 배열을 반환하며 각 항목에는 subscribed flag도 포함합니다. 조건에 맞는 전체 후보를 반환하고, 요청하는 Peer가 토폴로지에 설명한 rendezvous 순위, connection budget과 retry를 적용합니다.
/api/v1/prometheus/controller-targets는 Controller의 --metrics-url (KPL_CONTROLLER_METRICS_URL)을 광고하며 미설정 시 []를 반환합니다. 인증 없이 조회하는 읽기 전용 endpoint입니다. URL은 HTTP(S)의 /metrics 경로여야 하며 인증정보·query·fragment·loopback/unspecified 주소를 허용하지 않습니다. Swarm helper가 control 노드 주소와 KPL_HTTP_PORT로 자동 설정합니다.
POST /api/v1/agents/refresh는 로그인 세션과 X-KPL-Request: dashboard를 요구합니다. Offline을 포함한 등록 Agent에서 최신 Agent·Peer 상태를 조회합니다. ?discover=true를 붙이면 실행 중인 Swarm Agent task도 탐색하여 누락된 등록을 복구합니다. Controller의 --agent-discovery-dns / KPL_AGENT_DISCOVERY_DNS 이름을 조회하고 각 task IP의 8090 포트를 확인합니다. Swarm stack은 tasks.<stack>_agent를 자동 지정합니다. Controller에 Docker socket을 마운트하거나 task를 재시작하거나 Peer를 정리하지 않습니다.
최대 4개를 동시에 확인하며 전체 제한 시간은 15초입니다. DNS 조회에는 3초 제한, 탐색에는 중복 제거한 task 주소 256개 제한을 적용합니다. DNS가 실패해도 기존 등록 주소는 조회합니다. 미등록·교체된 Agent에는 인증된 POST /api/v1/registration/refresh를 요청하며, Agent가 자신의 Controller URL과 서비스 키로 이 Controller에 다시 등록해야 합니다. 미등록 Agent의 상태 응답만으로 등록을 만들지 않습니다. 기존 ID·인스턴스 세대 검사와 저장된 용량·비활성화 설정을 유지합니다. 이미 등록된 인스턴스는 이력 재전송을 다시 시작하지 않고 상태만 확인합니다.
응답은 200과 {agents, requested, refreshed, failures}입니다. 탐색 시 discovery: {enabled, addresses, added, error?}도 포함합니다. addresses는 제한 적용 전 중복을 제거한 task 주소 수이며, added는 요청 시작 당시 목록에 없던 ID 중 갱신에 성공한 수입니다. 여러 주소에서 같은 Agent를 확인해도 성공 Agent와 실패한 알려진 ID는 각각 한 번만 집계합니다. 식별하지 못한 task 주소는 개별 실패로 집계합니다. 각 실패에는 {id, name, error}가 있으며, 미식별 주소는 빈 id와 URL을 name으로 사용합니다. DNS·설정 오류는 성공한 갱신을 유지한 채 discovery.error에 표시합니다. 연결 실패 시 Agent 기록을 삭제하지 않으며 Offline 판정에는 기존 heartbeat 만료 기준을 적용합니다. 새로고침·탐색이 이미 진행 중이면 409, 잘못된 discover 값은 400입니다. task 자체가 시작되지 않거나 제어 API를 열지 못한 경우에는 Swarm·Docker 시작 오류를 확인해야 합니다. 등록 복구를 사용하려면 Controller와 Agent 이미지를 함께 갱신하고 stack 설정을 재배포하십시오.
PUT /api/v1/agents/{agentID}/capacity는 로그인 세션과 X-KPL-Request: dashboard를 요구합니다. 필수 capacity 필드는 양의 정수 또는 예외값을 삭제하는 null을 받습니다. 알 수 없는 필드·소수·0 이하 값은 400입니다. 갱신한 Agent와 200을 반환하며, 없는 ID는 404, 이 기능 지원을 보고하지 않는 Agent는 409입니다. 등록된 오프라인 Agent도 설정할 수 있습니다. 저장 실패 시 오류를 반환하고 현재 설정은 변경하지 않습니다.
Agent 레코드에는 defaultCapacity(시작 CLI 값), 선택적 capacityOverride(저장 예외값), capacityPending(적용 확인 대기)을 제공합니다. capacity는 Controller가 현재 배치에 사용하는 한도입니다. 등록·heartbeat 응답의 X-KPL-Agent-Capacity, X-KPL-Agent-Capacity-Revision으로 설정을 전달하며 heartbeat 응답은 기존 204를 유지합니다. Agent는 생성 admission과 같은 잠금 아래 설정을 적용하고 다음 보고에 실제 capacity와 capacityRevision을 보냅니다. 현재 설정을 확인한 보고만 용량 증가를 허용하므로 지연된 보고가 감소 설정을 취소하지 않습니다. Controller 데이터 디렉터리의 agent-capacities.json에 예외값을 저장하며 파일이 손상되면 한도를 임의 복원하지 않고 Controller 시작을 중단합니다.
Agent의 로컬 용량 제한 때문에 create-node 요청을 거절하면 Peer를 만들기 전에 429를 반환합니다. Controller는 해당 예약을 해제하고 기존 작업 컨텍스트 안에서 배치를 대기·재시도합니다. 다른 생성 오류 처리는 유지합니다.
PUT /api/v1/agents/{agentID}/enabled에 {"enabled": true} 또는 {"enabled": false}를 보내 참여 여부를 설정하며 갱신한 Agent를 200으로 반환합니다. 누락·null·boolean 이외 값·알 수 없는 필드는 400, 존재하지 않는 Agent는 404입니다. 로그인과 X-KPL-Request: dashboard가 필요합니다. 제외된 Agent는 disabled: true로 표시하며 생략 또는 false는 활성입니다. online/offline 연결 상태 및 capacity 예외 설정과 독립적입니다.
비활성 Agent도 등록과 연결을 유지하며 기존 Peer 작업·정리가 가능합니다. 모든 새 배치 모드, 하드웨어 합계·gauge, Prometheus Agent target discovery, Dashboard 토폴로지에서 제외합니다. 이미 전송한 생성 요청과 기존 Peer는 계속 실행될 수 있으며 비활성화가 이를 종료하거나 실험 측정값을 다시 쓰지는 않습니다. 비활성 Agent를 지정한 explicit/single-Agent join은 재활성화 또는 기존 취소·제한시간을 기다립니다. 전체 REST 인벤토리에는 소속 Peer를 유지하며 Dashboard는 설정용 Agent 행을 남기고 Peer와 간선만 숨깁니다. ID별 설정은 Controller 데이터 디렉터리의 agent-availability.json에 저장하고 heartbeat로 덮어쓰지 않습니다. 저장 실패 시 이전 상태를 유지합니다. 파일을 읽을 수 없으면 등록된 Agent를 비활성으로 유지하며 파일 복구 후 Controller를 재시작해야 합니다.
브라우저는 GET /api/v1/stream?view=dashboard&topology=0으로 시작합니다. Overview를 켜면 topology=1로 새 연결을 열고, 끄면 topology=0으로 돌아갑니다. 연결마다 첫 데이터 snapshot은 선택한 대시보드 view의 전체 기준 데이터를 담은 event: snapshot이며, 이를 한 번 보낸 뒤, 변경이 있으면 최대 초당 1회 event: snapshot_delta를 보냅니다. 재접속은 항상 전체 데이터로 시작하므로 Last-Event-ID 재전송이 필요하지 않습니다. 유휴 연결도 시간에 따라 달라지는 값을 갱신하며, 15초 동안 변경이 없으면 event: heartbeat, data: {}를 보냅니다. SSE 주석과 달리 브라우저가 받을 수 있어 연결 무응답 감지에 사용합니다.
topology=0은 상세 노드 projection과 간선 계산을 생략하며 nodes: [], edges: []를 보냅니다. 두 dashboard 모드는 모두 topology: {included, total, ready, starting, other}를 포함하고, 피어 수 정의는 토폴로지 수명 상태 규칙을 따릅니다. 비활성 Agent와 stopping·stopped·failed Peer는 제외하며 레이어·topic 필터는 피어 수를 바꾸지 않습니다. Agent, 실험 진행 정보, 지표와 이벤트 요약은 두 모드 모두 제공합니다. 브라우저는 Overview가 Off이거나 로딩 중일 때 Transport links를 미상으로 표시합니다. topology를 생략하면 기존 dashboard 클라이언트를 위해 그래프를 포함합니다.
snapshot 계산은 raw view, 그래프를 포함하는 dashboard view, 그래프를 제외하는 dashboard view별로 최대 하나씩 공유합니다. 그래프를 제외하는 view는 진행 중인 그래프 계산을 기다리지 않습니다. 취소된 조회는 즉시 대기를 끝내고 재접속은 해당 모드의 기존 계산을 기다립니다. 계산 중에는 첫 snapshot 이전에도 15초마다 heartbeat를 보냅니다. 초기 대기 중 heartbeat는 30초 연결 감시 타이머를 다시 설정하지만 Dashboard를 Live로 표시하거나 기존 화면 데이터를 교체하지는 않습니다. 지표 재구성은 Run별 잠금을 사용하며 Agent heartbeat·배치가 사용하는 Controller 인벤토리 잠금 밖에서 수행합니다.
그래프를 포함하는 view의 node metadata에서는 예약 계획 전체가 반복 전송되지 않도록 networkRequested와 networkSchedule을 제외하고, 현재 network·적용 상태·revision은 유지합니다. 전체 REST snapshot에는 두 계획을 보존합니다.
그래프를 포함하는 대시보드 데이터는 현재 Peer(stopping·stopped·failed 제외), 전체 실험 진행 정보, 토폴로지, 지표, 최근 이벤트 요약 40개를 포함합니다. 이벤트 요약은 run/node ID, 종류, 상대 Peer, 시각, 지연과 화면 표시용 단일 값 latencyAvailable, direction, controlType, controlEntries, messageIdCount, peerExchangeCount만 포함합니다. 원본 message-ID/cohort 배열과 이벤트별 bandwidth 표본은 보내지 않습니다. 전체 trace 기록은 기존 snapshot API와 저장된 실험 내보내기에 보존됩니다.
변경분에는 바뀐 영역만 들어갑니다. agents, nodes, experiments는 선택 필드 upsert(추가·변경된 항목의 전체 내용, id 기준), remove(삭제 ID), order(순서가 바뀌면 전체 ID 순서)를 갖는 객체입니다. 삭제, upsert, 순서 적용 순으로 처리합니다. edges, events, metrics, topology는 필드가 있으면 빈 배열·null을 포함해 해당 영역 전체를 교체하며, 갱신 시 generatedAt도 바뀝니다. 연결마다 마지막으로 전달한 데이터를 기준으로 비교하므로 중간 갱신을 건너뛰어도 변경이 유실되지 않습니다.
view 없는 기존 GET /api/v1/stream은 다른 클라이언트와의 호환을 위해 원본 최근 이벤트 상세를 포함한 전체 snapshot 형식을 유지합니다. 알 수 없는 view 또는 비어 있지 않은 0/1 이외의 topology 값은 400을 반환합니다. 비어 있지 않은 topology 선택 값에는 view=dashboard도 필요합니다. 모든 모드에 같은 세션 인증을 적용하고 로그아웃·세션 만료·Controller 종료 시 연결을 닫습니다. 응답은 캐시를 금지하고 reverse proxy에 버퍼링 비활성화를 요청합니다(X-Accel-Buffering: no).
대시보드는 모바일 백그라운드 탭 등 문서가 숨겨지거나 페이지를 떠날 때 SSE를 닫습니다. 돌아오면 연결 하나를 다시 열어 전체 데이터를 받습니다. 화면이 보이는 동안 스트림은 계속 열려 있으므로 누적 수신 바이트는 정적 페이지 다운로드 크기와 다릅니다.
최초 snapshot은 30초, 이후 snapshot·변경분·heartbeat 사이는 45초까지 기다립니다. 이를 넘으면 멈춘 연결을 닫고 SSE 오류와 같은 복구 경로를 실행합니다. 세션 확인은 8초 뒤 취소하며, 실패·시간 초과여도 다음 연결을 시도합니다. 401은 Login required 안내 페이지로 이동합니다. 반복 실패 시 재시도 간격은 무작위 편차를 포함해 약 2초에서 최대 30초까지 늘어나고, 데이터를 받으면 초기화합니다. 브라우저 online 이벤트는 오래된 연결·인증 확인을 취소하고 화면이 보이면 바로 재연결합니다. 화면을 숨기거나 떠나면 SSE와 진행 중인 세션 확인을 함께 취소합니다. 서버의 10초 쓰기 제한시간은 각 전송·flush 중에만 적용하고 직후 해제하므로 HTTP/2 유휴 연결이 다음 heartbeat 전에 끊기지 않습니다.
Agent 등록·heartbeat·상태·대시보드 기록에는 선택적으로 resources가 포함됩니다. 필드는 sampledAt, 원시 사용 코어 수 cpuCores, 호스트 논리 CPU 수 cpuCapacityCores, memoryUsageBytes, memoryWorkingSetBytes, containers, measuredContainers, complete, 선택적 error입니다. 범위는 Agent와 소속 실행 중 Peer입니다. 부분 측정은 complete: false와 함께 측정된 컨테이너 합계를 보존하며 측정 0개는 사용 가능한 값이 없습니다. Controller는 표본 경과 시간을 정규화하고 Offline 또는 30초보다 오래된 표본은 현재 합계에서 제외합니다. 호스트 CPU 백분율은 cpuCores / cpuCapacityCores × 100이며 CPU 수 미상일 때 백분율만 생략하고 메모리는 유지합니다.
새 Agent 표본은 선택적 resources.agent, resources.peers 객체도 제공합니다. 각 객체는 cpuCores, memoryUsageBytes, memoryWorkingSetBytes, containers, measuredContainers, complete를 가지며 상위 표본 시각과 CPU 수를 공유합니다. 기존 상위 값은 Agent + Peers 합계로 유지합니다. 각 범위의 측정 유효성을 별도로 판단하므로 Agent 측정 실패가 측정된 Peer 값을 가리지 않습니다. 정상 조회 결과 빈 Peer 인벤토리는 containers: 0, measuredContainers: 0, complete: true와 사용량 0을 기록하며 누락·실패 표본을 0으로 바꾸지 않습니다.
GET /api/v1/agents/resources는 {generatedAt, agents, totals, totalsByScope, services}를 반환합니다. totals는 원시 cpuCores와 메모리 필드를 유지하고 cpuPercent, cpuCapacityCores, cpuMeasuredAgents, measuredAgents, totalAgents, partialAgents, containers, measuredContainers를 제공합니다. cpuPercent는 최신 표본을 호스트 CPU 용량으로 가중 계산하며 CPU 수 미상 Agent는 분자·분모 모두 제외합니다. 원시 코어·메모리 합계의 Agent 범위는 더 넓을 수 있으므로 개수를 함께 확인하십시오. ?format=csv는 Agent별 complete/partial/미상 상태, cpu_percent_host, cpu_capacity_cores, 두 메모리 값, 컨테이너 개수와 TOTAL 행을 내보냅니다. 누락값은 0이 아닌 빈 셀입니다. totals는 기존 합계이며 totalsByScope는 agent_and_peers, agent, peers 각각의 측정 범위와 CPU 용량 가중 분모를 제공합니다. 현재 CSV는 분리값을 보고한 Agent의 범위별 행과 유효 측정이 있는 범위별 TOTAL 행을 추가합니다.
GET /api/v1/agents/resources/history?range=1h는 Prometheus 원본 표본을 {from, to, scope, series}로 조회합니다. 상위 scope는 all이며 각 시리즈는 agentId, scope(agent_and_peers, agent, peers), metric, unit, samples: [{at, value}], mean, min, max를 가집니다. format=csv&kind=samples 또는 format=csv&kind=summary로 다운로드합니다. CPU는 cpu_percent 지표·percent_host 단위(호스트 전체=100%), 메모리는 bytes, 시각은 UTC입니다. 컨테이너 측정 범위도 containers_expected, containers_measured 시리즈와 containers 단위로 포함합니다. 합계 CPU는 kpl_agent_cpu_usage_percent, 분리 CPU는 kpl_agent_component_cpu_usage_percent gauge를 조회합니다. 원시 코어 gauge와 섞지 않으며 각 이력은 해당 gauge가 처음 수집된 시점부터 생성됩니다. range는 1m–24h입니다. 부분 측정값도 통계에 포함하되 누락 사용량은 추정하지 않습니다. 누락 구간과 비유한 값은 제외하고 동일 중복은 한 번만 계산하며 같은 Agent·scope·지표·시각의 충돌값은 오류입니다. 평균은 사용 가능한 scrape 표본 기준입니다.
GET /api/v1/agents/resources/measurements는 {generatedAt, maxDurationSeconds, measurements}를 반환하며 최신 구간부터 {id, startedAt, endedAt?, endReason?} 형식으로 제공합니다. 같은 경로의 POST는 공유 구간을 시작(201)하거나 이미 진행 중인 구간을 반환(200)하며 수집기를 재시작하지 않습니다. POST /api/v1/agents/resources/measurements/{id}/stop은 종료 시각을 확정합니다(200). 반복 종료는 기존 종료 시각을 보존하고, 이전 ID의 종료 요청은 현재 구간을 멈추지 않습니다. 변경 API는 다른 Dashboard 작업과 같은 브라우저 세션·CSRF 검사를 사용합니다. 인덱스는 Controller 로컬 저장소에 원자적으로 교체 저장하고 재시작 시 복원합니다. 메타데이터 읽기·쓰기 실패는 상태를 버리지 않고 503으로 알립니다.
이력과 구간 내보내기는 기존 합계 gauge와 새 kpl_agent_component_* gauge를 제한된 단일 쿼리로 함께 조회합니다. CSV 칼럼은 유지하고 행마다 scope로 구분하며 통계와 중복 검사는 scope별로 독립적입니다. 분리 시리즈는 업그레이드된 Agent가 보고한 시점부터 생성하고 기존 합계 전용 이력은 분리값을 추정하지 않고 유지합니다.
DELETE /api/v1/agents/resources/measurements/{id}는 완료 구간 하나를 삭제하고 갱신한 측정 목록을 200으로 반환합니다. 진행 중 구간은 409, 없거나 이미 삭제된 ID는 404입니다. 세션·CSRF 보호와 로컬 원자적 저장을 사용하며 쓰기 실패 시 실제 기록을 지우지 않고 503을 반환합니다. 해당 저장 구간으로의 내보내기는 더 이상 사용할 수 없습니다. 이미 구간을 확보한 다운로드는 완료할 수 있습니다. 공용 Prometheus 표본과 이미 내려받은 CSV는 유지합니다.
GET /api/v1/agents/resources/measurements/{id}/export는 저장된 구간에 대해 위와 같은 이력 JSON을 반환합니다. format=csv&kind=samples 또는 format=csv&kind=summary로 다운로드합니다. scrape 시각이 (startedAt, endedAt]인 표본을 선택하며 소수 초 경계를 보존하고, 재다운로드 시에도 시작·종료 시각을 유지합니다. 진행 중 구간은 종료 전까지 409, 알 수 없는 ID 또는 CSV 표본 부재는 404입니다. 짧은 구간도 허용하지만 표본이 없을 수 있습니다. 구간은 24시간 후 자동 종료(endReason: "time_limit", 수동 종료는 "stopped")하며 최근 100개 구간을 보존합니다. 표본 보존 기간은 Prometheus 설정을 따릅니다. 최근 이력 조회와 같은 내보내기 제한을 적용하고, 다운로드 실패 후에도 구간을 유지해 재시도할 수 있습니다.
모든 리소스 endpoint는 로그인이 필요합니다. 이력은 Controller에 설정된 Prometheus 주소와 고정된 리소스 selector를 사용하며 요청자가 임의 backend URL이나 PromQL을 지정할 수 없습니다. upstream 조회 동시 2개·15초·16 MiB·250,000개 표본 제한을 적용합니다. 잘못된 parameter는 400, CSV 표본 부재는 404, Prometheus 응답 실패·크기 초과·불완전 응답은 502, 조회 슬롯 부족은 503입니다. 현재 CSV는 Prometheus 연결 없이 동작합니다. 리소스 이력은 실험 ZIP과 별도이며 KPL 리소스 모니터링을 참고하십시오.
GET /api/v1/services/resources는 Dashboard 로그인이 필요하며 {generatedAt, controllerUptimeSeconds, services}를 반환합니다. 서비스 항목은 service, scope, nodeId, nodeName, reportedAt (Controller 수신 시각), state, 선택적 resources와 error를 포함합니다. 상태는 running, not_running, unknown, stale, awaiting이며 컨테이너 실행 상태와 자원 표본 유효성은 별개입니다. 자원 필드는 기존 Agent 합계와 같은 시각·호스트 CPU 수·메모리·커버리지 형식이며 Agent/Peer 하위 범위는 없습니다. /api/v1/agents/resources도 services를 추가하며 기존 Agent 합계는 바꾸지 않습니다.
POST /api/v1/services/resources/report는 내부 Bearer 인증만 허용합니다. 본문은 {nodeId, nodeName, reportedAt, services: [{service, state, resources?}], error?}이며 보고 상태는 running, not_running, unknown입니다. 요청 128 KiB, 노드당 중복 없는 서비스 64개, 서비스당 컨테이너 512개로 제한합니다. ID는 길이가 제한된 단순 식별자입니다. 표본 시각은 Controller 시계로 정규화하고 순서가 뒤늦은 보고는 무시합니다. 캐시는 노드 최대 512개이며 30초 뒤 stale, 10분 동안 미수신하면 만료됩니다. 정상 보고는 204, 잘못된 보고 400, 인증 실패 401, 캐시 한도 초과 503입니다. 브라우저 세션으로 텔레메트리를 제출할 수 없습니다.
자원 이력과 저장 구간 내보내기는 같은 제한된 Prometheus 요청에 kpl_service_* 지표를 포함합니다. 각 series에는 entityType (agent 또는 service)이 추가됩니다. 서비스 series에는 service·nodeId가 있으며 agentId는 비우고 scope=controller|third_party|monitor를 사용합니다. running과 resource_report_fresh의 단위는 boolean이고 CPU·메모리·커버리지의 이름과 단위는 기존과 같습니다. 통계와 중복 검사 키는 scope뿐 아니라 서비스와 호스트를 모두 구분합니다. 현재 값·이력·요약 CSV의 기존 열 뒤에 entity_type,service,node_id,node_name을 추가하며 이력의 node_name은 비웁니다. 기존 Agent 합계/하위 범위 이력도 유지됩니다. Controller 및 관리 서비스를 참고하세요.
POST /api/v1/scenarios/validate는 원본 YAML 본문(Content-Type: application/yaml)을 최대 1 MiB까지 받습니다. 유효하면 200과 {valid: true, name, phases}를 반환합니다. 빈 입력, YAML 문법 오류, 알 수 없는 필드, 잘못된 설정, 여러 YAML 문서는 400과 {error: "…"}를 반환하며 파서가 제공하는 줄 번호를 보존합니다. 본문 한도 초과는 413입니다. 저장·실행과 같은 파서를 사용하지만 기록이나 job을 만들지 않으며 로그인 세션을 사용합니다. 설정 검증이며 Agent 용량이나 실행 시 연결성을 검사하지는 않습니다.
요청과 응답 본문은 모두 JSON을 사용합니다. 큰 라이브러리도 가볍게 열 수 있도록 목록 응답에는 YAML을 넣지 않으므로, 편집하기 전에 개별 항목을 조회하십시오.
| Method | Path | 본문 | 성공 응답 |
|---|---|---|---|
GET |
/api/v1/scenarios |
없음 |
id, name, createdAt, updatedAt 요약 목록 |
POST |
/api/v1/scenarios |
{ "name": "…", "yaml": "…" } |
전체 항목과 201
|
GET |
/api/v1/scenarios/{id} |
없음 |
yaml을 포함한 전체 항목 |
PUT |
/api/v1/scenarios/{id} |
{ "name": "…", "yaml": "…" } |
갱신된 전체 항목과 200
|
DELETE |
/api/v1/scenarios/{id} |
없음 | 본문 없는 204
|
조회·검증·변경 모두 로그인 세션이 필요합니다. 변경 요청의 X-KPL-Request: dashboard 헤더는 UI가 자동으로 전달합니다. 해석한 YAML이 1 MiB를 넘는 경우를 포함하여 유효하지 않은 입력은 400입니다. JSON 요청 본문은 escape된 문자를 고려해 6 * 1 MiB + 64 KiB까지 허용하며 이 한도를 넘으면 413입니다. 알 수 없는 JSON 필드와 후행 JSON 값은 거부합니다. 없거나 유효하지 않은 ID는 404, 저장소 오류는 500입니다. ID는 소문자 16진수 32자로 구성됩니다.
control-node:8080은 access가 표시한 Controller 주소로 바꾸고, 먼저 API 인증으로 KPL_COOKIE_JAR를 생성하십시오.
curl --fail -b "${KPL_COOKIE_JAR:?Log in first}" http://control-node:8080/api/v1/scenarios
curl --fail -X POST http://control-node:8080/api/v1/scenarios \
-H 'Content-Type: application/json' \
-b "${KPL_COOKIE_JAR:?Log in first}" -H 'X-KPL-Request: dashboard' \
--data-binary @- <<'JSON'
{"name":"Smoke baseline","yaml":"version: 2\nname: smoke-baseline\nphases:\n - action: stop-all\n"}
JSON시나리오와 저장 결과는 정리용 그룹 목록을 공유합니다. 항목은 최대 한 그룹에 속하며, 소속 기록이 없으면 Ungrouped입니다. 라이브러리 정리용 그룹은 반복 실행 배치, 배치 메모, 시나리오의 Peer 그룹 및 분석 범위와 독립적입니다.
모든 endpoint는 성공 시 전체 snapshot {revision, groups, memberships}를 반환합니다. groups는 {id, name} 배열이며, memberships는 항목 키를 그룹 ID에 연결합니다. 그룹 ID는 소문자 16진수 32자입니다. 최초 빈 snapshot은 {"revision":"0","groups":[],"memberships":{}}입니다. 이후 revision은 해석하지 않는 문자열 토큰으로 취급하고 모든 변경 요청에 최신 값을 전달하십시오.
| Method | Path | JSON 본문 | 성공 응답 |
|---|---|---|---|
GET |
/api/v1/library-groups |
없음 | 현재 snapshot과 200
|
POST |
/api/v1/library-groups |
{name, revision} |
새 그룹이 포함된 snapshot과 201
|
PUT |
/api/v1/library-groups/members |
{groupId, keys, revision} |
선택 항목 전체 이동 후 200
|
PATCH |
/api/v1/library-groups/{id} |
{name, revision} |
그룹 이름 변경 후 200
|
DELETE |
/api/v1/library-groups/{id} |
{revision} |
그룹과 해당 소속 기록 제거 후 200
|
항목 키는 저장 시나리오의 scenario:<id>, 반복 실행 배치 전체의 batch:<batchId>, 배치 없는 결과의 run:<id>입니다. 배치는 이후 추가·재시도한 Run을 포함하여 소속 기록 하나를 공유합니다. 소속을 해제하려면 groupId: ""로 이동하십시오. 항목 이동과 그룹 삭제는 하나의 메타데이터 갱신으로 적용합니다. 그룹을 삭제해도 시나리오 YAML, Run, 배치 식별자, 메모, 아카이브와 분석은 보존되며 해당 항목은 Ungrouped가 됩니다.
모든 endpoint에 로그인 세션이 필요하며 변경 요청에는 X-KPL-Request: dashboard도 필요합니다. 응답은 Cache-Control: no-store를 사용합니다. 오래된 revision 또는 중복 이름은 409를 반환하므로 snapshot을 다시 조회하고 변경 내용을 확인한 뒤 명시적으로 재시도하십시오. 없는 그룹 또는 그룹에 넣으려는 항목을 찾을 수 없으면 404입니다. 잘못된 필드·키·이름과 그룹·소속 개수 한도 초과는 400이며, 알 수 없는 JSON 필드와 후행 값은 거부합니다. 요청 본문 2 MiB 초과는 413, 저장소 오류는 500입니다. 검증 실패 시 일부 항목만 이동하지 않으며, 네트워크 응답이 불확실하면 재시도 전에 다시 조회하십시오.
이름은 앞뒤 공백을 제거한 유효 UTF-8 문자 1–128자로, 제어 문자를 허용하지 않으며 Unicode 대소문자를 구분하지 않는 비교에서 고유해야 합니다. 최대 그룹 1,000개, 소속 기록 20,000개를 지원하고 한 번의 이동 요청은 키 1–2,000개를 받습니다. 메타데이터는 최대 4 MiB인 로컬 <data-dir>/library-groups.json에 저장되어 Controller 재시작 후에도 유지됩니다. 그룹 변경은 실험 입력이나 분석 해시를 바꾸지 않습니다. 읽을 수 없거나 유효하지 않은 메타데이터 파일은 빈 값으로 대체하지 않고 오류를 반환합니다. UI 사용 순서는 시나리오 라이브러리와 결과를 참고하십시오.
신규 제출·재시도·이어하기·추가 실행은 Controller 전체의 접수 순서 대기열을 공유합니다. 202는 접수 완료이며 즉시 실행을 뜻하지 않습니다. 차례가 오기 전에는 첫 Run도 state: "queued", 0인 startedAt을 유지하며 여러 제출을 예약할 수 있습니다. 실시간 experiment의 선택 필드 queuePosition은 앞선 제출 수(회차 수 아님)이고 0이면 생략합니다. 같은 제출의 반복은 동일한 위치를 사용하며 결과 manifest에는 저장하지 않습니다. 실패·취소는 해당 제출의 남은 반복만 끝내고, 이전 Peer 정리 확인 후 다음 제출을 시작합니다. 정리가 실패하면 다음 제출의 phaseName에 원인을 표시하며 자동 재시도합니다. 그 대기 항목을 중지해도 이후 제출의 정리 조건은 유지됩니다. 단일 Run이 유지한 Peer도 다음 제출 전에 정리합니다. 예약 대기는 phase나 schedule 시간을 소모하지 않습니다. Controller 재시작 후 남은 예약은 interrupted로 표시하며 수동 복구합니다. 실험 예약을 참고하십시오.
POST /api/v1/experiments는 첫 run의 experiment 객체와 202를 반환합니다. {scenario, repetitions, scenarioId?}를 제출하려면 Content-Type: application/json을 사용하십시오. scenario는 YAML 문자열이며 repetitions를 생략하면 기본값은 1입니다. 선택적인 scenarioId는 그룹을 상속할 저장 시나리오를 식별합니다. 다른 content type은 원시 YAML로 처리합니다. 원시 YAML과 JSON에서 해석한 scenario 문자열의 한도는 각각 1 MiB입니다. JSON 요청 본문은 escape를 고려해 6 * 1 MiB + 64 KiB까지 허용합니다. 요청 본문 자체가 한도를 넘으면 413, 해석한 YAML 문자열이 한도를 넘거나 scenario/repetition이 유효하지 않으면 400입니다.
scenarioId는 소문자 16진수 32자로 된 저장 시나리오 ID입니다. Controller는 제출한 scenario 본문을 실행하므로 편집기의 미저장 변경도 사용할 수 있습니다. 신규 접수 시 scenario:<scenarioId>의 현재 소속을 읽고 batch:<newBatchId>에 복사합니다. repetitions가 1이어도 배치 키를 사용합니다. 시나리오에 그룹이 없거나 ID를 생략하거나 원시 YAML로 제출하면 결과는 Ungrouped가 됩니다. 이후 원본 시나리오를 이동·삭제해도 복사한 소속을 바꾸지 않습니다. 재시도·이어하기·추가 실행은 기존 결과 소속을 유지합니다. 잘못된 ID는 400, 신규 접수 시 저장 시나리오가 없으면 404입니다. 저장 시나리오 읽기 오류와 그룹 조회·저장 실패는 500, 소속 개수 한도 초과는 400을 반환합니다. 초기 그룹 소속은 실행 접수 확정 전에 저장하므로 이 오류들은 실험 시작 전에 접수를 거부합니다.
Idempotency-Key는 호환성을 위해 선택 사항이지만 자동화에서는 고유 키(최대 128바이트)를 지정하고 응답이 불확실하면 같은 시나리오 본문·반복 수·scenarioId로 재사용하십시오. UI는 session storage로 자동 처리합니다. 중복 요청은 Controller 재시작 또는 원본 시나리오 삭제 후에도 기존 접수 Run을 반환하며 재실행하지 않습니다. 그룹 상속을 다시 수행하거나 이후 수동으로 이동한 결과 소속을 덮어쓰지 않습니다. 같은 키에 다른 입력(scenarioId 변경 포함)은 409, 원래 결과가 삭제된 키는 410입니다. 키를 생략하면 성공한 요청마다 새 배치를 만듭니다. run-submissions/를 백업하십시오.
최초 제출·재시도·이어하기·추가 실행마다 새 executionId를 부여합니다. 현재 Run 객체의 값을 X-KPL-Execution: <executionId>에 넣어 POST /api/v1/experiments/{id}/stop을 호출하십시오. 로그인과 X-KPL-Request: dashboard도 필요합니다. 실행 ID 누락·불일치와 과거 구성원 중지는 409입니다. 같은 실행에 대해 접수된 중지를 반복해도 안전하며 SSE의 stopRequested로 새로고침 후에도 취소 진행을 확인합니다.
3회 반복 JSON 요청 본문 예제:
{"scenario":"version: 1\nname: repeat-example\nphases:\n - action: wait\n duration: 1s\n","repetitions":3}반복 제출은 매 iteration에 별도 run ID와 결과 기록을 예약하고 batchId, iteration, repetitions를 공유합니다. 순차 실행하며 실패하거나 취소된 iteration은 대기 중인 나머지 실행도 취소합니다. 현재 실행 묶음이 진행 중일 때 해당 묶음의 구성원을 중지하면 그 실행 묶음을 취소합니다. repetitions > 1에서는 마지막을 포함한 매 iteration이 최종 상태를 기록하기 전에 Peer를 fence하고 제거합니다. 자연스럽게 성공한 단일 실행은 시나리오 종료 시 실행 중인 Peer를 유지할 수 있습니다.
POST /api/v1/result-batches/{batchId}/retry는 저장된 미완료 회차를 회차 순서대로 수동 재실행합니다. 예를 들어 10회 중 4회차가 실패했다면 완료된 13회차는 유지하고 410회차를 각 회차의 첫 phase부터 다시 실행합니다. 뒤쪽 회차 중 이미 완료된 회차도 유지합니다. 실패·중단(재시작 후 미시작으로 남은 기록 포함)·취소 기록을 대상으로 합니다. 시나리오와 seed를 유지하면서 새 run ID를 부여하며 experiment/result 메타데이터의 previousRunIds에 이전 시도 ID를 기록합니다. 기존 로그는 Previous attempts에서 다운로드할 수 있고, 회차별 가장 최근의 저장된 시도만 배치 진행률과 평균에 반영합니다. 기존 /resume은 시도한 회차를 건너뛰고 미시작 잔여 회차만 재개합니다.
result-groups/{batchId}/batch.json은 모든 접수 모드의 확정 경계이며 ID를 재사용하는 이어하기의 이전 메타데이터도 보존합니다. 확정 전 중단은 부분 예약을 롤백하고 확정된 미완료 작업은 interrupted로 남깁니다. superseded로 최신 결과를 삭제해도 이전 시도를 진행률에 다시 포함하지 않습니다. 손상된 그룹 기록은 해당 그룹을 차단하고 오류를 기록하되 다른 그룹의 Controller 시작은 계속합니다.
두 이어하기 모드는 접수한 HTTP 요청과 독립적으로 Controller에서 실행합니다. 새 회차 실행 전에 이전 Peer를 fence·제거하고 Agent 상태를 갱신합니다. 정리가 실패하거나 정리 중 사용자가 정지하면 대기 회차를 canceled로 기록하고 experiment 메타데이터에 이유를 남기며 새 회차는 시작하지 않습니다. Agent·정리 오류를 해결한 뒤 다시 이어하기를 실행할 수 있습니다. 실행·최종 정리·다운로드·개별 및 배치 분석이 진행 중이면 접수를 거절하며, 완료된 회차 기록은 덮어쓰지 않습니다.
cleanupState(pending, running, complete, failed, retained)와 dataState(pending, collecting, complete, incomplete, unverified)는 실행 state와 별개이며 cleanupError, integrityError에 문제를 보존합니다. 새 Agent는 runDrain을 알리고 과거 Agent를 사용한 Run은 unverified입니다. 상세 결과의 agents, controllerVersion은 실행 환경이며 SSE에서는 큰 환경 배열을 제외합니다. run-ownership/는 create RPC 전에 참여 Agent를 기록하여 Agent 재등록 전에도 정리 대상을 유지합니다.
GET /api/v1/experiments, Dashboard snapshot/SSE와 최초 제출 응답의 실시간 experiment 객체에는 timing이 포함될 수 있습니다. 필드는 estimatedFinishAt(UTC), 음수가 아닌 remainingSeconds, basis(scenario 또는 observed-runs), observedRuns, overdue입니다. 진행 중인 반복 Run에는 batchEstimatedFinishAt, batchRemainingSeconds, batchOverdue도 포함됩니다(0·false인 선택 필드는 생략 가능). 대기 Run의 종료 시각에는 같은 배치의 선행 Run 시간이 포함됩니다. 완료·실패·취소된 Run이나 시간 정보가 부족한 경우 timing을 생략하며 결과 manifest에 저장하지 않습니다. 계산 방식과 불확실성은 예상 종료 시각을 참고하십시오.
중지 endpoint는 cleanup 완료 전, 취소 요청을 접수하면 202와 JSON {"runId":"run-id","status":"stopping"}을 반환합니다. Dashboard는 snapshot에서 실행·대기 중인 구성원이 없음을 확인할 때까지 해당 실행 또는 배치의 버튼을 **Stopping…**으로 비활성화하며, 접수 응답만으로 버튼을 다시 활성화하지 않습니다. /api/v1/experiments 또는 snapshot에서 최종 상태를 확인하십시오. 취소 handle이 더 이상 없는 run은 404입니다. 경량·기존 스트림 계약은 Dashboard SSE를 참고하십시오. /api/v1/events는 현재 Controller 상태에서 가장 최근 event 최대 300개를 포함합니다.
저장소 루트에서 실행합니다. 호스트는 access가 표시한 Controller 주소로 바꾸고, 먼저 인증 절차로 KPL_COOKIE_JAR를 생성하십시오.
curl -X POST http://control-node:8080/api/v1/experiments \
-H 'Content-Type: application/yaml' \
-b "${KPL_COOKIE_JAR:?Log in first}" -H 'X-KPL-Request: dashboard' \
--data-binary @examples/smoke.yaml시나리오 라이브러리 endpoint는 재사용할 편집기 입력을 실험 결과와 별도로 저장합니다. 목록에서는 YAML을 제외하고, 개별 GET·POST·PUT 응답에는 포함합니다. UI 사용 순서와 저장 위치는 시나리오 라이브러리 안내, payload와 검증 제한은 라이브러리 API를 참고하십시오.
POST /api/v1/result-batches/{batchId}/append는 로그인 세션, X-KPL-Request: dashboard, JSON {"additionalRuns":10,"expectedRepetitions":10}을 요구합니다. 두 필드는 양의 정수이며 추가 후 총합은 100 이하여야 합니다. 성공하면 첫 새 대기 Run과 202를 반환합니다. 예시에서는 iteration: 11, repetitions: 20이며 기존 배치 ID와 완료 결과를 유지합니다. 실행은 Controller 수명에 연결하여 요청·브라우저 종료로 취소되지 않습니다.
접수 잠금 아래 현재 그룹 총합이 expectedRepetitions와 일치해야 합니다. 동시·오래된 중복 요청은 다시 추가하지 않고 409를 반환합니다. 누락·미완료·불일치 회차, 서로 다른 저장 시나리오, 활성 배치·다운로드·분석도 409입니다. 잘못된 본문·수량은 400, 없는 그룹은 404, 로컬 공간 부족은 507, 저장 오류는 500입니다. 접수 여부가 불명확하면 그룹을 새로고침한 뒤 재요청하십시오. 확정된 추가는 현재 총합과 배치 구성 서명을 바꾸므로 다음 통합 분석은 기존·추가 완료 Run을 함께 집계합니다.
로컬 그룹 기록이 원자적 확정 경계입니다. 준비 중인 추가분은 숨기고 복구 시 롤백하며, 확정된 대기 파일은 기존 재시도 절차로 재개할 수 있도록 보존합니다. 목록·새 분석은 과거 원본 메타데이터를 덮어쓰지 않고 현재 총합을 적용합니다. 두 결과 저장소와 result-groups/<batchId>/batch.json을 함께 백업해야 합니다.
결과 API는 시작 시각 최신순을 유지하되 queued처럼 시작 시각이 같으면 배치·회차·ID 순서로 정렬합니다.
원시 이벤트와 관측은 로컬 <data-dir>/current-run/<run-id>/에 기록합니다. 보관 작업자는 검증한 로그 조각과 큰 개별 분석 파일을 <data-dir>/runs/<run-id>/로 옮기고 작은 로컬 메타데이터 인덱스를 유지합니다. 기본 로컬 data 디렉터리는 data이며 Swarm은 /var/lib/kpl/data에 마운트합니다. NAS에는 runs/만 둘 수 있습니다. 복사·늦은 로그·복구·백업 규칙은 로컬 기록과 NAS 보관을 참고하십시오.
대시보드의 Download results로 실험 결과를 ZIP으로 받을 수 있습니다. Saved results에는 이전 Controller 실행에서 보존된 결과도 표시되며, Refresh로 목록을 다시 읽습니다. 실행 중 실험의 Download snapshot은 다운로드 시작 시점까지 저장된 기록을 담습니다. 최근 300개 이벤트 버퍼와 별개로 저장된 전체 이벤트 로그를 내보냅니다. 파일 구성과 수집 한계는 실험 결과 다운로드를 참고하십시오.
GET /api/v1/results는 저장 기록을 반환합니다. 결과 목록의 sourceBytes는 scenario.yaml, experiment.json, events.jsonl, observations.jsonl, 선택적 note.json 의 로컬 파일과 인덱스에 등록된 NAS 객체를 합한 논리 크기입니다. NAS에 접속하거나 로그 본문을 읽고 분석·압축하지 않고 로컬 파일 통계와 보관 인덱스만 조회하므로 큰 결과도 용량을 빠르게 표시합니다. 생성된 분석 캐시·이미지, 다운로드 시 생성하는 metrics.json·export.json, 파일시스템 할당 오버헤드는 제외합니다. 메타데이터가 손상되어도 원본 파일 크기를 조회할 수 있으면 용량을 표시합니다. 원본 파일 자체의 통계를 읽을 수 없으면 sourceBytes를 생략하고 화면에는 **Source · —**로 표시합니다.
DELETE /api/v1/results/{id}는 로컬 삭제와 삭제 표식 저장 성공 시 204(NAS 파일은 비동기 삭제), 결과가 없으면 404, 실행·배치가 활성 상태이거나 실제 GET 다운로드가 결과를 사용 중이면 409를 반환합니다. 목록 조회는 다운로드 충돌로 처리하지 않습니다. NAS 파일 descriptor를 여는 짧은 보호 구간에는 409가 가능하며 HEAD는 descriptor 캡처 후 이 보호를 해제합니다.
DELETE /api/v1/result-batches/{batchId}는 로그인 세션과 정확한 배치 ID를 사용합니다. 파일을 삭제하기 전에 전체 구성원을 검사하며 실행·정리 중인 run이나 구성원의 ZIP 다운로드가 있으면 409를 반환합니다. 실패·취소 run을 포함한 모든 저장 구성원, 개별 분석, 별도 통합 평균을 제거합니다. 성공 시 200과 {"deletedIds":["run-id", "..."]}를 반환하며 그룹이 없으면 404입니다. 원본 없이 남은 통합 평균도 제거할 수 있습니다. 저장 장치 오류는 500이며 일부 삭제가 끝났을 수 있으므로 목록을 갱신하고 저장 오류를 해결한 뒤 재시도하십시오. 브라우저는 요청에 30초 제한 시간을 적용하며 그 이후에도 Controller에서 완료될 수 있습니다.
대시보드는 용량 표시를 위한 HEAD 요청이나 ZIP 크기 재측정 타이머를 실행하지 않습니다. 직접 호출하는 HEAD /api/v1/experiments/RUN_ID/download는 기존처럼 ZIP encoder를 바이트 counter에 실행하여 정확한 Content-Length를 반환하며, 개별 GET 다운로드도 해당 측정을 사용합니다. 일괄 다운로드는 크기를 미리 계산하지 않고 전송합니다. ZIP은 메모리나 디스크에 통째로 보관하지 않고 스트리밍합니다. 같은 run의 동시 측정은 공유하고 서로 다른 run의 측정은 최대 두 개로 제한합니다. 기존 API 호환성을 위해 목록은 준비된 ZIP 크기가 유효하면 downloadBytes도 제공하지만 화면은 이를 사용하지 않습니다.
Pending publication이 없으면 파일과 상태가 같은 동안 측정한 바이트 수를 유지하면서 HEAD 또는 다운로드마다 정밀도를 유지한 새 export 시각을 기록합니다. 저장 방식이 고정된 export.json에는 고정 폭 시각 문자열을 사용하므로 시각이 바뀌어도 인코딩 길이는 같습니다. Stable 목록 행에는 downloadSizeMaxAgeMs가 없고 stable HEAD 응답에는 X-KPL-Result-Size-Max-Age-Ms가 없습니다. Pending publication이 있으면 바이트 수와 export 경계를 짧게 함께 재사용합니다. 목록 행은 downloadSizeMaxAgeMs를 포함하고 HEAD는 X-KPL-Result-Size-Max-Age-Ms를 제공합니다. 두 값은 서버의 남은 cache 유효 시간에서 응답 안전 여유 1초를 뺀 양의 밀리초이므로 client와 server의 시계가 일치하지 않아도 됩니다. 준비된 값을 처음 확인한 목록만 유효 시간을 한 번 연장하고, 연장한 경계에서 계산한 max age를 응답하므로 바로 이어지는 GET이 표시 크기와 일치합니다. 목록을 반복해서 새로고침해도 deadline이 계속 밀리지는 않습니다. 안전 여유를 제외하고 남은 유효 시간이 없으면 목록은 downloadBytes와 downloadSizeMaxAgeMs를 모두 생략하고, HEAD는 응답 전에 새 경계를 측정합니다.
Controller 시작 후 첫 HEAD나 원본 파일·상태 변경 뒤에는 캡처한 파일을 읽고 지표를 다시 구성해야 하며, pending 결과는 짧은 cache가 만료된 뒤 이 작업을 반복합니다. 측정 slot 대기, 원본 로그 읽기와 재구성한 지표 집계 단계에서 HTTP 요청 취소를 확인하며, 취소된 요청은 server error 응답 없이 중단합니다. 저장한 이벤트 로그가 크면 측정 시간이 길어질 수 있습니다. 이 비용은 직접 요청한 HEAD나 실제 다운로드에만 발생하며, 화면의 원본 용량 표시에는 필요하지 않습니다.
ZIP 원본 복사는 스트리밍하며 지표 재구성에는 고유 이벤트 ID·메시지/수신 쌍에 비례한 메모리가 필요합니다.
POST /api/v1/downloads는 {"kind":"scenarios","ids":["scenario-id"]} 또는 {"kind":"results","ids":["run-id"]}를 받습니다. 결과 ID는 배치 구성원을 포함한 개별 Run ID이며 대시보드가 선택한 배치를 Run 목록으로 확장해 전송합니다. 일반 로그인 세션과 X-KPL-Request: dashboard가 필요합니다. 요청당 ID 1~2,000개, JSON 본문 512 KiB까지 허용하며 중복 ID는 제거합니다. 검증은 로컬 메타데이터만 읽고 보관된 원본 로그는 열지 않습니다.
성공하면 201과 {"url":"/api/v1/downloads/<ticket>","count":1,"expiresAt":"<UTC timestamp>"}를 반환합니다. 잘못된 선택은 400, 없는 항목은 404, 읽을 수 없는 메타데이터는 500, 대기 티켓 저장소가 가득 차면 429입니다. 티켓은 10분 뒤 만료되고 메모리에만 보관하므로 Controller 재시작 시 무효화됩니다. 대기 티켓은 최대 16개입니다.
로그인 상태에서 반환된 URL로 GET하면 티켓을 소모하고 scenarios-<UTC timestamp>.zip 또는 results-<UTC timestamp>.zip 첨부 파일을 스트리밍합니다. 전체 ZIP을 브라우저 JavaScript나 서버에 보관하지 않고 사전 ZIP 크기 계산도 하지 않습니다. 일괄 전송은 최대 두 개를 동시에 처리하며, 초과 GET은 티켓을 소모하지 않고 429와 Retry-After: 5를 반환합니다. 만료·소모되었거나 없는 티켓은 410입니다. 다운로드 URL은 인증을 대신하지 않으며 HEAD는 지원하지 않습니다.
시나리오 항목에는 저장된 YAML 원문을 담습니다. 결과 항목에는 개별 Run ZIP을 담으며 각 Run 처리 차례에 스냅샷을 확보합니다. 티켓은 모든 원본을 미리 보호하지 않으므로 처리 전·도중의 삭제나 저장소 오류로 전송이 실패할 수 있습니다. ZIP 출력을 시작하기 전에는 누락 시 404, 읽기 실패 시 500을 반환합니다. 시작한 뒤에는 스트림을 중단하고 완성된 바깥 ZIP을 남기지 않습니다. 취소 시 현재 Run의 다운로드 보호와 전송 슬롯을 해제합니다. 파일명과 구성은 시나리오 다운로드, 결과 다운로드를 참고하십시오.
로그인이 필요한 GET /api/v1/result-storage는 localDirectory, archiveDirectory, availableBytes, minFreeBytes, checking, checkStartedAt, lastCheckedAt, phase, lastProgressAt, stalled, 선택적 error를 반환합니다. 공간은 Controller의 로컬 데이터 파일시스템에서 측정하고 NAS 상태는 최근 백그라운드 작업 결과이므로 요청이 NAS 조회를 기다리지 않습니다. phase는 preparing, discovering, copying, verifying, publishing, deleting, 실험 중 대기인 paused, idle을 구분합니다. 서버는 아카이브 I/O 단계에서 진행 기록이 15초 넘게 갱신되지 않을 때만 stalled를 설정합니다. 전체 작업 시간, 로컬 준비, 의도적인 실험 대기는 지연 판정에서 제외합니다. 복사·검증은 청크 완료마다 진행 시각을 갱신하고 대기 후 재개할 때 타이머를 새로 시작합니다. UI는 서버 판정을 사용하므로 브라우저 시계 차이로 경고가 발생하지 않습니다. 진행 지연 표시이며 NAS 성능 측정이나 자동 작업 취소를 의미하지 않습니다. 대시보드는 화면이 보일 때 SSE와 별도로 이 작은 endpoint를 15초마다 조회합니다.
결과 행에는 local, pending, archiving, archived 중 하나인 storage: {state, error?, updatedAt}를 포함합니다. 저장 상태는 실험 상태나 배치 평균 구성을 바꾸지 않습니다. sourceBytes는 NAS 로그 조각과 로컬 후속 기록을 각각 한 번만 셉니다. 보관한 원본 다운로드·분석에는 NAS 응답이 필요하지만 목록·메모는 로컬 인덱스를 사용합니다. 기존 NAS 결과는 시작 시·주기적 백그라운드 인덱스가 발견하면서 화면에 나타납니다.
새 POST /api/v1/experiments 요청은 로컬 여유가 설정 기준보다 작으면 507입니다. 반복·재개 배치는 다음 Run 전에 phaseName: "Waiting for local result storage"인 대기 상태가 될 수 있으며 취소할 수 있습니다. 표준 CLI 기본값은 --run-min-free-bytes / KPL_RUN_MIN_FREE_BYTES의 1 GiB이고 0이면 여유 공간 검사를 끕니다. 활성 Run에서도 약 1초마다 공간·기록 오류를 확인하며 문제 발생 시 데이터 불완전을 표시하고 실행·대기 회차를 중단합니다. 이 기준은 크기가 정해지지 않은 활성 Run의 전체 공간을 예약하지는 않습니다.
GET /api/v1/results/{id}/note는 Cache-Control: no-store와 {runId, text, revision, updatedAt?}를 반환합니다. 메모가 없으면 text는 빈 문자열, revision은 "0"이며 시각은 생략합니다. 조회·저장 모두 로그인 세션이 필요합니다. PUT은 X-KPL-Request: dashboard와 마지막 조회에서 받은 revision을 포함한 JSON {text, revision}을 요구합니다. 성공하면 200과 저장 레코드를 반환하며 내용이 바뀌면 새 불투명 revision을 발급합니다. updatedAt은 UTC RFC 3339 시각입니다. 오래된 revision은 409를 반환하므로 최신 내용을 조회·검토한 후 다시 제출하십시오. 메모는 세션별 비공개 데이터가 아닌 공유 기록입니다.
본문은 디코딩된 UTF-8 기준 최대 16,384바이트입니다. 공백만 입력하면 메모를 비우고, 그 외에는 공백과 줄바꿈을 보존합니다. 필수 필드 누락/null, 알 수 없는 필드, 후행 JSON과 본문 용량 초과는 400입니다. JSON 이스케이프와 메타데이터를 고려해 요청 전체는 6 * 16,384 + 4,096바이트까지 허용하며 초과 시 413입니다. 잘못되거나 없거나 삭제된 run ID는 404입니다. 저장소 오류는 500이며 읽을 수 없는 기존 메모를 임의로 교체하지 않습니다. 내용이 같으면 revision과 저장 시각을 유지합니다.
Controller는 결과 삭제와 동일한 저장 잠금 아래 로컬 current-run/<run-id>/note.json을 원자적으로 교체합니다. 보관 작업자는 이 잠금 밖에서 runs/<run-id>/note.json에 비동기 반영합니다. 비운 메모도 revision을 유지하여 오래된 편집으로 내용이 복원되지 않게 합니다. 기존 결과를 마이그레이션할 필요가 없고 실험 메타데이터나 분석 캐시를 다시 쓰지 않습니다. 실행·다운로드 도중에도 수정할 수 있으며 이미 캡처한 ZIP은 당시 메모 버전을 유지합니다. GET /api/v1/results는 비어 있지 않은 메모에 선택적 note: {preview, updatedAt}를 추가합니다. 미리보기는 공백을 정리하고 Unicode 코드 포인트 160개와 말줄임표로 제한하므로 전체 본문은 개별 메모 API로 조회하십시오. 대시보드 SSE에는 메모가 들어가지 않습니다.
GET / PUT /api/v1/result-batches/{batchId}/note는 같은 크기 제한·revision·상태 코드·로그인/CSRF 규칙을 적용하며 {batchId, text, revision, updatedAt?}를 반환합니다. 저장된 구성원이나 기존 그룹 메타데이터 디렉터리가 있어야 합니다. Group 메모는 로컬 result-groups/<batchId>/note.json에 저장하여 재시도·개별 Run 삭제·분석 갱신으로 덮어쓰지 않습니다. 그룹 전체 삭제는 모든 구성원 삭제가 성공한 뒤 메모를 제거하며, 구성원이 모두 삭제된 메모도 그룹 삭제 API로 정리할 수 있습니다. 오래된 편집창에서 저장해도 삭제된 그룹을 재생성하지 않습니다.
목록 응답은 같은 내용을 반복하지 않도록 그룹 구성원 중 최대 하나에 groupNote: {preview, updatedAt}를 포함합니다. 클라이언트는 batchId로 묶은 뒤 표시해야 합니다. 전체 Group 메모는 편집 시에만 조회하고 SSE와 개별 Run ZIP에 포함하지 않습니다. NAS Run 보관소에는 반영하지 않으므로 result-groups/를 로컬 Controller 데이터와 함께 백업하십시오.
저장 실행 하나를 분석하려면 POST /api/v1/analysis-jobs/{id}로 접수하고 같은 경로의 GET으로 상태를 확인합니다. POST와 GET/HEAD 모두 로그인 세션이 필요합니다. 접수된 대기·실행 작업은 202, 현재 버전의 완료 결과를 재사용하면 200입니다. Controller 전체에서 대기·실행 작업을 최대 32개 접수하고(가득 차면 503), 공유 분석 슬롯은 한 번에 하나의 run을 분석합니다. 클라이언트를 닫아도 접수된 작업은 취소되지 않습니다.
하나의 run 안에서는 독립된 관측 스냅샷의 그래프 메트릭을 병렬로 계산합니다. worker 수는 min(4, max(1, GOMAXPROCS - 1))이며 여러 CPU를 사용하는 Controller에서는 Go 실행 슬롯 하나를 여유로 남깁니다. 같은 그래프는 진행 중인 계산과 캐시를 공유하며 최대 256개를 보존합니다. 결과는 원래 관측 순서를 유지하고, phase: graph metrics 완료/전체는 계산이 끝난 관측 수를 표시합니다. 오류나 취소가 발생하면 남은 worker를 중단하고 종료를 기다립니다. 메트릭 정의와 캐시 분석 버전은 유지하며, 개별·배치·동기 분석에 모두 같은 계산 경로를 적용합니다.
상태 state는 idle, queued, running, completed, failed, canceled, interrupted입니다. idle은 해당 저장 실행에 요청한 분석이 없다는 뜻입니다. 실패·취소·중단 작업은 다시 접수할 수 있습니다. 재시작 후 복구한 미완료 기록은 interrupted이며 정상 종료 과정에서 이미 canceled를 저장했을 수도 있습니다. 별도 작업 취소 endpoint는 없습니다. 삭제 가능한 저장 결과를 지우면 분석 작업도 취소하고 분석 파일을 제거합니다.
| 상태 필드 | 의미 |
|---|---|
id, runId
|
분석 시도 ID와 원본 실행 ID. 재시도·갱신으로 새 시도가 생길 수 있음 |
version, analysisVersion
|
상태·응답 형식 버전 1, 현재 작업의 계산 버전 8. 지표의 definition, 로그의 rpcMetadataVersion과 별개 |
phase, processedBytes, totalBytes, progress
|
현재 단계와 로그 읽기 바이트 진행률(0–100). 읽기 100% 뒤에도 그래프·전파 계산과 저장이 남으므로 state: completed를 확인 |
createdAt, startedAt, updatedAt, finishedAt
|
작업 시각. 아직 도달하지 않은 단계의 시각에는 의미가 없음 |
sourceHash, sourceRevision, reused
|
원본 표본의 해시, 가벼운 변경 감지용 값, 해시 확인 후 기존 분석 재사용 여부 |
savedAnalysisId |
검증·재분석 동안 보존하는 이전 완료 분석 ID입니다. 교체 작업 중단·재시작 후에도 유지합니다. |
artifactSlot |
작업 메타데이터가 선택한 저장 슬롯 0 또는 1입니다. 생략하면 0이며 분석 endpoint가 해당 파일을 자동 선택합니다. |
snapshotAt |
POST 접수 시점이 아니라 worker가 분석 슬롯을 얻은 뒤 잡은 원본 경계 |
error, resultUrl
|
실패 내용 또는 시도 ID가 포함된 완료 전체 분석 URL |
단일·통합 분석은 phase: saving 동안에도 running 상태이며, 결과 파일을 저장하는 중에 상태 조회·실험 이벤트 수신·중단 요청 처리를 계속할 수 있습니다. 완료 결과를 게시하기 전에 현재 분석 시도인지와 취소 여부를 다시 확인하고, 개별 분석은 원본 삭제 여부도 확인합니다.
서버는 원본이 같아도 완료 분석의 계산 버전이 현재 분석기와 다르면 stale: true로 표시합니다. 개별·배치 상태와 Saved results 목록 모두에 적용합니다. 클라이언트는 분석기 버전 기준을 따로 관리하지 않고 이 표시로 갱신을 요청합니다. 기존 완료 분석은 교체 전까지 다운로드할 수 있습니다.
기존 대기·실행 작업은 ?refresh=1이어도 재사용합니다. 완료 작업의 sourceRevision은 가벼운 변경 감지용이며 목록·상태 조회에서 NAS 로그 본문을 읽지 않습니다. Controller의 아카이브 이동은 원본 내용이 같으면 로컬 로그 분리 중에도 revision을 보존합니다. 자세한 계약은 원본 변경을 참고하십시오. revision 변경 시 개별 GET은 completed, stale: true를 반환합니다. 일반 POST는 제한된 작업 슬롯에서 논리적 원본 바이트의 표본으로 만든 SHA-256인 sourceHash를 확인합니다. 해시와 analysisVersion이 같으면 기존 완료 작업 ID·분석 파일·스냅샷 시각을 복원하고 revision을 갱신하며 reused: true를 표시합니다. 따라서 확인 작업을 폴링하다 완료되면 원래 분석의 ID로 돌아올 수 있습니다. 해시가 다르거나 분석기 버전이 오래되었거나 콘텐츠 해시가 없는 이전 분석이면 다시 계산합니다. Images가 이 검사를 자동 수행하며 GET만으로 작업을 시작하지 않습니다. Reanalyze는 ?refresh=1로 완료 분석의 해시 재사용을 건너뜁니다. 새 분석으로 과거에 없던 기록을 복원할 수는 없습니다.
표본 해시 형식은 sampled-sha256-v1:<hex>입니다. 논리적 원본(scenario.yaml, experiment.json, events.jsonl, observations.jsonl)마다 이름·전체 바이트 길이·표본 위치·표본 바이트를 반영합니다. 64 KiB 이하는 전체를 읽고, 더 큰 파일은 앞·뒤를 포함한 512바이트 구간 128개를 읽습니다. 내부 위치는 알고리즘 버전·이름·길이로 재현 가능한 의사 난수로 정하므로 다른 스냅샷이나 아카이브 조각 배치에서도 같은 위치를 비교합니다. 원본별 최대 64 KiB를 임의 접근으로 읽으며 검증의 바이트 진행률은 이 읽기 양을 나타냅니다. 같은 크기의 변경이 표본 밖에 있으면 해시가 같아 변경을 놓칠 수 있습니다. 전체 파일 무결성 검증이 아닌 근사적인 캐시 확인입니다. 분석 계산 자체는 필요한 입력 전체를 읽고 ?refresh=1은 그 계산을 강제합니다. 기존 전체 해시 기반 분석은 원본 revision이 같으면 캐시를 유지하며, revision이 바뀌면 표본 기준을 만들기 위해 한 번 계산합니다. 보관 복사의 무결성 검증과 보안·빌드 해시는 전체 내용을 계속 사용합니다.
개별 실행과 배치 평균 모두 원본 해시를 비교하거나 새 분석을 계산하는 동안 저장된 분석을 먼저 표시합니다. 이전 스냅샷 시각을 유지하며 검증 진행 상황을 별도로 표시합니다. 해시가 같으면 그래프·검색 상태를 그대로 유지하고 새 분석이 완료되면 교체합니다. 검증 실패 시에도 기존 결과와 오류 안내를 함께 남깁니다. 수동 재분석도 대기 중 현재 화면을 유지합니다.
교체 작업이 진행 중이거나 실패한 상태에서 이전 분석을 읽을 수 있으면 savedAnalysisId를 제공합니다. 기존 /result 또는 개별 /summary 경로에 해당 jobId와 &saved=1을 명시하면 이전 결과를 조회할 수 있습니다. 일반 요청은 미완료 작업에 여전히 409를 반환합니다. 이전 결과 참조는 Controller 재시작과 파일 게시 실패에도 보존하며 완료된 교체 메타데이터를 게시할 때 해제합니다. 모든 분석 버전을 영구 보관하는 기능은 아니며 불일치·교체된 ID는 409입니다. 화면의 다운로드 링크는 표시 중인 분석 ID를 사용합니다.
완료 후 GET 또는 HEAD /api/v1/analysis-jobs/{id}/result?jobId={jobId}는 전체 JSON, /summary?jobId={jobId}는 비교용 자료를 반환합니다. jobId를 지정하면 다른 분석 시도를 내려받지 않도록 확인하며 생략하면 현재 완료 시도를 선택합니다. 미완료·시도 불일치는 409, 실행 부재는 404, 잘못되거나 읽을 수 없는 저장 자료는 보통 422입니다. 경량 응답은 observations, scoreTimeline, timeline, bandwidthTimeline, research.messages를 빈 배열로 두고 messageCount·지표·집계·분포·적합 결과를 유지합니다. 개별 메시지 경로나 원본 시계열 용도로 사용할 수 없습니다. 두 응답 모두 analysisId, analysisVersion, 원본 경계 asOf를 포함합니다.
scoreTimeline은 일반 관측 축약과 별도로 상세 점수 구성요소를 보존합니다. 그룹·측정점 필드, 한도와 결측 규칙은 점수 구성요소 시계열에 정의합니다. 개별 전체 분석과 배치 분석에 보존하고 개별 비교용 요약에서는 빈 배열로 반환합니다.
research.receiverGroups는 수신 피어 그룹(group; 빈 문자열은 정보 누락·충돌), 수신 대상 수, 메시지 수, 요약 통계, 경량 overview, 전파·중복·홉·eager/lazy 곡선과 기본 latencyCDF/latencyHistogram을 제공합니다. 지연 분포는 기본 지표 정의를, 나머지 그룹 곡선은 연구 지표 정의를 유지합니다. 그룹 자료는 배치 경량화 후에도 보존됩니다. 새 TraceEvent에는 선택 필드 group이 포함되며, 과거 분석은 화면용 다운샘플링 전에 전체 저장 그래프에서 그룹을 복원합니다. 수신 그룹별 집계 기준을 참고하십시오.
GET /api/v1/experiments/{id}/analysis는 2분 요청 제한의 동기 호환 경로입니다. 보존되는 백그라운드 작업을 만들지 않고 응답을 계산합니다. 긴 분석과 나중 다운로드에는 job API를 사용하십시오. 연구 정의는 지표 가이드, 보존 파일은 모니터링, 브라우저 조작은 시각화에서 관리합니다.
POST /api/v1/batch-analysis-jobs/{batchId}는 실행 제출에 기록된 동일 배치의 완료 run을 분석합니다. 모든 배치 구성원의 실행·정리 완료와 최소 2개의 완료 run이 필요하며, 실행 중이면 409, 유효 완료 run 부족·메타데이터 불일치는 422, 배치가 없으면 404입니다. 인증·중복 접수·재시도·재시작 처리는 개별 분석과 같고, 개별·배치 작업이 합쳐서 최대 32개의 대기·실행 작업을 공유합니다. 최대 100회 반복을 하나의 배치 작업으로 순차 처리합니다.
상태의 배치 식별자는 batchId이며 runId는 비어 있습니다. 선택한 runIds, completedRuns, totalRuns, expectedRuns, 구성원 서명 membership도 제공합니다. 공통 작업 슬롯에서 run을 하나씩 캡처해 원본 해시를 먼저 확인하며, 바이트 필드는 현재 단계와 run을 나타냅니다. 계산 진행률은 run별 동일 가중치입니다. snapshotAt은 가장 최근에 분석한 run의 경계이며 runs[].asOf는 개별 경계입니다. 구성원·상태·원본 revision 변경 시 이전 완료 캐시를 idle, stale: true로 표시합니다. 일반 POST는 완료 run의 표본 해시, 구성원 식별자·결과, 과거 시도 집계를 반영한 배치 sourceHash를 비교합니다. 같으면 원래 배치 분석 파일과 ID를 재사용하고 다르면 다시 계산합니다. 제외된 이벤트 로그와 메모는 통계 입력이 아닙니다. 로컬 revision은 제공되는 파일 식별자·변경 시각도 반영하므로 크기·mtime을 유지한 재작성도 해시 확인을 요청합니다. 아카이브 revision은 NAS 본문 대신 manifest 메타데이터를 사용합니다. NAS 객체는 불변으로 취급하며 저장 워크플로를 통해 변경해야 합니다. ?refresh=1은 강제로 다시 계산합니다. 해시 확인·계산·저장 중 구성원이나 원본 revision이 바뀌면 작업을 실패 처리하므로 현재 원본으로 재시도하십시오.
구성원 서명에는 현재 완료 기여 Run의 원본 revision만 반영합니다. 제외된 시도와 이전 시도의 식별자·결과는 계속 반영하지만, 통계에 사용하지 않는 로그 변경으로 배치 평균의 원본 검증을 예약하지 않습니다.
통합 결과의 reliability는 attempts, failed, canceled, interrupted, retries, failedAttemptRate, unknownAttempts, unverifiedRuns, incompleteRuns, environments, environmentChanged, warnings를 제공합니다. 실제 시작한 시도와 삭제 후 보존된 시도 이력을 집계하며 미시작 대기 기록은 제외합니다. 실패율은 failed / attempts이고 지표 평균은 계속 최신 완료 회차만 사용합니다. 환경별 fingerprint, Run ID, Agent 스냅샷과 Controller 빌드 정보를 제공하지만 호스트 부하·미기록 네트워크 차이는 fingerprint 범위 밖입니다.
결과는 version: 1, analysisVersion: 8, aggregation: "equal-run-mean-v1", 배치·시도 ID, expectedRuns, missingRuns, excluded, runs, summary를 제공합니다. summary는 metrics.*, research.*, bandwidth.sentBytes, bandwidth.receivedBytes별 average, deviation(run 간 표본 SD), median, count(유효 run 수)입니다. 결측 평균은 null, 유효 run 1개의 SD도 null입니다. runs는 그래프 지표·관측·대역폭·분포와 메시지 시계열/수신 곡선/기원 수를 담은 research.overview를 보존하고 원시 메시지·노드 경로를 생략합니다. 평균 차트의 축 정렬·분포 규칙은 통합 평균 사용법을 참고하십시오.
완료 파일은 <data-dir>/batch-analyses/{batchId}/job.json, result.json에 저장됩니다. 창 종료와 서버 재시작 후 재사용할 수 있습니다. 명시적인 jobId 다운로드는 해당 완료 스냅샷에 고정되며 다른 시도로 교체됐으면 409입니다. 개별 원본 삭제는 기존 통합 스냅샷 파일을 지우지 않습니다. 변경된 구성으로 다시 분석하면 현재 파일을 교체하므로 이전 결과가 필요하면 먼저 다운로드하십시오.
제공되는 Swarm stack은 Controller의 --network-manager(KPL_NETWORK_MANAGER_URL)와 Agent의 --auto-reset-network --control-network NAME으로 이 경로를 활성화합니다. 이 옵션을 생략한 사용자 지정 배포는 이전 네트워크 수명주기를 유지합니다. Stack은 제어 서비스를 고정 overlay에 배치하고 control manager의 Docker socket과 영속 복구 volume을 사용하는 network-manager를 실행합니다. Standalone gateway에는 Docker socket이나 호스트 경로를 마운트하지 않습니다. 배포·업그레이드 요구사항을 참고하십시오.
Controller는 매 Run 전에 비활성 Agent를 포함한 등록 Agent 모두에 POST /api/v1/network/fence를 보냅니다. 본문은 {runId, epoch, requestedAt}입니다. Agent는 기존 프로세스 제거가 확인된 경우에만 새 입장을 막고 Docker에 관리 대상 Peer 컨테이너가 남았는지도 검사합니다. 거절하거나 연결되지 않는 Agent가 있으면 준비가 중단됩니다. 이후 같은 식별 정보와 Agent ID→제어 URL의 agents 맵을 Manager의 POST /api/v1/network/prepare로 보냅니다. Manager는 준비를 직렬화하고 오래된 요청을 거절하며 Swarm 노드 Ready, 네트워크 소유권·기본 IPv4 설정·서비스 참조를 확인합니다. Subnet과 gateway를 보존하면서 새 network ID로 생성하며 네트워크 강제 삭제는 하지 않습니다. 삭제 후 생성이나 응답이 실패해도 로컬 복구 파일에 기존 할당 설정을 보존합니다.
Docker 삭제 성공 응답 이후 Manager는 통합 network 목록에서 정확한 Peer network 이름이 사라진 것을 확인한 뒤 교체 네트워크를 생성합니다. 삭제 반영 대기, 명시적인 network with name ... already exists 거절의 재시도, 생성 성공 후 목록 반영 대기는 기존 Manager 작업 제한 안에서 합계 1분을 공유합니다. 폴링은 취소할 수 있고 조회 실패를 삭제 확인으로 취급하지 않습니다. 삭제를 기다리던 중 다른 ID가 나타나면 준비를 중단하며 해당 네트워크를 삭제하지 않습니다.
생성 응답이 실패하면 inspect로 실제 상태를 확인합니다. 새 네트워크가 현재 epoch·예상 소유권·할당 설정과 일치하고 endpoint 및 서비스 참조가 없을 때만 후속 준비를 계속할 수 있습니다. 그 외에는 명시적인 이름 충돌만 상태를 다시 확인한 뒤 재시도하며, 일반 오류는 생성 재시도로 처리하지 않습니다. 원래 network ID를 새 네트워크로 수락하지 않습니다. 목록 반영 지연, 지속적인 충돌, 취소 또는 확인 불가능한 상태가 해소되지 않으면 활성화하지 않습니다. 이 대기 시간에는 실험·시나리오 schedule 시계가 진행되지 않습니다.
응답은 {runId, epoch, networkId, networkName, gatewayUrl, peerGatewayUrl}입니다. Controller는 이를 각 Agent의 POST /api/v1/network/activate로 보내고 모두 확인된 뒤 Run의 startedAt을 기록하고 phase를 시작합니다. Experiment.peerNetworkId에 새 식별자를 보존합니다. CreateNodeRequest.peerNetworkId는 Agent가 준비한 현재 Run·시도의 입장 조건과 일치해야 하며 Docker 생성에는 고정 ID를 사용합니다. Worker는 첫 컨테이너 연결 때 overlay를 구성할 수 있으므로 Agent는 시작 후 실제 연결의 network ID도 확인합니다. 늦은 생성 요청이 같은 이름의 교체 네트워크에 입장할 수 없습니다. 준비 실패 시 phase를 시작하지 않고 기존 실패·정리·예약 처리 규칙을 따릅니다.
준비 endpoint에는 내부 Bearer 인증이 필요합니다. 각 endpoint는 JSON 값 하나만 허용하고 알 수 없는 필드를 거부합니다. Agent 본문은 64 KiB, Manager 본문은 1 MiB 한도입니다. Agent는 확인 시 204, 잘못된 입력은 400, 준비·정리·활성화를 확인할 수 없으면 409를 반환합니다. Manager는 성공한 JSON 응답에 200, 잘못된 본문에 400, 거절·실패한 준비에 409를 사용하며 인증 실패는 401입니다. 전체 준비 제한은 4분이며 Manager 작업은 3분, Agent 호출은 45초로 제한합니다. Gateway는 인증된 HTTP 제어·telemetry와 공개 Controller 시각 health 요청을 overlay 사이에서 전달하며 Peer의 P2P 통신은 직접 연결을 유지합니다. Gateway 자원은 제어 서비스 모니터링에 포함합니다. Gateway 교체는 다음 정리 확인 단계까지 미룹니다. 서비스 중지·Docker 재시작·데이터 volume 변경은 하지 않습니다. 운영 연결성과 대규모 반복 실행은 별도 배포 검증이 필요합니다.
이 REST/JSON endpoint는 컴포넌트 사이 통신에 사용합니다. 등록, heartbeat와 전달 event는 Controller로, lifecycle 명령은 Agent로 전송합니다.
| 호출자 → 서버 | Method | Path | 목적 |
|---|---|---|---|
| Agent → Controller | POST |
/api/v1/agents/register |
Agent instance 등록 |
| Agent → Controller | POST |
/api/v1/agents/heartbeat |
Agent와 Peer snapshot 보고 |
| Agent → Controller | POST |
/api/v1/events/batch |
batch당 최대 5000개 event 전달 |
| Controller → Agent | GET |
/api/v1/status |
Agent와 Peer 상태 갱신 |
| Controller → Agent | POST |
/api/v1/registration/refresh |
설정된 Controller에 인증된 재등록 후 Agent·Peer 상태 반환; Peer 재시작 없음 |
| Controller → Agent | POST |
/api/v1/nodes |
CreateNodeRequest로 Peer 생성 |
| Controller → Agent | DELETE |
/api/v1/nodes |
기존 run을 fence 처리하고 남은 Peer와 대기 telemetry를 모두 정리한 뒤 204 반환. Controller 종료 시 사용 |
| Controller → Agent | DELETE |
/api/v1/nodes/{nodeId} |
Peer 하나의 종료 요청 |
| Controller → Agent | POST |
/api/v1/nodes/{nodeId}/publish |
publish 요청 중계 |
| Controller → Agent | POST |
/api/v1/nodes/{nodeId}/mesh-freeze |
ready Peer에서 허용한 GossipSub 메시 동결을 적용하고 응답 반환 |
| Peer → Agent | POST |
/api/v1/nodes/{nodeId}/status |
해당 Peer의 최신 상태 보고 |
| Peer → Agent | POST |
/api/v1/telemetry |
telemetry batch 제출 |
| Agent → Peer |
GET / POST
|
/health / /publish
|
Peer HTTP API로 readiness 확인 또는 publish |
| Agent → Peer | POST |
/mesh-freeze |
해당 node·Run·generation에 메시 동결 적용 |
CreateNodeRequest.experimentStartedAt은 현재 실행의 startedAt을 복사한 RFC3339 시각이며 Peer 프로세스 설정에도 저장합니다. config.network.schedule에 변경 목록이 있고 reference: experiment-start이면 필수이며, 누락 시 Peer를 생성하기 전에 거부합니다. Controller가 자동으로 채우는 내부 요청 필드로 시나리오 YAML에는 넣지 않습니다. 정적 network 설정과 Peer 기준 예약에는 필요하지 않습니다.
createReplay: true를 알리는 Agent는 POST /api/v1/nodes 응답이 끊긴 경우의 복구를 지원합니다. Controller는 해당 Agent의 startedAt을 CreateNodeRequest.agentStartedAt에 복사하며, Agent 실행 인스턴스가 다르면 입장 전에 409로 거절합니다. 같은 노드 ID와 동일하게 디코딩된 요청이면 동시 요청은 최초 입장 처리가 끝나기를 기다리고, 이후 요청은 로컬 이력의 종료 상태를 포함한 기존 Peer 상태를 반환합니다. 요청 내용이 다르거나 최초 입장 처리가 실패했다면 409입니다. Agent는 기존 프로세스·이력 기록의 metadata.createRequestHash와 metadata.createAdmission에 SHA-256 요청 지문과 입장 결과를 보관하며 별도로 계속 커지는 요청 캐시를 만들지 않습니다. 두 필드는 Agent가 관리합니다. 복구로 Peer를 다시 실행하거나 lifetime을 초기화하지 않습니다. agentStartedAt을 생략한 요청은 기존처럼 중복 ID를 거절합니다.
지원 Agent에서는 EOF·응답 잘림·연결 reset/refusal·broken pipe·요청 timeout에 한해 같은 Agent URL·실행 인스턴스로 총 3회까지 시도하며, 사이 대기는 250ms와 500ms입니다. 각 요청의 기존 HTTP 제한은 10초이고 시나리오 context가 더 짧으면 이를 따릅니다. 복구 중 최종 프로필·seed·lifetime·노드 ID는 고정하며 실험 시계는 계속 진행합니다. HTTP 거절과 잘못된 확인 응답은 통신 재시도 대상이 아닙니다. 구버전 Agent에는 새 필드를 보내거나 생성 요청을 자동 재전송하지 않습니다. 전송한 요청의 생성 여부를 끝내 확인하지 못하면 Agent가 해당 Peer를 보고하거나 해당 실행 세대의 차단·제거 완료를 확인할 때까지 예약 용량을 유지합니다. 불확실한 요청을 다른 Agent에 재배치하지 않으며, 빈 inventory만으로 늦은 생성 요청이 도착하지 않을 것이라고 판단하지 않습니다.
예약 Peer의 node metadata.networkRequested는 요청한 network 설정을 유지하고, metadata.networkSchedule은 seed로 확정한 구체적 delay를 포함한 Agent의 예약 계획을 담습니다. 이 메타데이터 값은 JSON 문자열입니다. metadata.network는 현재 구체적인 조건을 나타내며 networkPending이 "true"인 동안은 아직 적용 확인 전입니다. Peer에서 적용이 성공하면 상태 보고에 networkPending: "false", networkRevision(10진수 문자열, 초기 상태는 0, 이후 변경 목록의 1부터 시작하는 번호), networkAppliedAt(RFC3339)을 설정합니다. 늦은 입장이나 타이머 지연 시 중간 revision을 건너뛸 수 있습니다. Agent는 이전 revision으로의 역전을 막고 heartbeat로 적용 상태를 Controller에 전달합니다. 이 필드는 보고 상태이며 실행 중인 Peer의 조건을 바꾸는 API가 아닙니다.
주기적인 Agent heartbeat는 partial: true를 사용하고 JSON 본문을 최대 10MiB로 나눕니다. 개별 Peer 기록이 한도를 넘으면 노드 이름을 포함한 오류를 기록하고 미확인 상태로 유지합니다. 실행 중 Peer, 컨테이너 제거에 실패한 Peer, Controller 확인을 기다리는 종료 Peer는 메모리에 남습니다. 제거가 확인된 기록에는 metadata.cleanupComplete="true"가 붙으며, 프로세스가 실패했더라도 컨테이너 제거가 성공하면 해당됩니다. 최종 상태·오류는 보존하면서 과거 토폴로지와 점수 데이터는 해제합니다.
Controller 확인 이후 백그라운드 작업은 최근 종료 기록 64개만 남기고 오래된 축약 기록을 로컬 bbolt 파일 하나인 <agent-data-dir>/peer-history/nodes에 제한된 트랜잭션으로 저장한 후 메모리에서 제거합니다. 주기 heartbeat는 확인된 종료 기록을 생략합니다. GET /api/v1/status와 GET /api/v1/nodes는 현재 inventory와 최근/미확인 기록을 반환하며 과거 모든 Peer를 반환하지 않습니다. full status는 누락된 활성 노드를 계속 정리하지만 제거가 확인된 실패 기록을 성공 상태로 바꾸지 않습니다. 과거 기록은 Controller 결과와 Agent 로컬 이력에 남습니다.
재등록하면 메모리의 최근 기록을 다시 전달하고, 매 활성 heartbeat 뒤에 디스크 이력을 제한된 페이지로 복구합니다. 페이지는 최대 128개·통상 1MiB이며 큰 단일 기록은 기존 heartbeat 상한까지 허용합니다. 실패한 페이지는 재시도하고 전체 이력을 프로세스 맵에 다시 적재하지 않습니다. 일시적인 통신 실패만으로 전체 재전송을 시작하지 않습니다. 복사한 DB 키를 기준으로 재전송을 이어가며 네트워크 전송 중 읽기 트랜잭션을 유지하지 않습니다. 제거된 노드 ID는 디스크 인덱스 조회로 계속 중복 생성을 거부하며 Agent 재시작 뒤에도 유지됩니다. 기존 JSON 이력은 시작 시 컨테이너 정리 전에 자동 변환하고 대조가 끝난 백업 파일을 점진적으로 정리합니다. 변환된 이력에는 호환되는 Agent 버전이 필요합니다. 이력 이전 설명을 참고하십시오. 실행 세대 차단 정보는 메모리에 128개를 캐시하고 나머지는 peer-history/fences/에 보관합니다. 오래된 stop 요청이 저장된 차단 세대를 낮출 수 없으며 전체 정지도 이미 메모리에서 제거한 run까지 차단합니다.
Controller 확인·로컬 정리를 기다리는 종료 기록 또는 이벤트 큐 진입을 기다리는 종료 표식이 각각 4,096개에 도달하면 새 Peer 생성은 Retry-After: 1과 함께 503을 반환합니다. 기존 Peer와 미전달 증거는 유지하고 Docker 제거 실패는 계속 용량을 점유합니다. 이력 조회·저장 I/O는 Agent 상태 잠금 밖에서 수행하며, 차단/이력 변경 번호를 재확인하여 디스크 조회와 정리가 교차할 때 오래된 생성 요청이 통과하지 못하게 합니다.
telemetry 요청은 이벤트 5000개와 JSON 본문 10 MiB로 제한됩니다. Peer와 Agent는 이스케이프와 envelope를 포함한 인코딩 바이트 수로 batch를 나누며, 재시도에도 원본 순서와 이벤트 식별자를 유지합니다. 수락된 앞부분은 제거한 후 나머지를 전송합니다. 두 수신자는 admission 전에 모든 이벤트를 검사합니다. 단일 이벤트가 인코딩된 10 MiB batch에 들어갈 수 없으면 413을 반환하며 해당 요청의 이벤트는 하나도 수락하지 않습니다. Agent는 자신의 식별자로 정규화한 뒤 검사합니다. Controller에 직접 제출한 이벤트도 이 검사를 거치므로 저장 분석에서 읽을 수 없는 과대 로그 행을 만들지 않습니다.
Controller는 디코딩부터 영구 저장까지 telemetry 요청을 최대 두 개 수락합니다. 추가 요청은 최대 64개까지 본문을 읽지 않은 채 최대 1초 대기하며, 취소되면 대기를 해제합니다. 포화 시 503, Retry-After: 1을 반환하고 수신 확인을 하지 않으므로 Agent는 해당 batch를 보관하고 재시도합니다. 저장을 시작한 요청은 클라이언트 시간 초과 후 완료될 수 있지만 이벤트 식별자로 재시도의 이중 집계를 막습니다. 이 제한은 동시에 디코딩한 본문의 수를 제한하며 전체 Go heap 상한은 아닙니다.
Agent는 수신 확인 전에 <agent-data-dir>/telemetry-spool/에 telemetry batch를 영구 저장합니다. 확인된 앞부분의 cursor를 저장하여 재시작 시 나머지를 재전송하고 Controller도 로컬 이벤트 batch를 디스크에 동기화한 뒤 수신 확인합니다. Agent 교체 시 데이터 volume을 유지해야 합니다. 최소 한 번 전달 방식이므로 재전송 때 이벤트 ID를 유지하며, Peer에서 수락하지 못한 이벤트나 잃어버린 volume의 자료는 복원하지 못합니다.
Spool 복구는 파일 위치/Run 인덱스와 최대 5,000개 / 직렬화 기준 1 MiB 전송 구간만 메모리에 두며, 개별적으로 유효한 큰 이벤트 하나는 구간 크기를 넘을 수 있습니다. telemetry 디코딩·저장은 동시 2개로 제한하고, 추가 64개 요청까지만 본문을 읽지 않고 최대 1초 대기합니다. 디코더 포화 또는 새 batch가 대기량 50,000개 / 256 MiB를 넘기면 503과 Retry-After를 반환하고 이미 수락한 디스크·전송 중 자료는 보존합니다. 대기량이 가득 찬 동안 새 Peer 배치도 일시 중단합니다. 기존 실행 Peer의 최종 증거는 일시적으로 한도를 넘을 수 있고 과거의 더 큰 적체도 삭제하지 않고 전달합니다. Run drain은 아직 디스크에 남은 해당 Run 이벤트까지 확인합니다.
POST /api/v1/nodes의 입장 전 거절에는 X-KPL-Admission-Retry: not-created와 Retry-After를 붙입니다. Controller는 예약을 반환하고 해당 Agent를 잠시 제외한 뒤 시나리오 context 안에서 재시도합니다. 미지정 배치는 다른 Agent를 사용할 수 있고, 지정/single-agent 배치는 대상을 유지합니다. 순차 업데이트 중에는 기존 telemetry·종료 증거 적체 오류 두 종류도 Retry-After가 있을 때 인식합니다. 일반 503/timeout은 무조건 재배치하지 않습니다.
새 spool은 4 MiB 목표 .jsonl 파일에 batch를 모으고 체크섬 ACK journal을 사용하며, 기존 .json도 복구합니다. format-v2.json 표식으로 구버전 Agent가 새 형식의 미전달 자료를 무시하지 못하도록 합니다. 전달을 마친 정상 종료에서 표식과 빈 활성 파일을 제거합니다.
Agent 정상 종료 또는 DELETE /api/v1/nodes의 telemetry drain은 마지막 Peer 이벤트를 포함해 대기 큐와 이미 전송 중인 batch 모두 Controller의 수신 확인을 받을 때까지 기다립니다. 실패한 batch는 순서를 유지해 재시도하며, 제한 시간 내 수신 확인을 완료하지 못하면 drain 실패를 보고합니다.
JSON 본문에는 값 하나만 있어야 합니다. 본문 한도 내의 후행 공백은 허용하며, 두 번째 JSON 값·후행 쓰레기 데이터·디코더 본문 한도 초과는 400을 반환합니다. Controller/Agent의 일반 JSON handler는 10 MiB, Peer /publish는 1 MiB, /mesh-freeze는 4 KiB 한도를 사용합니다. 시나리오 요청 envelope에는 위에서 설명한 별도 한도가 적용됩니다. Peer 내부에서 생성된 이벤트가 JSON으로 인코딩되지 않거나 단일 batch 한도를 넘으면 로그와 telemetry_drop에 유실 수를 남기고, 소스 sequence의 빈 번호를 유지한 채 후속 이벤트를 전송합니다. 네트워크 실패 시에는 대기 중인 batch를 보존해 재시도합니다.
Peer /publish의 10초 발행 제한시간에는 다른 로컬 발행 뒤에서 기다리는 시간도 포함됩니다. 요청 취소나 제한시간 만료 시 현재 발행의 종료를 기다리지 않고 대기에서 빠져나오며, gate/readiness 대기 중에는 504를 반환합니다. 대기 중 취소된 요청은 발행하거나 성공 publish 이벤트를 남기지 않습니다. 발행 시각은 기존처럼 gate와 readiness 대기 이후에 기록합니다.
Agent는 Controller가 cleanup에 사용하는 다음 endpoint도 제공합니다.
| Method | Agent path | 설명 |
|---|---|---|
POST |
/api/v1/runs/{runId}/drain |
해당 Run의 대기·전송 중 telemetry가 Controller에 수신 확인될 때까지 최대 30초 대기. 성공 204, 실패 503. 다른 Run의 전송은 기다리지 않음. |
DELETE |
/api/v1/runs/{runId}/nodes?generation=N |
unsigned generation이 필수이며, run fence를 N까지 원자적으로 높이고 이후 generation N 이하의 create를 거부하며 해당 generation의 기존 노드를 종료한 뒤 202 Accepted를 반환합니다. |
내부 endpoint는 운영자 API와 별개로 변경될 수 있습니다. 요청과 snapshot의 필드 정의는 internal/model/model.go, 대역폭 타입은 internal/model/bandwidth.go, handler는 internal/controller/api.go와 internal/agent/api.go에 있습니다.
Scoring과 inspection이 활성화되면 Peer 상태에 scoreSample: {observedAt, sequence, intervalSeconds, components}를 포함합니다. 각 항목은 해당 관측 Peer가 평가한 유효한 상대 Peer 표본의 {count, mean, min, max}입니다. 키는 p1, p2, p3, p3b, p4, p5, p6, p7, topicCap, total입니다. Agent는 snapshot을 복사해 보존하고 오래된 구성 항목 보고를 거부합니다. 전체 node/network/snapshot REST에는 제공하지만 Dashboard SSE에서는 구성 항목 요약을 제외합니다. peer_score 이벤트는 같은 scoreSample을 소스 식별자·이벤트 sequence와 함께 events.jsonl에 저장하며 최근 네트워크 이벤트 목록을 채우지 않습니다. 기존 상대 Peer별 peerScores 총점도 유지합니다. 이 값은 요약 통계이며 개별 상대 Peer·topic 쌍의 전체 행렬을 저장하지는 않습니다.
선택적 표본 sequence는 Peer 프로세스 안에서 증가하는 양수 inspection 순번이며 바깥 이벤트의 sequence와 독립적입니다. Agent는 이 값으로 Controller 시계 offset이 뒤로 보정된 뒤에도 더 최신인 관측과 빈 snapshot을 받아들입니다. observedAt은 보정한 snapshot 시각을 유지합니다. 순번이 없거나 0인 기존 표본은 순번 있는 표본이 도착할 때까지 observedAt으로 정렬하며, 순번 있는 표본을 덮어쓰지 못합니다. 새 Peer 프로세스는 Agent가 관리하는 상태 수명도 새로 시작합니다.
시나리오 v3의 mesh-freeze action은 내부 Bearer 인증으로 대상 Agent의 POST /api/v1/nodes/{nodeId}/mesh-freeze를 호출합니다. Agent는 내부 token과 X-KPL-Node-ID: {nodeId} 헤더를 붙여 같은 JSON을 Peer의 POST /mesh-freeze로 전달합니다. 요청과 성공 200 응답은 다음과 같습니다.
{"runId":"run-id","generation":1}{"nodeId":"node-id","peerId":"actual-libp2p-peer-id","frozen":true}Agent는 전송 전에 readiness, GossipSub 활성화, meshFreeze 허용 설정, 정확한 Run·generation과 run fence를 검사합니다. 응답의 node/Peer ID를 확인하고 같은 프로세스·실행 범위가 유지되는지 다시 확인한 뒤 적용 응답을 수락합니다. Peer도 node ID·Run·generation 일치를 요구합니다. 지연된 요청이나 재사용된 컨테이너 주소로 다른 Peer를 동결하지 않도록 하는 검사입니다. 성공은 로컬 PubSub 이벤트 루프가 단방향·멱등 동결을 적용했다는 뜻이며 여러 Peer의 동시 적용을 보장하지 않습니다. 계속되는 토폴로지·메시지 동작은 프로토콜 정의를 참고하십시오.
시나리오 timeout은 전체 선택 대상의 전송과 적용 응답을 함께 제한합니다. 각 Agent→Peer 요청과 Peer handler의 제한은 계속 10초이며 시나리오 timeout을 늘려도 개별 제한은 늘어나지 않습니다. 취소는 제어 요청을 따라 전달되어 이벤트 루프의 대기를 해제합니다.
Agent는 잘못된 입력이나 누락된 runId에 400, 대상 검사·전달·적용 응답 확인 실패에 502를 반환합니다. Peer는 잘못되거나 알 수 없는 JSON 필드에 400, 식별자 또는 설정 불일치에 409, 종료나 취소에 503, 10초 적용 제한시간 만료에 504를 반환합니다. 두 endpoint는 내부 인증 누락·불일치에 401을 반환하고 POST를 요구하며 다른 method에는 405를 반환합니다. 적용 후 응답이 유실되면 요청이 실패했어도 Peer는 동결된 상태일 수 있습니다. 재시도는 멱등이며 rollback이나 동결 해제는 없습니다.
Node metadata의 문자열 값 meshFreezeEnabled는 설정된 허용 여부, meshFrozen은 적용 확인 상태를 나타냅니다. meshFrozen이 true가 되면 이후 상태 보고가 이를 되돌리지 못합니다. Peer는 처음 동결을 확인할 때 fields.frozen: true, scope: "peer", membership: "pinned"를 포함한 mesh_freeze 이벤트를 내보내며 반복 성공 시 이벤트를 중복 생성하지 않습니다. 일반 telemetry의 전달·유실 경계를 따릅니다. 요청·응답 타입은 internal/model/mesh_freeze.go에 있습니다.
시나리오 v3의 topology는 Agent의 POST /api/v1/nodes/{nodeId}/topology를 호출하고 Agent가 Peer의 POST /topology로 전달합니다. 내부 Bearer 인증, X-KPL-Node-ID, 정확한 Run·generation, readiness와 run fence 검사는 mesh-freeze와 같습니다. 두 endpoint는 JSON 값 하나만 허용하고 알 수 없는 필드를 거부하며 요청과 응답을 1 MiB로 제한합니다.
{
"runId": "run-id",
"generation": 0,
"topologyId": "controller-generated-sha256",
"stage": "prepare",
"topic": "kpl/graph",
"neighbors": [{"peerId": "actual-libp2p-peer-id", "addresses": ["/ip4/10.0.0.2/tcp/20000"]}]
}prepare는 전체 입력을 검사하고 필요한 연결과 PubSub 구독을 확인한 뒤 준비된 계획 하나를 보관하며 메시를 설치하지 않습니다. 모든 대상이 준비되면 같은 계획 ID·토픽·이웃 목록의 apply가 정확한 토픽 메시를 설치하고 모든 로컬 토픽 메시를 동결합니다. topologyId는 전체 그래프와 Peer 실행 세대를 식별하며 도메인 전체가 공유합니다. 자기 자신·중복 이웃과 잘못되거나 다른 Peer를 가리키는 /p2p 주소 suffix는 거부합니다. 한 Peer의 이웃은 최대 9,999개, 이웃별 주소는 1–32개, 주소 길이는 최대 2,048 byte입니다.
성공 200 응답은 {nodeId, peerId, topologyId, stage, topic, frozen, neighbors}이며 neighbors는 정확한 Peer ID 목록을 정렬한 값입니다. Apply 응답은 frozen: true여야 합니다. Agent와 Controller는 식별자·이웃 목록을 검사한 뒤 같은 Peer 실행 세대인지 다시 확인합니다. Peer 작업 제한은 30초이고, 하위 단계의 timeout 응답을 받을 여유를 두어 Agent→Peer 요청은 35초, Controller→Agent 요청은 40초까지 기다립니다. 전체 시나리오 timeout이 더 짧으면 해당 제한을 우선합니다. 응답 유실 뒤 이미 적용한 동일 계획을 명시적으로 다시 제출하는 것은 허용하며, 물리 링크가 사라졌어도 멤버십 일치 시 성공합니다. 다른 적용 계획은 충돌입니다. Prepare 성공은 원격 상태를 예약하지 않으므로 apply에서 현재 조건을 다시 검사합니다.
Controller는 Peer별 준비 명령을 최대 세 번(최초 요청과 두 번의 재시도) 보냅니다. 일시적인 HTTP 503/504, 네트워크 오류, 개별 요청 timeout만 재시도합니다. 재시도 전 각각 250 ms와 500 ms를 기다리며 취소 시 대기도 종료합니다. 매 시도마다 선택한 Peer의 실행 세대를 다시 검사하고 같은 계획을 보냅니다. 모든 시도는 같은 전체 phase 제한시간을 공유합니다. 검증 오류, Peer 409 같은 영구 충돌과 잘못된 적용 응답은 재시도하지 않습니다. 정확한 계획의 prepare 응답을 모든 대상에서 받은 뒤에만 apply를 시작하며, apply는 자동 재시도하지 않습니다.
준비 중 각 Peer는 최대 네 이웃에 동시에 연결합니다. 일시적인 연결 실패는 기존 명령 제한시간 안에서 재시도하며, 대기 간격은 250 ms부터 최대 2초까지 늘어납니다. 첫 시도에서 libp2p dial backoff가 발생하면 재시도 전에 해당 이웃의 backoff를 한 번 해제합니다. 이후 시도에서 발생한 backoff는 유지하며 다른 Peer나 전역 연결 설정은 바꾸지 않습니다. 취소 시 재시도 대기도 종료하고, 설정한 transport 연결 timeout을 계속 적용합니다. 이후 backoff가 발생해도 Peer의 timeout 진단에는 앞서 확인한 실제 연결 실패 원인을 보존합니다. 도달할 수 없는 Peer나 연결·자원 한도 문제는 재시도만으로 해결되지 않습니다.
Agent는 잘못된 입력에 400, 대상 검사 실패·영구 Peer 오류·잘못된 적용 응답에 502를 반환합니다. Peer의 일시적인 503/504 응답은 그대로 전달하고, 일시적인 전달 오류는 503, 자체 요청 timeout은 504로 반환합니다. Peer는 잘못된 필드에 400, 식별자·기능 허용·계획 충돌에 409, 취소에 503, timeout에 504를 반환합니다. 두 endpoint 모두 내부 인증(401)과 POST(405)를 요구합니다. Controller의 prepare 단계 오류에는 apply가 시작되지 않았음을 표시합니다. 취소되거나 실패한 apply도 이미 적용되었을 수 있으며 분산 rollback은 제공하지 않습니다.
Controller의 topology_generated에는 모델·파라미터·실제 seed·생성기 버전·그래프 크기를 기록합니다. topology_assignment는 노드별 예정 이웃·선택적 좌표와 evidence: "planned"를 보존합니다. topology_stage는 prepare/apply 응답 수와 아래 필드를 기록합니다. Peer의 topology_applied는 로컬에서 확인한 계획 ID·토픽·이웃을 한 번 기록하며 일반 telemetry의 전달·유실 규칙을 따릅니다. 전체 이웃 목록은 저장 로그에 보존하고 최근 이벤트 목록에서는 제외합니다. 활성 간선은 계속 런타임 메시 snapshot으로 관측합니다. 타입은 internal/model/topology_command.go에 있습니다.
topology_stage 필드 |
의미 |
|---|---|
attempts |
해당 단계에서 실제로 보낸 Controller→Agent HTTP 요청 수이며 최초 시도와 재시도를 포함합니다. |
retries |
실제 추가 시도 수이며 apply는 항상 0입니다. |
elapsedSeconds |
해당 prepare 또는 apply 단계 시작부터 경과한 초입니다. |
timeoutSeconds |
대상 선택 시작을 기준으로 전체 phase에 적용한 유효 허용 시간(초)입니다. 상위 context 제한시간이 더 짧으면 그 값을 우선합니다. |
timeoutScope |
Timeout 실패 시 선택적으로 기록합니다. 전체 phase 제한시간 만료는 phase, 개별 요청 제한시간 만료 또는 Agent 504는 command입니다. 둘 다 해당하면 phase를 우선합니다. |
Scenario v3의 profile schedules는 Agent의 POST /api/v1/nodes/{nodeId}/profile을 호출하고 Agent는 Peer POST /profile로 전달합니다. 두 endpoint는 내부 인증·POST·단일 JSON 값을 요구하며 알 수 없는 필드를 거부하고 요청·응답을 32 KiB로 제한합니다. Agent는 X-KPL-Node-ID를 포함합니다. 두 경로는 정확한 Run·generation·준비 상태·Peer 개체를 검사하고 Agent는 전달 전후 Run fence를 검사합니다.
요청은 {runId, generation, revision, stage, set}입니다. revision은 Run 세대 내에서 증가하는 1–2147483647 정수이며 stage는 prepare 또는 apply입니다. set의 타입과 필드는 시나리오 레퍼런스에 정의합니다. 200 응답은 {nodeId, peerId, revision, stage, effective}이며 effective는 요청한 섹션의 완전한 구체적 설정입니다. Agent·Controller가 요청 값과 대상 식별자를 확인합니다. Prepare는 적용 없이 한 개의 준비 설정을 검증·보관합니다. Apply에는 동일 revision·패치의 준비가 필요합니다. 성공한 동일 요청은 다시 응답할 수 있지만 오래된 revision이나 같은 revision의 다른 값은 실패합니다. 신규 Peer는 Run의 최신 profile revision과 해당 패치를 상속하므로 지연된 이전 명령이 시작 설정을 되돌리지 못합니다.
Controller는 전체 대상의 prepare 완료 후 apply를 시작하며 apply를 자동 재시도하지 않습니다. 로컬 네트워크 변경과 PubSub 변경은 별도 연산이므로 나중 단계 실패 시 Peer 설정도 일부만 적용됐을 수 있습니다. Peer는 실패를 기록하고 후속 profile 명령을 거부합니다. 네트워크 연산은 5초, Peer 요청은 8초, Agent 전달은 10초, Controller 전달은 12초 한도이며 취소와 각 변경의 timeout이 더 짧으면 이를 따릅니다. Peer는 잘못된 입력 400, 충돌·적용 오류 409, 취소 503, 기한 초과 504를 반환합니다. Agent는 검증·전달·응답 오류에 502, 잘못된 입력에 400을 반환합니다. 인증·메서드 오류는 401·405입니다.
확인된 Peer 상태는 profileRevision, runtimeProfile, profileAppliedAt을 제공합니다. Agent는 응답의 변경 섹션을 합치고 더 새로운 Peer 상태로 유실된 응답을 복구할 수 있습니다. 불확실한 apply는 Agent의 profilePending: true를 설정하며 네트워크 적용도 불확실하면 확인될 때까지 networkPending: true를 설정합니다. 이전에 확인한 설정은 과거 근거이며 실패한 명령의 결과를 증명하지 않습니다. 이후 shaping 대상은 내부 생성 요청의 networkMutable: true로 컨테이너 NET_ADMIN을 준비하고 초기 상속 revision은 profileRevision으로 전달합니다. 설정 이벤트는 요청 값·확인 값·부분 실패를 구분해 보존합니다.
새 Peer는 events.jsonl에 upstream libp2p에서 실제 제공하는 메타정보를 추가합니다. 기존 publish/deliver/duplicate와 메인 Metrics 집계는 유지됩니다. messageId는 envelope의 애플리케이션 ID 또는 raw의 pubsub-<hex native ID>이며, fields.pubsubMessageId 및 아래 상세 ID 목록은 pubsub wire ID의 hex 표현입니다.
| 이벤트 / 필드 | 의미 |
|---|---|
send_*, recv_*, drop_*의 rpcMetadataVersion, rpcObservationId
|
메타정보 형식 버전 1, 한 로컬 RPC 콜백의 연결 ID. 각 이벤트의 eventId는 별도로 유지 |
rpcMessageCount, rpcSubscriptionCount
|
같은 RPC에 포함된 데이터 메시지·구독 항목의 전체 개수 |
IHAVE의 topicMessageIds
|
토픽별 광고 메시지 ID 목록 |
IWANT/IDONTWANT의 messageIds
|
요청·비요청 메시지 ID 목록. 해당 프로토콜 항목에는 원래 토픽이 없음 |
messageIdsComplete, omittedMessageIds
|
ID 목록 완전성 및 제한으로 생략한 개수. messageIdCount는 전체 개수 |
PRUNE의 peerExchangeIdsByTopic, peerExchangeIdsComplete, omittedPeerExchangeIds
|
실제 trace가 제공한 토픽별 교환 Peer ID 및 목록 완전성 |
rpc_metadata의 direction, messages, subscriptions
|
send/recv/drop 구분, {topic,pubsubMessageId} 목록, {topic,subscribe} 목록. 데이터·구독 메타정보가 있을 때만 별도 이벤트 생성 |
subscriptionsComplete, omittedSubscriptions
|
구독 목록 완전성 및 생략 개수 |
pubsub_reject의 reason, pubsubMessageId
|
libp2p가 보고한 메시지 거부 사유와 ID |
deliver / duplicate의 hopwaveMetadataVersion, hopwaveSource
|
버전 1, 출처 wire. HopWave 메타정보가 있을 때만 기록 |
hopwaveHopCountAvailable, hopwaveHopCount
|
음수가 아닌 wire 카운터의 가용 여부. 사용할 수 없으면 값은 생략. HopWave 전달에서는 절대 경로 길이가 아닌 순환 카운터 |
hopwavePropagationType |
있는 wire enum을 eager-push, lazy-pull, unknown으로 표현. enum이 없으면 생략 |
timestampSource, sourceTimestamp
|
trace 시각의 출처와 보정 전 원본 시각. upstream 시각이 없으면 peer-clock으로 표시하고 원본 시각을 만들지 않음 |
clockBasis, clockOffsetMs, clockUncertaintyMs
|
유효한 Controller 동기화 근거가 있을 때 추가하는 시각 보정 정보 |
ID 상세는 종류별 최대 8,192개 및 hex 합계 512KiB, 구독 목록은 최대 8,192개입니다. 상세 목록이 잘려도 해당 이벤트의 RPC·entry·ID 집계는 전체 개수를 유지합니다. 이벤트 유실은 기존 telemetry_drop과 소스 sequence로 추적합니다. 원본 payload, IWANT 발신 원인, wire에 없는 RPC 전역 ID는 만들지 않습니다. Eager/Lazy는 지표 문서의 메타정보 추정 규칙을 적용합니다.
hopwave* 필드는 서명하지 않는 wire 보고값입니다. 해석과 호환 요건은 HopWave를 따르며 기존 분석 추정은 유지됩니다. Wire 메타정보가 없는 로그에는 HopWave 필드를 만들지 않습니다.
Controller와 모든 Agent에 같은 KPL_USER·KPL_PASSWORD를 설정하며 시작 시 둘 다 필수입니다. 대시보드는 먼저 Log in 화면을 열고, 로그인 성공 시 임의의 서버 세션을 생성해 HttpOnly·SameSite=Strict 쿠키로 식별합니다. HTTPS에서는 Secure 쿠키를 사용하며 신뢰할 수 있는 reverse proxy의 X-Forwarded-Proto: https도 지원합니다. 세션은 12시간 뒤 만료되고 Log out 또는 Controller 재시작 시 무효화됩니다. 로그아웃·만료 시 해당 세션의 SSE 연결도 닫지만 실행 중 실험과 백그라운드 분석은 계속됩니다. 설정 계정 하나가 대시보드 전체 권한을 가집니다.
대시보드/API 조회·변경, SSE, 시나리오 검증, 분석, 다운로드는 모두 로그인 세션이 필요합니다. 미인증 API는 JSON 401을 반환하며, HTML을 요청하는 브라우저 GET/HEAD 탐색에는 같은 401 상태의 Login required 안내 페이지를 제공합니다. /login-required에서도 열 수 있으며 로그인 화면과 대시보드로 이동하는 링크가 있습니다. 대시보드 API/SSE의 세션 만료 시에는 이 페이지로 한 번 이동하고 재연결을 중단합니다. 사용자가 직접 로그아웃하면 /login으로 이동하고, 세션 없이 대시보드를 처음 열 때도 기존처럼 /login으로 리다이렉트합니다. 세션을 사용하는 변경 요청은 X-KPL-Request: dashboard 헤더도 필요하며 다른 origin의 요청은 거부합니다. UI는 이 헤더를 자동으로 넣으므로 추가 토큰 입력이 없고 비밀번호·세션 쿠키를 localStorage에 저장하지 않습니다. 로그인은 JSON {user, password}만 받으며 잘못된 계정은 401, 같은 접속 IP에서 5분 동안 10회 실패한 이후에는 429를 반환합니다. 세션과 실패 기록은 각각 최대 1,024개입니다. 로그인 응답 전송 중에는 세션 잠금을 유지하지 않으므로, 응답을 받지 못하는 로그인 클라이언트가 다른 세션 확인이나 로그아웃을 막지 않습니다.
로그인 리소스, health/시각 endpoint, /metrics, 두 Prometheus target-discovery endpoint만 공개로 유지합니다. 내부 Agent 등록·heartbeat·이벤트와 Peer bootstrap·discovery는 계정 설정에서 용도를 분리해 자동 생성한 서비스 키를 사용합니다. Peer에는 로그인 비밀번호 대신 이 키만 전달하며, 키로 대시보드 결과 API에 접근하거나 실험을 시작할 수 없습니다. KPL_API_TOKEN과 --token CLI 옵션은 폐기했습니다. 자격 증명 변경 시 이전 키를 쓰는 진행 중 실험·잔존 Peer를 정리한 뒤 Controller와 Agent를 함께 재배포하십시오. Grafana 로그인은 별도입니다.
REST 클라이언트는 아래 주소를 access 출력으로 바꾸고 한 번 로그인합니다. 비공개 쿠키 파일을 위 예제들에서 재사용하십시오.
KPL_URL=http://control-node:8080
KPL_COOKIE_JAR=$(mktemp)
read -r -p 'Username: ' KPL_USER
read -r -s -p 'Password: ' KPL_PASSWORD; printf '\n'
export KPL_USER KPL_PASSWORD
python3 -c 'import json,os; print(json.dumps({"user":os.environ["KPL_USER"],"password":os.environ["KPL_PASSWORD"]}))' |
curl --fail-with-body -c "$KPL_COOKIE_JAR" \
-H 'Content-Type: application/json' --data-binary @- "$KPL_URL/api/v1/auth/login"
unset KPL_PASSWORD
curl --fail-with-body -b "$KPL_COOKIE_JAR" "$KPL_URL/api/v1/snapshot"요청을 마친 뒤 로그아웃하고 쿠키 파일을 삭제합니다.
curl --fail-with-body -b "$KPL_COOKIE_JAR" -H 'X-KPL-Request: dashboard' \
-X POST "$KPL_URL/api/v1/auth/logout"
rm -f "$KPL_COOKIE_JAR"HTTP 자체는 자격 증명과 쿠키를 암호화하지 않습니다. HTTPS 또는 신뢰 관리망을 사용하고 HTTPS proxy는 forwarded protocol 헤더를 덮어써야 합니다.
시나리오의 같은 네 가지 job counter가 /api/v1/snapshot과 SSE snapshot에도 포함됩니다. 대시보드는 각 run에 이를 표시하므로 Controller 로그를 열지 않아도 실행 중, 성공, 실패, 취소된 background 작업 수를 확인할 수 있습니다.
시각화 가이드에서 분석 응답, 그래프와 내보내기 형식을 확인하십시오.
Home · Documentation guide · kpl-v3 source
Commands run from the kpl-v3 source checkout.