728x90
반응형

pytesseractGoogle’s Tesseract-OCR Engine 용 래퍼입니다. (OCR : Optical Character Recognition, 광학 문자 인식)

 

이를 사용하여 이미지에서 숫자를 추출하는 과정을 정리해 보았습니다. 

 

pytesseract는 pip로 설치만 하고 끝나는 것이 아닌, 환경변수와 Tesseract-OCR의 별도 빌드 과정이 필요합니다. 

 

tesseract-ocr.github.io/tessdoc/Home.html 문서를 참고하여 빌드 후 설치합니다.

 

그리고 "고급 시스템 설정" -> "환경 변수" 에서 시스템 변수 혹은 사용자 변수에 "TESSDATA_PREFIX"라는 변수 이름으로 tesseract가 설치된 위치에 포함된 tessdata 의 전체 경로를 넣어준 후 시스템을 재시작합니다. 이는 윈도우에서는 pytesseract가 해당 시스템에 설치된 tesseract.exe를 실행하는 이때 언어 데이터를 TESSDATA_PREFIX에 설정된 경로에서 읽어오는데, 이를 위해 필요한 설정입니다.

 

추후 tessdata에는 github.com/tesseract-ocr/tessdata 에서 인식을 원하는 언어 데이터를 다운받아 삽입 후 사용할 수 있습니다. 

 

설치 과정이 끝났다는 가정하에 관련 샘플 코드를 작성해 보겠습니다. 

 

아래 샘플 코드는 화면의 특정 영역을 캡쳐하여 해당 영역에 포함된 텍스트 중 숫자만 추출하는 작업을 하는 코드입니다.

from PIL import ImageGrab # 화면의 특정 영역을 이미지로 캡쳐할 때 사용합니다.
from pytesseract import *
pytesseract.tesseract_cmd = "C:\\Program Files (x86)\\Tesseract-OCR\\tesseract.exe" # 설치한 경로를 설정하세요.


def readDigitFromScreen(_rect):
    img = ImageGrab.grab(_rect) # 화면 중 _rect 영역을 이미지로 캡쳐합니다.
    
    digit=''
    try:
        text = image_to_string(img, lang='eng')
        numlist = re.findall("\d", text) # 문자를 한 자씩 확인하며 숫자만을 뽑아옵니다. 한번에 뽑고 싶다면 "\d+" 옵션을 사용하세요.
        for num in numlist:
            digit += num # 숫자는 문자열 형태로 numlist에 저장되니 이를 digit에 묶어줍니다.
    except TesseractNotFoundError: # Tesseract-OCR를 설치하지 않았다면 에러를 발생하고 종료합니다.
        print("you need to install Tesseract-OCR on your system")
        
    return int(digit) # digit에 string 형태로 정리된 문자를 integer로 변환하여 돌려줍니다.

 

최근 툴 제작에는 Python을 공부하면서 사용하고 있는데 C++로 제작할 때 생각하면 ... -0-;;; 편하긴 하네요. 편한만큼 조심해야 할 것들이 많긴 하지만요. :)

 

 

반응형
728x90
반응형

테스트 용 툴 제작하다 재밌어 보여서 동영상만 공개해 봅니다. 

 

Amazon Polly : aws.amazon.com/ko/polly/ , docs.aws.amazon.com/ko_kr/polly/latest/dg/API_Reference.html , pypi.org/project/boto3/

Google Translate API : pypi.org/project/googletrans/

 

엑셀 문서에 한글을 등록한 후 툴에서 오픈해 번역하고 싶은 언어를 추가 후 번역을 시도하면,

Google Translate API로 텍스트로 번역을 하고, Amazon Polly로 음성 파일을 만들어 저장합니다. 

해당 셀을 선택하면 음성 파일을 들을 수 있습니다.

 

 

 

반응형
728x90
반응형

자주 사용하는 툴들을 PyCharm에 External Tools에 연동하는 절차와 사용법을 기록합니다.

 

PyQt5 : pypi.org/project/PyQt5/

Qt Designer : pypi.org/project/PySide2/

PyUIC5 : pypi.org/project/pyqt5-tools/

PyInstaller : pypi.org/project/pyinstaller/

 

아래 내용은 PyCharm 2020.2.x, Python 3.8x와 Windows 10에서 진행된 내용입니다.

 

Qt Designer

Qt Designer는 Window를 디자인하기 위한 툴 입니다. 

 

PySid2를 설치하면 C:\Users\YOURNAME\AppData\Local\Programs\Python\Python38\Lib\site-packages\PySide2 에 설치됩니다.  (YOURNAME는 사용하는 계정으로 바꿔주세요.)

 

File -> Settings -> Tools -> External Tools 에서 + 아이콘을 눌러 툴을 설정합니다. 

 

Name에 "Qt Designer" 입력

Program에 C:\Users\YOURNAME\AppData\Local\Programs\Python\Python38\Lib\site-packages\PySide2\designer.exe 입력

Arguments에 $FilePath$ 입력

Working directory에 $ProjectFileDir$ 를 입력

 

등록이 완료되면 Tools -> External Tools 메뉴에서 Qt Designer를 확인할 수 있습니다. 

 

PyUIC5 

Qt Designer로 제작된 .ui 파일을 .py 파일로 변환해주는 툴입니다.

 

File -> Settings -> Tools -> External Tools 에서 + 아이콘을 눌러 툴을 설정합니다. 

 

Name에 "Ui2Py" 입력

Program에 C:\Users\YOURNAME\AppData\Local\Programs\Python\Python38\Scripts\pyuic5.exe 입력 (YOURNAME는 사용하는 계정으로 바꿔주세요.)

Arguments에 $FileName$ -o $FileNameWithoutExtension$.py 입력

Working directory에 $ProjectFileDir$ 를 입력

UI 파일을 열고 마우스 우측 버튼을 눌러 External Tools 메뉴에서 Ui2Py를 선택하면 ui파일을 py 파일로 변환합니다.

 

PyInstaller 

프로젝트를 단독으로 실행 가능한 파일로 만들어주는 툴입니다. 

 

File -> Settings -> Tools -> External Tools 에서 + 아이콘을 눌러 툴을 설정합니다. 

 

Name에 "PyInstaller" 입력

Program에 C:\Users\YOURNAME\AppData\Local\Programs\Python\Python38\Scripts\pyinstaller.exe 입력 (YOURNAME는 사용하는 계정으로 바꿔주세요.)

Arguments에 --onefile --windowed $FilePath$ 입력 (콘솔 응용 프로그램일 경우 --windowed 옵션을 삭제하시면 됩니다.)

Working directory에 $ProjectFileDir$ 를 입력

등록이 완료되면 Tools -> External Tools 메뉴에서 PyInstaller를 선택해 실행하면 빌드가 진행됩니다. 빌드 후 프로젝트 폴더에 dist 폴더가 생성되고 그 안에 실행 가능한 파일이 생성됩니다. 

 

프로젝트에 어떤 모듈을 설치했는지에 따라 간혹 "Failed to execute script ... " 라는 에러 메세지가 뜨면서 실행되지 않는 경우가 발생할 수 있습니다. 

 

이때는 --windowed 옵션을 제거 후 터미널 화면에서 실행해보면 어떤 모듈을 찾을 수 없어 실행이 안되는지 확인이 가능합니다.

 

이 문제가 발생하는 이유는 PyCharm의 경우 빌드환경을 툴에서 프로젝트 별로 운영하기 때문인데, 이는 해당 프로젝트에 venv로 시작하는 폴더명이 있는 것을 통해 알 수 있습니다. 

 

PyCharm에서 프로젝트를 만들면 기본적인 venv 폴더를 만들어 운영하며, 프로젝트에 설치한 모든 모듈들은 해당 프로젝트에만 종속적으로 설치됩니다. 설치 위치는 해당프로젝트폴더\venv\Lib\site-packages 입니다.

 

pyinstaller는 기본 설치 환경인 C:\Users\YOURNAME\AppData\Local\Programs\Python\Python38\Lib\site-packages 에서 모듈을 찾아 프로젝트에 포함시키기 때문에 PyCharm에서 프로젝트에 종속적으로 설치된 모듈의 경우는 찾을 수 없습니다. 

 

이를 해결하기 위해서는 터미널에서 다시 한번 관련 모듈을 설치한 후 재빌드하면 문제가 해결됩니다.

 

 

반응형
728x90
반응형

PyCharm 2020.1.x 이후 버전에 Packages 설치를 위한 저장소 설정에 문제가 있는지 패키지 리스트가 보이지 않네요.

 

찾아보니 daumkakao에서 저장소를 제공해서 pip 의 저장소 설정 변경 방법을 정리해 둡니다. 

 

저장소가 http로 되어 있어 몇 가지 세팅이 필요합니다.

 

 

우선 File -> Settings -> Project:your_project_name -> Project Interpreter 에서 '+' 기호를 누른 후 패키지 리스트가 잘 나오면 그냥 설치하시면 됩니다. ^^

 

만약 패키지 리스트가 안보인다면 하단의 "Manage Repositories"를 눌러 설정된 저장소를 삭제 후 "http://ftp.daumkakao.com/pypi/simple/"를 추가해 주세요.

이제 아래와 같은 패키지 리스크를 보실 수 있습니다. 그런데 저장소가 https가 아닌 http라서 trusted-host 옵션을 별도로 설정해야 설치가 가능합니다. 

 

원하는 패키지를 선택 후 우측 하단의 "Options" 버튼을 눌러 활성화 후 --trusted-host ftp.daumkakao.com 을 입력 후 인스톨 버튼을 누르시면 원하는 패키지를 정상적으로 설치하실 수 있습니다. 

(https는 아직 지원하지 않는 듯 하네요. 불편하지만 그래도 지원해줘서 무한 감사할 따름입니다. ^^)

 

반응형
728x90
반응형

 

레거시 서비스 중 윈도우 어플리케이션에 종종 문제가 발생할 때가 있습니다.

 

이를 수정하는게 올바른 방법이겠지만 현실적인 문제로 관리자들이 일정 기간마다 재시작을 수행할 때가 있고, 가끔 다운되면 주말에도 접속해서 재가동 해야 하는 일이 발생하곤 합니다.

 

pm2와 같은 윈도우용 프로그램이 있으면 하는 생각을 해봤지만, 그건 주기적인 재실행 처리가 불가능해서 python으로 직접 만들어 보았습니다.

 

python은 자동 테스트 프로그램 개발을 위해 최근에 사용하기 시작했는데 생각보다 훨씬 재밌었습니다. C++/JS/Python을 넘나들면서 코딩하는게 꽤 귀찮긴 하지만... 

 

Github(https://github.com/blackwitch/winPM)에도 같이 올려두었으니 참고하시기 바랍니다. 앞으로 편의를 위해 일부 기능을 개선/추가할 예정에 있습니다. 예를들어 지금은 이 프로그램을 콘솔 형태로 계속 띄워두어야 합니다. 이를 개선하기 위해 서비스 프로그램으로 수정할 예정입니다.

 

필요한 기능은 아래 두 가지였습니다. 

 

- 정기적으로 재가동 할 것!

   : 어떤 서비스는 장기적으로 가동될 때 메모리가 문제가 될 때가 있습니다. 이를 해소하는 가장 간편한 방법은 유저들이 가장 적은 시간에 몰래 재시작을 하는 거였죠. 물론 서비스가 재가동 되어도 유저들에게 전혀 문제가 없는 프로젝트이거나 유저가 0명인 시간대를 잘 골라야겠죠.

 

- 갑작스럽게 다운된 경우 즉시 재가동 할 것!

  : 당연히 절대 다운되지 않을 완전한 서비스를 만들 수 있다면 좋겠지만 일반적으로 서비스는 출중한 한 명의 손에 의해서만 만들어지는 경우가 드물며, 그 한명도 천재이기 힘들고, 천재도 가끔 실수합니다. 게다가 그 천재가 자신이 만든 그 서비스를 영구히 수정/업데이트 하지 않죠. 종종 의도치 않게 다운되는 래거시 서비스들은 담당자들을 힘들게 합니다. 그래서 다운되는걸 못 막을 상황이라면 ... 즉시 재가동이 답이겠죠.

 

위 두 가지 기능을 넣고, 현재 상황을 확인할 수 있는 기능을 담기 위해 list, start, stop, restart의 명령을 처리할 수 있는 프로그램를 만들었습니다. 시스템이 다운되거나 급작스럽게 재부팅 될 경우가 있을 수 있으니 필요하다면 이 프로그램을 시작 프로그램에 등록 하는 것도 좋은 방법이라 생각되네요.

 

바로 코드로 들어가 보겠습니다.

 

 

 

import os, sys , subprocess, _thread
import win32gui, win32api, win32con, win32process
import psutil
import time
import logging
import json
import time
from datetime import datetime

# PMList에는 우리가 관리할 프로그램에 대한 정보와 현황을 담아둡니다.
# 코드에서는 관리할 프로그램들에 대한 작업을 task라는 명칭으로 사용하겠습니다.
PMList = dict()  # process infomation list

# 등록된 프로그램의 full path로 키를 만들어 PMList의 키로 활용합니다.
def getPMKey(cfg_task):
    try:
        return hash(cfg_task["path"] + "/" + cfg_task["file"])
    except Exception as e:
        print("ERROR : Incorrect \"config.json\" file! ERROR => ", e)
        sys.exit(0)


#############################################################
## crontab에 관련된 잘 정리된 코드가 있어 해당 코드를 활용합니다. 
## crontab-like code is from https://github.com/idning/pcl/blob/master/pcl/crontab.py 
class Event(object):
    def __init__(self, desc, func, args=(), kwargs={}, use_thread=False):
        """
        desc: min hour day month dow
            day: 1 - num days
            month: 1 - 12
            dow: mon = 1, sun = 7
        """
        self.desc = desc 
        self.func = func
        self.args = args
        self.kwargs = kwargs
        self.use_thread = use_thread

    #support: 
    # * 
    # 59
    # 10,20,30
    def _match(self, value, expr):
        #print 'match', value, expr
        if expr == '*':
            return True
        values = expr.split(',')
        for v in values:
            if int(v) == value:
                return True
        return False

    def matchtime(self, t):
        mins, hour, day, month, dow = self.desc.split()
        return self._match(t.minute       , mins)  and\
               self._match(t.hour         , hour)  and\
               self._match(t.day          , day)   and\
               self._match(t.month        , month) and\
               self._match(t.isoweekday() , dow)

    def check(self, t):
        if self.matchtime(t):
            if self.use_thread:
                thread.start_new_thread(self.func, self.args, self.kwargs)
            else:
                try:
                    self.func(*self.args, **self.kwargs)
                except Exception as e:
                    logging.exception(e)

class Cron(object):
    def __init__(self):
        self.events = []

    def add(self, desc, func, args=(), kwargs={}, use_thread=False):
        self.events.append(Event(desc, func, args, kwargs, use_thread))

    def run(self):
        last_run = 0
        while True:
            #wait to a new minute start
            t = time.time()
            next_minute = t - t%60 + 60
            while t < next_minute:
                sleeptime = 60 - t%60
                logging.debug('current time: %s, we will sleep %.2f seconds' %(t, sleeptime))
                time.sleep(sleeptime)
                t = time.time()

            if last_run and next_minute - last_run != 60:
                logging.warn('Cron Ignored: last_run: %s, this_time:%s' % (last_run, next_minute) )
            last_run = next_minute

            current = datetime(*datetime.now().timetuple()[:5])
            for e in self.events:
                e.check(current)
            time.sleep(0.001)
## crontab-like codes
#############################################################

#############################################################
## command thread
## 유저 입력을 받아 필요한 정보를 출력하거나 지정한 명령을 수행하기 위한 코드입니다.
def command_thread():
    while True:
        try:
            line = input('>> ')
            params = line.split()
            if len(params) == 0:
                continue
        
            cmd = params[0]
            # 도움말을 볼 수 있는 command
            if cmd == "?" or cmd =="h" or cmd =="help" : 
                print ("usage : <command> [options]")
                print ("Command list")
                print ("===============================")
                print ("command         | desc")
                print ("list            | show the process list you added")
                print ("stop [title]    | kill a process by the title in config")
                print ("start [title]   | start a process by the title in config")
                print ("restart [title] | restart a process by the title in config")
                print ("exit            | exit this program ")
            # 현재 등록된 task들의 현황을 보여줍니다.
            elif cmd == "list":
                print ( psutil.cpu_percent() , psutil.virtual_memory())
                print ("---------------------------------------------------------------------------------------------")
                print ("| %s | %s | %s | %s | %s | %s | %s |"% ("name".ljust(16)[:16],"job".ljust(12)[:12],"status".ljust(12)[:12],"restart".ljust(7)[:7],"cpu".ljust(4)[:4],"mem".ljust(8)[:8],"uptime".ljust(12)[:12]))
                print ("---------------------------------------------------------------------------------------------")
                try:
                    for key in PMList:
                        info = PMList[key]
                        print ("| %s | %s | %s | %s | %s | %s | %s |" % (info["name"].ljust(16)[:16],info["job"].ljust(12)[:12],info["status"].ljust(12)[:12],str(info["restart"]).ljust(7)[:7],str(info["cpu_usage"]).ljust(4)[:4],str(info["mem_usage"]).ljust(8)[:8],str(info["uptime"]).ljust(12)[:12]) )
                except Exception:
                    import traceback
                    print (traceback.format_exc())
            # winPM을 종료합니다. 가동중인 프로세스들은 그대로 둡니다.
            elif cmd == "exit":
                print("Press CTRL + C to exit this program.")
                sys.exit(0)
            # 멈춰있던 task를 실행합니다. 
            elif cmd == "start":
                if len(params) < 2: # start 뒤에는 config.json 파일에 등록된 task 정보 중 title 값을 입력합니다.
                    print ("Invalid paramater(s). Type ""?"" will show you manpage")
                    continue
                for key in PMList: # 입력된 task의 title 값은 PMList에 "name"으로 저장됩니다. 이를 비교해 해당 정보를 찾아 실행합니다.
                    if params[1] == PMList[key]["name"]:
                        startProcess(PMList[key])
                        break
			# 실행중인 task를 중지합니다.                        
            elif cmd == "stop":
                if len(params) < 2: # stop 뒤에는 config.json 파일에 등록된 task 정보 중 title 값을 입력합니다.
                    print ("Invalid paramater(s). Type ""?"" will show you manpage")
                    continue
                for key in PMList:
                    if params[1] == PMList[key]["name"]:
                        stopProcess(PMList[key])
                        break
			# 실행중인 task를 재시작합니다.
            elif cmd == "restart":
                if len(params) < 2:
                    print ("Invalid paramater(s). Type ""?"" will show you manpage")
                    continue
                for key in PMList:
                    if params[1] == PMList[key]["name"]:
                        stopProcess(PMList[key])
                        print("Wait for a sec")
                        time.sleep(3) # 중지에 많은 시간이 소요된다면.. 대기 시간을 늘리는 것도 좋은 방법입니다. config.json 파일 설정에까지는 넣지 않았습니다.
                        startProcess(PMList[key])
                        break
        except KeyboardInterrupt:
            print (" CTRL + C")
## command thread
#############################################################

# 실행 파일명으로 process id를 확인합니다.
def get_pid(process_name):
    try:
        processes = filter(lambda p: psutil.Process(p).name() == process_name, psutil.pids())
        for pid in processes:
            return pid
        return 0
    except Exception as e:
        print ("GET_PID NOT FOUND ", process_name, e)
        return 0

# pid로 window handle을 확인합니다.
def get_hwnd(pid):
    def callback (hwnd, hwnds):
        if win32gui.IsWindowVisible(hwnd) and win32gui.IsWindowEnabled(hwnd):
            _,found_pid=win32process.GetWindowThreadProcessId(hwnd)
            if found_pid==pid:
                hwnds.append(hwnd)
            return True
    hwnds = []
    win32gui.EnumWindows(callback, hwnds)
    return hwnds[0] if hwnds else 0

# 실행 파일명으로 window handle을 확인합니다
def get_hwnd_byName(process_name):
    pid = get_pid(process_name)
    return get_hwnd(pid) if pid else 0

# 지정된 프로세스를 중단합니다.
def stopProcess(task):
    HWnd = get_hwnd_byName(task["proc_name"])
    if HWnd > 0:
    	# 프로세스 중단 시 필요하다면 중지 버튼과 종료 버튼을 눌러서 종료하게 합니다.
        # 이미지 프로세싱을 넣을까 했지만 서비스 프로그램에 설마 이미지 버튼을 사용하지는 않겠죠?
        try:
            if task["buttons"]["STOP"]:
                btnHnd= win32gui.FindWindowEx(HWnd, 0 , "Button", task["buttons"]["STOP"])
                if btnHnd != 0:
                    win32api.SendMessage(btnHnd, win32con.BM_CLICK, 0, 0)
                else:
                    print(task["buttons"]["STOP"], " button isn't there.")
                time.sleep(5);

            if task["buttons"]["QUIT"]:
                btnHnd= win32gui.FindWindowEx(HWnd, 0 , "Button", task["buttons"]["QUIT"])
                if btnHnd != 0:
                    win32api.SendMessage(btnHnd, win32con.BM_CLICK, 0, 0)
                else:
                    print(task["buttons"]["QUIT"], " button isn't there.")
                time.sleep(5);

        except:
            time.sleep(1)

    # 종료 버튼이 지정되지 않거나 혹은 어떤 이유로 아직 종료된 상태가 아니라면 강제 종료 시킵니다.
    # 종료 시 후속 작업이 많은 경우 별도 조정해서 사용하세요.
    HWnd = get_hwnd_byName(task["proc_name"])
    if HWnd != 0:
        os.system("taskkill /f /im "+task["proc_name"])
        time.sleep(1)
    # 관리 정보들을 모두 초기화 합니다.
    task["bStop"] = True
    task["status"] = "stop"
    task["pid"] = 0
    task["hwnd"] = None
    task["mem_usage"] = 0
    task["cpu_usage"] = 0
    task["uptime"] = 0
    print (" STOP [" + task["name"] + "]")

# 지정된 프로세스를 가동합니다.
def startProcess(task):
    # execute the app
    HWnd = get_hwnd_byName(task["proc_name"])
    boolAlreadyStart = True
    # 현재 가동중인 상태가 아니면 프로세스를 가동합니다.
    if HWnd == 0:
        boolAlreadyStart = False
        os.chdir(os.path.realpath(task["app_path"]))
        myProcess = subprocess.Popen(task["app"])
        time.sleep(5)
        HWnd = get_hwnd_byName(task["proc_name"])

    if HWnd == 0:
        print(task["name"], " is failed to execute.")
    else:
    	# 프로세스 가동 후 시작 버튼을 누를 필요가 있을 경우 START 버튼 라벨을 지정해 두세요.
        if boolAlreadyStart == False:
            try:
                if task["buttons"]["START"]:
                    btnHnd= win32gui.FindWindowEx(HWnd, 0 , "Button", task["buttons"]["START"])
                    if btnHnd != 0:
                        win32api.SendMessage(btnHnd, win32con.BM_CLICK, 0, 0)
                    else:
                        print(task["buttons"]["START"], " button isn't there.")
            except:
                time.sleep(1)
            task["bStop"] = False
            task["status"] = "running"
            task["restart"] = int(task["restart"]) + 1
            print (" START [" + task["name"] + "]")
        else:
            print("[" + task["name"],"] is running already")

# main 함수
if __name__ == "__main__":
    try:
    	# 분 단위로 task의 상태를 업데이트 합니다.
        def task_update():
            for key in PMList:

                PMList[key]["pid"] = get_pid(PMList[key]["proc_name"])
                if PMList[key]["hwnd"] == None and PMList[key]["pid"] != 0:
                    PMList[key]["hwnd"] = get_hwnd(PMList[key]["pid"])
                else:
                    PMList[key]["hwnd"] = None

				# 사용자가 stop 으로 프로세스를 중단한 상태("bStop"가 True일 경우)라면 재가동 시키지 않습니다. 
                if PMList[key]["pid"]> 0 and PMList[key]["bStop"] == False:
                    PMList[key]["status"] = psutil.Process(PMList[key]["pid"]).status()
                    PMList[key]["cpu_usage"] = psutil.Process(PMList[key]["pid"]).cpu_percent()
                    try:
                        PMList[key]["mem_usage"] = psutil.Process(PMList[key]["pid"]).memory_full_info().uss
                    except:
                        PMList[key]["mem_usage"] = 0
                    try:
                        pidcreated = datetime.fromtimestamp(psutil.Process(PMList[key]["pid"]).create_time())
                        diff = datetime.now() - pidcreated
                        PMList[key]["uptime"] =diff
                    except Exception:
                        import traceback
                        print (traceback.format_exc())
                        PMList[key]["uptime"] = -1
                else:
                    PMList[key]["status"] = "stop"
                    PMList[key]["hwnd"] =  None
                    PMList[key]["mem_usage"] = 0
                    PMList[key]["cpu_usage"] = 0
                    PMList[key]["uptime"] = 0
		
        # config.json 에 정의된 task의 job을 처리합니다.
        def runTask(task, schedule, cron):
            myProcess = None
        
        	# 프로세스가 다운된 상태라면 재가동 합니다.
            def sustenance_task():
                if task["bStop"] == True:
                    return

                pid = get_pid(task["proc_name"])
                if pid == 0:
                    startProcess(task)

			# 지정된 시간에 프로세스를 재가동합니다.
            def restart_task():
                if task["bStop"] == True:
                    return
                stopProcess(task)
                startProcess(task)

            if task["job"] == "sustenance":
                cron.add(schedule, sustenance_task)
            elif task["job"] == "restart":
                cron.add(schedule, restart_task)
            else:
                print(" Incorrect job type = ", task["job"])

        # loading config file
        # task에 대한 정의를 config.json이라는 파일에 별도 명시해야 합니다.
        try:
            with open('config.json') as config_file:
                config = json.load( config_file )
                if len(config['task']) <=0:
                    print(" There is no Task.")
        except Exception as e:
            print("ERROR : You need \"config.json\" file!", e)
            sys.exit(0)

		# task에 명시된 cron 정보로 스케쥴링에 등록합니다.
        cron = Cron()
        for cfg_task in config['task']:
            key = getPMKey(cfg_task)
            app_path = cfg_task["path"]
            app =cfg_task["path"] + "/" + cfg_task["file"]
            PMList[key] = {"key" : key, "app_path" : app_path, "app":app, "proc_name":cfg_task["file"],"buttons":cfg_task["buttons"], "job":cfg_task["job"], "hwnd": None, "pid": None, "name": cfg_task["title"],"bStop": False, "status": "stop", "cpu_usage": -1, "mem_usage": -1, "uptime": -1, "restart": 0}
            runTask(PMList[key],cfg_task["schedule"]["cron"], cron)
        # add task for updating task status
        task_update()
        cron.add("* * * * *", task_update)
        # console interface
        cmdThread = _thread.start_new_thread(command_thread, ())
        cron.run()
    except KeyboardInterrupt:
        print(" BYE!! ")
        sys.exit(0)

 

config.json 파일은 아래 예제를 참고하세요.

{ 
	# task는 필요한 만큼 정의 할 수 있습니다.
	"task": [ 
    	{ 
		"name": "샘플 A 프로세스",  # task에 대한 설명입니다. 작업에 영향을 미치지 않습니다.
            "path": "D:/sampleA", 		# 실행할 파일이 있는 path를 지정합니다.
            "file": "hello.exe", 		# 실행할 파일명을 지정합니다.
            "title": "hello_1", 		# task 관리 시 사용하는 명칭입니다. 16자를 넘지 않게 지정하세요.
            "schedule": { 				# cron은 체크할 주기를 지정하고 finish_datetime은 task의 종료일정을 지정하세요. (현재는 사용하지 않습니다.)
            	"cron": "* * * * *", 
                "finish_datetime": "2999-12-31" 
            }, 
            "buttons": { 
            	"START": "START" 		# 시작 시 어떤 버튼을 눌러야 한다면 해당 버튼의 라벨을 지정하세요.
            }, 
            "job": "sustenance" 		# 프로세스가 다운된 경우 재시작 합니다.
        }, 
        { 
		"name": "샘플 B 프로세스", 
            "path": "E:/sample/sampleB", 
            "file": "hello2.exe", 
            "title": "hello_2", 
            "schedule": { 
            	"cron": "0 0 * * *", 
                "finish_datetime": "2999-12-31" 
            }, 
            "buttons": { 
            	"QUIT": "EXIT" 			# 종료 시 버튼을 눌러야 한다면 해당 버튼의 라벨을 지정하세요. 시작 시 눌러야할 버튼이 필요하다면 START를 같이 명시하세요.
            }, 
            "job": "restart" 			# 지정된 시간에 프로세스를 재시작 합니다.
        }
    ] 
}

 

필요한 서버에 python을 직접 설치하지 마시고 pyinstaller로 exe 파일로 만들어 사용하시길 추천 드립니다.

 

사실 자원이 충분하다면 문제가 되는 프로젝트를 수정하는게 좋겠지만 그렇지 않다면 이런 코드도 대안이 될거라 생각됩니다.

 

윈도우 서비스 형태로 개선된 버전이 필요하시면 github에 알람을 설정해 두세요.

 

이상입니다. 모두 즐거운 하루 되시기 바랍니다.

 

반응형
  1. 프링이 2020.08.04 13:21

    좋은 정보 감사합니다~~

728x90
반응형

CentOS에 Elasticsearch를 설치해 보았다. 정리할 생각이 없었는데, 하다보니 중간에 막히는 것들이 처리하면서 정리 한번 해둬야겠단 생각이 들었다. 


일단 처음 설치할 때 주의할 점은 Elasticsearch는 root 계정으로 실행할 수 없다. (실행 가능하게 하는 옵션이 있던데, 최신 버전에서는 안되는 듯. 그리고 보안의 측면에서도 당연히 좋지 않아 추천하지 않는다.) 그러므로 전용 계정을 설정해서 설치, 실행하자.


설치 환경 및 각종 버전은 아래와 같다. 

CentOS  7.5.x

Elasticsearch 6.3.0 (https://www.elastic.co/downloads/elasticsearch)

plugin, Elastic-HQ (https://github.com/ElasticHQ/elasticsearch-HQ)



wget으로 다운로드 받아 설치한다. 별도 인스톨 과정은 없다. bin/elasticsearch 로 실행 가능.


wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.0.tar.gz

tar xvzf elasticsearch-6.3.0.tar.gz


실행전에 한 가지 수정하고 진행하자.

config/elasticsearch.yml에서 http.host를 자신이 사용하고자 하는 ip로 수정한다. 예를들어 내 ip가 172.150.x.x 이며 config를 수정하지 않고 실행할 경우 로컬에서 localhost로 접속은 되지만, 다른 PC에서 172.150.x.x로 접속되지는 않는다.


처음이니 데몬 옵션 주지말고 elasticsearch로 실행해보라. -d로 실행하며 혹시 모를 에러가 보이지 않으니 주의가 필요하다. 잘 실행되었는지 일단 확인해보자.


root 권한으로 실행되지 않으니 주의!


cluster 설정을 한 후 root로 실행하면 root권한으로 실행되지 않는다는 메세지와 함께, logs 폴더 아래 몇몇 로그 파일이 생긴다. 이를 지우지 않고 다른 계정으로 실행하면 log 파일 접근 실패 에러가 뜨면서 실행되지 않는다. 해당 로그 파일들을 삭제 후 다시 시작할 것!


curl 172.150.x.x:9200 ( 혹은 config를 수정하지 않았다면 localhost:9200)  

아래와 같은 간단한 health 정보가 출력되면 정상이다. 





혹시 그래도 접속이 안된다면 iptables에 9200번 ACCEPT 처리를 해주자.


 firewall-cmd --zone=public --permanent --add-port=9200/tcp


이제 플러그인 Elastic-HQ를 설치해보자. 이를 설치하기 위해 python 3.4 이상의 버전이 필요하다. CentOS 7.5 버전에는 기본적으로 2.x 버전이 설치되어 있다. 3.4 이상의 버전을 추가로 설치한 후 아래로 진행하자. 


( http://docs.elastichq.org/installation.html 참고)


git clone https://github.com/ElasticHQ/elasticsearch-HQ.git


설치에 필요한 추가 라이브러리를 설치한다. elasticsearch-HQ 폴더로 가서 아래를 실행.

(pip가 없다면 "yum install python-pip"로 설치.)


pip install -r requirements.txt

아래와 같은 에러 발생.

python-socketio 2.0.0 has requirement python-engineio>=2.2.0, but you'll have python-engineio 2.0.2 which is incompatible.

바로 업그레이드 시킴
pip3.6 install --upgrade python-engineio
(pip도 link설정 수정 안했다면 pip버전 지정해서 설정해야 함.)

이제 아래와 같이 실행 가능해졌다.

python3 application.py &
위에서 따로 언급 안했지만 python 3.x 버전을 추가로 설치 후 python 링크를 python3.6으로 대체했다면 시스템에 여러 문제가 발생할 수 있다. 

리눅스 시스템, 특히 CentOS는 python2를 많이 사용하고 있기 때문이다. 그래서 가능하면 python3.6.x 버전을 python3으로만 단축해서 사용해야 한다. 

HQ의 접속 포트는 5000번이다. iptables에 추가 후 브라우져로 접속해보면 아래와 같은 화면을 볼 수 있다.




주소를 지정하면 다음 화면으로 넘어간다.

HQ는 백그라운드로 실행했으므로 종료 시킬 때는 kill로... 


공식 문서에 python manage.py runserver 이걸로 실행하라는 이야기 있는데 에러가 나고, 해결책은 관심없어서 안 찾아봤음. 게다가 root 로 실행해야 함. -0-a 해결 방법 아시는 분 계시면 댓글 부탁드립니다. (공손..)


이까지.. 


다음은 Kibana 설치와 샘플 올려서 그래프 보는 방법에 문제가 있으면 이어서 계속, 별 문제 없으면 스킵.  :)


큰 건 아니고 작은 허들이 있어서 기록해 둠.

다운로드는 아래와 같이. (kibana는 elasticsearch와 동일한 버전을 다운받아 설정해야 한다. 안그럼 시작 시 경고 등장함.)

wget https://artifacts.elastic.co/downloads/kibana/kibana-6.3.0-linux-x86_64.tar.gz
압축을 풀고 config 일부를 수정하자.

config/kibana.yml에서  아래 사항을 설정한다. 


server.host: "yourkibanaip"

elasticsearch.url: "http://yourip:9200"

xpack.security.enabled: false   <<- 이에 대한 경고가 뜬다. 일단 테스트 과정이니 넘어감.


elasticsearch.url을 현재까지는 멀티로 지정할 수 없다. 커뮤니티에서도 다들 stack 솔루션 중 kibana만 안된다고 성토 중. 
( 관련 링크 : https://github.com/elastic/kibana/issues/214)

host 주소를 정확히 입력해두고, elasticsearch 주소 설정, 시작할 때 관리자 비번 안물어보게 설정.


이제 실행!!


bin/kibana


그럼 아래와 같은 화면을 볼 수 있다.




이제 logstash와 실제 데이터를 연동해서 결과를 만들어 볼 차례.


그 과정에 또 허들이 생기면 이젠 다른 글에 이어 정리할 예정. 바이~~~!


[아래 문서를 같이 봐야 함.]


About max file descriptors



반응형

+ Recent posts