Translate

2015년 2월 26일 목요일

[Linux/Unix] 많이 사용하지 않는 find 의 고급기능



파일을 찾을 때 find 명령어와 xargs 명령어를 파이프(|)로 묶어서 여러가지 기능을 사용을 했었는데 find 명령어에 대해서 자세히 찾아보니 다른 블로그에서 쉽게 보지 못했던 기능들이 있어서 정리를 해둔다.



find 는 기본적으로 다음의 방법으로 사용을 한다.


// 문법
$ find path operators


// 현재 디렉토리(하위포함 이후 표현 동일함)에서 이름이 *.c 에 해당하는 결과
$ find . -name *.c

// 루트(/) 디렉토리에서 최근 3일 이내에 수정된 자료의 결과
$ find / -mtime -3

// 사이즈가 512byte 보다 큰 결과
$ find . -size +512

// .o 로 끝나는 파일을 찾아서 모두 삭제
$ find . -name "*.o" -exec rm -rf {} \;

// [주의] !, \(, \), {} 를 비롯한 모든 연산자의 앞뒤에는 반드시 공백이 있어야 한다.
// 아래의 명령은 정상적으로 실행되지 않는다.
$ find . \!\(-atime +5 \(-name "*.o" -o -name "*.tmp"\)\)

// 현재 디렉토리에서 일반파일 유형만 검색
// b(블록특수파일), c(문자특수파일), d(디렉토리), f(일반파일), l(심볼릭링크), p(이름있는 파이프파일), s(소켓)
$ find . -type f







AND/OR/NOT 연산에 대해서 다음과 같이 사용이 가능하다.


 operator1 -a operator2  // AND 연산
 operator1 -o operator2  // OR 연산
 ! operator                   // NOT 연산
 \(expression\)            // 논리 우선순위 지정


// 파일이름이 *.o 또는(OR) *.tmp 와 일치하는 결과
$ find . -name "*.0" -o -name "*.tmp"

// 접근한지 5일이 넘었고 파일이름이 *.o 또는 *.tmp 와 일치하는 결과
$ find . -atime +5 \( -name "*.o" -o -name "*.tmp" \)

// [주의] 두 연산자 사이에 -o 가 없으면 AND로 간주 (아래의 두 명령은 같은의미)
$ find . -atime +5 -name "*.o" -o -name "*.tmp"
$ find . \( -atime +5 -name "*.o" \) -o -name "*.tmp"

// [공백주의](접근한지 5일이 넘었고 파일이름이 *.o 또는 *.tmp) 와 일치하지 않는 결과
$ find . \! \( -atime +5 \( -name "*.o" -o -name "*.tmp" \) \)






특정 디렉토리를 제외하고 검색하기


// -prune 옵션: find의 검색 작업을 현재 경로명에서 끊어주는 역할을 한다.


// 현재 디렉토리의 파일들 모두 찾기
1004lucifer:rabbit huhwook$ find .
.
./.DS_Store
./log
./log/nohup.out_bak
./sp
./sp/20150211.log
./sp/20150212.log
./sp/20150213.log
./sp/20150214.log
./sp/20150214_notNull.log
./sp/20150215.log
./sp/20150216.log
./sp/20150217.log
1004lucifer:rabbit huhwook$ 

// sp 단어의 디렉토리를 제외하고 찾기
1004lucifer:rabbit huhwook$ find . \! \( -type d -name sp -prune \)
.
./.DS_Store
./log
./log/nohup.out_bak
1004lucifer:rabbit huhwook$ 

// ./sp 경로의 디렉토리를 제외하고 찾기
1004lucifer:rabbit huhwook$ find . \! \( -type d -path ./sp -prune \)
.
./.DS_Store
./log
./log/nohup.out_bak
1004lucifer:rabbit huhwook$ 
1004lucifer:rabbit huhwook$ 

// ./sp 와 ./log 경로의 디렉토리를 제외하고 찾기
1004lucifer:rabbit huhwook$ find . \! \( \( -type d -path ./sp -o -type d -path ./log \) -prune \)
.
./.DS_Store
1004lucifer:rabbit huhwook$






검색 디렉토리 최대 레벨 제한 걸기


1004lucifer:rabbit huhwook$ ll
total 2483808
-rwxrwxrwx  1 huhwook  staff  1271705839  2 26 18:03 20150226_RabbitMQ_Access_Log.zip
drwxr-xr-x  5 huhwook  staff         170  2 26 18:24 rabbit
drwxr-xr-x  5 huhwook  staff         170  2 26 18:36 tomcat
1004lucifer:rabbit huhwook$ 
1004lucifer:rabbit huhwook$ find . -maxdepth 1
.
./.DS_Store
./20150226_RabbitMQ_Access_Log.zip
./rabbit
./tomcat
1004lucifer:rabbit huhwook$ 






특정 단어가 들어있는 파일을 찾는 방법


// 현재디렉토리의 일반파일에서 $12.99 라는 문자열이 있는 파일을 검색
$ find . -type f | xargs grep "$12.99"









참고서적

유닉스 파워 툴 - 8점
셸리 파워즈 지음, 서환수 옮김/한빛미디어


[Linux/Unix] unzip 명령으로 압축을 풀려고 할때 "checkdir error" 라고 나오며 오류 발생하는 경우



문제

윈도우에서 압축한 파일을 맥(OSX Mountain Lion) 에서 unzip 을 이용해서 압축을 풀려고 하면 아래와 같이 에러가 발생했다.
(윈도우에서 압축툴을 사용하지 않고 '보내기' => '압축(ZIP) 폴더' 를 이용해서 압축을 했었다.)


1004lucifer:Downloads huhwook$ unzip 20150226_RabbitMQ_Access_Log.zip 
Archive:  20150226_RabbitMQ_Access_Log.zip
   creating: rabbit/log/
  inflating: rabbit/log/nohup.out_bak  
   creating: rabbit/sp/
  inflating: rabbit/sp/blablaPushSend_20150211.log  
  inflating: rabbit/sp/blablaPushSend_20150212.log  
  inflating: rabbit/sp/blablaPushSend_20150213.log  
  inflating: rabbit/sp/blablaPushSend_20150214.log  
  inflating: rabbit/sp/blablaPushSend_20150215.log  
  inflating: rabbit/sp/blablaPushSend_20150216.log  
  inflating: rabbit/sp/blablaPushSend_20150217.log  
checkdir error:  tomcat exists but is not directory
                 unable to process tomcat/log_85/.
checkdir error:  tomcat exists but is not directory
                 unable to process tomcat/log_85/localhost_access_log.2015-02-13.txt.
checkdir error:  tomcat exists but is not directory
                 unable to process tomcat/log_85/localhost_access_log.2015-02-14.txt.
checkdir error:  tomcat exists but is not directory
                 unable to process tomcat/log_85/localhost_access_log.2015-02-15.txt.
checkdir error:  tomcat exists but is not directory
                 unable to process tomcat/log_86/.
checkdir error:  tomcat exists but is not directory
                 unable to process tomcat/log_86/localhost_access_log.2015-02-13.txt.
checkdir error:  tomcat exists but is not directory
                 unable to process tomcat/log_86/localhost_access_log.2015-02-14.txt.
checkdir error:  tomcat exists but is not directory
                 unable to process tomcat/log_86/localhost_access_log.2015-02-15.txt.
1004lucifer:Downloads huhwook$ 








해결방법

권한에 문제가 있어서 그런지 권한을 바꿔주니 정상적으로 압축이 풀렸다.


1004lucifer:rabbit huhwook$ ll
total 2483808
-rw-r--r--  1 huhwook  staff  1271705839  2 26 18:03 20150226_RabbitMQ_Access_Log.zip
1004lucifer:rabbit huhwook$ 
1004lucifer:rabbit huhwook$ chmod 777 ./20150226_RabbitMQ_Access_Log.zip 
1004lucifer:rabbit huhwook$ 
1004lucifer:rabbit huhwook$ ls -al
total 2483824
drwxr-xr-x    4 huhwook  staff         136  2 26 18:13 .
drwx------+ 345 huhwook  staff       11730  2 26 18:13 ..
-rw-r--r--@   1 huhwook  staff        6148  2 26 18:13 .DS_Store
-rwxrwxrwx    1 huhwook  staff  1271705839  2 26 18:03 20150226_RabbitMQ_Access_Log.zip
1004lucifer:rabbit huhwook$ 
1004lucifer:rabbit huhwook$ unzip 20150226_RabbitMQ_Access_Log.zip 
Archive:  20150226_RabbitMQ_Access_Log.zip
   creating: rabbit/log/
  inflating: rabbit/log/nohup.out_bak  
   creating: rabbit/sp/
  inflating: rabbit/sp/blablaPushSend_20150211.log  
  inflating: rabbit/sp/blablaPushSend_20150212.log  
  inflating: rabbit/sp/blablaPushSend_20150213.log  
  inflating: rabbit/sp/blablaPushSend_20150214.log  
  inflating: rabbit/sp/blablaPushSend_20150215.log  
  inflating: rabbit/sp/blablaPushSend_20150216.log  
  inflating: rabbit/sp/blablaPushSend_20150217.log  
   creating: tomcat/log_85/
  inflating: tomcat/log_85/localhost_access_log.2015-02-13.txt  
  inflating: tomcat/log_85/localhost_access_log.2015-02-14.txt  
  inflating: tomcat/log_85/localhost_access_log.2015-02-15.txt  
   creating: tomcat/log_86/
  inflating: tomcat/log_86/localhost_access_log.2015-02-13.txt  
  inflating: tomcat/log_86/localhost_access_log.2015-02-14.txt  
  inflating: tomcat/log_86/localhost_access_log.2015-02-15.txt  
1004lucifer:rabbit huhwook$



PS.
굳이 777 권한이 필요할까라고 생각이 들기도 하지만 최소 권한이 어느정도 있어야 하는지 알아보기 귀찮아졌다.



참조
http://devnumbertwo.com/checkdir-error-cannot-create/

[OSX] MS Offiece 2011 for Mac 의 등록된 이름을 변경하는 방법



MS Offiece 2011 for Mac 프로그램을 설치 시 처음에 해당 OSX의 사용자 이름이 기본으로 들어간다.

추후에 사용자 이름을 변경했는데도 Office 프로그램의 등록된 사용자 이름은 변경되지 않아서 이것저것 알아보았다.



환경: OS X 10.9.4 (Mountain Lion)



다음과 같이 작업을 했다.

1. ~/Library/Preferences/com.microsoft.office.plist 파일의 14\UserInfo\UserName 속성값에 원하는 이름으로 변경시켜준다.

2. 터미널을 열고서 defaults write com.microsoft.com 14\\UserInfo\\UserName "Your Name" 라고 입력을 해준다.


1004lucifer:Utilities huhwook$ 
1004lucifer:Utilities huhwook$ defaults write com.microsoft.com 14\\UserInfo\\UserName "허 욱"
1004lucifer:Utilities huhwook$


3. 컴퓨터 재부팅




위의 작업 후 Office 프로그램을 실행시키면 이름이 변경되어 있는것을 확인할 수 있다.




참고
http://forums.macrumors.com/showthread.php?t=1057605
http://answers.microsoft.com/en-us/mac/forum/macoffice2011-macstart/change-registered-company-name-office-2011-for-mac/b140de7e-630d-41c4-82cc-cfb6a614194b


2015년 2월 24일 화요일

[리뷰][서적] 시작하세요! 티타늄을 활용한 스마트폰 앱 개발






꽤 괜찮은 책이라 생각을 한다.
적어도 나에게는 '티타늄'에 대해서 궁금했던 부분을 많이 해소시켜 주었다.

위의 책을 보게된 이유는 Android Native 앱을 하이브리드앱으로 변경을 하기위해 기술조사 차원에서 티타늄이 어떤 것인지 알아보려 본 것이었다.

반나절 이라는 단기간에 대략적으로 이렇게 사용을 해야하고 어떻게 output 이 나오게 되며 장점과 단점을 알 수 있게 해준 책이었다.
(기술검토 목적이기 때문에 예제를 따라하지 않고 술술 읽고 넘어가서 금방 읽었다.)



그림도 많고 소스도 깔끔하게 나와있어서 프로그램을 시작한지 얼마 된 사람이 아니라면 이해하는데 크게 어렵지 않다.

다만 서버쪽과의 통신이 필요할 수 있는데 웹서버를 설정한다던지..
서버쪽의 소스는 PHP로 되어있는데 설명(소스)도 너무 간단히 나와있다.
하지만 그만큼 티타늄에 대해 더 충실히 설명이 나와있어서 좋았다.





내가 본 티타늄에 대해서 간단히 정리하면 다음과 같다.

장점
1. 결과가 Native App 으로 만들어진다.
2. Javascript 만으로 제작이 가능하다. (Custom 모듈제작 제외)
3. Native Language 를 몰라도 Android, iOS 와 같은 Native App 제작이 가능하다.
4. 하나의 소스로 Cross Platform Native App 제작이 가능하다.

단점
1. Native App 으로 만들어지기 때문에 수정이 필요하다면 앱을 다시 배포해야 한다.
2. UI 구성이 제한적이다. (간단한 UI 정도는 괜찮지만 디자인이 많이 들어간 Native 정도의 UI는 힘들어 보인다.)


위의 단점외에 티타늄으로 만든 앱이 Device 버전에 따라서 모두 정상적으로 작동이 될지는 잘 모르겠다.

2015년 2월 23일 월요일

[리뷰][서적] 실전 안드로이드 4 게임 개발



실전 안드로이드 4 게임 개발 - 6점
J. F. DiMarzio 지음, 박수현.김세연 옮김/길벗

위의 서적을 보면서 실습을 좀 따라해봤다.
내가 OpenGL을 처음 접해서 그런건지..
아니면 설명이 너무 간단해서인지는 잘 모르겠지만 1/3 정도 본 지금의 시점에서 이해하기가 힘이든다;;
책의 예제를 따라하면서 작업을 했는데 실행이 안되는 문제도 있었고..
초보들에게는 예제가 틀리면 정말 치명적인데 개선된 버전의 서적이 다시 나오지 않는것이 많이 아쉽다.

소스 다운로드 페이지 (바로 다운로드)




아래는 책을 따라하면서 발생한 문제들을 집어봤다.
(예제의 소스의 굵은 글씨가 엉터리로 되어있는 부분이 있지만 그런 부분의 문제가 아니라 처음 접하는 사람이 책만으로는 해결하기 힘든 문제들이 있었다.)



1. chapter_04(환경그리기) 예제 실행 시 아래와 같은 오류가 나며 프로그램이 종료된다.


D/AndroidRuntime( 4344): Shutting down VM
V/AudioFlinger(  312): presentationComplete() mPresentationCompleteFrames 660480 framesWritten 654720
E/AndroidRuntime( 4344): FATAL EXCEPTION: main
E/AndroidRuntime( 4344): Process: com.example.myapp, PID: 4344
E/AndroidRuntime( 4344): java.lang.RuntimeException: Unable to resume activity {com.example.myapp/com.example.myapp.SFGame}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.opengl.GLSurfaceView$GLThread.onResume()' on a null object reference
E/AndroidRuntime( 4344): at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2798)
E/AndroidRuntime( 4344): at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2827)
E/AndroidRuntime( 4344): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2258)
E/AndroidRuntime( 4344): at android.app.ActivityThread.access$800(ActivityThread.java:142)
E/AndroidRuntime( 4344): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1203)
E/AndroidRuntime( 4344): at android.os.Handler.dispatchMessage(Handler.java:102)
E/AndroidRuntime( 4344): at android.os.Looper.loop(Looper.java:136)
E/AndroidRuntime( 4344): at android.app.ActivityThread.main(ActivityThread.java:5120)
E/AndroidRuntime( 4344): at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime( 4344): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)
E/AndroidRuntime( 4344): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)
E/AndroidRuntime( 4344): Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.opengl.GLSurfaceView$GLThread.onResume()' on a null object reference
E/AndroidRuntime( 4344): at android.opengl.GLSurfaceView.onResume(GLSurfaceView.java:561)
E/AndroidRuntime( 4344): at com.example.myapp.SFGame.onResume(SFGame.java:23)
E/AndroidRuntime( 4344): at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1192)
E/AndroidRuntime( 4344): at android.app.Activity.performResume(Activity.java:5354)
E/AndroidRuntime( 4344): at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2788)
E/AndroidRuntime( 4344): ... 10 more
V/AudioFlinger(  312): presentationComplete() mPresentationCompleteFrames 660480 framesWritten 654720
W/ActivityManager(  997):   Force finishing activity com.example.myapp/.SFGame
D/ActivityManager(  997): setFocusedStack: mFocusedStack=ActivityStack{674731d0 stackId=3, 17 tasks}
V/ActivityManager(  997): Moving to PAUSING: ActivityRecord{662a8930 u0 com.example.myapp/.SFGame t105 f}
W/ActivityManager(  997):   Force finishing activity com.example.myapp/.SFMainMenu
D/ActivityManager(  997): setFocusedStack: mFocusedStack=ActivityStack{674731d0 stackId=3, 17 tasks}
V/ActivityManager(  997): Moving to FINISHING: ActivityRecord{662defc8 u0 com.example.myapp/.SFMainMenu t105 f}
D/ActivityManager(  997): allPausedActivitiesComplete: r=ActivityRecord{662a8930 u0 com.example.myapp/.SFGame t105 f} state=PAUSING
V/ActivityManager(  997): resumeTopActivityLocked: Skip resume: some activity pausing.





원인
책에서 소스의 일부가 빠져있었다. 아래와 같이 소스의 빠진 부분을 채워넣으니 정상적으로 작동을 했다.

package com.example.myapp;

import android.content.Context;
import android.opengl.GLSurfaceView;

/**
 * Created by 1004lucifer on 15. 2. 22.
 */
public class SFGameView extends GLSurfaceView {

    private SFGameRenderer renderer;

    public SFGameView(Context context) {
        super(context);
        renderer = new SFGameRenderer();
        this.setRenderer(renderer);
    }
}






2. chapter_04(환경그리기) - p166 에서 앱 구동 시 움직이는 배경이 나오지 않는다.

p186 까지 소스를 따라하면 그때는 배경이 나오는 것을 확인 할 수 있다.
아래의 내용은 뒷페이지에서 나온다.

SFGameRenderer.java
@Override
public void onDrawFrame(GL10 gl) {
  gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
  scrollBackground1(gl);
}



[JSP] ThreadDump 생성/모니터링 페이지 만들기



웹 어플리케이션에 hang이 걸리거나 제대로 뜨지 않을 때 원인분석 할 수 있는 좋은 방법은 Thread Dump 를 생성하여 해당 덤프를 분석하는 방법이다.

하지만 문제가 되는 시점에 Thread Dump 를 생성해야 하며 외부에서 맘대로 서버에 접속 할 수 없는 상황이라면 Thread Dump 를 생성하는 jsp 페이지를 하나 만들어 호출하는 방법이 있다.



방법은 다음과 같다.


아래와 같이 현재 수행중인 WAS(tomcat, jeus) 의 PID 를 알아내는 스크립트를 서버의 특정 위치에 저장한다.
(SERVICE_NAME 부분은 각각의 서버 상태에 따라 문자열을 변경해 주어야 한다. 하단에 설명추가)

GIT Source: https://github.com/1004lucifer/JSP_Create_ThreadDump



pid.sh


#!/bin/bash
ps -ef | grep java | grep SERVICE_NAME | awk '{printf $2}'



dump.jsp


<%@ page import="java.io.*" %>
<%@ page import="java.util.Date" %>
<%@ page import="java.text.SimpleDateFormat" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<html>
<%!
    private String executeCommand(String command, String seperator) {
        StringBuffer result = new StringBuffer();
        String line = null;
        BufferedReader br = null;
        Process ps = null;
        try {
            Runtime rt = Runtime.getRuntime();
            ps = rt.exec(command);
            br = new BufferedReader(new InputStreamReader(new SequenceInputStream(ps.getInputStream(), ps.getErrorStream())));

            while( (line = br.readLine()) != null ) {
                result.append(line + seperator);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try { br.close(); } catch(Exception e) {}
        }

        return result.toString();
    }

    private void saveLog(String file, String contents) {
        FileOutputStream fos = null;
        Date now = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String dt = sdf.format(now);
        try {
            fos = new FileOutputStream(file+"_"+dt.substring(0, 8)+".log", true);   // true:이어쓰기, false:덮어쓰기
            for(int i=0; i<contents.length(); i++){
                fos.write(contents.charAt(i));
            }
            fos.write('\r');
            fos.write('\n');
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            if(fos != null){
                try{fos.close();
                }catch(IOException e){}
            }
        }
    }
%>
<body>
<%
    // pid.sh 가 위치한 경로
    String commandGetPid = "/opt/tomcat/project_name/threadDump/pid.sh";
    // ThreadDump 가 기록될 경로
    String logPath = "/opt/tomcat/project_name/threadDump/threadDump_tservice_api";

    String commandThreadDump = "jstack ";   // ThreadDump 를 생성하는 명령어


    String pid = null;
    String threadDump = null;

    try{
        pid = executeCommand(commandGetPid, "");
        threadDump = pid == null ? null : executeCommand(commandThreadDump + pid, "\n");
        
        if (threadDump != null) {
            saveLog(logPath, threadDump);
            out.println(threadDump.replaceAll("\n", "<br/>"));
        }
    }catch(Exception e){
        e.printStackTrace();
    }
%>
</body>
</html>









PS.

1. pid.sh 추가설명

아래와 같이 해당 WAS 의 pid를 가져오기 위해 awk 명령어를 이용했으며 서버 하나에서 구동하는 WAS가 두개 이상이라면 grep 명령어로 하나만 나올 수 있도록 pid.sh 를 변경해 주면 된다.



[1004lucifer@1004lucifer ~]$ ps -ef | grep java
500       6419  6391  0 12:06 pts/1    00:00:00 grep java
root     19629     1  0  2014 ?        02:45:42 /usr/bin/java -Djava.util.logging.config.file=/opt/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -server -Xms1024m -Xmx2048m -XX:PermSize=256m -XX:MaxPermSize=512m -Djava.endorsed.dirs=/opt/tomcat/endorsed -classpath /opt/tomcat/bin/bootstrap.jar -Dcatalina.base=/opt/tomcat -Dcatalina.home=/opt/tomcat -Djava.io.tmpdir=/opt/tomcat/temp org.apache.catalina.startup.Bootstrap start
[1004lucifer@1004lucifer ~]$
[1004lucifer@1004lucifer ~]$ ps -ef | grep java | grep Bootstrap
root     19629     1  0  2014 ?        02:45:42 /usr/bin/java -Djava.util.logging.config.file=/opt/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -server -Xms1024m -Xmx2048m -XX:PermSize=256m -XX:MaxPermSize=512m -Djava.endorsed.dirs=/opt/tomcat/endorsed -classpath /opt/tomcat/bin/bootstrap.jar -Dcatalina.base=/opt/tomcat -Dcatalina.home=/opt/tomcat -Djava.io.tmpdir=/opt/tomcat/temp org.apache.catalina.startup.Bootstrap start
[1004lucifer@1004lucifer ~]$
[1004lucifer@1004lucifer ~]$ ps -ef | grep java | grep Bootstrap | awk '{printf $2}'
19629[1004lucifer@1004lucifer ~]$ 
[1004lucifer@1004lucifer ~]$






2. dump.jsp 위치 추가설명 (중요)

dump.jsp 를 만들어서 문제가 되는 시점에 ThreadDump 를 생성하여 현재의 Thread 상태와 그 상태를 Dump파일로 남기려는게 목적이지만 현재 운영중인 WAS 에 해당 페이지를 넣게된다면 해당 WAS 가 문제가 되었을 경우 경우에 따라 dump.jsp 조차도 페이지가 실행이 되지 않을 수 있다.

처음에는 Context 를 하나 더 만들어 추가하면 되지 않을까 싶었는데 복수개의 Context 가 WAS의 Connection 개수를 같이 공유한다면 특정 Context 가 문제가 있을 때 dump.jsp 를 넣은 다른 Context 에 영향을 미치지 않을까 싶기도 하다.
(WAS에 대해서 잘 아는 분이 있다면 댓글로 설명을 달아줬으면 좋겠다.)

운좋게 현재 상황은 Tomcat Instance 가 하나 더 기동이 되어있어서 다른 Instance(프로세스) 의 Tomcat 에 dump.jsp 를 같이 올렸다.

만일 모니터링 하려는 Tomcat이 hang 발생을 하더라도 안심하고 사용할 수 있게 되었다.



2015년 2월 16일 월요일

[Oracle][Linux] 패스워드에 특수문자가 있을 때 sqlplus 접속방법



OS: Red Hat Enterprise Linux Server release 5.8 (Tikanga)
Oracle: 11g


오라클 계정 패스워드에 특수문자가 있는경우 SQLPLUS 접속 시 오류가 나는 증상이 있어 이것저것 시도를 해봤다.




아래와 같이 일단 sqlplus 접속 후 CONN id/"password" 라고 패스워드 앞뒤에 쌍따옴표(")만 넣어주면 접속이 가능하다.


[oracle@DB1 ~]$ sqlplus / as sysdba

SQL*Plus: Release 11.2.0.3.0 Production on Fri Feb 13 20:26:55 2015

Copyright (c) 1982, 2011, Oracle.  All rights reserved.


Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL>
SQL> CONN id/password!@#
ERROR:
ORA-12154: TNS:could not resolve the connect identifier specified


Warning: You are no longer connected to ORACLE.
SQL>
SQL> CONN id/"password!@#"
Connected.
SQL>







하지만 sqlplus 에 해당 아이디로 바로 로그인하기 위해서는 다음과 같이 역슬래시(\)를 붙여줘야 접속이 가능했다.


[oracle@DB1 ~]$ sqlplus id/\"password\!\@\#\"

SQL*Plus: Release 11.2.0.3.0 Production on Fri Feb 13 20:14:35 2015

Copyright (c) 1982, 2011, Oracle.  All rights reserved.


Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL>
SQL> quit
Disconnected from Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
[oracle@DB1 ~]$



2015년 2월 4일 수요일

[Android] Samsung Android Kitkat 핸드폰에서 Webview 의 내용이 갱신되지 않는 문제(Cache)



문제

단말기 - Samsung Android KitKat Webview
문제 - 보여지는 Content 가 서버에서는 변경되었지만 Webview 에서는 Cache 기능으로인해 갱신되지 않고 그대로 보여진다.

PS.
  - LG 단말기와 같은 다른 단말기에서는 App 종료 후 다시 기동 시 문제가 없었음.








증상 및 분석

1. tPacketCapture(Link) 프로그램을 이용해 Webview 를 호출 할 때 패킷을 캡쳐함.

2. Response 받을 때 HTTP status 가 200(OK) 이나 304(Not Modified) 가 아니라 301(Moved Permanently) 이나 302(Found) 로 내려온다.

3. App을 재구동 시 서버에 요청을 하면 302 코드로 응답을 받을 후 원래의 URL 뒤에 슬래시(/)를 붙여서 다시 요청을 했다.
  ex) 1. http://test.com/test  (Response 302)
        2. http://test.com/test/ (Response 200)


PS.
1. Http 의 Response 에서 HTTP status 301, 302 값은 Redirect 기능을 하는 스펙으로 정의가 되어있는데 App 이 종료되지 않는 상황에서는 Webview 내부적인 Cache 때문에 해당 Contents 를 새로 받아올 수 없었고 App을 재구동 한 시점에는 다시 Contents 를 받아올 수 있었다. (Samsung Android Kitkat 제외)

2. Samsung Android Kitkat 에서는 컨텐츠를 처음 받을때에만 원래 URL 뒤에 슬래시(/)를 붙여서 요청을 했다.(이후에는 App 재구동 시에도 Response 302를 받고 진행 없음)






원인

Android 에서 필요한 Contents 의 URL은 다음과 같다.
http://test.com/test/index.html

하지만 웹서버에서 Redirect 를 시켜 마지막에 슬래시(/)를 붙여주었고 index 우선순위에 따라 자동으로 필요한 Contents 를 내려주었다.

만약에 마지막에 슬래시(/)를 붙여서 웹서버에 요청을 했다면 Response HTTP status 가 200 또는 304 가 되었을텐데 매번 302로 내려받다보니 특정 단말기에서 변경된 Contents 를 받아오지 못하는 현상이 생겼다. (Samsung Android Kitkat 의 버그로 봐야 할까..)






해결방안

이미 Android App 은 사람들에게 설치가 되어있는 상황이니 Android 에서 변경하지 않고 웹서버에서 변경을 시켰다.

사용하고 있는 웹서버가 Nginx 인데 rewrite 기능을 이용하여 내부 리다이렉트(Internal Redirect) 기능을 이용하여 http://test.com/test URL로 요청 시 바로 응답을 줄 수 있도록 설정을 변경했다.


서버 설정방법
http://1004lucifer.blogspot.kr/2015/02/nginx-internal-redirection-301302-200.html



2015년 2월 3일 화요일

[Linux][Java] JSP 와 리눅스(Linux)를 이용한 간단한 배치(Batch) 프로그램 만들기




얼마전 지인이 물어보길래 알려준 방법인데
인터넷으로 찾아봤는데 정보를 찾기 힘든것 같아 나에게 물어봤을듯..ㅠㅠ
그런 분들을 위해 너무나도 알고나면 쉬운 개념이지만 이렇게 정리를 해본다.





배치(Batch)프로그램이란?

보통은 특정 시간마다 수행이 되는 일괄처리 프로그램을 의미하고 있다.







배치프로그램의 종류!?

프로그램 이라는게 한가지 방법만 있는게 아니니.. 방법은 여러가지가 있다.

1. 데몬형식 (Deamon Type)
    - 프로세스가 계속 띄워져 있으며 특정 시간이 되면 로직이 구동된다.

2. 크론형식 (Cron Type)
    - OS에서 주기적으로 작동시키는 명령어를 이용 (Linux 에서는 Cron 이라고 말한다)
    - Web에서 많이 사용하는 Spring 에서도 Quartz 라는 크론 모듈이 있다. (해당 모듈은 OS와는 별도로 Java 프로그램으로 되어있다.)

뭐,, 다른 방법으로 더 있을 수 있다;;






어떤 형식으로 만들어 볼까?

빠르고 간편하게 만들 수 있는 방식이 JSP 페이지에 비지니스 로직을 구현하고 OS에서 사용하는 Cron을 이용하여 주기적으로 호출을 하는 방법이 아닐까 싶다.






어떻게 만들어야 할까!?

1. JSP 페이지에 특정 시간마다 수행되어야 하는 비즈니스 로직을 구현한다.
    - http://test.com/test.jsp 라고 웹브라우저에 입력하면 수행이 되도록..

2. OS의 Cron 을 이용해 해당 페이지를 주기적으로 호출시킨다.

  1) cron 을 편집한다.

1004lucifer:~ 1004lucifer$ crontab -e


  2) cron 형식에 맞게 명령어를 입력한다. (사용방법은 vi 와 같다.)
    - 소문자 i 를 누르면 입력모드로 전환이 되어 글자 입력이 가능해 진다.
    - 커맨드에서 curl 을 입력하여 해당 유틸이 설치되어있는지 확인 후 알맞게 선택한다.

# 작성방법: 분 시 일 월 년 요일 명령어

# 매 시(한시간에 한번씩) 페이지를 호출한다.

0 * * * * * curl http://test.com/test.jsp

# curl 명령어가 없다면 wget 명령어를 이용한다.
# wget 명령어는 인자에 있는 주소의 리소스를 다운로드 한다.
# 때문에 jsp 파일이 지속적으로 쌓이지 않게 파일이 있다면 같이 삭제시켜준다.
0 * * * * * wget http://test.com/test.jsp
0 * * * * * rm -rf test.jsp*


  3) cron 을 저장 후 종료한다.
    - 입력이 끝났으면 esc 입력 후 ':wq' 입력하여 저장하고 종료한다.



PS.
테스트 할 때에는 아래와 같이 1분마다 수행이 되도록 하면 편하다.

# 일분에 한번씩 페이지를 호출한다.
* * * * * * curl http://test.com/test.jsp



OS 에서 Cron 의 수행로그는 보통 다음과 같이 확인이 가능하다.
(리눅스의 버전이나 유닉스의 여부에 따라 달라질 수 있다.)

1004lucifer:~ 1004lucifer$ tail -f /var/log/cron









이렇게만 만들면 되는걸까!?

급하게 만들어야 하는 경우에는 빠르고 쉽게 만들 수 있기 때문에 만들때에는 편리하겠지만 유지보수 하는 입장에서는 이런 배치는 정말 난감하다.

어떤 권한의 계정이던지 보통은 ps 명령어 권한이 있어서 데몬형식의 배치를 만들게 된다면 새로 유지보수를 받는 사람 입장에서는 누가 알려주지 않더라도 배치의 존재를 쉽게 파악 할 수가 있다.

하지만 이런 OS의 Cron 을 이용한 배치의 경우 계정의 cron 을 뒤져봐야 알 수 있으며 주로 사용하는 계정과 배치가 수행되는 계정이 다르다면 모든 계정을 뒤져봐야 알 수가 있고 유지보수 메뉴얼 조차 없다면 배치의 존재를 인지하지 못하고 유지보수를 하게 될 수 있다.


각각의 장단점이 있지만 유지보수적인 측면만 보면 좋은 순서는 다음과 같다고 생각한다.
(개발의 난이도는 역순이다.)

데몬형식 > 크론형식(Web Spring) > 크론형식(OS Cron)




데몬형식이 리소스를 적게 차지하면서 자기의 할일을 빠르게 수행할 수 있다는 장점이 있지만 쓰레드를 적절히 사용하기 힘들다면 만들기 힘들 수 있다.

Spring 의 Quartz 를 이용한 방법은 꼭 웹이 아니어도 상관은 없지만 보통 Web 어플리케이션의 Spring 에서 그냥 많이들 사용하는 것 같다.
기본적으로 WAS 가 필요하니 서버의 리소스 사용량이 높아지는 단점이 있다.

OS Cron 을 이용한 방법은 만들기는 쉽지만 그리 깔끔하지 못하다는 느낌??
왠지 다음 유지보수 담당자에게 미안해 질 것 같다.



[Nginx] 내부 리다이렉션(Internal Redirection) 사용하여 Redirect(301,302)없이 바로 Response(200) 받을 수 있게 변경하기





이슈

http://localhost/test
위의 URL을 호출 시 아래와 같은 URL로 Redirect 되어버린다.
http://localhost/test/








원하는 것

http://localhost/test
위의 URL을 호출 시 Nginx 에서 내부적으로 Redirect 해서 바로 응답코드 200을 돌려준다.






수정방법


nginx.conf


#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;
        error_log /opt/local/share/nginx/logs/error.log debug;

        location / {
            root   share/nginx/html;
            index  index.html index.htm;
            rewrite ^(.*/test)$ $1/;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   share/nginx/html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           share/nginx/html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   share/nginx/html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   share/nginx/html;
    #        index  index.html index.htm;
    #    }
    #}

}



- rewrite 방법을 이용해 redirect 시켜주었다.
- error_log 는 디버깅을 위해 넣어주었다.




위의 설정을 해준뒤 Nginx 를 다음과 같이 재기동 했다.


1004lucifer :~ 1004lucifer$ cd {Nginx_HOME}/sbin
1004lucifer :sbin 1004lucifer$ ./nginx -s reload








결과

다음과 같이 URL 이 바뀌지 않고 처리가 되었다.
(이미 캐시 처리가 되어있어 응답코드가 304로 보여진다.)










추가문제점 및 해결방안

URL 이 'http://localhost/test 와 같이 변경이 되면서 추가적인 문제가 발생을 했다.

1. http://localhost/test/index.html 내부에 있는 리소스(image, css, script) 들이 상대경로로 되어있다.

2. img/a.png 리소스의 이미지는 다음과 같이 브라우저에 요청을 했다.
    - http://localhost/img/a.png


따라서 해당 html 페이지 내부에 있는 img, css, script 리소스를 절대경로로 변경하여 해결을 했다.







참조
http://opentutorials.org/module/384/4337
http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#rewrite


[Oracle] dbms_space.auto_space_advisor_job_proc 프로시저에 따른 서버 부하량 증가 이슈



환경
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production



이슈
AWR 리포트를 봤을 때 특정 시간대(14~19시)에 평균 CPU 가 80% 이상으로 사용량이 떨어지지 않고 유지가 되었다.


 






SELECT X.SQL_ID
      ,X.CPU_TIME
      ,X.EXECUTIONS
      ,X.CPU_TIME_PER_EXECUTIONS
      ,D.SQL_TEXT AS SQL_FULLTEXT
  FROM (  SELECT SQL.DBID
                ,SQL.SQL_ID
                ,SUM (SQL.CPU_TIME_DELTA) / 1000000 AS CPU_TIME
                ,SUM (SQL.EXECUTIONS_DELTA) AS EXECUTIONS
                ,ROUND ((SUM (SQL.CPU_TIME_DELTA) / 1000000) / DECODE(SUM (SQL.EXECUTIONS_DELTA),0,1,SUM (SQL.EXECUTIONS_DELTA)) / DECODE(SQL.PX_SERVERS_EXECS_DELTA,0,1,SQL.PX_SERVERS_EXECS_DELTA)) AS CPU_TIME_PER_EXECUTIONS
            FROM DBA_HIST_SQLSTAT SQL
                ,(SELECT MIN (SNAP_ID) AS START_SNAP_ID 
                        ,MAX (SNAP_ID) AS END_SNAP_ID
                        ,MIN(BEGIN_INTERVAL_TIME) AS BEGIN_INTERVAL_TIME
                        ,MAX(END_INTERVAL_TIME) AS END_INTERVAL_TIME
                  FROM DBA_HIST_SNAPSHOT
                 WHERE BEGIN_INTERVAL_TIME BETWEEN TO_DATE('201502011100','YYYYMMDDHH24MI')
                                               AND TO_DATE('201502011200','YYYYMMDDHH24MI')
                ) SNAP                                               
           WHERE SQL.SNAP_ID BETWEEN SNAP.START_SNAP_ID AND SNAP.END_SNAP_ID
        GROUP BY SQL.DBID
                ,SQL.SQL_ID
                ,SQL.PX_SERVERS_EXECS_DELTA
          HAVING SUM (SQL.EXECUTIONS_DELTA) >= 0
        ORDER BY CPU_TIME_PER_EXECUTIONS DESC) X
      ,DBA_HIST_SQLTEXT D
 WHERE D.SQL_ID = X.SQL_ID
   AND D.DBID = X.DBID;

위의 쿼리를 사용하여 해당 시간대에 사용된 쿼리 데이터를 추출했다.
(http://apollo89.com/wordpress/?p=543 사이트 참고)




해당 날짜의 00시 부터 24시까지 1시간 간격으로 데이터를 뽑아서 분석을 해 보았다.





분석

1. 평소에는 보이지 않던 쿼리가 있다.
    - call dbms_space.auto_space_advisor_job_proc ()
    - call dbms_stats.gather_database_stats_job_proc ()


2. 'call dbms_space.auto_space_advisor_job_proc ()' 프로시저를 호출한 시간은 다음과 같다.
    1) 10 ~ 11시
    2) 14 ~ 15시
    3) 18 ~ 19시
    4) 22 ~ 23시


3. 평균 CPU사용량이 80%이상이 되는 시간이
'call dbms_space.auto_space_advisor_job_proc ()' 프로시저를 2번째 호출한 시점부터 3번째 호출한 시점까지 이다.


4. 데이터를 보면 해당 시간에 특출나게 CPU 자원을 많이 차지하는 쿼리는 없다.






추측

1. dbms_space.auto_space_advisor_job_proc 프로시저에 의한 Oracle Process 의 CPU 사용량 증가와 System CPU 사용량 증가

2. Oracle 퍼포먼스 이슈(버그)
  1) Oracle 10g 에서 해당 프로시저 사용시 퍼포먼스 이슈가 있었다.
    - http://www.dba-village.com/village/dvp_forum.OpenThread?ThreadIdA=36819
    - 10.0.2.4 버전에서 이슈가 해결되었으며
      'execute dbms_scheduler.disable('sys.auto_space_advisor_job’);’ 명령어로 스케쥴러를 비활성화 시켜 문제해결이 가능

  2) Oracle 11g 또한 해당 프로시저 사용시 과도한 redo log 가 생성되는 문제가 발견되었다.
    - http://www.dbi-services.com/index.php/blog/entry/compression-advisor-excessive-redo-log-file-generation
    - 11.2.0.2 버전에서 이슈가 해결되었으나 같은 문제가 계속 발생하여 10g에서와 같이 스케쥴러를 비활성화 시켜 문제를 해결






해결방안!?

execute dbms_scheduler.disable('sys.auto_space_advisor_job’);
명령어를 이용하여 스케줄러를 비활성화 시킨 후 상황을 모니터링 한다.





PS.
아직 해결방안대로 할 수 없는 상황이다보니 시스템 팀에서 어떻게 조치를 취할지 알아보고 나중에 서버 부하 이슈가 없어졌는지 글을 다시 업데이트 해야 할 것 같다.

===============================
2015.02.04 추가
시스템팀에서 불가피한 AWR 통계수집이 아닌 auto_space_advisor, sql_tuning_advisor 를 비활성화 한다고 통보를 받았다.
앞으로 비슷한 문제가 발생을 하는지 모니터링 예정중..



===============================
2015.03.20 추가
1. AWR 그래프 추가
2. 그래프와 같이 지속적으로 부하가 올라가는 문제가 없어졌다.