VitalRecorder
A free software to record and analyze biosignal waveforms and vital signs from GE® (Solar 8000®, Dash, Bx50®), Philips® (Intellivue®), Drager® (Infinity®, Nihon Kohden® (PSM®, Primus®), Maquet® (Flow-i®, Servo-i®) ventilators, Fresenius Kabi® (Orchestra®, Agilia®, and Link+®), BBraun® syringe pumps, Medtronic® BIS®, Masimo® Root®, DataQ® ADCs, etc. (Full list of supported devices)The use of the VitalRecorder program is completely free. Download and try the latest version.
(Downloaded 178,677 times since Feb 2017)
OS:
Version:
View All Versions
1.18.43
1.18.43: 멀티베드 console (PACU 10베드 Pi) syslog noise 80,500/hr → ~1,400/hr (-98.3%), cpu_usage 측정 1.16.12 (9f26e36) 이후 8년치 영구 -1 버그 수정, 멀티베드 환경 베드 식별용 [bedname / devicename] 로그 prefix 도입. 동기는 PACU production Pi (1.18.40) 4일치 syslog 분석 — 1.24M 라인 중 vr 라인 836K, idle 8시간 평균 시간당 80,500 라인 (베드 10 × 1초마다 "Waiting for next patient" × syslog 2x 중복). (1) VRApp.cpp:2696-2710 timer_thread_func 의 cpu usage 계산이 `if (totalcpu && m_cpu_total_last && totalcpu > m_cpu_total_last)` 가드 안에서만 m_cpu_total_last 갱신해 첫 호출 시 m_cpu_total_last=0 → else 분기 → -1 → m_cpu_total_last 갱신 안 됨 → 영원히 false → 영원히 -1 데드락. PACU 1,048 샘플 100% -1 실측 확인. m_cpu_total_last/m_cpu_used_last 갱신을 if 밖으로 빼서 첫 호출만 baseline 으로 -1, 두 번째 (60s 후) 부터 정상 측정. /proc/stat 의 iowait/irq/softirq 까지 7개 필드 fscanf 로 정확도 ±5% 개선. 부수: 같은 함수의 cpu_temp 가 `temp / 1000` int 나눗셈으로 소수점 절단되어 PACU 측정값이 정수 50/51/...,/58 9종만 출력 (millidegree 정밀도 손실) → `temp / 1000.0f` 로 fix. ram 계산은 `(totalram + totalswap) * mem_unit` 결과에 다시 mem_unit 곱하던 차원 혼용 (Linux 가 mem_unit=1 이라 우연히 정상 동작했지만 dimensionally wrong) → `(units1 + units2) * mem_unit` 로 정정 + underflow 가드. (2) DISPLAY::log(string/printf) + DEVICE::log(string/printf) 멤버 추가. DISPLAY::log 는 `[bedname] msg`, DEVICE::log 는 m_pdisp 위임 없이 직접 `[bedname / devicename] msg` 한 줄 prefix (nested `[bed] [dev]` 보다 가독성 우선). m_pdisp 또는 bedname null 케이스 safe fallback. (3) show_msg 의 console path TRACE 를 log() 로 교체 → "Waiting for next patient" 자동 [bedname] prefix. ADT patient id unset 도 log() 로. (4) 이미 수동으로 get_bedname() 을 인자로 넘기던 8군데 (DISPLAY ptcon_thread_func / add_trk / add_dev / del_dev / cut-recording, Device add_rec 콜백, FILT stop, VRApp add_tab/del_tab) 를 log() 로 전환 — 이중 prefix 방지 + 일관성. Serial::reading_thread / tcp_thread / open_socket waiting / timeout / interrupted / got-client 6군데 DEVICE::log() 전환 → `[bedname / Intellivue]` 등 자동 prefix, 멀티탭 환경에서 어느 베드 어느 device 출처인지 즉시 식별. (5) wss 재시도 폭주 throttle. WSSClient 에 m_verbose (debug step 로그 게이트) + m_dt_last_err_log (60s window) 추가. connect() 내부 step-by-step TRACE 4개 ("connecting" / "connect error" / "connected") 는 m_verbose 가드, 최종 "wss connect error" 는 60s window throttle (첫 줄 즉시, 그 다음 60s 1회). connect 성공 path 에서 throttle reset 으로 다음 disconnect → 첫 에러 즉시 로깅. sendweb_thread_func 의 "connecting websocket server" 호출자 라인도 같은 60s window, m_bDebug 면 verbose 활성화. PACU idle 8h 측정 7,024/hr (4종 × 1,756) → 약 60/hr (-99%). (6) "Waiting for next patient" / "ADT patient id unset" 의 60s throttle 자체는 feature/wav-mem-retention 브랜치 ptcon_thread_func 재구성 (b75928f 시리즈) 에서 이미 들어가 있어 이 릴리즈에 자동 포함. 종합 영향: idle 시간당 syslog 라인 80,500 → ~1,400 (-98.3%), active 시간당 14,900 → ~700 (-95%). 진단 측면: cpu_usage 가 부활하면서 웹모니터링 json 의 cpu_usage 필드도 한 번도 안 채워지던 게 정상 전송 (VRApp.cpp:2499 의 `if (m_cpu_usage > 0)` 가드가 영원히 false 였음). 별건: syslog 라인 2× 중복은 application 측이 아니라 rsyslog imuxsock + imjournal + journald.ForwardToSyslog=yes 의 Debian Bookworm 전형 misconfig — admin 측 fix (rsyslog.conf 에서 imjournal 주석 처리 + restart) 로 추가 50% 감소 가능 (별도 문서). MFC + Qt + Pi (RPI64) + Ubuntu CLI 빌드 모두 통과.
1.18.41
1.18.41 Hotfix: TCP-server protocol-initiator device (Intellivue/S5/Datex/Hamilton/MPS/Agilia/IAP) 가 open() 직후 보내는 첫 패킷이 wire 에 안 나가던 회귀. 1.18.34 의 PortRouter refactor 가 Serial::open_socket() 의 8초 handoff blocking 을 제거하고 비동기 모델로 전환했는데, protocol-initiator 의 open() 코드가 m_sock=INVALID 상태에서 write 를 시도해 silent fail (Linux 는 m_fd=0 이라 false 반환). Intellivue 케이스 production journald 의 Pi → ESP TX 700K~3M bytes 는 monitor 측 abort 로 늦게 트리거된 AB_SPDU_SI recovery assoc_req 후 request_thread 의 정상 polling 트래픽이지만 일부 베드 (abort 없는 monitor) 는 recovery 도 안 되어 영구 silent (RX 300 bytes). 수정 방향: open() 의 옛 contract ("return 시점에 통신 가능") 복구. (1) Serial::open_socket() TCP-server 분기가 PortRouter::subscribe 후 m_sock_cv 로 첫 client handoff 까지 block (timeout = device read_timeout, 기본 30s) — 옛 모델 그대로. lock_and_open 이 false 면 다음 tick 에서 retry. close 신호로 abort 가능. (2) Serial::close() 는 PortRouter::unsubscribe 안 함 — subscribe 는 device 생애 동안 sticky 라 read_timeout 으로 인한 close→re-open 사이클 동안 listen socket 이 유지되어 1.18.29/1.18.34 의 anti-churn 이익 보존. unsubscribe 는 ~Serial() 에서만. (3) Per-device lifecycle thread (DEVICE::m_thread_restart): 각 device 가 자기 open/close/timeout 재시작을 별도 thread 에서 관리. 옛 모델의 DISPLAY::bed_manager 가 베드의 모든 device 를 sequential iterate 하던 구조에선 한 device 의 blocking open() 이 같은 베드의 다른 device 의 lifecycle 을 차단했는데, per-device thread 로 분리하여 멀티 device 베드도 병렬 진행. add_dev 에서 start_lifecycle, del_dev / ~DISPLAY 에서 stop_lifecycle. (4) Serial::tcp_thread_func 단순화 — outer m_sock_cv wait 패턴 (1.18.34/1.18.39 가 추가했던 것) 제거. open_socket 이 m_sock valid 보장하므로 reader thread 시작 시점에 simple inner recv loop 만. accept_handoff 의 replace (zombie cleanup) 케이스는 recv error 후 m_sock 변경 확인 + retry 로 처리. (5) reading_thread_func 의 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 가드 복원 (1.18.39 가 제거). (6) Serial::write 의 silent-fail 케이스에 WARN trace 추가 — 미래 동종 회귀 즉시 노출. (7) PortRouter::subscribe 가 같은 (port, type) 에서 같은 IP filter 또는 둘 이상 catch-all 발견 시 reject (config 실수 원천 차단, 옛 WARN-only 동작 강화). (8) DISPLAY::bed_manager_thread_func 의 restart 블록 제거 — ptcon + adt 만 담당. "Waiting for next patient" TRACE 가 Linux/console 에서 1초 → 60초마다 throttle (별개 journald 부풀림 fix). 영향 범위: Intellivue / S5 / Datex / Hamilton / MPS / Agilia / IAP TCP-server 모드 베드 전부. 1.18.34 이후 우회 방법은 race-win 케이스 (VRN 이 PortRouter::subscribe 직후 connect) 외엔 없었음. MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.40
Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.39
Fixed: PortRouter (TCP server) 모드에서 reader thread 가 startup 시점에 죽어 client 데이터를 영구히 못 받던 문제. 1.18.34 (9bd252f) 의 PortRouter refactor 가 m_sock 세팅을 dispatch 시점으로 옮겼는데 reading_thread_func (Serial.cpp:354) 의 가드 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 는 옛 모델 (open 시점 이미 connect 완료) 가정 그대로라 — 시작 직후 m_sock=INVALID 면 tcp_thread_func 진입 안 하고 reader thread 즉시 종료. 이후 VRN 이 connect → PortRouter::dispatch → accept_handoff 가 m_sock 채우고 m_sock_cv.notify_all() → 그러나 wait 중인 reader 가 없음 (이미 죽음) → kernel recv buffer 에만 데이터 적재. 증상은 ss -tn state established 의 Recv-Q 가 0 이 아닌 값 (수백 bytes) 으로 누적, 베드 화면에는 데이터 안 들어옴. 그 베드의 VRN 이 Serial::open() 의 PortRouter::subscribe 직후~reader thread spawn 사이의 짧은 윈도우 안에 connect 한 경우만 우연히 정상 동작 (race-win). 수정: 가드 제거 — TCP 분기에 항상 진입. tcp_thread_func 자체에 이미 m_sock_cv outer-wait 루프가 있어 m_sock 이 INVALID 인 동안 dispatch 가 깨워줄 때까지 안전하게 대기. Pi 핫스팟에 VRN 여러 대 (e.g. 10.42.0.21~25) 를 같은 (4343, Philips:Intellivue) 키로 묶고 IP filter 로 라우팅하는 시나리오에서 가장 자주 노출. Added: 진단 로그 보강 — PortRouter::accept_loop 의 accept 이벤트, dispatch 의 라우팅 결정 (ip_specific/catch_all subs 수, 어느 dev 가 받았는지), subscribe/unsubscribe 의 entry 생성/append/destroy, reading_thread / tcp_thread 의 시작·wait·wake 라이프사이클 — 이전엔 모두 m_bDebug 게이트 안에 있어서 production journald 에서 안 보였는데, 이제 항상 출력 (이번 같은 dispatch routing 문제 진단을 production 에서 바로 가능). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.38
Fixed: 웹모니터링 우클릭 → 디바이스 세팅 (websocket edit_bed 명령) 으로 베드의 device/filter 를 변경할 때 베드 이름이 매번 "_2" suffix 로 자동 리네임되어 vr.conf (Pi: /data/vr.conf) 에 잘못된 이름으로 저장되던 문제. 원인은 1.18.22 (cda8aa5) 에서 add_bed 에 추가된 bedname dedup ("최후 방어선" 으로 forward_frame auto-create / 수동 편집된 conf 의 중복 이름 처리용). edit_bed 핸들러는 옛 pdel 이 m_pdisps 에 그대로 있는 상태에서 같은 bedname 으로 new DISPLAY 를 add_bed 하고 그 다음 del_bed(pdel) 하는 순서였는데, add_bed 가 dup 감지해 새 pdisp 를 bedname_2 로 자동 리네임 → save_settings 가 [BED/bedname_2] 로 직렬화 → 사용자 입장에선 "설정 적용 안됨" 으로 보였다. 추가로 옛/새 DISPLAY 가 잠깐 m_pdisps 에 공존하면서 같은 device 포트를 동시에 들고 있으려 해 로그가 dup 으로 찍히는 부수 증상도 있었음. 수정: del_bed(pdel) 를 add_bed(pdisp) 보다 앞으로 이동 — 옛 베드의 device 포트가 깨끗이 release 된 뒤 새 pdisp 가 같은 포트를 reopen, dedup 발동 안 함. 다른 베드의 recording 은 영향 없음 (인메모리 hot swap 설계 유지). 부수: edit_bed 핸들러의 데드 코드 (`break` 뒤의 unreachable olddevs populate 루프 + `if (olddevs.find(name) == olddevs.end())` 항상-true 래퍼) 정리. edit_conf 와 save_settings 는 손대지 않음. MFC + Qt + Pi (RPI64) 빌드 모두 통과.
1.18.43
1.18.43: 멀티베드 console (PACU 10베드 Pi) syslog noise 80,500/hr → ~1,400/hr (-98.3%), cpu_usage 측정 1.16.12 (9f26e36) 이후 8년치 영구 -1 버그 수정, 멀티베드 환경 베드 식별용 [bedname / devicename] 로그 prefix 도입. 동기는 PACU production Pi (1.18.40) 4일치 syslog 분석 — 1.24M 라인 중 vr 라인 836K, idle 8시간 평균 시간당 80,500 라인 (베드 10 × 1초마다 "Waiting for next patient" × syslog 2x 중복). (1) VRApp.cpp:2696-2710 timer_thread_func 의 cpu usage 계산이 `if (totalcpu && m_cpu_total_last && totalcpu > m_cpu_total_last)` 가드 안에서만 m_cpu_total_last 갱신해 첫 호출 시 m_cpu_total_last=0 → else 분기 → -1 → m_cpu_total_last 갱신 안 됨 → 영원히 false → 영원히 -1 데드락. PACU 1,048 샘플 100% -1 실측 확인. m_cpu_total_last/m_cpu_used_last 갱신을 if 밖으로 빼서 첫 호출만 baseline 으로 -1, 두 번째 (60s 후) 부터 정상 측정. /proc/stat 의 iowait/irq/softirq 까지 7개 필드 fscanf 로 정확도 ±5% 개선. 부수: 같은 함수의 cpu_temp 가 `temp / 1000` int 나눗셈으로 소수점 절단되어 PACU 측정값이 정수 50/51/...,/58 9종만 출력 (millidegree 정밀도 손실) → `temp / 1000.0f` 로 fix. ram 계산은 `(totalram + totalswap) * mem_unit` 결과에 다시 mem_unit 곱하던 차원 혼용 (Linux 가 mem_unit=1 이라 우연히 정상 동작했지만 dimensionally wrong) → `(units1 + units2) * mem_unit` 로 정정 + underflow 가드. (2) DISPLAY::log(string/printf) + DEVICE::log(string/printf) 멤버 추가. DISPLAY::log 는 `[bedname] msg`, DEVICE::log 는 m_pdisp 위임 없이 직접 `[bedname / devicename] msg` 한 줄 prefix (nested `[bed] [dev]` 보다 가독성 우선). m_pdisp 또는 bedname null 케이스 safe fallback. (3) show_msg 의 console path TRACE 를 log() 로 교체 → "Waiting for next patient" 자동 [bedname] prefix. ADT patient id unset 도 log() 로. (4) 이미 수동으로 get_bedname() 을 인자로 넘기던 8군데 (DISPLAY ptcon_thread_func / add_trk / add_dev / del_dev / cut-recording, Device add_rec 콜백, FILT stop, VRApp add_tab/del_tab) 를 log() 로 전환 — 이중 prefix 방지 + 일관성. Serial::reading_thread / tcp_thread / open_socket waiting / timeout / interrupted / got-client 6군데 DEVICE::log() 전환 → `[bedname / Intellivue]` 등 자동 prefix, 멀티탭 환경에서 어느 베드 어느 device 출처인지 즉시 식별. (5) wss 재시도 폭주 throttle. WSSClient 에 m_verbose (debug step 로그 게이트) + m_dt_last_err_log (60s window) 추가. connect() 내부 step-by-step TRACE 4개 ("connecting" / "connect error" / "connected") 는 m_verbose 가드, 최종 "wss connect error" 는 60s window throttle (첫 줄 즉시, 그 다음 60s 1회). connect 성공 path 에서 throttle reset 으로 다음 disconnect → 첫 에러 즉시 로깅. sendweb_thread_func 의 "connecting websocket server" 호출자 라인도 같은 60s window, m_bDebug 면 verbose 활성화. PACU idle 8h 측정 7,024/hr (4종 × 1,756) → 약 60/hr (-99%). (6) "Waiting for next patient" / "ADT patient id unset" 의 60s throttle 자체는 feature/wav-mem-retention 브랜치 ptcon_thread_func 재구성 (b75928f 시리즈) 에서 이미 들어가 있어 이 릴리즈에 자동 포함. 종합 영향: idle 시간당 syslog 라인 80,500 → ~1,400 (-98.3%), active 시간당 14,900 → ~700 (-95%). 진단 측면: cpu_usage 가 부활하면서 웹모니터링 json 의 cpu_usage 필드도 한 번도 안 채워지던 게 정상 전송 (VRApp.cpp:2499 의 `if (m_cpu_usage > 0)` 가드가 영원히 false 였음). 별건: syslog 라인 2× 중복은 application 측이 아니라 rsyslog imuxsock + imjournal + journald.ForwardToSyslog=yes 의 Debian Bookworm 전형 misconfig — admin 측 fix (rsyslog.conf 에서 imjournal 주석 처리 + restart) 로 추가 50% 감소 가능 (별도 문서). MFC + Qt + Pi (RPI64) + Ubuntu CLI 빌드 모두 통과.
1.18.41
1.18.41 Hotfix: TCP-server protocol-initiator device (Intellivue/S5/Datex/Hamilton/MPS/Agilia/IAP) 가 open() 직후 보내는 첫 패킷이 wire 에 안 나가던 회귀. 1.18.34 의 PortRouter refactor 가 Serial::open_socket() 의 8초 handoff blocking 을 제거하고 비동기 모델로 전환했는데, protocol-initiator 의 open() 코드가 m_sock=INVALID 상태에서 write 를 시도해 silent fail (Linux 는 m_fd=0 이라 false 반환). Intellivue 케이스 production journald 의 Pi → ESP TX 700K~3M bytes 는 monitor 측 abort 로 늦게 트리거된 AB_SPDU_SI recovery assoc_req 후 request_thread 의 정상 polling 트래픽이지만 일부 베드 (abort 없는 monitor) 는 recovery 도 안 되어 영구 silent (RX 300 bytes). 수정 방향: open() 의 옛 contract ("return 시점에 통신 가능") 복구. (1) Serial::open_socket() TCP-server 분기가 PortRouter::subscribe 후 m_sock_cv 로 첫 client handoff 까지 block (timeout = device read_timeout, 기본 30s) — 옛 모델 그대로. lock_and_open 이 false 면 다음 tick 에서 retry. close 신호로 abort 가능. (2) Serial::close() 는 PortRouter::unsubscribe 안 함 — subscribe 는 device 생애 동안 sticky 라 read_timeout 으로 인한 close→re-open 사이클 동안 listen socket 이 유지되어 1.18.29/1.18.34 의 anti-churn 이익 보존. unsubscribe 는 ~Serial() 에서만. (3) Per-device lifecycle thread (DEVICE::m_thread_restart): 각 device 가 자기 open/close/timeout 재시작을 별도 thread 에서 관리. 옛 모델의 DISPLAY::bed_manager 가 베드의 모든 device 를 sequential iterate 하던 구조에선 한 device 의 blocking open() 이 같은 베드의 다른 device 의 lifecycle 을 차단했는데, per-device thread 로 분리하여 멀티 device 베드도 병렬 진행. add_dev 에서 start_lifecycle, del_dev / ~DISPLAY 에서 stop_lifecycle. (4) Serial::tcp_thread_func 단순화 — outer m_sock_cv wait 패턴 (1.18.34/1.18.39 가 추가했던 것) 제거. open_socket 이 m_sock valid 보장하므로 reader thread 시작 시점에 simple inner recv loop 만. accept_handoff 의 replace (zombie cleanup) 케이스는 recv error 후 m_sock 변경 확인 + retry 로 처리. (5) reading_thread_func 의 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 가드 복원 (1.18.39 가 제거). (6) Serial::write 의 silent-fail 케이스에 WARN trace 추가 — 미래 동종 회귀 즉시 노출. (7) PortRouter::subscribe 가 같은 (port, type) 에서 같은 IP filter 또는 둘 이상 catch-all 발견 시 reject (config 실수 원천 차단, 옛 WARN-only 동작 강화). (8) DISPLAY::bed_manager_thread_func 의 restart 블록 제거 — ptcon + adt 만 담당. "Waiting for next patient" TRACE 가 Linux/console 에서 1초 → 60초마다 throttle (별개 journald 부풀림 fix). 영향 범위: Intellivue / S5 / Datex / Hamilton / MPS / Agilia / IAP TCP-server 모드 베드 전부. 1.18.34 이후 우회 방법은 race-win 케이스 (VRN 이 PortRouter::subscribe 직후 connect) 외엔 없었음. MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.40
Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.39
Fixed: PortRouter (TCP server) 모드에서 reader thread 가 startup 시점에 죽어 client 데이터를 영구히 못 받던 문제. 1.18.34 (9bd252f) 의 PortRouter refactor 가 m_sock 세팅을 dispatch 시점으로 옮겼는데 reading_thread_func (Serial.cpp:354) 의 가드 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 는 옛 모델 (open 시점 이미 connect 완료) 가정 그대로라 — 시작 직후 m_sock=INVALID 면 tcp_thread_func 진입 안 하고 reader thread 즉시 종료. 이후 VRN 이 connect → PortRouter::dispatch → accept_handoff 가 m_sock 채우고 m_sock_cv.notify_all() → 그러나 wait 중인 reader 가 없음 (이미 죽음) → kernel recv buffer 에만 데이터 적재. 증상은 ss -tn state established 의 Recv-Q 가 0 이 아닌 값 (수백 bytes) 으로 누적, 베드 화면에는 데이터 안 들어옴. 그 베드의 VRN 이 Serial::open() 의 PortRouter::subscribe 직후~reader thread spawn 사이의 짧은 윈도우 안에 connect 한 경우만 우연히 정상 동작 (race-win). 수정: 가드 제거 — TCP 분기에 항상 진입. tcp_thread_func 자체에 이미 m_sock_cv outer-wait 루프가 있어 m_sock 이 INVALID 인 동안 dispatch 가 깨워줄 때까지 안전하게 대기. Pi 핫스팟에 VRN 여러 대 (e.g. 10.42.0.21~25) 를 같은 (4343, Philips:Intellivue) 키로 묶고 IP filter 로 라우팅하는 시나리오에서 가장 자주 노출. Added: 진단 로그 보강 — PortRouter::accept_loop 의 accept 이벤트, dispatch 의 라우팅 결정 (ip_specific/catch_all subs 수, 어느 dev 가 받았는지), subscribe/unsubscribe 의 entry 생성/append/destroy, reading_thread / tcp_thread 의 시작·wait·wake 라이프사이클 — 이전엔 모두 m_bDebug 게이트 안에 있어서 production journald 에서 안 보였는데, 이제 항상 출력 (이번 같은 dispatch routing 문제 진단을 production 에서 바로 가능). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.38
Fixed: 웹모니터링 우클릭 → 디바이스 세팅 (websocket edit_bed 명령) 으로 베드의 device/filter 를 변경할 때 베드 이름이 매번 "_2" suffix 로 자동 리네임되어 vr.conf (Pi: /data/vr.conf) 에 잘못된 이름으로 저장되던 문제. 원인은 1.18.22 (cda8aa5) 에서 add_bed 에 추가된 bedname dedup ("최후 방어선" 으로 forward_frame auto-create / 수동 편집된 conf 의 중복 이름 처리용). edit_bed 핸들러는 옛 pdel 이 m_pdisps 에 그대로 있는 상태에서 같은 bedname 으로 new DISPLAY 를 add_bed 하고 그 다음 del_bed(pdel) 하는 순서였는데, add_bed 가 dup 감지해 새 pdisp 를 bedname_2 로 자동 리네임 → save_settings 가 [BED/bedname_2] 로 직렬화 → 사용자 입장에선 "설정 적용 안됨" 으로 보였다. 추가로 옛/새 DISPLAY 가 잠깐 m_pdisps 에 공존하면서 같은 device 포트를 동시에 들고 있으려 해 로그가 dup 으로 찍히는 부수 증상도 있었음. 수정: del_bed(pdel) 를 add_bed(pdisp) 보다 앞으로 이동 — 옛 베드의 device 포트가 깨끗이 release 된 뒤 새 pdisp 가 같은 포트를 reopen, dedup 발동 안 함. 다른 베드의 recording 은 영향 없음 (인메모리 hot swap 설계 유지). 부수: edit_bed 핸들러의 데드 코드 (`break` 뒤의 unreachable olddevs populate 루프 + `if (olddevs.find(name) == olddevs.end())` 항상-true 래퍼) 정리. edit_conf 와 save_settings 는 손대지 않음. MFC + Qt + Pi (RPI64) 빌드 모두 통과.
1.18.43
1.18.43: 멀티베드 console (PACU 10베드 Pi) syslog noise 80,500/hr → ~1,400/hr (-98.3%), cpu_usage 측정 1.16.12 (9f26e36) 이후 8년치 영구 -1 버그 수정, 멀티베드 환경 베드 식별용 [bedname / devicename] 로그 prefix 도입. 동기는 PACU production Pi (1.18.40) 4일치 syslog 분석 — 1.24M 라인 중 vr 라인 836K, idle 8시간 평균 시간당 80,500 라인 (베드 10 × 1초마다 "Waiting for next patient" × syslog 2x 중복). (1) VRApp.cpp:2696-2710 timer_thread_func 의 cpu usage 계산이 `if (totalcpu && m_cpu_total_last && totalcpu > m_cpu_total_last)` 가드 안에서만 m_cpu_total_last 갱신해 첫 호출 시 m_cpu_total_last=0 → else 분기 → -1 → m_cpu_total_last 갱신 안 됨 → 영원히 false → 영원히 -1 데드락. PACU 1,048 샘플 100% -1 실측 확인. m_cpu_total_last/m_cpu_used_last 갱신을 if 밖으로 빼서 첫 호출만 baseline 으로 -1, 두 번째 (60s 후) 부터 정상 측정. /proc/stat 의 iowait/irq/softirq 까지 7개 필드 fscanf 로 정확도 ±5% 개선. 부수: 같은 함수의 cpu_temp 가 `temp / 1000` int 나눗셈으로 소수점 절단되어 PACU 측정값이 정수 50/51/...,/58 9종만 출력 (millidegree 정밀도 손실) → `temp / 1000.0f` 로 fix. ram 계산은 `(totalram + totalswap) * mem_unit` 결과에 다시 mem_unit 곱하던 차원 혼용 (Linux 가 mem_unit=1 이라 우연히 정상 동작했지만 dimensionally wrong) → `(units1 + units2) * mem_unit` 로 정정 + underflow 가드. (2) DISPLAY::log(string/printf) + DEVICE::log(string/printf) 멤버 추가. DISPLAY::log 는 `[bedname] msg`, DEVICE::log 는 m_pdisp 위임 없이 직접 `[bedname / devicename] msg` 한 줄 prefix (nested `[bed] [dev]` 보다 가독성 우선). m_pdisp 또는 bedname null 케이스 safe fallback. (3) show_msg 의 console path TRACE 를 log() 로 교체 → "Waiting for next patient" 자동 [bedname] prefix. ADT patient id unset 도 log() 로. (4) 이미 수동으로 get_bedname() 을 인자로 넘기던 8군데 (DISPLAY ptcon_thread_func / add_trk / add_dev / del_dev / cut-recording, Device add_rec 콜백, FILT stop, VRApp add_tab/del_tab) 를 log() 로 전환 — 이중 prefix 방지 + 일관성. Serial::reading_thread / tcp_thread / open_socket waiting / timeout / interrupted / got-client 6군데 DEVICE::log() 전환 → `[bedname / Intellivue]` 등 자동 prefix, 멀티탭 환경에서 어느 베드 어느 device 출처인지 즉시 식별. (5) wss 재시도 폭주 throttle. WSSClient 에 m_verbose (debug step 로그 게이트) + m_dt_last_err_log (60s window) 추가. connect() 내부 step-by-step TRACE 4개 ("connecting" / "connect error" / "connected") 는 m_verbose 가드, 최종 "wss connect error" 는 60s window throttle (첫 줄 즉시, 그 다음 60s 1회). connect 성공 path 에서 throttle reset 으로 다음 disconnect → 첫 에러 즉시 로깅. sendweb_thread_func 의 "connecting websocket server" 호출자 라인도 같은 60s window, m_bDebug 면 verbose 활성화. PACU idle 8h 측정 7,024/hr (4종 × 1,756) → 약 60/hr (-99%). (6) "Waiting for next patient" / "ADT patient id unset" 의 60s throttle 자체는 feature/wav-mem-retention 브랜치 ptcon_thread_func 재구성 (b75928f 시리즈) 에서 이미 들어가 있어 이 릴리즈에 자동 포함. 종합 영향: idle 시간당 syslog 라인 80,500 → ~1,400 (-98.3%), active 시간당 14,900 → ~700 (-95%). 진단 측면: cpu_usage 가 부활하면서 웹모니터링 json 의 cpu_usage 필드도 한 번도 안 채워지던 게 정상 전송 (VRApp.cpp:2499 의 `if (m_cpu_usage > 0)` 가드가 영원히 false 였음). 별건: syslog 라인 2× 중복은 application 측이 아니라 rsyslog imuxsock + imjournal + journald.ForwardToSyslog=yes 의 Debian Bookworm 전형 misconfig — admin 측 fix (rsyslog.conf 에서 imjournal 주석 처리 + restart) 로 추가 50% 감소 가능 (별도 문서). MFC + Qt + Pi (RPI64) + Ubuntu CLI 빌드 모두 통과.
1.18.41
1.18.41 Hotfix: TCP-server protocol-initiator device (Intellivue/S5/Datex/Hamilton/MPS/Agilia/IAP) 가 open() 직후 보내는 첫 패킷이 wire 에 안 나가던 회귀. 1.18.34 의 PortRouter refactor 가 Serial::open_socket() 의 8초 handoff blocking 을 제거하고 비동기 모델로 전환했는데, protocol-initiator 의 open() 코드가 m_sock=INVALID 상태에서 write 를 시도해 silent fail (Linux 는 m_fd=0 이라 false 반환). Intellivue 케이스 production journald 의 Pi → ESP TX 700K~3M bytes 는 monitor 측 abort 로 늦게 트리거된 AB_SPDU_SI recovery assoc_req 후 request_thread 의 정상 polling 트래픽이지만 일부 베드 (abort 없는 monitor) 는 recovery 도 안 되어 영구 silent (RX 300 bytes). 수정 방향: open() 의 옛 contract ("return 시점에 통신 가능") 복구. (1) Serial::open_socket() TCP-server 분기가 PortRouter::subscribe 후 m_sock_cv 로 첫 client handoff 까지 block (timeout = device read_timeout, 기본 30s) — 옛 모델 그대로. lock_and_open 이 false 면 다음 tick 에서 retry. close 신호로 abort 가능. (2) Serial::close() 는 PortRouter::unsubscribe 안 함 — subscribe 는 device 생애 동안 sticky 라 read_timeout 으로 인한 close→re-open 사이클 동안 listen socket 이 유지되어 1.18.29/1.18.34 의 anti-churn 이익 보존. unsubscribe 는 ~Serial() 에서만. (3) Per-device lifecycle thread (DEVICE::m_thread_restart): 각 device 가 자기 open/close/timeout 재시작을 별도 thread 에서 관리. 옛 모델의 DISPLAY::bed_manager 가 베드의 모든 device 를 sequential iterate 하던 구조에선 한 device 의 blocking open() 이 같은 베드의 다른 device 의 lifecycle 을 차단했는데, per-device thread 로 분리하여 멀티 device 베드도 병렬 진행. add_dev 에서 start_lifecycle, del_dev / ~DISPLAY 에서 stop_lifecycle. (4) Serial::tcp_thread_func 단순화 — outer m_sock_cv wait 패턴 (1.18.34/1.18.39 가 추가했던 것) 제거. open_socket 이 m_sock valid 보장하므로 reader thread 시작 시점에 simple inner recv loop 만. accept_handoff 의 replace (zombie cleanup) 케이스는 recv error 후 m_sock 변경 확인 + retry 로 처리. (5) reading_thread_func 의 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 가드 복원 (1.18.39 가 제거). (6) Serial::write 의 silent-fail 케이스에 WARN trace 추가 — 미래 동종 회귀 즉시 노출. (7) PortRouter::subscribe 가 같은 (port, type) 에서 같은 IP filter 또는 둘 이상 catch-all 발견 시 reject (config 실수 원천 차단, 옛 WARN-only 동작 강화). (8) DISPLAY::bed_manager_thread_func 의 restart 블록 제거 — ptcon + adt 만 담당. "Waiting for next patient" TRACE 가 Linux/console 에서 1초 → 60초마다 throttle (별개 journald 부풀림 fix). 영향 범위: Intellivue / S5 / Datex / Hamilton / MPS / Agilia / IAP TCP-server 모드 베드 전부. 1.18.34 이후 우회 방법은 race-win 케이스 (VRN 이 PortRouter::subscribe 직후 connect) 외엔 없었음. MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.40
Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.39
Fixed: PortRouter (TCP server) 모드에서 reader thread 가 startup 시점에 죽어 client 데이터를 영구히 못 받던 문제. 1.18.34 (9bd252f) 의 PortRouter refactor 가 m_sock 세팅을 dispatch 시점으로 옮겼는데 reading_thread_func (Serial.cpp:354) 의 가드 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 는 옛 모델 (open 시점 이미 connect 완료) 가정 그대로라 — 시작 직후 m_sock=INVALID 면 tcp_thread_func 진입 안 하고 reader thread 즉시 종료. 이후 VRN 이 connect → PortRouter::dispatch → accept_handoff 가 m_sock 채우고 m_sock_cv.notify_all() → 그러나 wait 중인 reader 가 없음 (이미 죽음) → kernel recv buffer 에만 데이터 적재. 증상은 ss -tn state established 의 Recv-Q 가 0 이 아닌 값 (수백 bytes) 으로 누적, 베드 화면에는 데이터 안 들어옴. 그 베드의 VRN 이 Serial::open() 의 PortRouter::subscribe 직후~reader thread spawn 사이의 짧은 윈도우 안에 connect 한 경우만 우연히 정상 동작 (race-win). 수정: 가드 제거 — TCP 분기에 항상 진입. tcp_thread_func 자체에 이미 m_sock_cv outer-wait 루프가 있어 m_sock 이 INVALID 인 동안 dispatch 가 깨워줄 때까지 안전하게 대기. Pi 핫스팟에 VRN 여러 대 (e.g. 10.42.0.21~25) 를 같은 (4343, Philips:Intellivue) 키로 묶고 IP filter 로 라우팅하는 시나리오에서 가장 자주 노출. Added: 진단 로그 보강 — PortRouter::accept_loop 의 accept 이벤트, dispatch 의 라우팅 결정 (ip_specific/catch_all subs 수, 어느 dev 가 받았는지), subscribe/unsubscribe 의 entry 생성/append/destroy, reading_thread / tcp_thread 의 시작·wait·wake 라이프사이클 — 이전엔 모두 m_bDebug 게이트 안에 있어서 production journald 에서 안 보였는데, 이제 항상 출력 (이번 같은 dispatch routing 문제 진단을 production 에서 바로 가능). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.38
Fixed: 웹모니터링 우클릭 → 디바이스 세팅 (websocket edit_bed 명령) 으로 베드의 device/filter 를 변경할 때 베드 이름이 매번 "_2" suffix 로 자동 리네임되어 vr.conf (Pi: /data/vr.conf) 에 잘못된 이름으로 저장되던 문제. 원인은 1.18.22 (cda8aa5) 에서 add_bed 에 추가된 bedname dedup ("최후 방어선" 으로 forward_frame auto-create / 수동 편집된 conf 의 중복 이름 처리용). edit_bed 핸들러는 옛 pdel 이 m_pdisps 에 그대로 있는 상태에서 같은 bedname 으로 new DISPLAY 를 add_bed 하고 그 다음 del_bed(pdel) 하는 순서였는데, add_bed 가 dup 감지해 새 pdisp 를 bedname_2 로 자동 리네임 → save_settings 가 [BED/bedname_2] 로 직렬화 → 사용자 입장에선 "설정 적용 안됨" 으로 보였다. 추가로 옛/새 DISPLAY 가 잠깐 m_pdisps 에 공존하면서 같은 device 포트를 동시에 들고 있으려 해 로그가 dup 으로 찍히는 부수 증상도 있었음. 수정: del_bed(pdel) 를 add_bed(pdisp) 보다 앞으로 이동 — 옛 베드의 device 포트가 깨끗이 release 된 뒤 새 pdisp 가 같은 포트를 reopen, dedup 발동 안 함. 다른 베드의 recording 은 영향 없음 (인메모리 hot swap 설계 유지). 부수: edit_bed 핸들러의 데드 코드 (`break` 뒤의 unreachable olddevs populate 루프 + `if (olddevs.find(name) == olddevs.end())` 항상-true 래퍼) 정리. edit_conf 와 save_settings 는 손대지 않음. MFC + Qt + Pi (RPI64) 빌드 모두 통과.
1.18.40
Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.37
Refactored: 필터 다이얼로그의 python 런타임 — pyvital → openvital 0.3.0 로 교체 + 동봉 python.zip 슬림화. 핵심 동기 셋: (1) pyvital 0.6.0 이 tensorflow+torch+keras 를 hard-dep 으로 끌어들여 PyPI upgrade 가 사실상 깨짐, (2) 기존 zip 121 MB 안에 botocore/aiobotocore/s3fs/pyarrow/pandas/aiohttp 등 vitaldb 클라우드 업로드용 deps 가 ~250 MB unpacked 차지 — 필터 서버는 한 번도 쓴 적 없는 dead weight, (3) sanic 25.x 의 multi-process worker 가 openvital __main__.py 를 fork 후 재import 하면서 worker 가 module-top app instance 를 못 찾아 "Sanic app name not found" 로 죽음 — 단순 endpoint 2개 로컬호스트 서버에 async stack 자체가 과함. 해결: openvital 0.3.0 (commit b6f445b) 가 sanic 을 stdlib http.server (BaseHTTPRequestHandler + HTTPServer) 로 50 LOC 재작성 — wire protocol (GET / 필터목록 JSON, POST /<modname> gzip 본문) 동일해 VR 클라이언트 변경 0줄. 단일 스레드로 기존 sanic event loop semantics (cfgs/default_cfgs lock-free 변경) 보존. build_python_zip.py (NEW) 가 Python 3.11.9 embed + numpy + openecg + openvital (local checkout 우선, fallback PyPI) 만으로 27 MB zip 빌드 — 기존 121 MB 대비 78% 감소. ML 필터 (sv_dlapco/abp_hpi/ecg_classifier/ecg_beat_noise_detector) 는 [Hybrid 모델] 사용자가 Add filter 다이얼로그의 "Install ML filters" 버튼 누를 때 silent pip install openvital[all] (cmd 창 미노출 — VRApp::install_pip_package + run_pip_silent 이 자체 progress 다이얼로그에 stdout heartbeat 흘림). 버튼 라벨은 "Upgrade pyvital" → "Install ML filters" 로 의도 명시. openvital __main__.py 가 missing extras 를 graceful skip 하므로 base 환경에서도 11개 필터 (ECG QRS detector, HRV, MTWA, annotator, eeg_fft, nirs_cox, pkpd_3comp, pleth_dpop/ptt/pvi, resp_compliance) 즉시 사용 가능. Migration: 기존 사용자의 user_dir/python 폴더는 pyvital + 옛 deps 가 그대로 남아있고 vitaldb.net/python.zip 만 새로 올리면 (a) 기존 폴더 삭제 후 install_python 으로 새 zip 받으면 깔끔, (b) 폴더 유지 시 server 가 openvital 못 찾아 필터 다이얼로그 에러 → release note 에 명시 필요. MFC + Qt 양쪽 코드: VRGui.rc 의 IDC_UPGRADE 버튼 라벨 + DlgAddFilter.cpp 의 dist-info prefix 매칭 (pyvital- 8자 → openvital- 10자) + QtDlgAddFilter.cpp 도 동일 변경 + onUpgrade 가 cmd /k QProcess::startDetached 대신 theApp.install_pip_package("openvital[all]") 호출 (Linux Qt 빌드는 #ifdef _WIN32 가드). VRApp.cpp:318 의 python -m pyvital → -m openvital. install_pip_package 는 anonymous namespace 의 is_safe_pkg_spec 화이트리스트 검증 + run_pip_silent (CreateProcessA + CREATE_NO_WINDOW + 자식 stdout pipe) + CDlgProgress / QtDlgProgress 자체 progress UI. 빌드 검증: python -m openvital 이 GET / 로 11 필터 반환, sanic import 부재 확인. zip 27.5 MB.
1.18.33
Fixed: Demo device never started since 1.18.11 (bed_manager skipped empty port_name as passive but Demo has no port and still needs lock_and_open); Fixed: Qt fit-to-window button rendered at top-left (0,0) instead of right-aligned in bottom nav bar; Fixed: Qt crash when closing tab / deleting device / deleting filter - paint dereferenced freed pointer before panel timer rebuilt items (added MFC-style stale-pointer guard to QtDeviceItem and QtFilterItem); Added: macOS DMG main window now shown on launch (restoreWindowPos had no Mac/Linux branch); Added: macOS Homebrew dylibs (openssl) bundled in Contents/Frameworks; Added: macOS .app bundle now notarized + stapled (drag-to-Applications no longer needs xattr -cr on first launch); Changed: Qt MSI ~1MB smaller (dropped unused iconengines and imageformats plugins)
1.18.31
Added: drug slot shows DOSE_AMOUNT (mg etc) in preference to VOL (mL) when available; Added: DOSE_RATE / DOSE_AMOUNT montypes bound to BBraun tracks for monitor view; Added: HL7 MDC_DOSE_DRUG_DELIV_TOTAL unit picked up dynamically from UCUM
1.18.43
1.18.43: 멀티베드 console (PACU 10베드 Pi) syslog noise 80,500/hr → ~1,400/hr (-98.3%), cpu_usage 측정 1.16.12 (9f26e36) 이후 8년치 영구 -1 버그 수정, 멀티베드 환경 베드 식별용 [bedname / devicename] 로그 prefix 도입. 동기는 PACU production Pi (1.18.40) 4일치 syslog 분석 — 1.24M 라인 중 vr 라인 836K, idle 8시간 평균 시간당 80,500 라인 (베드 10 × 1초마다 "Waiting for next patient" × syslog 2x 중복). (1) VRApp.cpp:2696-2710 timer_thread_func 의 cpu usage 계산이 `if (totalcpu && m_cpu_total_last && totalcpu > m_cpu_total_last)` 가드 안에서만 m_cpu_total_last 갱신해 첫 호출 시 m_cpu_total_last=0 → else 분기 → -1 → m_cpu_total_last 갱신 안 됨 → 영원히 false → 영원히 -1 데드락. PACU 1,048 샘플 100% -1 실측 확인. m_cpu_total_last/m_cpu_used_last 갱신을 if 밖으로 빼서 첫 호출만 baseline 으로 -1, 두 번째 (60s 후) 부터 정상 측정. /proc/stat 의 iowait/irq/softirq 까지 7개 필드 fscanf 로 정확도 ±5% 개선. 부수: 같은 함수의 cpu_temp 가 `temp / 1000` int 나눗셈으로 소수점 절단되어 PACU 측정값이 정수 50/51/...,/58 9종만 출력 (millidegree 정밀도 손실) → `temp / 1000.0f` 로 fix. ram 계산은 `(totalram + totalswap) * mem_unit` 결과에 다시 mem_unit 곱하던 차원 혼용 (Linux 가 mem_unit=1 이라 우연히 정상 동작했지만 dimensionally wrong) → `(units1 + units2) * mem_unit` 로 정정 + underflow 가드. (2) DISPLAY::log(string/printf) + DEVICE::log(string/printf) 멤버 추가. DISPLAY::log 는 `[bedname] msg`, DEVICE::log 는 m_pdisp 위임 없이 직접 `[bedname / devicename] msg` 한 줄 prefix (nested `[bed] [dev]` 보다 가독성 우선). m_pdisp 또는 bedname null 케이스 safe fallback. (3) show_msg 의 console path TRACE 를 log() 로 교체 → "Waiting for next patient" 자동 [bedname] prefix. ADT patient id unset 도 log() 로. (4) 이미 수동으로 get_bedname() 을 인자로 넘기던 8군데 (DISPLAY ptcon_thread_func / add_trk / add_dev / del_dev / cut-recording, Device add_rec 콜백, FILT stop, VRApp add_tab/del_tab) 를 log() 로 전환 — 이중 prefix 방지 + 일관성. Serial::reading_thread / tcp_thread / open_socket waiting / timeout / interrupted / got-client 6군데 DEVICE::log() 전환 → `[bedname / Intellivue]` 등 자동 prefix, 멀티탭 환경에서 어느 베드 어느 device 출처인지 즉시 식별. (5) wss 재시도 폭주 throttle. WSSClient 에 m_verbose (debug step 로그 게이트) + m_dt_last_err_log (60s window) 추가. connect() 내부 step-by-step TRACE 4개 ("connecting" / "connect error" / "connected") 는 m_verbose 가드, 최종 "wss connect error" 는 60s window throttle (첫 줄 즉시, 그 다음 60s 1회). connect 성공 path 에서 throttle reset 으로 다음 disconnect → 첫 에러 즉시 로깅. sendweb_thread_func 의 "connecting websocket server" 호출자 라인도 같은 60s window, m_bDebug 면 verbose 활성화. PACU idle 8h 측정 7,024/hr (4종 × 1,756) → 약 60/hr (-99%). (6) "Waiting for next patient" / "ADT patient id unset" 의 60s throttle 자체는 feature/wav-mem-retention 브랜치 ptcon_thread_func 재구성 (b75928f 시리즈) 에서 이미 들어가 있어 이 릴리즈에 자동 포함. 종합 영향: idle 시간당 syslog 라인 80,500 → ~1,400 (-98.3%), active 시간당 14,900 → ~700 (-95%). 진단 측면: cpu_usage 가 부활하면서 웹모니터링 json 의 cpu_usage 필드도 한 번도 안 채워지던 게 정상 전송 (VRApp.cpp:2499 의 `if (m_cpu_usage > 0)` 가드가 영원히 false 였음). 별건: syslog 라인 2× 중복은 application 측이 아니라 rsyslog imuxsock + imjournal + journald.ForwardToSyslog=yes 의 Debian Bookworm 전형 misconfig — admin 측 fix (rsyslog.conf 에서 imjournal 주석 처리 + restart) 로 추가 50% 감소 가능 (별도 문서). MFC + Qt + Pi (RPI64) + Ubuntu CLI 빌드 모두 통과.
1.18.41
1.18.41 Hotfix: TCP-server protocol-initiator device (Intellivue/S5/Datex/Hamilton/MPS/Agilia/IAP) 가 open() 직후 보내는 첫 패킷이 wire 에 안 나가던 회귀. 1.18.34 의 PortRouter refactor 가 Serial::open_socket() 의 8초 handoff blocking 을 제거하고 비동기 모델로 전환했는데, protocol-initiator 의 open() 코드가 m_sock=INVALID 상태에서 write 를 시도해 silent fail (Linux 는 m_fd=0 이라 false 반환). Intellivue 케이스 production journald 의 Pi → ESP TX 700K~3M bytes 는 monitor 측 abort 로 늦게 트리거된 AB_SPDU_SI recovery assoc_req 후 request_thread 의 정상 polling 트래픽이지만 일부 베드 (abort 없는 monitor) 는 recovery 도 안 되어 영구 silent (RX 300 bytes). 수정 방향: open() 의 옛 contract ("return 시점에 통신 가능") 복구. (1) Serial::open_socket() TCP-server 분기가 PortRouter::subscribe 후 m_sock_cv 로 첫 client handoff 까지 block (timeout = device read_timeout, 기본 30s) — 옛 모델 그대로. lock_and_open 이 false 면 다음 tick 에서 retry. close 신호로 abort 가능. (2) Serial::close() 는 PortRouter::unsubscribe 안 함 — subscribe 는 device 생애 동안 sticky 라 read_timeout 으로 인한 close→re-open 사이클 동안 listen socket 이 유지되어 1.18.29/1.18.34 의 anti-churn 이익 보존. unsubscribe 는 ~Serial() 에서만. (3) Per-device lifecycle thread (DEVICE::m_thread_restart): 각 device 가 자기 open/close/timeout 재시작을 별도 thread 에서 관리. 옛 모델의 DISPLAY::bed_manager 가 베드의 모든 device 를 sequential iterate 하던 구조에선 한 device 의 blocking open() 이 같은 베드의 다른 device 의 lifecycle 을 차단했는데, per-device thread 로 분리하여 멀티 device 베드도 병렬 진행. add_dev 에서 start_lifecycle, del_dev / ~DISPLAY 에서 stop_lifecycle. (4) Serial::tcp_thread_func 단순화 — outer m_sock_cv wait 패턴 (1.18.34/1.18.39 가 추가했던 것) 제거. open_socket 이 m_sock valid 보장하므로 reader thread 시작 시점에 simple inner recv loop 만. accept_handoff 의 replace (zombie cleanup) 케이스는 recv error 후 m_sock 변경 확인 + retry 로 처리. (5) reading_thread_func 의 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 가드 복원 (1.18.39 가 제거). (6) Serial::write 의 silent-fail 케이스에 WARN trace 추가 — 미래 동종 회귀 즉시 노출. (7) PortRouter::subscribe 가 같은 (port, type) 에서 같은 IP filter 또는 둘 이상 catch-all 발견 시 reject (config 실수 원천 차단, 옛 WARN-only 동작 강화). (8) DISPLAY::bed_manager_thread_func 의 restart 블록 제거 — ptcon + adt 만 담당. "Waiting for next patient" TRACE 가 Linux/console 에서 1초 → 60초마다 throttle (별개 journald 부풀림 fix). 영향 범위: Intellivue / S5 / Datex / Hamilton / MPS / Agilia / IAP TCP-server 모드 베드 전부. 1.18.34 이후 우회 방법은 race-win 케이스 (VRN 이 PortRouter::subscribe 직후 connect) 외엔 없었음. MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.40
Hotfix: Drager Medibus 프로토콜 사용 베드에서 1.15.11 이후 vr.service 가 SIGSEGV 로 무한 재시작하던 문제. 1.15.10 (a8c7b2a) 의 "Sampling rate estimation" 리팩터링이 m_ptrk_awp/awf/co2 트랙을 add_trks() 에서의 eager 생성 (default srate=62.5Hz) 에서 realtime data 100 샘플 모인 후 lazy 생성으로 바꿨는데, Medibus 핸드셰이크 순서 (0x51 ICC → 0x52 DeviceID → 0x53 RealtimeConfig 응답 → 0x54 → realtime data 시작) 상 0x53 응답 핸들러 (Drager.cpp:295-307) 가 트랙들이 아직 nullptr 인 시점에 `ptrk->m_srate = 1000000.0/interval` 로 NULL 역참조 → 즉시 SEGV. 그 베드의 vr.service 가 systemd Restart=on-failure 로 재시작 → 다시 connect → 동일 0x53 패킷 받고 동일 SEGV → 무한 루프 (journald 로그상 restart counter 61 까지 누적, code=killed status=11/SEGV). 1.15.9 까진 트랙이 add_trks() 에서 생성되어 0x53 도착 시 항상 non-NULL 이라 안전했음. 수정: (a) add_trks() 에서 m_ptrk_awp/awf/co2 eager 생성 복원 (srate=62.5Hz default) — 0x53 는 ptrk->m_srate 만 갱신 (트랙 재생성 X). (b) on_received() 의 lazy creation + packet-timing srate 추정 블록 제거 — 0x53 가 권위 있는 srate 소스이고 packet 타이밍 기반 추정은 network jitter 에 약해 불안정. (c) 0x53 핸들러에 `if (interval > 0)` guard 추가 — 페이로드 깨졌을 때 div-by-zero / inf srate 방지. 영향 범위: Drager Primus / Apollo / Perseus / Fabius 등 Medibus 사용 마취기 전부. 우회 방법은 1.15.9 다운그레이드 외엔 없었음 (그 베드의 모든 데이터 유실 + 환자 모니터링 불가). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.39
Fixed: PortRouter (TCP server) 모드에서 reader thread 가 startup 시점에 죽어 client 데이터를 영구히 못 받던 문제. 1.18.34 (9bd252f) 의 PortRouter refactor 가 m_sock 세팅을 dispatch 시점으로 옮겼는데 reading_thread_func (Serial.cpp:354) 의 가드 `else if (m_sock != INVALID_SOCKET) tcp_thread_func();` 는 옛 모델 (open 시점 이미 connect 완료) 가정 그대로라 — 시작 직후 m_sock=INVALID 면 tcp_thread_func 진입 안 하고 reader thread 즉시 종료. 이후 VRN 이 connect → PortRouter::dispatch → accept_handoff 가 m_sock 채우고 m_sock_cv.notify_all() → 그러나 wait 중인 reader 가 없음 (이미 죽음) → kernel recv buffer 에만 데이터 적재. 증상은 ss -tn state established 의 Recv-Q 가 0 이 아닌 값 (수백 bytes) 으로 누적, 베드 화면에는 데이터 안 들어옴. 그 베드의 VRN 이 Serial::open() 의 PortRouter::subscribe 직후~reader thread spawn 사이의 짧은 윈도우 안에 connect 한 경우만 우연히 정상 동작 (race-win). 수정: 가드 제거 — TCP 분기에 항상 진입. tcp_thread_func 자체에 이미 m_sock_cv outer-wait 루프가 있어 m_sock 이 INVALID 인 동안 dispatch 가 깨워줄 때까지 안전하게 대기. Pi 핫스팟에 VRN 여러 대 (e.g. 10.42.0.21~25) 를 같은 (4343, Philips:Intellivue) 키로 묶고 IP filter 로 라우팅하는 시나리오에서 가장 자주 노출. Added: 진단 로그 보강 — PortRouter::accept_loop 의 accept 이벤트, dispatch 의 라우팅 결정 (ip_specific/catch_all subs 수, 어느 dev 가 받았는지), subscribe/unsubscribe 의 entry 생성/append/destroy, reading_thread / tcp_thread 의 시작·wait·wake 라이프사이클 — 이전엔 모두 m_bDebug 게이트 안에 있어서 production journald 에서 안 보였는데, 이제 항상 출력 (이번 같은 dispatch routing 문제 진단을 production 에서 바로 가능). MFC + Qt + Pi (RPI64) + Ubuntu Qt + AppImage 빌드 모두 통과.
1.18.38
Fixed: 웹모니터링 우클릭 → 디바이스 세팅 (websocket edit_bed 명령) 으로 베드의 device/filter 를 변경할 때 베드 이름이 매번 "_2" suffix 로 자동 리네임되어 vr.conf (Pi: /data/vr.conf) 에 잘못된 이름으로 저장되던 문제. 원인은 1.18.22 (cda8aa5) 에서 add_bed 에 추가된 bedname dedup ("최후 방어선" 으로 forward_frame auto-create / 수동 편집된 conf 의 중복 이름 처리용). edit_bed 핸들러는 옛 pdel 이 m_pdisps 에 그대로 있는 상태에서 같은 bedname 으로 new DISPLAY 를 add_bed 하고 그 다음 del_bed(pdel) 하는 순서였는데, add_bed 가 dup 감지해 새 pdisp 를 bedname_2 로 자동 리네임 → save_settings 가 [BED/bedname_2] 로 직렬화 → 사용자 입장에선 "설정 적용 안됨" 으로 보였다. 추가로 옛/새 DISPLAY 가 잠깐 m_pdisps 에 공존하면서 같은 device 포트를 동시에 들고 있으려 해 로그가 dup 으로 찍히는 부수 증상도 있었음. 수정: del_bed(pdel) 를 add_bed(pdisp) 보다 앞으로 이동 — 옛 베드의 device 포트가 깨끗이 release 된 뒤 새 pdisp 가 같은 포트를 reopen, dedup 발동 안 함. 다른 베드의 recording 은 영향 없음 (인메모리 hot swap 설계 유지). 부수: edit_bed 핸들러의 데드 코드 (`break` 뒤의 unreachable olddevs populate 루프 + `if (olddevs.find(name) == olddevs.end())` 항상-true 래퍼) 정리. edit_conf 와 save_settings 는 손대지 않음. MFC + Qt + Pi (RPI64) 빌드 모두 통과.
Apple Silicon (arm64) only · Requires macOS 11 or later · Developer ID signed & notarized
If you use the VitalRecorder in your research, please cite the following publication:
Lee HC, Jung CW. VitalRecorder-a free research tool for automatic recording of
high-resolution time-synchronised physiological data from multiple anaesthesia devices. Sci Rep.
2018 Jan 24;8(1):1527. doi:
10.1038/s41598-018-20062-4. Available from: https://www.nature.com/articles/s41598-018-20062-4