Notice
Recent Posts
Recent Comments
Link
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

ssuperjun 님의 블로그

[과제5-1] DB 모니터링 프로그램 Python 변환 1편: 과제 개요, 파일 용도 분석, Python 변환 본문

인턴

[과제5-1] DB 모니터링 프로그램 Python 변환 1편: 과제 개요, 파일 용도 분석, Python 변환

ssuperjun 2026. 1. 17. 13:48

과제 개요

요구사항: 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는 기계 친화적 언어지만 메모리를 직접 할당/해제 필요