Java Stack dump 다운로드를 편하게

실행 중인 Java 프로세스의 Stack dump를 뜨는 작업은 간단합니다. Jstack 이나 kill -3 뒤에 process id를 지정하기만 하면 됩니다. Stack dump를 서버에 있는 stack dump파일을 PC로 옮길때면 Secure CRT나 putty에서 console에 찍히는 내용을 파일로 저장하는 기능을 주로 쓰고 있습니다.

언젠가 테스트를 한다고 자주 Stack dump를 뜨다보니 이런 작업마저도 번거롭게 느껴졌습니다. 그리고 파일이름에 timestamp가 들어가 있으면 나중에 찾아보기가 편한데, 그런 파일명 지정도 수동으로 하려니까 귀찮았습니다. 그래서 간단한 프로그램을 만들어 봤었습니다.

보통은 서버에서 vi로 덤프를 보는 때가 많아서 저도 잘 쓰지는 않는데, 그래도 코딩하면서는 재밌었던 기억이 나네요.

다운로드

wget file.benelog.net/dumper.jar 혹은 웹브라우저로 benelog.net/dumper.jar 접근해서 저장

실행

  • Windows : java -cp "dumper.jar;%JAVA_HOME%/lib/tools.jar" Start [port number]

  • Linux : java -cp "dumper.jar:$JAVA_HOME/lib/tools.jar" Start [port number]

Console 창에는 아래 메시지가 찍힙니다.

Usage:

 (Windows)

   Prompt>java -cp "dumper.jar;%JAVA_HOME%/lib/tools.jar" Start [port]

 (Linux)

   Prompt>java -cp dumper.jar:$JAVA_HOME/lib/tools.jar Start [port]

-----------------------------

The port is selected by ramdom.

Web address: http://192.168.0.11:17456

2011-11-20 04:04:32.901:INFO::jetty-7.x.y-SNAPSHOT

2011-11-20 04:04:33.248:INFO::started o.e.j.s.ServletContextHandler\{/,null}

2011-11-20 04:04:34.526:INFO::Started SelectChannelConnector@0.0.0.0:17456

파라미터로 port번호를 넘겨서 원하는 포트로 웹페이지를 띄울 수가 있는데, 지정된 포트가 없다면 10000번에서 20000번 사이의 포트를 임의로 찍어서 띄어줍니다. 위에 "Web address" 라고 적힌 칸 옆의 주소가 접근할 수 있는 URL입니다.

사용법

Console창에서 알려주는 주소를 웹브라우저 입력하면 아래처럼 jps의 정보를 보여주는 화면이 뜹니다.

dumper jps

pid를 찍으면 지정한 JVM의 jstack dump를 바로 다운로드할 수 있습니다.

파일명은 {pid}_{연도}-{일월}-{시분초}.log의 형식으로 했습니다.

혹시 덤프파일을 생성하는데 아래 에러 메시지가 뜬다면, 장비에 여러개의 JVM이 설치되어 있으면서 덤프를 뜨려는 JVM 프로세스와 tools.jar를 classpath로 지정한 경로의 JVM이 다르지 않는지 확인해보셔야 합니다.

java.lang.UnsatisfiedLinkError: no attach in java.library.path
com.sun.tools.attach.AttachNotSupportedException: no providers installed
        at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:190)

구현방식

Jps나 Jstack과 유사하게 sun.jvmstat.monitor 패키지의 클래스를 이용한 프로그램입니다.

참고로 jdk의 tools.jar는 따로 배포하면 license에 어긋난다고 알고 있어서 묶어서 패키징하지 않고 classpath로 지정하도록 했습니다.

전체 소스는 github에 올렸습니다.

간단하게 서버에 파일을 올리기 + MockMultipartFile을 이용한 테스트

서버에 1,2개 파일만을 올려야할 때는 FTP를 따로 설치하는 일이 번거롭다고 느껴집니다. 그럴 때 간단하게 다운받아서 실행할 수 있는 웹어플리케이션을 만들어봤습니다.

jar파일 하나만 다운로드 받아서 바로 실행시키면 됩니다.

다운로드
wget file.benelog.net/uploader.jar
실행
java -jar uploader.jar (디폴트로 8080포트)
java -jar uploader.jar --httpPort=2010 (포트지정)
서버를 브라우저로 접속해서 파일을 올리기

예) http://localhost:8080/

uploader.png

따로 Tomcat과 같은 WAS를 설치할 필요가 없도록 경량 WAS인 Winstone(http://winstone.sourceforge.net/) 과 함께 패키징했습니다.

소스코드는 github에 올려놨습니다. ( https://github.com/benelog/uploader/ )

특별한 코드는 없지만, 아래 클래스에서 MockMultipartFile를 이용해서 파일업로드에 대한 테스트코드를 만들면서 나름 재미있었습니다.

Btrace로 DBCP의 connection정보를 모니터링 하기

Connection pool을 library로 DBCP를 많이 사용하고 있습니다. DataSouce가 선언되어 있는 설정 파일을 보면 min, max Connection 숫자는 쉽게 알 있지만, 현재 Active 한 것이 몇개 인지등은 실행시간에 쉽게 알아보기가 쉽지가 않죠. 그래서 JMX로 현재 Active한 Connection 수 등을 노출하는, DBCP를 한번 감싼 DataSource를 사용하기도 합니다.

Btrace를 이용하면 비교적 간단하게 DBCP의 속성들을 값들을 확인할 수 있습니다. 이미 실행되고 있는 JVM에도 붙일 수 있으니 설정을 바꾸고 WAS를 재시작하지 않아도 됩니다.

아래와 같이 간단한 소스로도 가능합니다.

import static com.sun.btrace.BTraceUtils.*;
import java.lang.reflect.Field;
import com.sun.btrace.BTraceUtils.Sys;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.Self;

@BTrace
public class DbcpMonitorSimple {

  private static final String DS_CLASS = "org.apache.commons.dbcp.BasicDataSource";

  @OnMethod(clazz = DS_CLASS, method = "getConnection")
  public static void onGetConnection(@Self Object basicDataSource) {
    Field urlField = field(DS_CLASS, "url");
    Object url = get(urlField, basicDataSource);
    print("=====DBCP BasicDataSource info (");
    print(url);
    println(" ) ==========");
    printFields(basicDataSource);
    Field poolField = field(DS_CLASS, "connectionPool");

    Object pool = get(poolField, basicDataSource);
    println("=====connectionPool (GenericObjectPool) info====");
    printFields(pool);
    println("==========");
    Sys.exit(0);
  }
}

DataSource.getConnection 요청이 있을 때 해당 객체를 얻어와서 속성값들을 찍어줍니다.

그리고 필요한 필드만 찍는다던지, 보기좋게 정렬한다던지 하는 작업은 필요에 따라 하면 되겠죠.

jps로 모니터링하고자 하는 JVM의 pid를 확인하고, btrace [pid] DbcpSimpleMonitor.java 로 실행하면 아래와 같이 속성값들을 쭉 찍어줍니다.

>btrace 4288 DbcpMonitor.java


=====DBCP BasicDataSource info (jdbc:hsqldb:file:store;shutdown=true ) ========

{defaultAutoCommit=true, defaultReadOnly=null, defaultTransactionIsolation=-1, d

efaultCatalog=null, driverClassName=org.hsqldb.jdbcDriver, driverClassLoader=nul

l, maxActive=8, maxIdle=8, minIdle=0, initialSize=0, maxWait=-1, poolPreparedSta

tements=false, maxOpenPreparedStatements=-1, testOnBorrow=false, testOnReturn=fa

lse, timeBetweenEvictionRunsMillis=-1, numTestsPerEvictionRun=3, minEvictableIdl

eTimeMillis=1800000, testWhileIdle=false, password=, url=jdbc:hsqldb:file:store;

shutdown=true, username=sa, validationQuery=null, validationQueryTimeout=-1, con

nectionInitSqls=null, accessToUnderlyingConnectionAllowed=false, restartNeeded=t

rue, connectionPool=org.apache.commons.pool.impl.GenericObjectPool@1f31079, conn

ectionProperties={user=sa, password=}, dataSource=org.apache.commons.dbcp.Poolin

gDataSource@be8958, logWriter=java.io.PrintWriter@12b1ff9, abandonedConfig=null,

 closed=false, }

===== number of Active : 0

==========

FIeld 객체를 얻어올 때 field(String,String)을 쓰면 static 필드초기화나 @OnMethod 메소드가 붙지 않은 메소드에서는 객체가 얻어지지 않는 에러가 있었습니다.

인터넷을 찾아보니 아래와 같이 비슷한 현상을 겪은 사람이 있었습니다.

결국 @OnMethod 가 붙지 않는 메소드에서 호출을 할 때는 field(classof(obj), "fieldName") 과 같이 피해가는 방법을 썼습니다. 되도록 메소드에서 매번 호출할 필요가 없는 부분은 static 초기화를 시킬 수 있었으면 하는데, static 초기화로는 하는 것도 잘 되지 않았습니다. 직접 field(BasicDataSource.class, "fieldName)으로 를 참조하기는 것도 잘 안 되었고, field(String,String)으로는 참조가 안 되었습니다. 이것만 아니면 좀 더 최적된 호출을 할 수 있었을 것 같습니다.

첨부한 파일들은 아래와 같은 다소 다른 방식이나 출력형식으로 택했습니다.

  • DbcpMonitorSimple.java : BasicDataSource.getConnection이 한번 호출될 때 BasicDataSource의 모든 정보와 BasicDataSource.connetionPool의 정보를 출력

  • DbcpMonitor.java : 이벤트 방식은 1과 같고, BasicDataSource의 모든 필드와 BasicDataSource.connetionPool에서 _numActive 값만을 보여줌

  • DbcpActiveConnectionMonitor.java : 여러 개의 Datasource의 Active connection 갯수를 구할 때 사용. getConnection에서 URL별로 active connection 갯수를 저장해 두었다가, Btrace에서 이벤트를 날리면 출력을 해주고 종료

첨부한 스크립트 중 1,2번은 getConnection이 한번 호출되고 나면 스크립트를 종료하는 구조라서 큰 부하는 없겠지만, 증가 추이 등의 통계 정보를 쌓는기능 등이 필요하다면 Field poolField = field(DS_CLASS, "connectionPool"); 같은 부분도 중복호출되지 않게 하는 처리가 필요합니다.

소스는 gist에 올렸습니다. ( https://gist.github.com/1246239 )