wireshark 패키지 설치는 pacman 을 이용하면 됩니다. wireshark-gtk 는 wireshark-cli 패키지에 의존함으로 두 패키지를 모두 설치하려면 다음 명령을 실행합니다.
pacman -S wireshark-gtk
wireshark-cli 패키지만 설치하려면 다음 명령을 실행합니다.
pacman -S wireshark-cli
wireshark 를 구동하기 위해서는 적합한 캡쳐 권한 (Capture Privilege)이 있어야 합니다. 물론 root 권한으로 실행하면 캡쳐 권한이 있으니 구동하는데 문제는 없겠지만, 이는 심각한 보안 문제가 발생시킬 수 있습니다. 따라서, wireshark 가 구동하는데 필요한 최소한의 권한만 부여하여 실행하는 것이 바람직합니다. archlinux 에서는 다음과 과정을 통해 해당 작업을 수행할 수 있습니다. username 에는 wireshark를 구동할 사용자 아이디를 넣으면 됩니다.
최근 클러터를 이용한 프로그램을 개발하면서 메모리 누수 현상을 발견했습니다. 코드를 하나 하나 막아가면서 테스트를 한 결과 ClutterGstVideoSink 객체를 사용하지 않으면 메모리 누수가 발생하지 않았습니다. 하지만, 아무리 소스를 분석해도 원인을 찾아낼 수 없었고, 잘못된 부분도 없는 것 같았습니다. 물론 구글링을 해도, 검색 실력이 미천한지, 답을 찾을 수 없었습니다.
그래서 결국 예전에 소개한 적 있는 구글 성능 도구(google-perftools)를 이용해 디버깅을 했습니다. 그런데 문제는, 아치 리눅스(Arch Linux) x86_64 환경으로 개발 환경을 바꾸면서 메모리 프로파일 기능이 제대로 동작하지 않는다는 사실인데, 특히 메모리 누수 발생 지점을 정확하게 알기 위해서 필요한 함수 호출 백트레이스(backtrace) 정보가 추출되지 않는 게 가장 큰 문제였습니다. 이 문제를 해결하기 위한 과정을 기록으로 남겨봅니다.
구글 성능 도구 설치
아치 리눅스(Arch Linux) x86_64 환경에서 구글 성능 도구(google-perftools)가 정확한 메모리 프로파일 결과를 얻으려면 libunwind 라이브러리를 설치해야 하는데, 아치리눅스 AUR 패키지를 yaourt를 이용해 다음과 같이 쉽게 설치했습니다.
$ yaourt -S libunwind
그리고 다음과 같이 구글 성능 도구를 빌드하고 설치합니다.
$ cd google-perftools
$ ./configure --prefix=/usr --enable-frame-pointers
$ make
$ sudomakeinstall
라이브러리 패키지 재생성 및 재설치
정확한 함수 호출 백트레이스(backtrace) 정보를 얻기 위해 프로그램에 사용되는 모든 라이브러리를 다시 컴파일해 패키지를 다시 설치해야 하는데, 그 과정은 다음과 같습니다. (관련 위키 페이지 참고)
먼저 아치 리눅스 빌드 시스템(ABS) 정보를 동기화합니다.
$ sudo abs
그러면 /var/abs 디렉토리 밑에 모든 공식 패키지의 빌드 정보가 다운로드됩니다.
라이브러리의 패키지 빌드 옵션을 수정하기 위해, /etc/makepkg.conf 파일에서 아래 부분을 찾아 디버그 심볼(-g)과 프레임 포인터 포함(-fno-omit-frame-pointer) 컴파일 옵션을 추가하고 빌드 옵션에서 strip을 제외합니다.
이 그래프를 분석해서 관련 코드를 분석해 보니, 결정적으로 두 군데에 문제가 있습니다. 첫번째는 cogl_pipeline_fragend_arbfp_start() 함수 내부에서 생성한 arbfp_program_state 객체를 해제하는 곳이 없다는 점이고, 두번째는 cogl_pipeline_get_layers() 함수에서 생성한 deprecated_get_layers_list 리스트를 해제하는 곳이 없다는 점입니다. 그런데 최근 클러터 1.8 버전 소스를 보면 두번째 문제는 해결이 된 것 같은데, 첫번째 문제가 있는 곳은 코드 수정이 많이 되어 해결 여부를 알 수가 없습니다.
그래서 결론은, 며칠 전에 릴리스된 클러터 1.8 안정 버전이 아치 리눅스 패키지로 올라오면 다시 메모리 누수 여부를 확인해볼 예정입니다. (GNOME 3 핵심 라이브러리를 직접 컴파일해서 사용하는게 귀찮기도 하고 두렵기도 해서입니다… :)
[UPDATE 2011-10-04] 클러터 1.8 버전에서 확인해 보니 메모리 누수 문제가 해결된 것 같습니다. 역시, 미루기를 잘 했습니다. ;)
[UPDATE 2011-10-05] 다시 확인해 보니, 이제는 다른 부분에서 메모리 누수가 발생합니다. 그래서 이번에는 당당히(?) 버그 리포팅(Bug 660985, Bug 660986) 했습니다.
[UPDATE 2011-10-10] CPU 사용량이 가장 많은 함수를 프로파일링하려면 다음과 같이 실행하면 됩니다.
클러터(Clutter) 라이브러리를 이용하면서 부딪친 대부분의 문제는 성능과 관련된 것입니다. 클러터 라이브러리 자체가 느리다는 얘기가 아니라, 주로 개발하는 분야에서 요구되는 16채널 이상 다채널 라이브 / 녹화 동영상 재생을 구현할 때, 고사양 장비는 문제가 되지 않지만 저사양 임베디드 보드에서는 성능 저하가 발생하기 때문입니다. 하지만, 효율적인 2D 그래픽을 위한 3D 그래픽 라이브러리로서 클러터는 아직까지 만족스럽습니다. OpenGL 기반 라이브러리는 기존 2D 그래픽 라이브러리와 여러가지 기본 개념이 달라서, 저처럼 이쪽 세상에 입문한지 얼마 안되는 개발자는 많은 시행 착오를 겪을 수 밖에 없는데, 이미 사용해 본 GTK+ / GObject 방식에 익숙한 점도 유리하게 작용했지만, 2D 그래픽 + 효과를 위한 약간의 3차원 API 조합은 복잡하고 어려운(…) 3D 라이브러리를 직접 사용하는 것보다 훨씬 수월했습니다.
아무튼 그래서, 지금까지 겪은 경험 중 몇 가지를 정리해 보았습니다. 당연하지만, 아직 OpenGL에 대한 이해가 부족해 틀린 내용이 있을 수도 있으니, 감안해 주시기 바랍니다.
1. 클러터 라이브러리는 계속 버전업 되는데 예전에 작성된 튜토리얼이나 예제는 갱신되지 않아 잘못되거나 사용을 권장하지 않는(deprecated) API를 사용하는 경우가 많이 있습니다. 가능한 클러터 개발자들이 라이브러리와 함께 직접 업데이트하는 클러터 해설서(The Clutter Cookbook)를 참고하는게 가장 정확했습니다.
2.
OpenGL 기반 클러터 라이브러리 동작 방식은 일반적인 2D GUI 프로그래밍과 달리 화면, 즉 스테이지(stage)에 조그만 변화라도 있으면 그때마다 스테이지를 다시 그립니다. 즉, 클러터의 기본 단위인 액터(actor) 하나가 다시 그려져야 하면 액터가 속한 스테이지의 모든 액터를 다시 그립니다. 그리고 이로 인해 스테이지에 보이는 모든 액터의 paint() 함수가 매번 호출되기 때문에 이 함수를 최적화하는 게 매우 중요합니다.
3.
내부적으로 캐싱(caching)이 이용되긴 하지만, 한 액터의 좌표(크기 + 위치)가 변경되면 스테이지의 모든 액터의 크기를 다시 계산하기 위해 모든 액터의 allocate() 함수가 호출됩니다. 예를 들어 텍스트(text) 액터 구현을 보면, 문자열이나 폰트, 크기 등이 변경되었을때 텍스트 액터를 포함하는 부모 컨테이너 액터가 변경된 크기를 감지하고 자신의 크기를 조정할 수 있도록 clutter_actor_queue_relayout() 함수가 호출됩니다. 그리고, 이 함수가 호출되면 결국 스테이지 단계까지 호출이 계속된 다음, 다시 스테이지에 속한 액터의 allocate() 함수가 재귀적으로 호출됩니다. 따라서 액터의 allocate() 함수 역시 내부적으로 최적화되는 것이 좋습니다. 참고로 clutter_actor_queue_relayout() 함수가 호출되면 자동으로 clutter_actor_queue_redraw() 함수가 호출되어 스테이지의 모든 객체를 다시 그립니다.
4.
클러터에서 기본으로 제공하는 박스(ClutterBox), 그룹(CutterGroup) 등과 같은 컨테이너 액터를 사용하지 않고 직접 컨테이너 액터를 구현해 자식 액터를 배치하고 싶거나 혹은 기존 컨테이너 액터를 상속받아 새 컨테이너 액터를 구현할 때가 있습니다. 그런데, 컨테이너 액터 좌표(크기 + 위치) 변경에 따라 자식 액터의 좌표를 자동으로 변경할 필요가 있으면 대개 "allocation-changed" 시그널을 이용해 감지한 뒤 clutter_actor_set_size(), clutter_actor_set_position() 함수 등을 이용해 자식 액터의 좌표를 조정하거나, 제약(ClutterConstraints) 기능을 이용하는데, 위에서 설명한 것처럼 좌표가 변환되면 자동으로 clutter_actor_queue_relayout() 함수가 호출되면서 “The actor ‘xxx’ is currenty inside an allocation cycle; calling clutter_actor_queue_relayout() is not recommended” 디버그 경고 메시지가 계속 출력됩니다. 메시지니를 무시할 수도 있지만, 문제는 한 액터의 좌표 변경으로 인해 매번 화면 전체가 다시 좌표를 다시 계산하기 때문에 결국 모든 액터의 allocate() 함수가 계속 호출되면서 CPU 점유율이 매우 높아진다는 점입니다. 여기에 좌표를 이용한 애니메이션까지 사용하면 CPU 점유율은 상상을 초월할 정도로 올라갑니다. 이 문제를 해결하려면 반드시 allocate() 함수에서 자식 액터의 좌표를 지정할때 clutter_actor_allocate*() 종류의 함수만 이용해야 하고, 어쩔 수 없을 경우 g_idle_add_full() 함수를 이용해 자식 액터 좌표 지정 루틴의 실행을 뒤로 조금 미룬 뒤에 좌표 중복 검사 등을 통해 가능한 화면 재구성(relayout) 작업이 덜 일어나게 해야 합니다. 메인루프 우선 순위는 CLUTTER_PRIORITY_EVENTS 값을 사용하는 게 좋습니다.
6. 텍스트(ClutterText) 액터의 위치가 정수가 아니라면, 즉 소수점 이하 값이 존재하는 실수일 경우 글꼴 선이 흐려지거나 뭉개지는 현상이 발생합니다. 텍스트 액터 뿐 아니라 사각형(ClutterRectangle) 액터처럼 그림이 아니라 직접 그려지는 액터들도 비슷한 현상이 발생합니다. 비단 액터의 위치가 정수일 지라도 이를 포함하는 상위 컨테이너 액터의 위치가 소수점 이하를 포함하는 실수일 경우, 즉 화면(stage)상 절대 좌표가 정수가 아닐 경우 이 현상이 발생합니다. 따라서 액터의 좌표를 계산해서 지정할 경우 반드시 floor() / ceil() 등의 함수를 이용해 소수점 아래 값을 없애주는 것이 좋습니다.
7.
액터에 배경 또는 테두리를 장식하고 싶을때 보통 떠오르는 구현 방법은 두 가지가 있습니다. 첫번째는 컨테이너 액터를 이용해 사각형 액터를 맨 아래 두고 대상 액터를 위에 두는 방법을 이용한 것이고, 두 번째는 이런 작업을 하는 커스텀 액터를 직접 구현하는 것입니다. 그런데 이보다 더 좋은 방법은, 효과(ClutterEffect) 객체를 구현해서 사용하는 것입니다. 효과 객체가 액터 객체에 추가되면 효과의 pre_paint() / post_paint() 함수가 액터의 paint() 함수 호출 전후에 자동으로 호출되므로, 동일한 디스플레이 루틴을 여러 객체에 쉽게 적용할 수 있습니다. 클러터에서 이미 기본으로 제공하는 고급 효과를 사용해도 되지만, 예를 들어 텍스트에 그림자를 넣어주는 예제를 그대로 이용해 테두리 효과처럼 구현할 수도 있는 셈입니다.
8.
클러터에서 직접 그리기 위해 사용하는 OpenGL 랩퍼 API Cogl 함수를 사용할때 경로(Path) 등을 사용하지 않고 가능하다면 기본(Primitives) 함수만 사용해서 구현하는게 성능이 좋습니다.
9. Cogl 함수를 이용해 직접 그리는 방식과 모든 것을 그림 이미지로 처리하는 텍스쳐(ClutterTexture)를 이용하는 방식의 장단점을 아직 잘 모르겠습니다. 다만, 텍스쳐는 내부적으로 사용하는 메모리량이 더 많은 것이 분명하고, 현재 개발 중인 시스템에서는 수많은 채널의 비디오 동시 재생을 위해 어차피 많은 텍스쳐가 사용되기 때문에 가능한 텍스쳐 사용을 자제했습니다. 하지만 영역 크기에 따라 크기가 달라지는 GUI 부분을 구현할때는 이미지 기반 텍스쳐보다 일종의 벡터 그래픽이라고 할 수 있는 Cogl 함수를 이용해 직접 그리면 훨씬 깔끔한 GUI를 얻을 수 있는 것도 사실인 것 같습니다.
12.
AMD(ATI) 그래픽 카드를 장착한 리눅스 상에서 클러터를 실행할때 Catalyst 상용 X 드라이버와 최신 오픈소스 X 드라이버와 성능 차이가 거의 없어진 것 같습니다. 물론 NVidia는 상용 드라이버 성능이 월등이 더 좋고, 인텔 칩셋은 오픈 소스 드라이버만 있고 성능도 좋습니다.
이해에 도움이 될까 싶어, 아직 프로토타입이고 많은 기능이 빠져있지만, 현재 개발 중인 시스템의 동작 화면을 녹화한 영상을 보여드립니다. 녹화에 사용한 프로그램은 gtk-recordMyDesktop입니다.
GUI 프로그래밍을 할 때마다 느끼는 점은 구현하기 위한 기술도 중요하지만, 결국 사용자를 배려하면서도 아름다움을 잃지 않는 참신한 아이디어 기반의 디자인이 더 중요하다는 점입니다. 물론 그렇기 때문에 디자이너라는 직업이 따로 있는 것이겠지만, 좋은 프로그램과 삶의 다양한 모습을 많이 보고, 많이 경험하고, 많이 참고해야 하는 지적 즐거움을 언제부터인가 프로그래머들은 남의 영역이라 멀리한 채 무미건조한 기술에만 전념하고 있는 건 아닌지 모르겠습니다.