쓰레드 덤프는 프로세스에서 실행중인 쓰레드들의 스냅샷입니다. 쓰레드 덤프를 통해서 덤프시점에 현재 어떤 쓰레드들이 있는지, 쓰레드들의 상태는 어떤지, 쓰레드가 어떤 메소드를 실행하며 지나왔고 어떤 메소드를 실행중인지 파악할 수 있습니다.

1. DDMS 쓰레드 뷰
안드로이드는 DDMS를 통해서 쓰레드 스택을 쉽게 분석할 수 있게 지원합니다.

(1) DDMS에서 프로세스를 선택후 Update Threads 아이콘을 클릭합니다. 
(2) 오른편 Threads 탭에 선택된 프로세스의 쓰레드 목록이 나타납니다.  
(3) 쓰레드를 한개 선택하고 Refresh 버튼을 누르면 그 순간의 쓰레드스택(쓰레드가 실행한 객체 메소드) 정보가 나타납니다.


Threads 목록의 각 컬럼에 대한 설명은 다음과 같습니다.
  • ID : VM 내의 쓰레드 ID. DVM의 경우 3부터 시작하는 홀수. 앞의 별표는 데몬(daemon) 쓰레드.
  • Tid : 리눅스 쓰레드 ID. main 쓰레드의 경우 프로세스 ID와 일치.
  • Status : 쓰레드 상태 
running - executing application code(실행중)
sleeping - called Thread.sleep() 
monitor - waiting to acquire a monitor lock(다른 쓰레드가 작업을 마치기를 기다리는 중)
wait - in Object.wait() 
native - executing native code 
vmwait - waiting on a VM resource 
zombie - thread is in the process of dying 
init - thread is initializing (you shouldn't see this) 
starting - thread is about to start (you shouldn't see this either) 

  • utime : 사용자 코드 누적 실행시간. cumulative time spent executing user code, in "jiffies" (usually 10ms). 
  • stime : 시스템 코드 누적 실행시간. cumulative time spent executing system code, in "jiffies" (usually 10ms). 
  • Name : 쓰레드 이름

의미있는 스택 트레이스 정보를 보기 위해서는, 단말의 기능을 사용하면서 순간을 잘 포착하여 Refresh 버튼을 눌러야 트레이스를 제대로 볼 수 있습니다. 때를 놓치면 이미 실이 지나가고 난 뒤의 고요한 트레이스만 보이게 됩니다. ^^
애플리케이션이 응답 없음 상태(ANR)일 때 Refresh 버튼을 눌러, 도대체 쓰레드가 현재 어떤 메소드를 실행 중이길래 반응이 없는 건지 알 수 있을 것으로 보입니다. 또한 락(lock)이 걸렸을 때 이것이 쓰레드 경합(monitor 상태의 쓰레드들)에 의한 문제인지 파악할 수 있을 것으로 보입니다. (그러나 2경우 모두 아직 이런 식으로는 확인 못해봤습니다.-.-; )

2. /data/anr/traces.txt
애플리케이션에 에러가 발생해서 프로세스가 죽게 되면 달빅 VM은 보통의 JVM처럼 자동으로 쓰레드덤프(javacore) 파일을 떨궈줍니다.  "/data/anr/traces.txt" 파일이 그것입니다. 
아래는 애플리케이션 에러로 애플리케이션이 중지되는 시점의 로그입니다. 맨 아랫줄을 보시면 스택트레이스가 "/data/anr/traces.txt" 파일에 쓰여진다는 정보를 볼 수 있습니다.


경우에 따라서는 애플리케이션 프로세스가 "/data/anr/traces.txt" 파일에 쓰기 권한이 없어서(permission denied) traces.txt에 기록을 못할 수도 있습니다. 하지만 꼭 traces.txt가 아니더라도 위에 나오듯이 로그캣에 친절하게 Exception 스택 트레이스 정보가 나오기 때문에 에러 분석에는 큰 문제가 없을 것입니다.

쓰레드 덤프는 사용자가 직접 kill -3 <PROCESS ID> 명령을 날려서 만들 수도 있습니다. ANR이 발생하거나 lock이 걸리면 이렇게 쓰레드덤프를 떠야겠죠. 참고로, /data/anr/traces.txt 파일은 매번 새로 생성되지 않고 기존 파일에 쓰레드 덤프가 누적되어 기록됩니다. 아래는 쓰레드 덤프 파일에 기록된 내용의 예입니다.

----- pid 224 at 2010-02-12 05:41:20 -----
Cmd line: com.example.android.notepad

DALVIK THREADS:
"main" prio=5 tid=3 WAIT
  | group="main" sCount=1 dsCount=0 s=N obj=0x4001b268 self=0xbd00
  | sysTid=224 nice=0 sched=0/0 cgrp=default handle=-1344001384
  at java.lang.Object.wait(Native Method)
  - waiting on <0x14c328> (a android.os.MessageQueue)
  at java.lang.Object.wait(Object.java:288)
  at android.os.MessageQueue.next(MessageQueue.java:148)
... 중략 ...
  at java.lang.reflect.Method.invoke(Method.java:521)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
  at dalvik.system.NativeStart.main(Native Method)

"Binder Thread #1" prio=5 tid=11 NATIVE
  | group="main" sCount=1 dsCount=0 s=N obj=0x43cfb248 self=0x134170
  | sysTid=228 nice=0 sched=0/0 cgrp=default handle=1259984
  at dalvik.system.NativeStart.run(Native Method)

"JDWP" daemon prio=5 tid=9 VMWAIT
... 중략 ...
"Signal Catcher" daemon prio=5 tid=7 RUNNABLE
... 중략 ...
"HeapWorker" daemon prio=5 tid=5 VMWAIT
... 중략 ...
----- end 224 -----

* 일부 내용에 오류가 있을 수 있습니다.
Posted by 에코지오
,

영역 구분
  • 워킹 트리 working tree (workspace)
  • 인덱스 index (planned tree, staging area)
  • 로컬 브랜치(현재 브랜치) local branch[local repository]
  • 리모트 트래킹 브랜치 remote tracking branch[local repository] : 리모트 브랜치를 로컬에서 참조가능(fetch 명령으로 갱신)
  • 리모트 브랜치 remote branch[remote repository]

1. 워킹 트리 → 인덱스
git status : 워킹트리에서 아직 인덱스에 add되지 않은 파일내역
git diff : 워킹 트리와 인덱스간 소스내용 차이점
git diff <파일> : 해당 파일에 대한 워킹트리와 인덱스간 소스내용 차이점

2. 인덱스 → 로컬 브랜치
git status : 인덱스에서 아직 commit되지 않은 파일내역
git diff --cached : 인덱스와 로컬브랜치(HEAD) 간의 차이점. "commit" 명령을 수행할 경우 반영되는 내용들

3. 워킹 트리 → 로컬 브랜치
git diff HEAD : 워킹 트리와 로컬 브랜치(HEAD)의 소스내용 차이. 마지막 commit 이후 변경사항.
                     "commit -a" 명령을 수행할 경우 반영되는 내용들
git diff HEAD -- <파일> : 해당 파일에 대한 워킹트리와 마지막 commit 이후의 소스내용 차이점

4. 워킹 트리 → 리모트 브랜치
git diff FETCH_HEAD : 워킹 트리와 리모트 브랜치(FETCH_HEAD)의 소스내용 차이점
git diff FETCH_HEAD -- <파일> : 해당 파일에 대한 워킹 트리와 리모트 브랜치 소스내용 차이점

5. 로컬 브랜치 → 리모트 브랜치 : Outgoing Changes
git log FETCH_HEAD.. : 로컬 브랜치에서 리모트 브랜치에 반영할 변경내역(커밋기록)
git diff FETCH_HEAD... : 로컬 브랜치에서 리모트 브랜치에 반영할 소스 변경내용 

6. 리모트 브랜치 → 로컬 브랜치 : Incoming Changes
git log ..FETCH_HEAD : 리모트 브랜치에서 로컬 브랜치로 가져와야할 변경내역(커밋기록)
git diff ...FETCH_HEAD : 리모트 브랜치에서 로컬 브랜치로 가져와야할 소스 변경내용


* 참고 
git fetch : 기본 리모트 트래킹 저장소의 브랜치들을 업데이트
git fetch <리모트트래킹저장소> <리모트브랜치> : 해당 리모트트래킹저장소의 리모트브랜치만 업데이트
git remote update : 트래킹 중인 모든 저장소들 업데이트
git pull : fetch 포함. 즉 FETCH_HEAD 업데이트 됨.

git log -- <파일> : 해당 파일의 커밋 기록 보기
git log -p : diff 포함해서 보기
git log --stat : 통계형식으로 보기
git log -n : 최근 n개의 기록만 보기
git log <since>..<until> : 두 커밋 사이의 기록만 보기(둘 중 하나 생략시 HEAD)

git diff --ignore-space-change : 라인 끝 공백 비교안함
git diff --stat : 통계형식으로 차이점 보기
git diff -- <파일> : 해당 파일에 대한 차이점 보기

git whatchanged : git log와 유사함
git show <object> : 현재 브랜치상의 주어진 커밋에 대한 기본 정보 및 소스변경 내용(diff). obj 생략시 HEAD. log보다 좀더 상세함
git reflog : 로컬 저장소 내의 브랜치들이 시간에 따라 어떻게 변경되어 왔는지 보여줌
Posted by 에코지오
,
개별파일 원복
git checkout  -- <파일> : 워킹트리의 수정된 파일을 index에 있는 것으로 원복
git checkout HEAD -- <파일명> : 워킹트리의 수정된 파일을 HEAD에 있는 것으로 원복(이 경우 --는 생략가능)
git checkout FETCH_HEAD -- <파일명> : 워킹트리의 수정된 파일의 내용을 FETCH_HEAD에 있는 것으로 원복? merge?(이 경우 --는 생략가능)

index 추가 취소
git reset -- <파일명> : 해당 파일을 index에 추가한 것을 취소(unstage). 워킹트리의 변경내용은 보존됨. (--mixed 가 default)
git reset HEAD <파일명> : 위와 동일

commit 취소
git reset HEAD^ : 최종 커밋을 취소. 워킹트리는 보존됨. (커밋은 했으나 push하지 않은 경우 유용)
git reset HEAD~2 : 마지막 2개의 커밋을 취소. 워킹트리는 보존됨.
git reset --hard HEAD~2 : 마지막 2개의 커밋을 취소. index 및 워킹트리 모두 원복됨.
git reset --hard ORIG_HEAD : 머지한 것을 이미 커밋했을 때,  그 커밋을 취소. (잘못된 머지를 이미 커밋한 경우 유용)
git revert HEAD : HEAD에서 변경한 내역을 취소하는 새로운 커밋 발행(undo commit). (커밋을 이미 push 해버린 경우 유용)

워킹트리 전체 원복
git reset --hard HEAD : 워킹트리 전체를 마지막 커밋 상태로 되돌림. 마지막 커밋이후의 워킹트리와 index의 수정사항 모두 사라짐.
                                  (변경을 커밋하지 않았다면 유용)
git checkout HEAD . : ??? 워킹트리의 모든 수정된 파일의 내용을 HEAD로 원복.
git checkout -f : 변경된 파일들을 HEAD로 모두 원복(아직 커밋하지 않은 워킹트리와 index 의 수정사항 모두 사라짐. 신규추가 파일 제외)


* 참조 : reset 옵션
--soft : index 보존, 워킹트리 보존. 즉 모두 보존.
--mixed : index 취소, 워킹트리만 보존 (기본 옵션)
--hard : index 취소, 워킹트리 취소. 즉 모두 취소.

* untracked 파일 제거
git clean -f
git clean -f -d : 디렉토리까지 제거

Posted by 에코지오
,


Git 공식 문서 페이지

Git 사용자 설명서(한글 번역)
http://namhyung.springnote.com/pages/3132772


Git - SVN 비교(영문)

Eclipse EGit 플러그인 사용가이드

Tortoise Git - Git Windows GUI

Git Extensions - Git Windows GUI

Posted by 에코지오
,

안드로이드 Resources, Assets, R 클래스에 대해 간단히 알아봅니다.

1. Resources

  • 리소스는 애플리케이션이 필요로 하는 코드 이외의 자원
  • 이미지, 오디오, 비디오, 텍스트 문자열, 레이아웃, 테마 등
  • 리소스는 res/ 폴더에 위치시킴
  • 리소스 파일명은 소문자, 숫자, 마침표(.), 언더바(_)로만 구성
  • 리소스는 빌드타임에 안드로이드 빌드시스템에 의해 감지되며, 각 리소스는 고유의 ID를 부여받음
  • 안드로이드는 리소스ID를 포함하는 R 클래스를 생성해줌(aapt)
  • 리소스는 Resources 인스턴스를 통해 액세스됨
  • 리소스는 빌드시 컴파일되어 애플리케이션 바이너리에 포함됨


2. Assets

  • Resources와 비슷하나 자주 사용되지 않음
  • assets/ 폴더에 위치함
  • 어떤 종류의 파일이든 가능
  • 컴파일되지 않고 원시형태 그대로(raw) 패키징됨
  • 바이트스트림(InputStream) 형식으로 읽기 가능
  • 파일시스템의 파일처럼 다루어짐(리소스ID 없음)
  • AssetManager를 이용


* 파일 시스템처럼 다루어진다는 의미
 - 파일목록을 구할 수 있고 순회할수 있고 원하는 파일을 찾을 수 있다(listed, iterated, discovered)

* 하위 디렉토리 구조
 - Assets 디렉토리(assets/)는 하위 디렉토리 구조(hierachy)를 유지할 수 있다.
 - Resources 디렉토리(res/)는 하위 디렉토리 구조를 가질 수 없다(R 클래스에 나타나지 않게됨)
 
* res/raw/ 폴더와 assets/ 폴더의 차이점
 - 리소스ID가 있는가 없는가
 - Resources를 이용하는가 AssetManager를 이용하는가
 - 그 외 큰 차이없는 듯

3. R 클래스
안드로이드는 애플리케이션 소스코드에서 리소스 ID를 쉽게 참조할 수 있게 aapt(android asset packaging tool)를 이용하여 R클래스를 자동으로 만들어서 제공합니다.

 

R 클래스의 구조는 다음과 같습니다.

참고로 framework-res.apk에 포함된 시스템 리소스들의 ID는 android.R 클래스에 정의되어 있습니다.

Posted by 에코지오
,
자바 프로세스에서 어떤 객체들이 얼만큼의 메모리를 점유하고 있는지 알아내기 위해 우리는 Heap 메모리를 분석합니다. 
안드로이드에서는 달빅 VM의 힙 메모리를 3가지 수준에서 분석할 수 있습니다. 

1. 개략적인 수준 - DDMS의 VM Heap 탭



아주 간략히 힙 메모리 통계 정보를 보여줍니다. Total/Allocated/Free 메모리 크기, 주요 타입별 객체 갯수 및 크기를 알 수 있습니다. 이게 전부입니다. 아쉽지만 특정 객체를 꼭 찝어서 할당된 갯수와 크기를 알 수는 없습니다. 
그렇지만 메모리 누수가 의심될 때 실제로 누수인지 아닌지 간단하게 판단하는 용도로 활용할 수 있어보입니다. 의심되는 구간에 대해서 [Cause GC] 버튼을 눌러도 객체들이 GC되지 않고 계속해서 쌓인다면 메모리 누수를 의심할 수 있습니다. (안드로이드에서도 GC log 분석이 된다면 gc 로그를 분석하여 메모리 누수 여부를 판별할 수 있겠지만 제가 아직까지 이 방법을 찾지못했습니다)


2. 약간 상세한 수준 - DDMS의 Allocation Tracker 탭



Allocation Tracker는 트래킹을 시작한 이후로 새롭게 할당된 객체들 정보를 비교적 상세히 볼 수 있습니다. Allocation Tracke 사용에 대한 자세한 내용은 Tracking Memory Allocations 기사를 참고하시면 됩니다.(한글 번역 : http://blog.naver.com/huewu/110082424176)


3. 아주 상세한 수준 - Eclipse Memory Analyzer



전문가 수준으로 힙 메모리를 파악하기 위해서는 힙 덤프를 생성해서 분석해야 합니다. 현존 최고의 무료 자바 힙메모리 분석 도구는 아마 Eclipse Memory Analyzer(MAT)일 겁니다. MAT는 힙 분석을 위한 너무나 방대하고 다양한 기능을 제공하기 때문에 제가 감히(?) 여기서는 사용법을 설명드리지 않겠습니다. -.-;; 자세한 사용법은 MAT 공식 사이트나 Eclipse Memory Analyzer, 10 useful tips/articles 를 참고하시기 바랍니다(실토하자면 MAT는 저도 제대로 사용해보질 못했습니다. 머리 싸매고 힙 분석할 일이 아직 없네요. 문제 생기면 그때 공부하렵니다 ㅎㅎ).

ps. 어째 글이 점점 성의가 없어지는 거 같네요. 하핫.
Posted by 에코지오
,
1. adb 이용
- 먼저 root 권한이 있는지, /data/misc에 쓰기 권한이 있는지 확인합니다.
- shell로 들어가서 해당 프로세스에 SIGUSR1 시그널을 날리면 힙덤프 파일이 떨궈집니다.

$ adb shell
# mkdir /data/misc
# chmod 777 /data/misc (/data/misc에 쓰기권한 확인)
# ps (애플리케이션 프로세스 ID 확인)
# kill -10 <pid> (SIGUSR1 시그널 전송)

(잠시후 /data/misc 에 heap-dump-*.hprof 파일 생성됨)

$ adb pull <dump-file> <as-file> (로컬로 파일 추출)


2. API 이용
- android.os.Debug 클래스의 public static void dumpHprofData(String fileName) 메소드를 호출합니다.
- 단, fileName은 쓰기 권한 있는 경로를 지정해야합니다.


3. DDMS 이용 


- 애플리케이션에 WRITE_EXTERNAL_STORAGE 퍼미션이 설정됐는지 확인합니다.
- sdcard가 있고, sdcard에 쓰기 권한이 있는지 확인합니다.
- 해당 프로세스 선택후 위 그림에서처럼 힙덤프 버튼을 누르면 /sdcard 경로에 <패키지>.hprof 파일이 생성됩니다.


힙덤프 파일 포맷 변경
위 세가지 방법으로 생성한 힙덤프 파일은 Dalvik VM 고유의 포맷을 가집니다. 안드로이드 SDK에 포함된 hprof-conv.exe를 이용하여 표준 hprof 포맷으로 변경할 수 있습니다.

$ hprof-conv.exe <달빅 hprof 파일> <표준 hprof 파일>

이렇게 변경된 hprof 파일은 jhat이나 Eclipse Memory Analyzer(MAT) 같은 Heap 덤프 분석툴을 이용하여 시각적으로 분석할 수 있습니다.
Posted by 에코지오
,
프로세스 : 문제식별 -> 데이터 수집 -> 데이터 분석 -> 가설설정 -> 원인분석 -> 개선방안 -> 해결조치


문제해결 접근방식
  • Divide&Conquer 분석 : 문제를 작은 구간으로 나누어서 분석
  • Event 분석 : log,dump,trace 등 각종 기록 분석
  • Queue 분석 : 자원(cpu/network/memory/connection 등)의 대기행렬 분석. 병목파악

Event 분석
  • log : 시점별 이벤트. history
  • dump : 특정 시점의 리소스 상태. snapshot
  • trace : 리소스 활동상태 추적. profiling

안드로이드에서 주로 발생하는 문제
  • 애플리케이션 응답없음(ANR)
  • 시스템 응답없음(먹통, lock up)
  • 프로그램 오류
  • 낮은 성능
  • Crash
     => 경험에 의한 자료 부족

주요 원인
  • bug : 애플리케이션 오류
  • resource leak : 메모리 누수, 자원미반납 등
  • bottleneck : 디스크 IO 병목? 네트워크 병목?
  • dead lock : 쓰레드 경합
  • bad query : Provider의 악성 SQL
  • unoptimized : 튜닝 안된 파라미터 설정
     => 경험에 의한 자료 부족

분석 방법
  • 힙덤프 분석
  • 쓰레드 분석
  • 가용자원 분석
  • 프로파일링
  • IO 병목 분석
  • 네트워크 분석
  • 소스 디버깅
  • 로그 분석
  • 파일/데이터(DB) 분석

주요 도구
  • ADB
  • logcat
  • TraceView
  • DDMS
  • Eclipse ADT
  • Eclipse MAT
  • 쉘 명령어 : ps, top, ...

데이터 수집

1. 시스템 정보수집 : 분석의 기본
- SW, HW, 네트워크 정보수집
- cpu,memory,network,disk 등 장치 및 kernel 정보
- 리소스 사용량

2. 특정시점의 자원/상태 정보 : 정적 데이터, snapshot, dump
- 힙메모리 덤프 : kill -10 <pid>
- 쓰레드 덤프 : kill -3 <pid>, /data/anr/traces.txt
- App 상태 정보 : dumpsys
- Device 상태 정보 : dumpstate
- 무선연결 정보 : DDMS > Dump radio state

3. 시스템의 활동상태/이벤트 정보 : 동적 데이터, tracing, monitoring
 - 이벤트 로그(히스토리 분석) : logcat
 - 실행 기록(동적활동 추적) : DDMS > Start Method Profiling
 - 리소스 모니터링 : vmstat, top, ...


문제의 사전예방
  • FindBugs : 잠재된 소스오류 검출
  • JUnit : 단위 테스트
  • Monkey : 스트레스 테스트, 랜덤 이벤트 발생
  • Best Practice 준수
Posted by 에코지오
,
한 건의 데이터를 입력하는 테스트는 개발자가 손으로 디바이스에 직접 틱틱 입력하면서 금방 테스트할 수 있습니다. 그러나 1000 건의 데이터를 입력하는 행위를 재현해야 한다면 이건 도저히 사람이 수동으로 할 일이 아니게 됩니다. 얼마전 저한테 그런 일이 닥쳤습니다. -.-;

시나리오는 이렇습니다.

대기 화면 -> 목록 화면 -> 입력 화면 -> 여러가지 값 입력 -> 저장 버튼 -> 목록 화면 -> back 버튼 -> 대기 화면

이 시나리오를 1000 번, 2000 번 반복해야 합니다.

처음엔 JUnitPerf를 참조해서 테스트케이스 안에서 코딩을 통해 반복을 구현하려고 했지만, 답이 안나오더군요. 제가 안드로이드 테스트 프레임워크를 아직 잘 이해를 못한 탓인거 같습니다. 그래서 결국은 그냥 1 건 입력하는 테스트케이스를 만들고 am instrument를 1000번 반복해서 실행하는 것으로 구현했습니다. ^^;

아래처럼 배치파일을 만듭니다. 

@rem 반복횟수. 첫번째 파라미터로 받음.
set REPEAT=%1

@rem 실행할 명령(my.app.test는 테스트케이스가 포함된 패키지명, my.app.test.InsertTest는 테스트케이스 클래스)
set TEST=adb shell am instrument -w -e class my.app.test.InsertTest  my.app.test/android.test.InstrumentationTestRunner

@rem FOR /L %variable IN (start,step,end) DO command [command-parameters]
FOR /L %%A IN (1,1,%REPEAT%) DO %TEST%

배치파일이 TestInsert.bat라면 반복횟수를 파라미터로 주어 실행합니다.

C:\TestInsert.bat 1000

와우! 성공했습니다. 밥먹으러 가기 전에 돌려놓고 나는 맛있게 밥만 먹으면 됩니다. ^^
Posted by 에코지오
,

안드로이드 테스트 기본

am instrument 명령어 자세한 사용법

테스트케이스 차이점

2개의 핵심 테스트케이스
 isolated testing of a single activity
- 시스템 인프라와 약한 연결
- 일부 시스템 자원을 Mock으로 대체

- functional testing of a single activity
- 시스템 인프라 그대로 이용
- 실제 환경과 동일

테스트케이스 작성 샘플

안드로이드용 다른 테스트 프레임워크
(1) Robotium
- 블랙박스 테스트용
- 하나의 테스트케이스에서 여러 액티티비 사이를 이동 가능
- 뷰 요소를 화면상의 index(몇 번째로 나타나는가)로 참조 : 블랙박스 테스트라면 뷰의 ID를 알 수 없기 때문. 
- 그냥 뷰의 ID(R.id.xxx)로 참조하는 API도 제공하면 더 좋을 듯.

(2) Calculon
- 화이트박스 테스트용
- DSL 개념도입 : Watij와 비슷한 방식으로 테스트 코드 작성
- 다른 액티비티로 이동시 해당 액티비티를 참조하기가 어려움

* Robotium과 Calculon을 잠깐 써본 바로는 둘다 아직은 부족한 점이 많아 보입니다.

Posted by 에코지오
,