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는 아직 지원하지 않는 듯 하네요. 불편하지만 그래도 지원해줘서 무한 감사할 따름입니다. ^^)

 

'개발 이야기 > Python' 카테고리의 다른 글

PyCharm Repositories 변경  (0) 2020.09.14

타 팀에서 서비스 중인 DB 중 트랜잭션 로그 백업 처리 스케쥴을 등록하지 않아 디스크 용량이 0이 되버려서 이러지도 저러지도 못하는 경우가 있어 처리해준게 벌써 두 번째. 

 

RDBMS에서 기본 복구 모델 설정은 보통 full로 되어 있기 때문에 Management studio에서 축소 처리를 해도 백업 전에는 줄어들지 않습니다. 

 

DB 관리를 위해 트랜잭션 파일 옵션에 대해서는 두 가지를 신경써야 합니다. 

 

1. 사고 발생 시 복구가 필요한 DB 인가.. 

 

중요한 정보가 기록되고 사고 발생 시 분 단위로 정보를 복구해야 하는지를 생각해서 꼭 필요한 경우 full로 그렇지 않고 하루 혹은 주 단위로 전체 백업을 한 파일로 복구 하거나 혹은 분실되도 상관없는 DB는 simple로 설정하는게 좋습니다. 

 

2. 스케쥴러에 정기적으로 백업하는 옵션 설정. 전체 백업 주기에 맞춰 자동으로 삭제.

 

트랜잭션 로그는 정기적으로 백업을 해서 파일 크기를 일정하게 유지하는게 좋습니다. 그리고 전체 백업 파일이 생성되는 시점 이전의 트랜젝션 로그들은 존재 의미가 없습니다. 

 

예를들어 매주 월요일 0시에 전체 백업을 한다면 그 이전에 생성된 트랜젝션 로그 파일들은 자동 삭제되도록 설정하는게 좋습니다.

 

만약 최신 데이터 뿐만 아니라 1년전 정보를 특정 시간 간격 단위로 복구해야 한다면 그 기간안에 백업된 파일들은 유지해야겠죠.

 

 

그리고 간혹 트랜잭션 로그가 별 의미없는 DATABASE를 만들어 사용하는 경우가 있습니다. 

 

테스트 DB용, 로그 DB 등 ... 

 

테스트 DB의 경우 일정 기간 사용 후 삭제한다면 모를까.. 지속적으로 사용할 경우 트랜잭션 로그 설정을 잊지 말아야 합니다. 

 

로그  DB를 RDBMS에 기록한다는게 아이러니 하지만... 써야만 하는 상황이라면... 복구 옵션을 simple로 두고 문제 발생 시 언제든 삭제되도록 하는게 좋습니다. 

 

 

[복구 모델 변경 쿼리]

USE myDBName;
ALTER DATABASE myDBName SET RECOVERY FULL;   -- 쿼리 하나하나를 확인하며 복구가 필요한 경우
ALTER DATABASE myDBName SET RECOVERY SIMPLE;

[로그 파일 삭제]

DBCC SHRINKFILE (myDBName_log, TRUNCATEONLY)

파일 축소 시 DB 명은 select * from sys.database_files 로 타겟 DB명을 확인하세요. 일반적으로 트랜잭션 로그 파일은 DB명 뒤에 _log가 붙어 있습니다.

 

 

'개발 이야기 > DATABASE' 카테고리의 다른 글

MSSQL RECOVERY 옵션  (0) 2020.09.09
MSSQL 버전 별 암호화 지원 정리  (0) 2020.01.30
MSSQL Linked Server 설정 방법  (0) 2020.01.27
MariaDB, Galera Cluster, MaxScale 전체 정리  (0) 2019.07.04
SELinux for Galera cluster  (0) 2019.07.03
mariadb 시작 오류  (0) 2019.03.18

https://aws.amazon.com/ko/blogs/korea/amazon-ebs-update-new-elastic-volumes-change-everything/

 

Amazon EBS 업데이트 – 언제나 자유롭게 볼륨 유형 및 크기 변경 가능 | Amazon Web Services

AWS 고객은 서비스를 시작한 후, 사용자 트래픽 및 데이터 사이즈가 증가함에 따라 기존 볼륨을 수정하여 용량을 추가하거나 I/O 성능 특성을 변경해야 할 경우가 생깁니다. 이를 변경하기 위해, 2

aws.amazon.com

https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/recognize-expanded-volume-linux.html

 

볼륨 크기 조정 후 Linux 파일 시스템 확장 - Amazon Elastic Compute Cloud

볼륨 크기 조정 후 Linux 파일 시스템 확장 EBS 볼륨 크기를 늘리고 난 후에는 파일 시스템 관련 명령을 사용하여 파일 시스템의 크기를 늘려야 합니다. 볼륨이 optimizing 상태가 되자마자 파일 시스�

docs.aws.amazon.com

2017년 발표된 신규 EBS가 출시되면서 EC2에서 EBS를 사용할 경우 저장 공간에 대한 탄력적인 운영이 가능해졌습니다. 

 

예를들어 용량이 부족할 때는 일정 비율 이상으로 자동 증가하는 방법들을 선택할 수 있게 되었죠.

 

최근 수동으로 용량을 증가할 일이 있어서 관련 내용을 정리해봤습니다. 

 

만약 해당 시스템이 현재 서비스 중인 시스템이라도 중단 없이 처리가 가능합니다. 하지만, EBS에 손실될 경우 문제가 될만한 중요한 정보가 있다면 스냅샷을 꼭 만든 후 진행하시기 바랍니다. 

 

스냅샷을 생성해두면 문제가 발생 시 기존 EBS를 해제하고 스냅샷으로 새로운 볼륨을 생성 후 기존 인스턴스에 연결할 수 있습니다. 

 

우선 AWS Console에서 EC2 >> EBS >> 볼륨을 선택합니다. 

그리고 수정이 필요한 볼륨을 선택한 후 "볼륨 수정"을 선택합니다.

그리고 필요한 볼륨으로 크기를 조정합니다.

이제 시스템에 접속해서 lsblk 명령으로 인스턴스에 연결된 블록 디아비스 정보를 확인합니다. 

 

현재 8G를 사용중이며 총 용량이 16G인 것을 확인할 수 있습니다. 

사용하지 않고 있는 8G를 xvda1에 추가해 보겠습니다. 

growpart 명령으로 파티션을 확장합니다. 디바이스 이름과 파티션 번호 사이에는 띄어쓰기가 있으니 유의하세요.

df 로 파일 시스템을 확인해보면 아직 이전 상태인 것을 확인 할 수 있습니다. 

xfs_growfs 명령으로 파일 시스템을 확장합니다. 

이후 df로 다시 확인하면 xvda1 의 용량이 8G 에서 16G로 확정된 것을 확인 할 수 있습니다.

 

확장된 volume은 다시 축소할 수 없으니 꼭 필요한 경우 진행하시고, 확정 전에 꼭 스냅샷을 만들어 두시는걸 추천드립니다.

 

복잡하진 않지만 찾아보지 않으면 알 수 없는 내용이라 간단히 정리해 보았습니다. 

 

조금이라도 도움이 되셨길 바랍니다. :)

일반적으로 시스템 부팅 후 자동으로 시작되도록 서비스를 등록할 때 systemctl 혹은 init.d 스크립트 등록하여 사용합니다. 

 

systemctl 혹은 init.d 스크립트를 등록하는 방법이 쉽지많은 않으며, 혹시 해당 서비스에 문제가 발생할 경우 서비스가 다운되면 다시 시작되지 않는 문제도 있습니다.

 

이를 해결하기 위해 upstart 같은 프로그램을 사용하여 간단히 해결할 수 있습니다. 하지만 이마저도 각 서비스 별로 별도 등록, 관리해야 합니다. 

 

만약 당신이 pm2를 사용하여 Node.js로 제작된 서비스를 관리하고 있다면 위의 모든 문제를 손쉽게 해결할 수 있습니다. 

 

pm2에 서비스를 등록, 중단, 삭제하는 방법은 아래와 같이 간단합니다. 

 

pm2 start your_app.js  >> 서비스 등록 및 시작하기
pm2 stop your_app      >> 서비스 중단하기
pm2 restart your_app.js>> 서비스 재시작하기
pm2 del your_app       >> 등록된 서비스 정보를 삭제하기

부트 스크립트에 추가하기 전에 당신의 서비스를 pm2에 모두 등록했다고 가정하고 진행하겠습니다. 

 

부트 스크립트와 관련해서 startup과 unstartup 명령만 알고 있으면 됩니다. 

 

startup은 해당 시스템의 init system을 찾아 pm2의 초기 실행 스크립트를 등록하고, 필요한 환경 변수 설정을 위한 값을 출력해 줍니다. 실제 환경 변수 등록과 부팅 스크립트에 등록을 위해서는 출력된 내용을 복사하여 실행해야 하니 해당 과정을 잊지 마세요. 

 

$ pm2 startup
[PM2] Init System found: systemd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/home/user/.nvm/versions/node/v14.7.0/bin /home/user/.nvm/versions/node/v14.7.0/lib/node_modules/pm2/bin/pm2 startup systemd -u user --hp /home/user
# 위 sudo env .. 라인 전체를 복사해서 실행하면 필요한 설정이 모두 적용됩니다.

끝!!!

>> EPEL(Extra Packages for Enterprise Linux) 저장소 활성화하기

https://aws.amazon.com/ko/premiumsupport/knowledge-center/ec2-enable-epel/

>> 원격접속을 위한 키 발급 및 설정

EC2는 기본적으로 생성 시 발급한 공개키를 사용하여 원격접속 할 수 있도록 설정되어 있습니다. EC2에 키 페어를 설정하는 방법은 아래와 같습니다.

 

EC2 생성 전에 키 페어로 가서 ...

"키 페어 생성" 버튼을 눌러 ppk를 생성합니다. 이때 비번 설정은 없으니 참고하세요. 

 

EC2 기본 설정을 완료 후 시작을 누르면 키 페어 설정 화면이 나옵니다. 여기서 새로 생성할 수도 있으며 생성된 키 페어를 설정할 수 있습니다. 

 

키 페어가 변경된 경우에 대해서는 이 링크를 참고하세요. 

 

이제 putty에서 Auth에 다운받은 ppk 파일을 설정합니다. 

그리고 ssh 연결 설정에서 host name에 public dns 값을 입력, 사용자에 ec2-user를 입력 후 연결할 수 있습니다. 

 

파일 이동을 쉽게 하기 위해 WinSCP를 사용하는 경우 아래 이미지를 참고해서 ppk 파일을 설정하면 됩니다. 

 

보안 그룹에서 접속할 instance의  inbound 설정에 ssh 연결을 위해 22 번 포트를 가능한 제한되고 안전한 PC에서만 접속할 수 있도록 접속 가능한 IP를 제한해야 합니다. 

 

예를들어 자신의 회사에서 사용하는 public ip가 1.1.1.0 ~ 1.1.1.255까지 사용하며 자신의 ip가 1.1.1.11이라고 가정해 봅시다. 

 

인바운드 규칙에서 소스에 값을 적용할 때는 CIDR 표기법으로 기록해야 합니다. 

 

만약 회사원 일부가 접속을 할 경우 1.1.1.0/24 로... 자신만 접속한다면 1.1.1.11/32로 설정하면 됩니다. 

 

혹시 원격접속을 "브라우저 기반 ssh 연결"하려면 추가 설정이 필요합니다. 

 

"브라우저 기반 ssh 연결"은 "웹 브라우저 -> EC2 Instance Connect -> EC2 Instance"의 과정을 거쳐 연결됩니다. 이때 EC2 Instance Connect은 각 지역별로 별도의 IP 대역이 할당되어 있습니다.  예를들면 서울 리전(ap-northeast-2)의 경우 13.209.1.56/29를 ssh의 포트에 같이 포함하여 적용되어 있어야 합니다. 이에 대한 상세한 내용은 https://ip-ranges.amazonaws.com/ip-ranges.json 파일을 참고하시면 됩니다.

 

>> VPC를 생성하면..

라우팅 테이블 , 네트워크 ACL, 보안 그룹이 자동으로 같이 생성됩니다.

>> 실행중인 EC2로 이미지 생성 시

실행중인 인스턴스로 이미지 생성을 하게 되면 인스턴스가 일시적으로 중단된 듯 한 상황이 발생할 수 있습니다. 주의하세요.

>> EC2에서 다른 EC2로 원격 접속하기

해당 EC2에 설정된 키 페어를 다운받고, ppk일 경우 pem 파일로 변경(https://aws.amazon.com/ko/premiumsupport/knowledge-center/convert-pem-file-into-ppk/)합니다.

$ sudo puttygen ppkkey.ppk -O private-openssh -o pemkey.pem

puttygen이 설치되지 않은 경우 putty(혹은 putty-tools) 패키지를 설치하면 됩니다. EC2의 경우 epel을 활성화되지 않을 경우 패키지를 찾을 수 없습니다. 

sudo ssh -i your-key.pem ec2-user@ec2-ip

>> node 설치 방법

https://docs.aws.amazon.com/ko_kr/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html

 

# download
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash

# nvm 활성화
. ~/.nvm/nvm.sh

# nodejs 설치
nvm install node

#pm2 설치
npm install pm2 

 

 

Redis와 같은 VPC 안에 EC2를 생성했다고 가정합니다. 다른 VPC에 있을 경우 VPC Peering 설정이 필요하며 이 내용은 곧 다시 업로드할 예정입니다. 

 

일단 EC2에 redis-cli 설치를 위해 gcc를 설치합니다. redis를 별도 설치해되 되지만 ec2 에서는 redis-cli만 사용할 예정이니 redis 자체 설치를 하는 방법은 사용하지 않겠습니다.

 

 

sudo yum install -y gcc 

 

redis-stable 버전을 다운받아 빌드를 해야 하니 임시 폴더 혹은 프로그램 빌드를 위한 별도 폴더를 만들어 작업하실것을 추천드립니다.

 

wget http://download.redis.io/redis-stable.tar.gz && tar xvzf redis-stable.tar.gz && cd redis-stable && make

 

redis 자체를 쓰지는 않고 redis-cli만 사용합니다. redis-cli를 사용하기 쉽게 /usr/bin에 복사합니다. 

sudo cp src/redis-cli /usr/bin 

 

redis-cli가 잘 실행된다면 redis에 원격접속을 해봅니다.

 

redis-cli -h redis-endpoint-address

redis의 주소는 기본 엔드포인트나 리더 엔드 포인트나 상관없이 잘 연결됩니다. 

 

만약 접속이 안된다면 보안 그룹에 설정된 인바운드 설정에 port가 잘 열려 있는지 확인해야 합니다. 

 

VPC 의 보안 -> 보안 그룹에서 redis에 설정된 보안 그룹을 찾아 inbound 규칙에서 TCP 6379 포트 혹은 별도 설정한 redis 포트가 규칙에 적용되어 있는지 확인해 보세요.

 

아래와 같은 설정으로 규칙이 적용되어 있어야 합니다. 

 

 

이상입니다.

 

다음은 VPC Peering 설정 방법과 EC2와 Redis가 서로 다른 VPC, 다른 리전에 있을 경우 연동하는 방법을 정리해 보겠습니다.

이 내용은 Dev Weeks 중 https://www.youtube.com/watch?v=4kVffWfmJ60&list=PL412Ym60h6utrWNnHpZYSlG_pwRafryMO&index=28 의 내용을 보면서 인상적이었던 몇 가지만 추려봤습니다.

 

상세한 내용은 해당 동영상을 보는게 더 좋습니다. 강추입니다!! 

 

 

* Profile Analyzer : 인상적이었던건 수정 전 상황을 불러와서 수정 후 상황과 비교도 가능한 내용이었습니다. 

 

* Memory Profiler


* iOS는 ogg 지원 안함. MP3 사용 권장.

 

* 오디오 리소스의 경우 메모리에 압축을 풀어서 로드하도록 지정할 수 있지만 메모리 사용량이 증가할 수 있으므로 주의가 필요함. 



* 같은 리소스 파일을 다른 폴더에 쓰면 메모리에 따로 올라감. 
 
* Model 의 Read/Write Enabled 는 옵션을 꺼두는게 좋음. API를 통해 Mesh에 접근 가능한가를 결정. 켜면 GPU와 CPU에 중복되어 업로드됨. API로 Mesh 건드릴거 아니면 옵션 꺼둘 것. 최신 버전에서는 Default가 Disable임.


* fbx 파일... 애니메이션 파일을 위한 fbx에서 다른 데이터도 읽어서 업로드 함. 이거 하려면 코드로 막아야 함. 

using UnityEditor;
public class ResourceModifierTest : AssetPostprocessor {
	void OnPreprocessModel(){
		var importer = (assetImporter as ModelImporter);
		importer.isReadable = false;
		importer.optimizeMesh = true;
		if( assetPath.Contains("@")){
			importer.importMaterials = false;
		}
	}
}

* 텍스쳐 용량 이야기도 있었는데... 이건 ... 패스!!

 

이 정도네요. 영상 꼭 확인해 보시기 바랍니다.

 

Unity Dev Weeks에 유용한 영상들이 많으니 꼭 참고해 보시기 바랍니다. :)

 

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

 

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

 

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

    좋은 정보 감사합니다~~

scp(secure copy) 사용법을 정리해 보겠습니다.

 

원격지의 서버들은 모두 공인 IP를 가지지 않는 경우가 많고, 보안 문제로 접속하는 터미널 외에는 직접 연결이 안되는 경우가 대부분입니다. 

 

원격지에 172.31.5.31 ~ 172.31.5.39 까지의 서버들이 있으며, 하나의 공인 IP를 통해 172.31.5.31로 접속할 수 있고, 31번으로 전송한 파일들을 32 ~ 39번 서버들로 복사해야 하는 상황을 가정해 보겠습니다.

 

scp의 기본 사용법은 아래와 같습니다. 

scp [옵션] [[user@]host1:]file1 ... [[user@]host2:]file2

1:1 전송뿐만 아니라 n:n 전송도 지원됩니다. 또한 로컬 파일을 원격지로 보내거나 원격지의 파일을 로컬에 복사할 수 있으며, 원격지의 파일을 다른 원격지로 보낼 수 있습니다. 

 

지정할 수 있는 옵션은 아래와 같습니다.

-P  -P 3010 처럼 원격지의 ssh 포트 번호를 지정할 수 있습니다. 기본값은 22 입니다. 대문자임을 주의하세요.
-r 디렉토리의 하위 폴더, 파일을 모두 복사합니다.
-p 복사할 때 원본 파일의 의 수정/사용 시간, 권한 등이 모두 유지됩니다.
-c 압축 후 복사합니다. 용량이 클 때 사용하세요.
-v 복사되는 과정을 볼 수 있게 정보를 출력합니다.

 

아래 몇 가지 샘플을 통해 살펴보고 마무리 하겠습니다. 원격 포트는 모두 3010이라고 가정하겠습니다.

 

로컬파일을 원격지로 복사

 

로컬에 /tmp/test.tar 이라는 파일을 172.31.5.32 서버(port number 3010)의 /tmp 폴더에 복사합니다.

scp -P 3010 /tmp/test.tar abc@172.31.5.32:/tmp

 

로컬파일들을 원격지로 복사

 

복사할 파일 리스트를 필요한 만큰 나열하면 됩니다.

scp -P 3010 /tmp/test.tar /tmp/test2.tar abc@172.31.5.32:/tmp

 

원격지 파일을 로컬에 복사

 

172.31.5.32 서버(port number 3010)의 /tmp/test.tar 파일을 로컬의 /tmp 라는 폴더에 복사합니다.

scp -P 3010 abc@172.31.5.32:/tmp/test.tar /tmp

 

원격지 파일들을 로컬에 복사

 

원격지의 파일들은 ""로 감싸서 나열하면 됩니다.

 

scp -P 3010 abc@172.31.5.33:"/tmp/test.tar /tmp/test2.tar" /tmp

 

위의 두 예제처럼 -P 옵션은 둘 중 앞에 선언된 원격지의 port로 사용됩니다. 

 

복사할 파일의 원본 위치와 복사될 위치 모두 원격지일 경우 앞에 선언된 원격지의 포트로 인식합니다. 

 

만약 사용되는 포트가 22번이라면 상관없지만 두 원격지 모두 사용하는 포트가 22번이 아닐 때는 앞에 있는 원격지는 -P옵션으로 포트를 지정할 수 있지만 그 다음 원격지부터는 모두 22번으로 인식합니다. 

 

이런 경우에는 ssh_config를 설정하여 해결할 수 있습니다. 상세 내용은 man ssh_config를 통해 확인할 수 있습니다. 

 

아래 예제는 172.31.5.33, 172.31.5.34 서버의 ssh 포트를 3010으로 지정한다고 가정하겠습니다. 

vi ~/.ssh/config  // 기존에 지정한 내용이 없다면 신규 파일을 생성하게 됩니다.

// 파일 내용은 아래와 같이 지정할 수 있습니다.
Host 172.31.5.33
Port 3010

Host 172.31.5.34
Port 3010

 

원격지 파일을 다른 원격지에 복사

 

172.31.5.35 서버(port number 3020)의 /tmp/test.tar 파일을 172.31.5.33 서버의 /tmp 라는 폴더에 복사합니다. 33번의 ssh port는 위와 같이 ssh_config에 지정했다고 가정하겠습니다.

 

scp -P 3020 abc@172.31.5.35:/tmp/test.tar abc@172.31.5.32:/tmp

35번 서버의 포트인 3020을 옵션 P로 지정했으며 32번의 포트는 ssh_config에 정의된 포트를 읽어서 복사합니다. 

 

 

이상입니다. 자주 쓰지 않다보니 할 때마다 검색해서 찾는 저 자신을 보고 한번 정리해야겠다라는 생각이 들더군요. 

 

여러분들에게도 도움이 됬으면 합니다. :)

DSL에서는 precision_threshold값을 적용하여 사용했는데, Kibana에서는 적용해본 적이 없다가 사용하시는 동료들이 값이 이상하다며 내용을 찾아보게 되었습니다. 

 

Metrics에 Y-Axis의 Aggregation을 Unique Count로 지정했을때 아래와 같은 값이 출력되고 있었습니다.

이 값이 terms로 뽑을 때와 3% 정도 차이가 나고 있어 DSL에 적용한 precision_threshold 설정을 적용해 봤습니다.

Metrics 하단에 JSON input 란이 있습니다. 여기에 아래와 같이 입력하면 됩니다. precision_thredshold의 기본값은 3000이며, 최대값은 40000입니다.

아래와 같이 조정이 되지만 terms와 완전히 동일한 값이 되지는 않습니다. 

 

hits 수는 2천만개에 terms의 결과는 16779개라 애매하네요. -_-a 최대치로 올려도 조금의 차이는 발생합니다. 

 

가끔 정확한 수치가 필요한 경우 raw data를 모두 검색해서 별도로 지표 처리를 하면 할 수는 있지만 이런 경우는 대처하기 애매하네요. 

 

 

 

 

 

 

+ Recent posts