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-2] DB 모니터링 프로그램 Python 변환 2편: process2.c 코드 분석 본문

인턴

[과제5-2] DB 모니터링 프로그램 Python 변환 2편: process2.c 코드 분석

ssuperjun 2026. 1. 17. 14:14

process2.c 첫인상

코드 큰 맥락

1. 헤더파일, 매크로 값, 전역변수 선언

2. 구조체 선언: dbHost_struct(이름, 포트, ip, 서비스 종류), Interval_Server(시작값, 종료값)

3. 함수 선언

4. 함수 실제 구현부

 

각 변수의 역할이 뭔지 알기 전에, 일단 몇개의 함수가 있는지 파악하기

 

int InitTable();

void ThreadMain(int);

void *ThreadProcessList(void *);

int main(int argc, char *argv[])

=> 함수 4개 존재

 

main 함수에선 ThreadMain을 실행(총 3번 등장)

initTable 함수는 ThreadMain 함수 첫부분에 등장해 1번만 실행

ThreadProcessList 함수는 ThreadMain 함수 내 Thread Run 부분에서 1번만 등장, nThreadCount만큼 반복

// ThreadProcessList 함수 내 일부분
/* Thread Run */
// nThreadCount만큼 반복
pthread_create(&pThread[i], NULL, ThreadProcessList, (void*)(unsigned long)i);

 

ThreadProcessList 함수가 가장 방대하다. 반 이상


process2.c의 기능

= 모니터링 프로그램

여러 MySQL 서버에 접속해 아래의 정보를 주기적으로 수집하고, 로그 DB에 기록

 

주요 기능

  • 특정 관리자의 책임 서버 목록을 조회 (DB에서)
  • 각 서버의 SHOW PROCESSLIST, SHOW ENGINE INNODB STATUS, INNODB LOCK 정보를 수집
  • 쓰레드로 병렬 수집 (최대 15개 쓰레드)
  • 수집된 결과를 로그 파일과 DB에 저장
  • 수집 주기는 INTERVALTIME_SEC (1초로 설정해둠)

 

함수별 기능

main 함수

프로그램 진입점. signal handler 설정, 주기적 호출 준비

알아야 할 것: sigaction 함수, 핸들러, 기타(pause 함수, ifdef ~ endif문)

 

InitTable 함수

대상 서버 리스트(DB에서) 불러오기

알아야 할 것: mysql_real_connect 함수, SELECT host... 구문, dbHost 구조체(어떻게 사용되는지)

 

ThreadMain 함수

쓰레드 준비, 로깅 준비, 타이밍 측정, 쓰레드 실행 및 종료 대기

알아야 할 것: pthread_create, pthread_join, IntervalServer 구조체

 

ThreadProcessList 함수

각 서버에 접속해 정보 수집(SHOW PROCESSLIST, SHOW ENGINE INNODB STATUS, INNODB LOCK)

mysql_query, mysql_stmt_bind_param, stmt_execute

 

📂 주요 흐름 요약

  • 실행 (main)
    → 사용자 ID(argv[1])을 인자로 받아 관리 대상 서버 목록 조회 준비
  • InitTable
    → MYMON DB에서 admin_user와 serverinfo 테이블을 조인하여 대상 서버 정보 획득
    → 구조체 배열 dbHost[]에 저장
  • ThreadMain
    → 현재 시간 기반 로그 파일 생성
    → 수집 대상 서버를 쓰레드 수에 맞게 분할
    → pthread_create()로 ThreadProcessList() 실행
  • ThreadProcessList
    → 각 서버에 대해:
  • SHOW FULL PROCESSLIST → 비정상 쿼리/긴 쿼리 감지 및 로깅
  • SHOW ENGINE INNODB STATUS → InnoDB 내부 상태 수집
  • INNODB_LOCK_WAITS, INNODB_TRX → 락 대기 정보 수집
    → 모든 결과는 log DB에 insert (stmt 방식 + compress)

수집 대상 테이블: MYMON.serverinfo, MYMON.admin_user

InitTable 함수에 명시됨


C와 익숙해지기

조건부 전처리기 지시문

#ifdef _DEBUG
#define OUTPUT stderr
#else
#define OUTPUT g_fCheckLog
#endif

 

코드 상단에

#define _DEBUG

가 정의돼 있으면 if문을 실행(#define OUTPUT stderr)

그렇지 않으면 else문을 실행(#define OUTPUT g_fCheckLog)

디버그 모드에선 OUTPUT을 stderr로 매핑(콘솔로 직접 출력)하고, 비 디버그(릴리즈) 모드에서는 OUTPUT을 파일 핸들인 g_fCheckLog로 사용(fopen으로 연 로그 파일에 출력)하겠다.

 

전처리기는 #include처럼 컴파일 직전에 수행되는 별도의 프로그램이다.

일반적인 조건문과 비슷하지만 큰 차이점은

  1. 조건이 성립하지 않으면 실행도, 컴파일도 안된다.
  2. 괄호를 사용하지 않으므로 #if문이 끝나면 #endif를 사용해야 한다.

 

로그 파일 핸들

FILE *g_fCheckLog = NULL;

핸들 = 운영체제에서 라이브러리나 리소스를 간접적으로 조작하기 위한 추상적인 참조 값

실제 파일 자체를 다루는 게 아니라, 파일을 가리키는 포인터를 통해 파일에 대한 핸들(손잡이)을 쥐고 조작하는 셈

 

핸들을 사용하는 목적

  1. 효율성: 파일이나 DB를 매번 열고 닫지 않기 위해
  2. 상태 유지: 어디까지 작업했는지 기억하기 위해
  3. 추상화: 복잡한 하드웨어 구조를 몰라도 알아서 처리하도록

MySQL 핸들

MYSQL g_mysql;

MYSQL은 MySQL 연결 정보를 관리하는 구조체

이 구조체를 직접 조작하지 않고, 간접적으로 mysql_query, mysql_real_connect 같은 함수에 핸들을 넘겨 사용

 

MYSQL conn;

mysql_init(&conn);

mysql_real_connect(&conn, ...);

여기서 conn이 바로 MySQL 커넥션 핸들


코드 본격 해부

전역 변수

MyMon은 그저 데이터베이스 이름. My Monitoring의 약자로 추정

LogDB는 수집 결과가 기록되는 DB

dbHost_struct dbHost[MYSQLHOST_NUM]; // 감시할 서버들의 정보 배열

nterval_Server g_IntervalServer[THREADCOUNT]; // 각 스레드에 할당된 서버 인덱스 범위. 서버 수집 작업을 스레드별로 분할 처리하기 위해, 범위 정보를 저장

char strUserID[20]; // 감시 대상 서버의 담당자 ID(main 함수에서 argv[1]로 입력받음)

 

전역변수 'g_이름’은 앞에 g_가 붙여서 global 변수임을 나타내는 듯

 

main 함수

[개요]

프로그램 진입점. signal handler 설정, 주기적 호출 준비

 

[흐름]

사용자 ID(argv[1])를 strUserID에 저장

시그널 핸들러 설정: SIGALRM 시 ThreadMain 함수 호출

무한 루프: SIGALRM 시그널을 주기적으로 발생시켜 ThreadMain 호출

디버그 모드일 경우, 무한 루프에서 ThreadMain을 계속 호출하고 g_nCount 증가로 호출 횟수 추적

 

[참고]

실행하며 발생한 에러 로그 등은 cb801의 ./log 경로에 _myproc_username.log.20260116 형태로 저장됨.

    username은 ./process2 username 명령 실행 당시 기입한 이름

에러 시만 찍히는 로그를 제외하면 로그는

"현재 시각, 감시 서버 개수, 전체 수행시간, 서버당 평균 수행시간"

스레드 실행 부분에서 "특정 조건(실행 시간 긴 쿼리, Locked, Waiting 등) 시에만 발생하는 경고성 로그"

둘 뿐임

InitTable 함수

[개요]

감시(모니터링)할 서버 목록을 MyMon DB에서 조회하여 dbHost 배열에 저장

 

[흐름]

사용자 ID(strUserID)에 해당하는 감시 대상 서버 목록 조회 쿼리 저장

MyMon DB에 연결

앞서 만든 쿼리를 실행

쿼리 실행 성공한 경우, 쿼리 결과를 받아서, 각 레코드를 순회하며 dbHost[] 배열에 저장. 이 배열은 이후 ThreadProcessList 함수에서 수집 루프에 사용됨

쿼리 실행은 됐지만, 쿼리 결과가 없을 경우 에러 핸들링

 

[참고]

MYMON DB 내 MYMON.serverinfo, MYMON.admin_user 테이블을 조인해 정보를 가져옴

ThreadMain 함수

[개요]

각 스레드별로 수집 대상 서버 목록을 InitTable 함수에서 받아와서, 수집 작업(ThreadProcessList 함수)을 병렬 처리한 뒤 결과를 LogDB에 기록

 

[흐름]

nitTable 함수를 호출해 서버 목록 초기화(사용자 ID에 해당하는 서버 목록을 DB에서 불러옴 - dbHost[], g_nServerCnt)

현재 시간 정보를 얻어와서 로그 파일 이름 생성

스레드 생성 개수: 감시할 서버 개수가 많다면 THREADCOUNT(15)개, 적다면 1개

스레드별로 수집할 서버 범위 설정

Thread Run: 각 스레드는 ThreadProcessList 함수 실행, g_IntervalServer[i] 범위의 서버들을 수집 처리

Thread Wait: 모든 스레드가 종료될 때까지 대기

수행시간 등 로그 파일에 기록

리소스 해제

 

ThreadProcessList(void *arg) 함수

[개요]

  1. 인자로 받은 arg(= thread 번호)에 따라 서버 구간을 분할하여 병렬 처리.
  2. 각각의 대상 MySQL 서버에 접속.
  3. 다음의 3가지 정보 수집:
  • SHOW FULL PROCESSLIST: 현재 실행 중인 쿼리/세션 정보
  • SHOW ENGINE INNODB STATUS: InnoDB 엔진 상태 정보
  • InnoDB 락 대기 정보 (버전에 따라 INFORMATION_SCHEMA 또는 PERFORMANCE_SCHEMA 쿼리 실행)

    4. 수집된 정보를 로그 DB에 저장 (processlist, innodb, innodb_lock 테이블)

 

[흐름]

변수들 미리 선언

스레드별로 각자 맡은 대상 서버에 접속해 정보 수집, 로그DB에 기록

Show processlist, SHOW ENGINE INNODB STATUS, SHOW MYSQL VERSION, SHOW INNODB LOCK 구현 부분은 분량이 많지만, 흐름 자체가 동일했음

 

[구체적인 흐름]

스레드가 대상 서버 접속에 성공한 경우

에러 처리: 쿼리 실행 실패 경우, 쿼리 결과가 null인 경우, 쿼리 결과는 정상적으로 받아왔지만, row 수가 0인 경우

쿼리 결과를 정상적으로 가져왔다면, 각 row(쿼리 실행 결과의 row 하나는 곧 프로세스 하나의 레코드를 의미)별로 처리

stmt과 bind를 사용하여 쿼리 준비, 실행

 

[참고]

SHOW INNODB LOCK 구현부의 경우, 실행할 InnoDB Lock 쿼리는 SHOW MYSQL VERSION 구현부에서 생성, 저장됨


궁금증

MYSQL_STMT란?

하나의 SQL문을 서버에 미리 준비해두고, 실행 시에는 값만 바꿔서 여러번 반복 실행 가능하게 해줌

 

MYSQL_BIND란?

MYSQL_BIND는 prepared statement의 각 파라미터(?) 자리에 값을 넣는 구조체

예를 들어, INSERT INTO processlist VALUES (?, ?, ?, ..., ?) 이런 쿼리에 대해, 각 ? 자리에 들어갈 값을 바인딩한다.

바인딩 = 각 ?에 들어갈 데이터 포장

 

[C 코드 실행 흐름]

stmt = mysql_stmt_init(&mysql); // 초기화

mysql_stmt_prepare(stmt, query, strlen(query)); // 쿼리 준비

mysql_stmt_bind_param(stmt, bind); // 바인딩된 파라미터를 stmt에 적용

mysql_stmt_execute(stmt); // 실제 쿼리 실행

 

goto QUERYERROR; // 에러 처리 루틴으로 이동

goto는 중첩된 if나 loop 안에서 빠져나갈 때 간결하게 사용할 수 있으나, 남용 시 디버그에 불리

QUERYERROR 라벨은 코드 후반부에 구현된 에러 처리 부분이지만, 실제 코드 상에선 ; 한줄짜리 빈 코드로 구현됨. 그저 goto를 통해 루프 빠져나오는 용도인 듯

 

코드 중간이나 맨 밑의 주석은 무엇인가

향후 유지보수나 재구현, 참고를 위한 MySQL 쿼리 모음으로 사용되며, ThreadProcessList() 함수 또는 관련 기능에서 사용하는 DB 테이블 구조, 처리 로직, 쿼리 흐름을 문서화한 개발자용 메모

 

[구체적으로]

1. 관련 테이블 정의 및 재생성 시 참고용

CREATE TABLE `processlist` (...)

CREATE TABLE `innodb` (...)

CREATE TABLE `innodb_lock` (...)

=> 이건 process2.py 코드 구현 시 유용하게 사용했다.

 

2. 임시 데이터 가공 쿼리 기록

수집된 로그 데이터를 일 단위 요약 테이블로 변환하기 위한 가공 쿼리

INSERT INTO accessList_Day

SELECT ...

FROM processlist_log

WHERE logdate = '20100126'

...

 

3. 불필요한 데이터 정리 스크립트

DELETE FROM processlist_log

WHERE date_format(logtime,'%y%m%d') < DATE_SUB(NOW(), INTERVAL 2 DAY)

LIMIT 10;

 

4. 모니터링 대상 서버 선정 기준 쿼리

시스템이 어떤 기준으로 서버를 수집 대상으로 삼는지를 문서화함

SELECT host, port, ...

FROM MYMON.tb_serverinfo B

WHERE m_status = 1

  AND s_os NOT IN('Windows')

  AND MONITORING <> ''

  AND NOT EXISTS (

      SELECT 'a' FROM skipProcesslist A WHERE ...

  )

 

5. InnoDB 트랜잭션이 어떤 이유로 대기(Blocked) 중인지 확인하는 진단 쿼리

SELECT r.trx_id waiting_trx_id, ...

FROM information_schema.innodb_lock_waits w

JOIN information_schema.innodb_trx ...

 

6. 참조용 공식 문서 링크
http://dev.mysql.com/doc/refman/5.5/en/innodb-information-schema-transactions.html