Setup network streaming player (네트워크 스트리밍 플레이어)

배경

요즘 Audiophiler 들에게 각광받고 있는 기기는 단연 network stream player (a.k.a network player)이다. 마치 신개념을 무장한 기기로 보일 수도 있으나, 알맹이를 보면 기존 기기에 networking 기능을 더한 것이다. 네트워크 환경이 좋아지고, 편리한 부가기능들이 표준화 되면서 오디오 기기에 부가기능들이 추가되는 자연스런 행보라 하겠다. 하지만 역시나 오디오 기기에 그러한 기능들이 들어오면 가격이 무척 비싸 직딩에게는 그림의 떡일 뿐이다. 🙁

나는 바쁜 일상에서도 소중한 음악을 언제 어디에서나 쉽게 찾아 들고 싶었고, 네트워크 사운드 서버를 구성하여 집에있는 스피커를 최대한 활용해보고 싶었다. 지난 2년 동안 시행착오를 겪고 난 뒤 나름 만족할 만한 network streamer 를 구축하게 되었다. 그리하여 NAIM의 ND5 XS 처럼 멋있는 기기를 사용하고 싶지만,  자금이 넉넉지 않은 audiophiler 에게 기존 오디오 기기들을 100% 활용하면서 적은 비용으로 network stream player 를 구축하는데 도움이 되고자 포스팅을 시작한다.

naim_ND5_XS
NAIM ND5 XS

목적

  • streaming music server
    • 언제 어디에서나 음악을 들을 수 있어야 한다.
  • jukebox
    • 집에서는 고음질의 음악을 들을 수 있어야 한다. (a.k.a PC-FI)
  • network sound server
    • 쇼파위에서 맥북으로 youtube 볼 때, 스피커로 소리를 들을 수 있어야 한다.

Overview

구축 환경

  • Ubuntu Server 14.04.1 LTS (GNU/Linux 3.13.0-36-generic x86_64)
  • openjdk 7
  • nginx 1.4.6 (option)

Network sound server

여기서 pulseaudio 를 설치하는 가장 궁극적인 목적은 network sound server 를 구축하기 위함이다. pulseaudio 외에도 다른 방법이 존재하지만, 경험상 가장 간편하고 깔끔하였다. 엮으로 pulseaudio 가 작성된 이유이기도 하다. network sound server 기능이 필요 없다면 alsa 로 음악을 재생하는것이 음질면에서 더욱 유리하다.

1. install pulseaudio

sudo apt-get install pulseaudio pulseaudio-esound-compat alsa-tools alsa-utils

2. configuration

대부분 alsa usb audio hotplug 로 DAC를 연결할 텐데,  hardware index 가 정상적으로 등록되어 있는지 확인해보자.

april1024@ns01:~$ sudo aplay -l
**** List of PLAYBACK Hardware Devices ****
Home directory not accessible: Permission denied
card 1: CODEC [USB Audio CODEC], device 0: USB Audio [USB Audio]
 Subdevices: 0/1
 Subdevice #0: subdevice #0

* /etc/asound.conf

pcm.!default {
    type hw
    card 1
}
ctl.!default {
    type hw
    card 1
}

* start pulseaudio

sudo service pulseaudio start

* speaker test

pulseaudio daemon 이 system mode 로 동작할 때에는 “pulse-access” group 에 속하지 않은 user 는 소리를 재생할 수 없다.

april1024@ns01:~$ speaker-test

speaker-test 1.0.27.2

Playback device is default
Stream parameters are 48000Hz, S16_LE, 1 channels
Using 16 octaves of pink noise
ALSA lib pulse.c:243:(pulse_connect) PulseAudio: Unable to connect: Access denied

Playback open error: -111,Connection refused

user를 pulse-access group 에 추가한다.

april1024@ns01:~$ sudo usermod -a -G pulse-access april1024
april1024@ns01:~$ speaker-test

speaker-test 1.0.27.2

Playback device is default
Stream parameters are 48000Hz, S16_LE, 1 channels
Using 16 octaves of pink noise
Rate set to 48000Hz (requested 48000Hz)
Buffer size range from 192 to 2097152
Period size range from 64 to 699051
Using max buffer size 2097152
Periods = 4
was set period_size = 524288
was set buffer_size = 2097152
0 - Front Left
Time per period = 10.948270
0 - Front Left
^CTime per period = 10.953592

network를 통하여 sound를 재생하기 위해서는 pulseaudio에서 제공하는 module을 적재해야하며, ACL 추가가 필요하다.

* /etc/pulse/default.pa, /etc/pulse/system.pa

# ESD protocol 설정
.ifexists module-esound-protocol-tcp.so
load-module module-esound-protocol-tcp auth-ip-acl=127.0.0.1;192.168.0.0/16
.endif

# PulseAudio native protocol 설정
.ifexists module-native-protocol-tcp.so
load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1;192.168.0.0/16
.endif

3. add client

soundflower (Mac 10.9.5)

pulseaudio 는 ESD 를 지원하여 외부에서 ESD protocol 로 송신만 해주면 pulseaudio 는 그 소리를 수신하여 재생시킬 수 있다. soundflower 라는 mac 용 프로그램을 이용하여 ESD protocol 로 pulseaudio 에 전달해보자.

soundflower 를 설치하고나면 sound preferences 에서 soundflower device 가 추가된 것을 볼 수 있다.  output device 에서 soundflower device 를 선택한다.

ss_soundflower1

이제 mac 의 모든 소리를 sound server 로 전송할 차례만 남았다. 간단한 shell script 하나 작성해두고 두고두고 사용하자.

* ~/bin/send_sound_to_esound_server.sh

#!/bin/sh

SOUND_SERVER="192.168.0.101"

esd -tcp -bind ::1 &
sleep 2;
esdrec -s ::1 | esdcat -s ${SOUND_SERVER}

Streaming music server

1. install subsonic

subsonic 공식페이지의 설치 가이드를 참고하여 설치한다.

2. configuration

* /etc/default/subsonic

# tomcat의 context path 설정
#
# 아래 context-path 는 service URL 이 http(s)://hostname/subsonic 이라고 가정했을 때의 예이다.
# 만약 service URL 을 http(s)://hostname 으로 하고 싶다면, context-path=/ 로 설정하면 된다.
SUBSONIC_ARGS="--max-memory=128 --context-path=/subsonic"
# subsonic process 의 owner,group 설정
#
# audio files 에 접근 할 수 권한으로 process 를 실행해야 한다.
SUBSONIC_USER=share

* /etc/nginx/nginx.conf

nginx 에 연동하지 않고 직접 http://hostname:4040/subsonic 으로도 접근이 가능하다.

...
# subsonic default running port number 는 4040 이다.
# /var/subsonic/subsonic.properties 에서 변경 가능하다.
location /subsonic {
    proxy_pass    http://localhost:4040/subsonic;
}
...

* start subsonic & restart nginx

sudo service subsonic start
sudo service nginx restart

* add audio file paths

http(s)://hostname/subsonic 에 접속하여 admin 권한을 가지는 계정을 생성한다.

그리고나서 admin 계정으로 로그인하여 “Settings > Media folders” tab 에서 audio files 이 있는 directory 를 추가한다.

ss_subsonic_media_folders

3. add client

subsonic 은 많은 device 를 지원한다. 자신의 device 에 맞는 app 을 설치하고나서 subsonic server 를 추가하면 audio files 를 재생할 수 있다.

* iSub (iphone app)

iSub 는 cache (pre downloading) 기능이 있어, off line mode 에서도 음악을 들을 수 있다.

ss_isub

Jukebox

1. install mpd

sudo apt-get install mpd

2. configuration

* /etc/mpd.conf

# audio files path
music_directory "/mnt/share/media/music"
playlist_directory "/var/lib/mpd/playlists"
# audio files 에 접근 권한이 있는 group 을 지정한다.
group "audio"
# NIC 에 bind 할 address 를 지정한다.
bind_to_address "localhost"
bind_to_address "192.168.0.101"
port "6600"
follow_outside_symlinks "yes"
follow_inside_symlinks "yes"
# audio output 지정
# cf. 음질을 위해서는 alsa 에 직접 붙히는것을 추천함
audio_output {
    type "pulse"
    name "MPD"
}

* pulseaudio-access group 에 mpd 추가

sudo usermod -a -G pulse-access mpd

* start mpd

sudo service mpd start

3. add client

mpd 도 많은 device 를 지원한다. mpd clients 를 보고 자신의 구미에 맞는 것을 고르자.

* MPoD (iphone app)

ss_mpod

 

Implementation of java.util.Map 의 보이지 않는 위험

a pit of infinite loopeness

배경

Server scale-up 이후부터 Web Application을 배포하고 나면 몇시간 이내에 특정 서버의 CPU 사용률이 100%에 이르는 사태가 발생하였다. thread dump를 확인해 봐도 특별한 이상 징후는 발견되지 않았다. 단지 아래와 같이 여러 Thread 들이 java.util.WeakHashMap.get(WeakHashMap.java:355) 에 걸려있었으나, Thread.State가 RUNNABLE 이기 때문에 주의깊게 보지 않았다. 왜냐하면 지금까지 Java core package에서 문제가 발생한 적은 한 번도 없거니와, Java core package를 의심하게 되면 밑도끝도 없이 모든것을 의심해야 하는 불상사가 발생하기 때문이다. 난 적어도 절친은 믿고 세상을 살아간다. 훗날 내 뒤통수를 칠 지언정…

"TP-Processor233" daemon prio=10 tid=0x8d848800 nid=0x717d runnable [0x893f6000]
   java.lang.Thread.State: RUNNABLE
	at java.util.WeakHashMap.get(WeakHashMap.java:355)
	at com.thoughtworks.xstream.io.xml.XmlFriendlyReplacer.unescapeName(XmlFriendlyReplacer.java:118)
	at com.thoughtworks.xstream.io.xml.AbstractXmlReader.unescapeXmlName(AbstractXmlReader.java:42)
	at com.thoughtworks.xstream.io.xml.AbstractPullReader.getNodeName(AbstractPullReader.java:199)
	at com.thoughtworks.xstream.io.ReaderWrapper.getNodeName(ReaderWrapper.java:44)
	at com.thoughtworks.xstream.io.path.PathTrackingReader.moveDown(PathTrackingReader.java:38)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:199)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:162)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:82)
	at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:63)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:76)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshallField(AbstractReflectionConverter.java:246)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:218)
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:162)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:82)
	at com.thoughtworks.xstream.core.AbstractReferenceUnmarshaller.convert(AbstractReferenceUnmarshaller.java:63)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:76)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:60)
	at com.thoughtworks.xstream.core.TreeUnmarshaller.start(TreeUnmarshaller.java:137)
	at com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy.unmarshal(AbstractTreeMarshallingStrategy.java:33)
	at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:923)
	at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:909)
	at com.thoughtworks.xstream.XStream.fromXML(XStream.java:861)

문제가 된 web application에서 사용하는 XStream의 version이 1.3인데, 혹시나 하는 마음에 공식 XStream site에 들어가서 change history를 보다가 XSTR-584: Race condition in XmlFriendlyReplacer issue를 발견했다. Issue description 에는 내가 겪은 증상과 동일했고, 문제의 원인은 not synchronized java.util.WeakHashMap 을 사용했기 때문이라고 간단히 쓰여 있었다.

com.thoughtworks.xstream.io.xml.XmlFriendlyReplacer makes use of 2 WeakHashmaps to hold string.
Those maps are accessed multi-threaded, but are not synchronized.

This can lead to access on those maps occupying all CPU-resources, which can only get solved by restarting the VM.
This happens under rare heavily multi-threaded circumstances, nevertheless the impact is critical.

Implementation of java.util.Map이 가진 잠재적 문제점

구글링을 해보니 Multi-threaded situation(under high concurrency) 에서 Implementation of java.util.Map 의 get() 호출 시 method 내부에서 infinite loop에 빠질 수 있다고 한다. 해당 되는 Implementation of java.util.Map 는 API Document에 아래와 같이 설명된 Class 들이다.

Note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with a key that an instance already contains is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the map. If no such object exists, the map should be “wrapped” using the Collections.synchronizedMap method. This is best done at creation time, to prevent accidental unsynchronized access to the map:

Map m = Collections.synchronizedMap(new HashMap(…));

  • java.util.HashMap
  • java.util.LinkedHashMap
  • java.util.TreeMap
  • java.util.WeakHashMap

더욱이 문제는 대부분의 개발자들은 concurrency로 인한 data corruption 을 염두해두고 위 Class들을 자주 사용한다는 점이다. 즉 개발자가 Java Document의 “Note that this implementation is not synchronized” 의 의미를 race condition 에 의한 data corruption 까지만 이해하고 위 Class들을 자주 사용할수록, infinite loop에 빠질 확률이 더 높아진다.

그 결과 JVM hanging 뿐만 아니라 해당 서버에서 제공하는 서비스들(i.e daemon)까지도 CPU 자원을 사용할 수 없기 때문에 문제는 더욱 커진다. 이렇게 불시에 찾아와 CPU 자원을 모두 고갈시키는 불청객의 위험이 우리 주위에 도사리고 있다.

어느 CS 전공자가 위 Note만 보고 JVM이 hanging 될꺼라 예상할 수 있을까? 5년 넘게 Java language 의 API들을 많이 사용해 왔지만, 위 처럼 황당한 경우는 처음이다. 최소한 infinite loop 에 대한 경고는 해야 하지 않았을까? Synchronization에 따른 deadlock은 들어 봤어도, not synchronization에 따른 infinite loop는 들어 본 적이 없다.

언제 infinite loop가 발생하는가?

기가 막히게 제목을 잘 지은 “A Beautiful Race Condition” article 에서 infinite loop가 언제 발생할 수 있는지에 대해 설명이 잘 되어 있다.

java.util.HashMap.transfer() 를 호출하고 난 뒤 Entry[] table의 상태를 그려보았다.

    • normal case

    • abnormal case

    1. 2개 이상의 thread가 Map에 동시에 write할 때 rehash의 조건이 true 이면
    2. Map의 어느 bucket의 Entry.next 가 서로를 가리킨다. infinite loop 생성
    3. infinite loop Entry가 있는 bucket 을 read 할 때 해당 thread는 hanging

대응 방법

  1. java.util.concurrent.ConcurrentHashMap 사용
  2. Collections.synchronizedMap method 사용하여 wrapping
  3. mutator method 에 접근 할 때 synchronized 처리

HowTo : collectd 설치하기

collectd logo

가볍고 사용하기 쉬운 network monitoring system “collectd”

Background

평소 SNMP(Simple Network Management Protocol)MRTG(Multi Router Traffic Grapher)를 이용하여 System Resource Monitoring을 해왔으나, 지표 추가하는데 MRTG 설정 파일 수정이 복잡하고, 귀찮아서 좀 더 편하게 System Monitoring을 하고자 자료 좀 찾아봤다. Network monitoring tool에 대한 위키페이지를 보던 중 회사, 해외 *nix community에서 종종 접해 눈에 익숙한 collectd를 선택했다.

What is collectd?

collectd는 한 host의 system resources data를 수집하고 central host로 data를 전송하는 daemon이다. 특별한 기능이 있지는 않지만 풍부한 Plug-in이 많으며 설치/설정이 간단한 것이 특징이다. 지속적인 서버 유지보수를 위해서 간단한 설정이 요구된다고 말 하는 것은 말하는 내 입만 아프게 할 뿐이다. collectd 공식 홈페이지에서 설명하는 major feature들을 살펴보자.

* Modularity / Portability
POSIX 준수는 기본이다. 장점이라기 보다는 오히려 지키지 않는 것이 문제 있는 것 아닌가?

* Reasonable defaults
대부분의 plug-in 설정을 수정하지 않고 사용할 수 있다. Ubuntu Server(10.10)의 경우 apt-get으로 collectd를 설치하면 별도 설정 수정없이 11개(cpu, df, disk, entropy, interface, irq, load, memory, processes, swap, users)지표를 볼 수 있다. 배포판 마다 enabled plug-in 수는 다르다. CentOS의 경우 4개(cpu, interface, load, memory) plug-in이 enable 되어 있다.

* High-resolution statistics
C language로 implementation되어 server에 부담이 없기 때문에 10초 단위로 system resources data를 수집한다. 혹시라도 service에 지장을 줄 수 있으니 적용하는 서버에 맞게 interval을 조정하는 것을 추천한다.

* Sophisticated network code
unicast, multicast, proxy는 기본이며 encrypted data transfer까지 지원한다. router의 “accept multicast” option을 enable 시키고 collectd data transfer 설정을 multicast로 해두면 Server가 증설 될 때 보다 설정이 간편해 진다.

* Custom extensions
collectd의 기본 plug-in에 없는 metric을 수집하기 위해 충분한 Custom extension을 제공한다. C, Perl, Python, Java, Execute binaries or scripts extension으로 못 하는 것이 있을까?

* Build to scale
가벼운 daemon, 가벼운 udp datagram, multicast 지원으로 scalability도 괜찮을 것 같다.

* SNMP support
위에 언급한 Custom extension으로 수집할 수 있는 data를 별도의 SNMP Plug-in을 지원함으로써 보다 간단한 설정으로 SNMP query result를 수집 할 수 있다.

* Integration with monitoring solutions
collectd 는 monitoring solution이 아니기 때문에 현재 monitoring system feature가 없다고 하면서 sophisticated monitoring solution을 발견하면 collectd와 통합할 계획이라고 한다.

Environment Setup

network diagram

machine information

[table id=1 /]

Installation

CentOS(5.5) guide

* Enable EPEL (Extra Packages for Enterprise Linux) Repository
CentOS-5의 Base, Extras repository에는 collectd package가 없기 때문에 EPEL repository를 enable 해야 한다.

rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-4.noarch.rpm

* install yum-priorities
대부분의 system administrator들이 CentOS를 선택한 이유는 stable version package를 사용하기 위해서다. EPEL를 enable하고 나서 “yum update”를 하면 EPEL의 package 들이 기존 package들을 overwrite하기 때문에 system을 망칠 수 있다. 그래서 CentOS base repository를 보호하기 위해서 yum-protectbase package를 설치하면 해결되지만, 필자는 repository의 priority를 1부터 99까지 세세하게 설정할 수 있는(more fine-grained repository ordering) yum-priorities를 설치하였다. 왜냐하면 필자의 개발 서버에는 3rd party repositories를 enable하여 여러 repository간의 priority를 정해야 하기 때문이다.

yum install yum-priorities

자세한 내용은 공식 CentOS package management wiki page를 참고하기 바란다.

위 설정이 귀찮아 혹시라도 shell alias의 yum command에 “–disablerepo=epel” option을 추가하여 간단히 해결하고자 한다면 필자는 적극 말리고 싶다. 운영중인 server에 다른 sysadmin이 default shell이 아닌 다른 shell로 “yum update”를 실행 할 수도 있으며, 언제 3rd party repository를 enable할지 모르기 때문이다.

* install collectd

yum install collectd-*

* Hostname 설정
/etc/collectd.conf file을 열어 server hostname과 동일하게 맞춘다.

Hostname    "server01.svc"

* rrd DataDir path 설정
default save DataDir은 “/var/lib/collectd” 로 되어 있지만, rrd도 하나의 plug-in이기 때문에 “/var/lib/collectd/rrd”로 설정을 하였다.
/etc/collectd.conf file을 열어 rrdtool plug-in 설정을 아래와 같이 설정한다.

#   DataDir "/usr/var/lib/collectd/rrd"
    DataDir "/var/lib/collectd/rrd"
#   CacheTimeout 120
#   CacheFlush   900

CGI script의 default read DataDir도 “/var/lib/collectd”로 되어 있기 때문에 위에서 설정한 동일한 경로로 수정한다.
/etc/collection.conf file을 열어 아래와 같이 수정한다.

DataDir "/var/lib/collectd/rrd/"

* start collectd

/etc/init.d/collectd start

rrd file이 정상적으로 쌓이고 있는지 확인하자.

ls /var/lib/collectd/rrd/server01.svc

* extract RRD data in a graphical format
엄밀히 말해서 collectd의 R&R은 rrdtool plug-in을 이용하여 rrd file을 생성하는 것이지만, 친절하게도(?) perl CGI script를 제공하고 있다. 따라서 외부에서 접근 못 하도록 ACL만 수정하면 된다.

/etc/httpd/conf.d/collectd.conf file을 열어서 private network에서만 접근 가능하도록 ACL 을 수정한다.

    AddHandler cgi-script .cgi
    DirectoryIndex bin/index.cgi
    Order Deny,Allow
    Deny from all
    Allow from 127.0.0.1
    Allow from 192.168.0.0/24

자 이제 웹 브라우저 주소창에 아래 주소를 입력하면 collectd가 쌓아놓은 데이터를 그래프로 볼 수 있다.

http://192.168.0.101/collectd/

Ubuntu(10.10) guide

* install collectd

sudo apt-get install collectd

* Hostname 설정
/etc/collectd/collectd.conf file을 열어 server hostname과 동일하게 맞춘다.

Hostname    "server01.box"

* disable rrdtool plug-in (option)
server01.box 에서는 rrd file을 남기지 않고 network를 통해 server01.svc로 전송 할 예정이므로 rrdtool plug-in 을 disable 하여 불필요한 리소스(CPU, Disk) 사용을 줄이는 것이 좋겠다.
/etc/collectd/collectd.conf file을 열어 “LoadPlugin rrdtool” 라인을 주석처리한다.

#LoadPlugin rrdtool

* start collectd

sudo /etc/init.d/collectd start

Centralize Data

collectd는 UDP를 이용하여 data를 전송한다. 간단하게 unicast 방식으로 설정할 수 있으며, switch 장비가 multicast stream을 지원한다면 multicast 방식을 사용하는 것이 more sustainable 하다고 생각한다. 왜냐하면 data를 집중화하는 server 를 옮길 때 unicast 방식은 다른 서버들의 설정 수정이 불가피하기 때문이다. 물론 내부 DNS server를 운영하여 앞서말한 문제를 해결 할 수도 있지만, multicast 가 가진 장점을 이용한다면 좀 더 유연하게 문제들을 대처할 수 있으리라 생각한다.

unicast

* server01.svc
/etc/collectd.conf file을 열어 NIC address를 Listen에 등록한다.

LoadPlugin network

Listen "192.168.0.101" "25826"

* server0[1-4].box
/etc/collectd/collectd.conf file을 열어 전송하고자 하는 server address를 등록한다.

LoadPlugin network

Server "192.168.0.101" "25826"

multicast

* server01.svc
/etc/collectd.conf file을 열어 Multicast Group Address를 Listen에 등록한다.

LoadPlugin network

Listen "239.192.74.66" "25826"

* server0[1-4].box
/etc/collectd/collectd.conf file을 열어 Multicast Group Address를 Server에 등록한다.

LoadPlugin network

Server "239.192.74.66" "25826"

* enable multicast
대부분의 switch, router 장비는 multicast disabled 되어 있을 것이다. 필자는 web으로 router(DIR-615)에 접속하여 enable multicast stream 을 체크하였다.

multicast address(239.192.74.66)가 router 장비에 등록 되어있음을 확인 할 수 있다.

Plug-in Configuration

collectd plug-in 페이지를 참고하여 몇가지 plug-in을 등록해 보았다. 다시 한번 말하지만 간단한 설정파일 수정으로 짧은 시간 내에 여러 가지 지표를 볼 수 있는 것이 collectd의 가장 큰 매력이다.

* logfile
default로 syslog plug-in이 enable되어 있어 /var/log/messages 에서 collectd의 log를 확인 할 수 있다. 하지만 다른 facility들과 섞여 있어 가독성이 좋지 않아서 별도 파일로 기록했다.

LoadPlugin logfile

LogLevel "info"
File "/var/log/collectd.log"
Timestamp true

* apache
apache 지표를 보려면 apache status module을 load하고 적절히 ACL을 수정해야 한다. 자세한 설정은 apache module mod_status document를 참고하기 바란다.

LoadPlugin apache

URL http://localhost/server-status?auto

* cpu (default)

* df
shell에 “df -T”를 입력하여 device와 filesystem을 확인하고 그대로 등록을 해주면 된다.

LoadPlugin df

Device "/dev/mapper/VolGroup00-LogVol00"
FSType "ext3"
IgnoreSelected false
ReportByDevice true
ReportReserved false
ReportInodes false

* disk
/proc/diskstats (Linux 2.6) or /proc/partitions (Linux 2.4)를 참고하여 perl regular expression으로 등록을 해주면 된다. (대부분의 경우 아래 주석처리한 첫 번째 라인으로 등록 가능함)

LoadPlugin disk

#Disk "/^[hs]d[a-f][0-9]?$/"
Disk "/^sd[a-f][0-9]?$/"
Disk "/^dm-[0-9]?$/"
IgnoreSelected false

* interface (default)

* irq
모든 irq number를 보기위해 아래와 같이 설정하였다.

LoadPlugin irq

IgnoreSelected true

* load (default)

* memory (default)

* mysql
localhost에서만 접근 가능한 monitor 전용 계정을 추가하였다.

LoadPlugin mysql

Host "localhost"
User "monitor"
Password ""
Database ""
# Specifies the path to the UNIX domain socket of the MySQL server. This option only has any effect, if Host is set to localhost (the default).
# Otherwise, use the Port option above. See the documentation for the mysql_real_connect function for details.
Socket "/var/lib/mysql/mysql.sock"
MasterStats true
#SlaveStats true
#SlaveNotifications true

* ntpd

LoadPlugin ntpd

Host "localhost"
Port 123
ReverseLookups false

* rrdtool (enable cache)
default로 collectd는 10초 주기로 rrd file을 update한다. 그런데 rrd file을 update하는데 load가 적잖게 걸린다고 하니 data를 memory에 임시로 저장해놨다가 한꺼번에 rrd files에 update 하는 것이 좋겠다. 단, 그만큼 rrd file이 늦게 update되기 때문에 실시간성이 떨어진다.

LoadPlugin rrdtool

DataDir "/var/lib/collectd/rrd"
# The trade off is that the graphs kind of "drag behind" and that more memory is used
CacheTimeout 120 # interval for rrd files to be updated
# If some RRD-file is not updated anymore for some reason (the computer was shut down, the network is broken, etc.) some values may still be in the cache.
# If CacheFlush is set, then the entire cache is searched for entries older than CacheTimeout seconds and written to disk every Seconds seconds
CacheFlush   900

* tcpconns

LoadPlugin tcpconns

ListeningPorts false
LocalPort "22"
LocalPort "80"
LocalPort "3306"

* uptime
별도 설정 필요 없음

* vmem

LoadPlugin vmem

Verbose false

Pros and Cons

Trouble Shooting

* client가 network를 통해 전송한 data가 rrd file로 기록은 되는데 web page에서 보이지 않는다.
우선 정상적으로 rrd file에 기록이 되는지 확인하기 위해 rrdtool dump 를 이용하여 눈으로 직접 데이터 값을 확인한다. 확인 해야 할 항목 : date, value

Jakarta Project 에피소드 Ⅰ: ORO의 보이지 않는 위험

Jakarta Project : Episode 1
Jakarta Project : Episode 1

Jakarta Project 에피소드Ⅰ

ORO의 보이지 않는 위험

조금 먼 옛날 Jakarta Project 저편에…

1996년 1월 23일 JDK 1.0이 세상에 처음 나온 이후 예상과 달리 많은 사람들로부터 관심을 받기 시작한다. 인기가 많아 질수록 언어 스펙 역시 요구가 많아지게 되는데, 그 중에 하나가 불편한 text-processing이었다.

때마침 1999년 Java Language 창시자인 James Gosling과 Open source 수호자인 Brian Behlendorf가 만나서 Jakarta Project를 설립 하고, Jakarta Project에 힘입은 Savarese는 2000년 7월에 Jakarta ORO project를 발족하여 Java Language에서 Regular Expression(정규 표현식)을 사용할 수 있게 하는데…

Jakarta ORO란?

JAVA는 J2SE 1.4 (February 6, 2002)때부터 Regular Expression(정규 표현식)을 지원하기 시작했다. 그러므로 초창기 JAVA는 text-processing이 타 언어에 비해 사용이 불편했다. 2000년 7월 23일 ORO(Original Reusable Objects) company가 JAVA에서 Regular Expression을 사용할 수 있도록 하기 위해 Set of text-processing Java classes를 Jakarta project에 기증하였다. 그 project가 바로 ORO project이다.

Jakarta ORO가 가진 잠재적 문제점

ORO project는 2003년 12월 30일 마지막 release이후, 많은 개발자로부터 J2SE 1.4에 포함된 JAVA CORE regular expression package(java.util.regex)에 비해서 현저히 떨어지는 성능과 높은 메모리 사용률에 대한 개선 요구사항들이 쇄도하게 된다. 이에 대해 original code contributor인 Daniel Savarese는 J2SE 1.4 이후 추가된 java.lang.CharSequence를 사용하면 위 언급한 성능/메모리에 대한 요구사항을 만족시킬 수는 있지만, backward compatibility(J2SE 1.4 이전 하위 호환성) 문제 때문에 요구사항을 들어줄 수 없었다고 한다.

그러므로 org.apache.oro package는 Perl5/AWK-like compatible regular expression을 사용할 수 있다는 장점이 있으나, java core regular expression package에 비해 현저히 떨어지는 성능과 높은 메모리 사용률이라는 단점 때문에 이를 감수하면서 사용할 경우, 언제 running service에 장애가 발생할 지 모른다.

더 큰 문제는 J2SE 1.4 이전에 개발 시작한 open source가 ORO package를 많이 사용하고 있으며, 특히 Jakarta subproject 상당수가 ORO package를 사용하고 있는 실정이란 점이다.

대응 방법

ORO package에 dependent한 대표적 open source인 spring framework, Jakarta ant 그리고 json-lib가 어떻게 ORO package를 사용하고 있는지 살펴봤다.

Json-lib와 spring framework는 JAVA version 1.4를 기준으로 ORO에 binding할지 Java core regular expression에 binding할지를 결정한다. 그러므로 version 1.4 이후의 JAVA를 사용하면 ORO에 binding되지 않기 때문에 ORO의 잠재적 문제로부터 안전하다.

한편 Jakarta ant는 default로 Java core regular expression에 binding되기 때문에 애초부터ORO의 잠재적 문제로부터 벗어날 수 있다. (jakarta ant는 property설정으로 jakarta ORO, Jakarta Regexp, Java core regular expression중 하나를 선택하여 사용하는 구조임)

위 3가지 open source 조사결과로 미루어 볼 때, ORO는 backward compatibility를 제공하는데 주로 사용되며 version 1.4 이후의 JAVA를 사용할 경우에는 ORO에 binding되지 않을 것이라 추측한다. 왜냐하면 2002년 JAVA version 1.4가 release된 이후 open source는 많은 사람들로부터 오랜 기간 동안 사용되면서 충분히 검증 받은 code라고 생각하기 때문이다. 하지만 직접 눈으로 확인하는 것이 가장 확실한 방법이다.

정리하면,

  • The latest stable version of JAVA(가장 최신의 안정버전)를 사용한다.
  • 규모가 작은 open source일 경우 직접 눈으로 확인한다.

대응 방법 예시 (직접 눈으로 source code 검증)

  1. json-lib (version 2.2.2)
      • source code > net.sf.json.regexp.RegexpUtils.java
    package net.sf.json.regexp;
    
    public class RegexpUtils {
    …
    (중간 생략)
    …
    
       /**
        * Returns a RegexpMatcher that works in a specific environment.
    
        * When in a JVM 1.3.1 it will return a Perl5RegexpMatcher, if the JVM is
        * younger (1.4+) it will return a JdkRegexpMatcher.
        */
       public static RegexpMatcher getMatcher( String pattern ) {
          if( isJDK13() ){
             return new Perl5RegexpMatcher( pattern );
          }else{
             return new JdkRegexpMatcher( pattern );
          }
       }
    
    …
    (중간 생략)
    …
    }
    
      • Inheritance diagram

    Inheritance diagram

  2. spring framework (version 2.0.6)
      • source code > org.springframework.aop.support.RegexpMethodPointcutAdvisor.java
    package org.springframework.aop.support;
    …
    (중간 생략)
    …
    
    public class RegexpMethodPointcutAdvisor extends AbstractGenericPointcutAdvisor {
    
    …
    (중간 생략)
    …
    
    	/**
    	 * Create the actual pointcut: By default, a {@link Perl5RegexpMethodPointcut}
    	 * will be created if Perl5 syntax is enforced or when running on JDK 1.3.
    	 * Else, a {@link JdkRegexpMethodPointcut} (JDK 1.4+) will be used.
    	 * @return the Pointcut instance (never <code>null</code>)
    	 * @see #setPerl5
    	 * @see Perl5RegexpMethodPointcut
    	 * @see JdkRegexpMethodPointcut
    	 */
    	protected AbstractRegexpMethodPointcut createPointcut() {
    		if (this.perl5 || JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_14) { 
    			// needs Jakarta ORO on the classpath 
    			return Perl5RegexpPointcutFactory.createPerl5RegexpPointcut(); 
    		} else { 
    			// needs to run on JDK >= 1.4
    			return new JdkRegexpMethodPointcut();
    		}
    	}
    
    …
    (중간 생략)
    …
    }
    
      • Inheritance diagram

    Inheritance diagram

  3. Jakarta Ant (version 1.8)
      • source code > org.apache.tools.ant.util.regexp.RegexpFactory.java
    package org.apache.tools.ant.util.regexp;
    …
    (중간 생략)
    …
    public class RegexpFactory extends RegexpMatcherFactory {
    …
    (중간 생략)
    …
       /**
        * Create a new regular expression matcher instance.
        * Available implementations:
        *
        *   org.apache.tools.ant.util.regexp.Jdk14RegexpRegexp (default)
        *        Based on the JDK's built-in regular expression package
        *
        *   org.apache.tools.ant.util.regexp.JakartaOroRegexp
        *        Based on the jakarta-oro package
        *
        *   org.apache.tools.ant.util.regexp.JakartaRegexpRegexp
        *        Based on the jakarta-regexp package
        *
        * @param p Project whose ant.regexp.regexpimpl property will be used.
        * @return the matcher instance
        * @throws BuildException on error
        */
        public Regexp newRegexp(Project p) throws BuildException {
            String systemDefault = null;
            if (p == null) {
                systemDefault = System.getProperty(MagicNames.REGEXP_IMPL);
            } else {
                systemDefault = p.getProperty(MagicNames.REGEXP_IMPL);
            }
    
            if (systemDefault != null) {
                return createRegexpInstance(systemDefault);
                // XXX     should we silently catch possible exceptions and try to
                //         load a different implementation?
            }
    
            return new Jdk14RegexpRegexp();//         default
        }
    …
    (중간 생략)
    …
    
    }
    
      • Inheritance diagram

    Inheritance diagram