ssuperjun 님의 블로그
[과제5-1] DB 모니터링 프로그램 Python 변환 1편: 과제 개요, 파일 용도 분석, Python 변환 본문
과제 개요
요구사항: C 스크립트를 Python으로 변환
프로그램 동작 방식
C 스크립트를 실행해 서비스 DB의 processlist(프로세스 관련 정보), innodb_status(상태 정보), innodb_lock(lock 정보) 정보를 수집해서, 스크립트 실행 서버 내 로그 파일과 LOG_DB(스크립트 실행 서버와 다른 곳에 위치)에 저장
요약: DB 모니터링 프로그램

파일 용도 분석
ls 시 파일들 색상 구분으로 myproc 폴더 내 존재하는 파일 용도 예측
검정색: 일반적인 소스 코드
clean_proc.sh, watchCheck.c, watchCheck_lock.c, watchCheck_proc.c, process2.c
process2.c를 제외한 코드들은 process2.c가 동작 중일 때 외부에서 병렬적으로 실행되어 감시/정리 작업을 수행하는 코드로 보임
하늘색: 심볼릭 링크
libmysqlclient.so, libmysqlclient.so.21
각각 libmysqlclient.so.21, libmysqlclient.so.21.2.27을 참조하므로 심볼릭 링크를 실행하면 원본(참조 중인)이 실행된다.
초록색: 실행 가능한 파일
libmysqlclient.so.21.2.27
mylock_test, myproc, procCheck, procCheck_lock, procCheck_proc: c코드가 컴파일된 결과물일 듯
procRun.sh, restart_myproc.sh, stop_myproc.sh: 쉘 스크립트인데, 실행 속성이 부여돼 초록색
mylock_test: watchCheck_lock.c가 실행, 감시
myproc: watchCheck.c가 실행, 감시. restart_myproc.sh가 종료 후 재시작. stop_myproc.sh가 종료
procCheck: procRun.sh가 실행. restart_myproc.sh가 종료 후 재시작. stop_myproc.sh가 종료
소스 코드 들여다보기
procRun.sh
userID를 인자로 전달하며 procCheck 실행파일을 실행(백그라운드)
clean_proc.sh
MySQL Client를 실행. 미리 등록된 접속 정보(mymon)로 접속해
"delete from MYMON.skipProcesslist where exectime <> -1" 쿼리 실행
수동 or 크론으로 skipProcesslist 테이블을 정리함
watchCheck.c
특정 프로세스(/home1/irteam/script/myproc/myproc)를 60초 주기로 감시하고, 종료되면 다시 실행시키는 코드
코드 쉽게 풀이
/home1/irteam/script/myproc/myproc이 실행되어 만들어진 자식 프로세스 PID를 waitpid로 감시하다가, 자식 프로세스 PID가 0이 아니면(종료되면) /home1/irteam/script/myproc/myproc 을 다시 실행시켜 자식 프로세스를 다시 만듦
이 코드가 실행되려면
/home1/irteam/script/myproc/myproc 파일이 미리 존재해야 한다.
make_log 함수는 다른 파일에서 구현되어 있는 것으로 추정
그렇다면 이 코드만으로 다른 파일에서 make_log 함수를 불러오는 게 가능한가?
이 코드를 컴파일할 때 함수가 정의된 다른 소스 파일을 같이 링크하면 된다.
watchCheck_lock.c
마찬가지로 자식 프로세스를 감시하다가 종료되면 자동으로 다시 실행하는 기능
watchCheck.c와 유사하지만 실행 대상과 로깅 방식이 다르고, make_log() 함수가 직접 구현돼 있다.
실행(감시) 대상: /home1/irteam/script/myproc/mylock_test
로그는 시간 정보와 함께 저장된다. 로그 파일은 DBA 이름(strDBA)에 따라 분리된다.
예시: [01/13 14:23:10] watching....[/home1/irteam/script/myproc_test]
watchCheck_proc.c
이것 역시 프로세스 감시 및 자동 재시작 데몬(백그라운드에서 돌아가는 프로그램)
실행(감시) 대상: /home1/irteam/script/myproc/myproc_test
실행 대상 빼곤 watchCheck_lock.c와 완전히 동일
[실행 테스트]
실행(감시) 대상 경로를 지금 환경에 맞추니 잘 동작됨

watchCheck.c, watchCheck_lock.c, watchCheck_proc.c를 python 코드로 바꾸기
watchCheck_lock.py
import os
import sys
import time
import datetime
import subprocess
WAIT_TIME = 60 # 검사 주기 (초)
strDBA = ""
def make_log(msg):
""" 로그 파일에 메시지 기록 """
filename = f"/home1/irteam/bjpark00/myproc/log/_procCheck_lock_{strDBA}.log"
try:
with open(filename, "a") as fp:
now = datetime.datetime.now()
timestamp = now.strftime("[%m/%d %H:%M:%S]")
fp.write(f"\n{timestamp} {msg}")
except IOError:
pass # 로그 파일 열기 실패 시 무시
def load_ps(dba):
""" 외부 프로그램 실행 (mylock_test) """
try:
# subprocess.Popen으로 백그라운드 실행
proc = subprocess.Popen(
["/home1/irteam/bjpark00/myproc/mylock_test", dba]
)
return proc.pid, proc
except Exception as e:
make_log(f"Failed to start process: {e}")
return None, None
def execute(dba):
""" 감시 루프 실행 """
nflag = 0
pid, proc = load_ps(dba)
while True:
if proc.poll() is not None: # 프로세스 종료되었는지 확인
if nflag > 0:
make_log(f" ReTry...PID:{pid}, {dba}")
nflag += 1
pid, proc = load_ps(dba)
time.sleep(WAIT_TIME)
def main():
global strDBA
if len(sys.argv) < 2:
print("Usage: python watchCheck_lock.py <DBA>")
sys.exit(1)
strDBA = sys.argv[1]
make_log("=====================================")
make_log(" Sysmon Agent Process Watch ..Start")
make_log("=====================================")
make_log("watching....[/home1/irteam/bjpark00/myproc/myproc_test]")
execute(strDBA)
if __name__ == "__main__":
main()
실행 테스트
source /home1/irteam/venv-process2/bin/activate
python3 watchCheck_lock.py username

watchCheck_proc.py
import os
import sys
import time
import datetime
import subprocess
WAIT_TIME = 60 # 감시 주기 (초)
strDBA = ""
def make_log(msg):
""" 로그 파일에 메시지 기록 """
filename = f"/home1/irteam/bjpark00/myproc/log/_procCheck_proc_{strDBA}.log"
try:
with open(filename, "a") as fp:
now = datetime.datetime.now()
timestamp = now.strftime("[%m/%d %H:%M:%S]")
fp.write(f"\n{timestamp} {msg}")
except IOError:
pass # 로그 파일 열기 실패 시 무시
def load_ps(dba):
""" 외부 프로그램 실행 (myproc_test) """
try:
proc = subprocess.Popen(
["/home1/irteam/bjpark00/myproc/myproc_test", dba]
)
return proc.pid, proc
except Exception as e:
make_log(f"Failed to start process: {e}")
return None, None
def execute(dba):
""" 감시 루프 실행 """
nflag = 0
pid, proc = load_ps(dba)
while True:
if proc is None or proc.poll() is not None: # 프로세스 종료되었는지 확인
if nflag > 0:
make_log(f" ReTry...PID:{pid}, {dba}")
nflag += 1
pid, proc = load_ps(dba)
time.sleep(WAIT_TIME)
def main():
global strDBA
if len(sys.argv) < 2:
print("Usage: python watchCheck_proc.py <DBA>")
sys.exit(1)
strDBA = sys.argv[1]
make_log("=====================================")
make_log(" Sysmon Agent Process Watch ..Start")
make_log("=====================================")
make_log("watching....[/home1/irteam/bjpark00/myproc/myproc_test]")
execute(strDBA)
if __name__ == "__main__":
main()
실행 테스트
myproc 디렉토리 내에 myproc_test라는 실행파일이 없으므로 60초마다 실행 실패 및 재시도 수행 -> 정상

watchCheck.py
import os
import sys
import time
import datetime
import subprocess
WAIT_TIME = 60 # 감시 주기 (초)
strDBA = ""
def make_log(msg):
"""로그를 콘솔과 파일에 출력"""
log_dir = "/home1/irteam/bjpark00/myproc/log"
os.makedirs(log_dir, exist_ok=True)
filename = f"{log_dir}/_procCheck_{strDBA}.log"
timestamp = datetime.datetime.now().strftime("[%m/%d %H:%M:%S]")
full_msg = f"\n{timestamp} {msg}"
try:
with open(filename, "a") as f:
f.write(full_msg)
except IOError:
print(f"[로그 쓰기 실패] {full_msg}")
print(full_msg.strip()) # 콘솔 출력도 함께
def load_ps(dba):
"""외부 실행 파일(myproc) 실행"""
try:
proc = subprocess.Popen(
["/home1/irteam/bjpark00/myproc/myproc", dba]
)
return proc.pid, proc
except Exception as e:
make_log(f"[ERROR] Failed to start process: {e}")
return None, None
def execute(dba):
"""감시 루프"""
nflag = 0
pid, proc = load_ps(dba)
while True:
if proc is None or proc.poll() is not None:
if nflag > 0:
make_log(f" ReTry...PID:{pid}, {dba}")
nflag += 1
pid, proc = load_ps(dba)
time.sleep(WAIT_TIME)
def main():
global strDBA
if len(sys.argv) < 2:
print("Usage: python watchCheck.py <DBA>")
sys.exit(1)
strDBA = sys.argv[1]
make_log("=====================================")
make_log(" Sysmon Agent Process Watch ..Start")
make_log("=====================================")
make_log("watching....[/home1/irteam/bjpark00/myproc/myproc]")
execute(strDBA)
if __name__ == "__main__":
main()
기존 C 코드에선 make_log 함수는 선언만 돼있고 구현되지 않아서 python 코드에선 구현함(로그 파일과 콘솔에 모두 출력되도록)
[참고]
import subprocess: 지금처럼 외부 실행파일을 병렬로 실행할 때 사용(일종의 멀티프로세싱 개념)
import threading: process2.py처럼 Python 내부 코드를 스레드로 병행 처리할 때 사용(GIL 때문에 CPU 코어를 병렬로 사용할 순 없음)
from multiprocessing import Process: 멀티프로세싱 기법. 메모리 공유가 안돼 Queue 등으로 통신해야 한다.
C 코드 더 들여다보기
int main(int argc, char *argv[])
main 함수를 int main(void)로 작성할 수 있지만, 위처럼 작성해 main 함수에 매개변수를 전달할 수 있다.
argc는 전달될 인수의 개수, argv[]는 포인터 배열이며, argv[0]에는 프로그램의 실행경로, argv[1], argv[2]에는 사용자가 입력한 매개변수가 저장된다.
예를 들어, int main(int argc, char *argv[])에 ./tiny 8000 aaa이라는 입력을 준다면
argc는 2개일 것이고, argv[0]에는 실행경로인 ./tiny가 들어가고, argv[1]에는 8000이 들어가고, argv[2]에는 aaa가 들어갈 것이다.
=> argv의 각 인자는 띄어쓰기로 구분된다.
sprintf vs printf
sprintf는 버퍼에 문자열 저장
printf는 화면에 바로 출력
waitpid 함수
기본적으로 정의된 함수인가? -> O
waitpid(자식 pid, 자식의 종료 상태, 옵션)
리턴값이 양수이면 자식 프로세스가 종료돼 그 pid를 반환, 0이면 종료된 자식 없음, -1이면 에러
waitpid의 WNOHANG 옵션은 뭐지?
자식 프로세스가 종료되지 않았더라도 기다리지 않고 리턴값 0을 즉시 반환 = 비동기
fork, execl 함수
프로세스 관련 함수
fork는 복사본 생성
execl은 현재 프로세스를 다른 프로그램으로 교체
fork + execl 조합으로 새로운 자식 프로세스를 만들 수 있다.
궁금증
멀티 스레딩을 코드로 구현할 때 c와 python의 차이점
가장 큰 차이: GIL
python은 GIL(Global Interpreter Lock)이 존재한다.
파이썬으로 멀티스레드 코드를 작성해도, 실제로는 멀티스레드를 수행하지 못한다.
GIL이란?
파이썬 인터프리터가 한번에 하나의 스레드만 실행하도록 제한하는 락
파이썬 인터프리터의 내부 구조가 스레드 세이프하지 않기 때문에 사용
왜 CPython 인터프리터는 메모리 관리를 포함한 많은 내부 구조가 스레드 세이프(thread-safe)하지 않을까?
파이썬이 처음 설계될 당시는 멀티코어 cpu가 아니었음
파이썬 객체마다 있는 참조 카운트(값 증가/감소)를 한번에 하나의 스레드만 조작하도록 설계함
파이썬 객체 = 실제 데이터
a = [1,2,3] # [1,2,3]은 리스트 객체. 변수 a는 참조(그 객체를 가리키는 이름표)
b = a # b도 [1,2,3]을 참조하면, 참조 카운트는 2가 됨
GIL은 멀티스레드랑 관련 있다. 멀티프로세싱 시 각 프로세스를 별개의 파이썬 인터프리터로 실행하므로 GIL의 영향을 받지 않는다.
GIL 때문에 멀티스레드를 사용해도 CPU 코어를 병렬로 활용할 수 없다.
하지만 IO 작업(파일 읽기 쓰기 등)은 GIL이 자동 해제되어 작업의 병렬처리가 가능하다.
용어 정리
멀티프로세스: 2개 이상의 프로세스가 동시에(동시성, 병렬성) 실행
멀티스레드: 한 프로세스 내에서 2개 이상의 스레드가 동시에(동시성) 실행
스레드: 프로세스와 달리 stack을 제외한 메모리(코드, 데이터, 힙)를 공유한다.
동시성: 실제로는 하나의 작업만 수행되지만 context switching을 통해 병렬적으로 수행되는 것처럼 보이는 것
병렬성: 여러 작업이 동시에 수행
그 외
파이썬은 GIL을 없애려는 시도를 계속 해 왔지만, 기존 생태계 호환성, 성능 때문에 최근까지도 조심스럽게 진행 중
C는 기계 친화적 언어지만 메모리를 직접 할당/해제 필요
'인턴' 카테고리의 다른 글
| [과제5-3] DB 모니터링 프로그램 Python 변환 3편: MySQL Client 설치, C 의존성 패키지 설치, C 코드 실행 테스트 (0) | 2026.01.17 |
|---|---|
| [과제5-2] DB 모니터링 프로그램 Python 변환 2편: process2.c 코드 분석 (0) | 2026.01.17 |
| [교육1] NHN Cloud Essentials(CES) 교육 메모 (1) | 2026.01.12 |
| AWS 교육(노션 백업) (0) | 2026.01.11 |
| [과제 4-2] MySQL 자동 설치 스크립트 생성 - 압축파일 이용 (1) | 2026.01.08 |