diamond 패키지 읽기

읽은 코드: diamond 패키지 https://github.com/python-diamond/Diamond

참고: 파이썬을 여행하는 히치하이커를 위한 안내서

Overall

다이아몬드는 system metrics을 수집하고, Graphite 같은 프로그램에 그를 전달하는 프로그램이다. 따라서 크게 수집(collect), 조정(handle) 기능을 가지고 있으며, Server, Collector, Handler 등이 클래스로 구현되어 있다.

흐름을 살펴보면, 우선 diamond 파일에서 arg parsing, initializing을 하고, args에 따라 pid 관리, user 관리, demonize 등을 한다.
그런 다음 Server 클래스의 인스턴스 server를 초기화하고 run 메서드를 실행한다.(server.run()).

Server 클래스는 server.py에 구현되어 있으며, 인스턴스 메서드로 run이 구현되어 있다. 함수명에서 추론할 수 있듯, 인스턴스가 run 함수를 호출함으로써 collector와 handler가 로딩되고 실행된다.
server.py는 diamond.utils.classes, diamod.utils.signals 등 구체적으로 collector, handler의 로딩과 실행에 기여하는 모듈들을 import하고 있다.

예를 들어, diamond.utils.classes 모듈을 보면 handler와 collector가 모두 클래스로 구현되어 있기 때문에, 그를 로딩하기 위해 해당 클래스들을 핸들링하는 함수들로 이루어져 있다.

Lessons learned

  1. isinstance()로 타입을 확인했다. type(a) == str과 달리 isinstance는 상속 관계도 지원한다.
    그러나 전자든 후자든 타입 체크 자체가 그닥 pythonic한 방법은 아니고, duck typing하고 발생 가능한 에러를 try/except으로 잡는게 이상적이다.
    참고: What are the differences between type() and isinstance()?
  2. signal.signal(): set handlers for asynchronous events. 여기서는 signal 받으면 handler를 종료하도록 사용되었다.
    signal.signal(signal.SIGINT, shutdown_handler)
    signal.signal(signal.SIGTERM, shutdown_handler)
    
  3. closure 사용: shutdown_handler 함수는 2번에서 보이듯 signal handler로 사용되는 함수이다.
    options.skip_pidfile, options.pidfile 등 outer function에서 정의되는 지역변수(즉, free variables)를 기억했다가 쓰고 있으므로 클로져이다.
     def shutdown_handler(signum, frame):
             log.info("Signal Received: %d" % (signum))
             # Delete Pidfile
             if not options.skip_pidfile and os.path.exists(options.pidfile):
                 os.remove(options.pidfile)
                 # Log
                 log.debug("Removed PID file: %s" % (options.pidfile))  
             for child in multiprocessing.active_children():
                 if 'SyncManager' not in child.name:
                     child_debug = "Terminating and joining on: {} ({})"
                     log.debug(child_debug.format(child.name, child.pid))
                     child.terminate()
                     child.join()
             sys.exit(0)
    
  4. import module dynamically. 공식 문서에는 __import__보다는 importlib.import_module() 사용을 권한다.
    __import__(modulename, globals(), locals(), ['*'])
    
  5. collector를 찾기위해 디렉토리를 탐색하는데 재귀를 사용했다.
    def load_collectors_from_paths(paths):
     """
     Scan for collectors to load from path
     """
        # 전략
        for f in os.listdir(path):
    
         # Are we a directory? If so process down the tree
         fpath = os.path.join(path, f)
         if os.path.isdir(fpath):
             subcollectors = load_collectors_from_paths([fpath])
             for key in subcollectors:
                 collectors[key] = subcollectors[key]
        # 후략