[JAVA] 실시간 Python 파일 실행하여 진행도 확인하기 (1) - ProcessBuilder / zt-exec

jinnnii·2023년 7월 8일
2

BackEnd

목록 보기
1/3

목적

회사에서 데이터를 자동으로 수집하고 처리하여 모니터링할 수 있는 페이지를 구현해야 하는 작업이 있었다. 이를 위해 Spring 프로젝트 내에서 Jython을 활용하려고 했으나, Python의 라이브러리가 더 다양하고 익숙하다고 판단되어 프로젝트 내에서 Python을 실행하는 방향으로 결정되었다.

프로젝트를 진행하면서 발생된 이슈와 과정을 포스팅하고 정리해본다.

파일 실행을 위한 ProcessBuilder 사용하기

ProcessBuilder는 자바에서 외부 프로그램을 실행하고 관리하기 위한 도구로, 해당 클래스를 사용하여 외부 프로그램의 실행, 인자 전달, 실행 결과 가져오기, 입출력 스트림 관리, 오류 처리 등을 편리하게 수행할 수 있다.

1. ProcessBuilder 사용법

1) 기본 구조

ProcessBuilder processBuilder = new ProcessBuilder("python", "test.py", "arg1","agr2");
Process process = processBuilder.start();
  • 외부 프로그램명을 "python" 으로 설정하면, 환경변수로 설정된 python 경로를 참조한다. 제대로된 경로를 읽지 못하는 경우 직접 경로를 설정해 주어도 된다.
ProcessBuilder("C:/python/python311/bin..", (생략))

2) 실행 결과 가져오기

InputStream inputStream = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

String line;
while ((line = reader.readLine()) != null) {
	// 실행 결과 처리
    System.out.println(line);
}
  • 외부 프로그램의 실행 중 InputStream을 사용해서 출력을 읽어온다.

3) 기타 함수 정리

// 1. 오류 메시지 출력
InputStream errorStream = process.getErrorStream();
BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream));

String errorLine;
while ((errorLine = errorReader.readLine()) != null) {
    // 오류 처리
}

// 2. 프로세스 종료 대기
int exitCode = process.waitFor();
System.out.println("종료 코드: " + exitCode);

// 3. 프로세스 강제 종료
process.destroy();

// 4. 종료 코드 확인
int exitCode = process.exitValue();
System.out.println("외부 프로그램 종료 코드: " + exitCode);

일반적으로 발생하는 종료코드는 다음과 같다.

  • 0 : 정상적으로 실행
  • 1 : 광범위한 에러
  • 127: 명령어의 경로($PATH) 문제 혹은 명령어 오타

4) python : 인자 받아 사용하기

  • 위의 코드에서 python 파일에 문자열 arg1agr2 를 전달하였다. 실제 파이썬 파일에서는 전달 받은 데이터를 어떻게 읽어오는지 보자.
  • 아래는 Java로 부터 인자를 받아서 대문자로 반환하여 출력하는 예제 코드이다.
import sys

# 데이터 수집 및 처리 함수 예시
def toUpper(data):
    # 데이터 처리 작업 수행
    upstr = data.upper()
    return upstr

# 데이터 입력 받기
data = sys.argv[1:]

print(data)

# 데이터 처리 함수 호출
result = [toUpper(upstr) for upstr in data]

# 결과 출력
print(result)

# result
#['arg1', 'arg2'] 입력
#['ARG1', 'ARG2'] 출력
  • sys.argv 를 통해서 인자를 전달 받았다. 인덱스 0은 파일 경로가 된다.

5) Python: 실시간 프로세스 출력

from tqdm import tqdm
import time

for i in tqdm(range(10)):
    print(i)
    time.sleep(1)

실행한 파일을 한 줄 씩 읽어 실시간으로 출력되도록 하기위해
간단한 반복문 예제를 테스트해보도록 한다. tqdm 라이브러리까지 준비했다.

하지만 해당 ProcessBuilder는 실행되는 동안의 출력 값이 실시간으로 찍히는 것이 아니고, 실행이 끝나고 나서야 한꺼번에 로그에 찍혔다.(ㅜㅜ)

결론적으로는 stack overflow 에서 해답을 찾았다.
python 파일의 print 함수에 flush를 True로 설정하면 된다.

flush : 버퍼에 저장된 데이터를 강제로 출력하는 동작

print(i, flush=True)

이후 ProcessBuilder를 대체할 수 있는 다른 방법은 없는지 더 찾아보기로 했다.



2. zt-exec - ProcessExecutor 라이브러리 사용법

ProcessBuilder와 유사하나, 비동기 실행, 스트림 리다이렉션, 타임아웃 설정, 중단 등의 기능을 추가로 제공하여 외부 프로그램과의 상호작용을 더 효율적으로 관리할 수 있는 라이브러리

1) 라이브러리 추가

<dependency>
 	<groupId>org.zeroturnaround</groupId>
    <artifactId>zt-exec</artifactId>
    <version>1.12</version>
</dependency>
  • xt-exe는 별도의 외부 라이브러리로서 추가적인 의존성을 추가해야 한다.

2) 실행 결과 가져오기

new ProcessExecutor().command("python", "test.py")
    .redirectOutput(new LogOutputStream() {
      @Override
      protected void processLine(String line) {
        System.out.println(line);
      }
    })
    .execute();
  • 위의 코드와 동일하게 프로세스가 진행하는 동안에 출력을 로그로 찍게하였다.
  • 다른 점이라면, python 코드의 tqdm 출력 까지 전부다 찍히는 점이었다.
    기존 ProcessBuilder 와 비교하여 표준 출력 스트림을 읽는 방식이 다른 것 같다.

3) 기타 함수 정리

ProcessExecutor 에서 다양한 함수가 제공되는 것 같아, 추후에 사용할 수 있을만한 재밌는 몇 가지 예제도 한번 테스트해보고 싶다.

자세한 내용과 예제는 아래 깃허브를 통해서 확인할 수 있다.
https://github.com/zeroturnaround/zt-exec

마무리

결과적으로는 zt-exec에서 제공하는 ProcessExecutor를 사용하기로 하였다. 또한 tqdm 출력 부분을 활용한다면 추가적인 코드 없이 뷰에서 ProgressBar 형태로 제공해도 될 것 같다.

다음 포스팅에서는 WebSocket을 사용하여 뷰에서 파일의 진행 상황을 실시간으로 확인할 수 있는 방법에 대해 다루어 보겠다.

profile
주니어 개발자 9개월

0개의 댓글