서버를 만들고, 여러 테스트하는 것 중에 영구 접속이 끊어지는 상황을 재현하는 것은 쉽지 않을 수 있다. 디버거를 이용하여 간단히 종료시켜 보는 법을 알아 본다.

python으로 돌고 있는 데몬(pid 11688)이 있다고 하고, 데몬의 DB 접속은 영구 접속에 해당하는데, 이 접속을 강제로 종료시키는 것을 가정한다.

$ lsof -n -p 11688

COMMAND   PID   USER   FD   TYPE   DEVICE  SIZE/OFF      NODE NAME
python  11688 pynoos  cwd    DIR    202,3        80   1636949 /misc/django_projects/app/broadcast
python  11688 pynoos  rtd    DIR    202,3      4096       128 /
python  11688 pynoos  txt    REG    202,3      7216     53035 /usr/bin/python
python  11688 pynoos  mem    REG    202,3     12408 134524267 /usr/lib64/python/lib-dynload/grpmodule.so
python  11688 pynoos  mem    REG    202,3     13520 134538649 /usr/lib64/python/lib-dynload/_bisectmodule.so
python  11688 pynoos  mem    REG    202,3     48240 134524376 /usr/lib64/python/lib-dynload/arraymodule.so
python  11688 pynoos  mem    REG    202,3    694224 134524317 /usr/lib64/python/lib-dynload/unicodedata.so
python  11688 pynoos  mem    REG    202,3     24176 134524160 /usr/lib64/python/lib-dynload/zlibmodule.so
python  11688 pynoos  mem    REG    202,3     14496  69180222 /usr/lib64/libutil-2.17.so
...
... (생략)
...
python  11688 pynoos  mem    REG    202,3     19288  69048848 /usr/lib64/libdl-2.17.so
python  11688 pynoos  mem    REG    202,3    142232  69048881 /usr/lib64/libpthread-2.17.so
python  11688 pynoos  mem    REG    202,3   1847496  67226793 /usr/lib64/libpython.so.1.0
python  11688 pynoos  mem    REG    202,3    163400  69048840 /usr/lib64/ld-2.17.so
python  11688 pynoos    0r   CHR      1,3       0t0      1028 /dev/null
python  11688 pynoos    1u   CHR    136,4       0t0         7 /dev/pts/4
python  11688 pynoos    2u   CHR    136,4       0t0         7 /dev/pts/4
python  11688 pynoos    3w   REG    202,3   3009102 136203946 /misc/log/projects/app.log
python  11688 pynoos    4r   CHR      1,9       0t0      1033 /dev/urandom
python  11688 pynoos    5w   REG    202,3 266251952 135014754 /misc/log/projects/app.sql
python  11688 pynoos    6u  IPv4 26138940       0t0       UDP *:9413
python  11688 pynoos    7u  IPv4 26137372       0t0       TCP 10.1.1.1:33148->10.1.1.2:mysql (ESTABLISHED)

먼저 열려 있는 파일들의 리스트(list of open files; lsof)를 확인한다. 위의 예에서는 7u로 되어 있는 값이 mysql 커넥션 디스크립터다. 이 디스크립터를 종료하면 mysql과의 접속이 종료되는 것이다.

$ gdb /usr/bin/python  11688
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-115.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /usr/bin/python...Reading symbols from /usr/bin/python...(no debugging symbols found)...done.
(no debugging symbols found)...done.
Attaching to program: /usr/bin/python, process  11688
Reading symbols from /lib64/libpython.so.1.0...Reading symbols from /lib64/libpython.so.1.0...(no debugging symbols found)...done.
(no debugging symbols found)...done.
Loaded symbols for /lib64/libpython.so.1.0
Reading symbols from /lib64/libpthread.so.0...(no debugging symbols found)...done.
[New LWP 12995]
[New LWP 12994]
[New LWP 12992]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Loaded symbols for /lib64/libpthread.so.0
Reading symbols from /lib64/libdl.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/libdl.so.2
...
... (생략)
...
Loaded symbols for /usr/lib64/python/lib-dynload/arraymodule.so
Reading symbols from /usr/lib64/python/lib-dynload/_bisectmodule.so...Reading symbols from /usr/lib64/python/lib-dynload/_bisectmodule.so...(no debugging symbols found)...done.
(no debugging symbols found)...done.
Loaded symbols for /usr/lib64/python/lib-dynload/_bisectmodule.so
Reading symbols from /usr/lib64/python/lib-dynload/grpmodule.so...Reading symbols from /usr/lib64/python/lib-dynload/grpmodule.so...(no debugging symbols found)...done.
(no debugging symbols found)...done.
Loaded symbols for /usr/lib64/python/lib-dynload/grpmodule.so
0x00007f0de576b953 in select () from /lib64/libc.so.6
(gdb) call close(7)
$1 = 0

gdb는 디버깅 중인 프로세스의 입장으로 간단한 시스템 콜을 호출 할 수 있는데, 마지막 명령처럼 "call close(7)" 을 호출하면, 디스크립터를 종료하게 된다. 그리고, ^D를 눌러 gdb를 종료하면 프로세스는 실행을 재개하며, 영문도 모르는 채 DB 접속이 끊어지는 상황을 만나게 된다.

Happy Debugging.

django로 만든 웹 서버가 메모리 릭이 있는 것 같아서, 임시로 클라이언트 요청 수에 제한을 걸고 새로 실행되도록 설정했다. 임시가 길어져 1년이 돼가는 동안 잊고 있다가 개발자의 자존심을 건드는 설정인지라 다시 봐야했다.

이 글은 메모리 릭을 잡았다는 것이 아니다.

서버들 중 적당히 스왑을 사용하고 있는 녀석을 골라서 들어갔다. gunicorn 들 중에 제일 pid가 큰 것을 골라낸 후, 해당 PID 에 strace 를 걸어서 GET/POST와 URL PATH에 해당하는 것만 출력하도록 awk 파이프 질을 했다. 동시에 "ps -efl" 로 해당 PID만 골라낸 다음 awk로 적당히 메모리 사용량 부분만 출력한다.

대략 512 개의 요청만 하면 사라지는 gunicorn의 PID이므로, 위 두 작업을 백그라운드로 실행시켜 놓고 fork되어 나간 bash 스크립트의 PID를 저장해 놓는다. 그리고 예의 주시 중인 PID를 1초에 한 번씩 kill -0로 죽었는지를 확인한다. 죽었다면 fork된 녀석들을 죽이고 종료하면 된다.

매우 민첩하게 위 작업들을 생각의 순서대로 뱉어 낸다. 물론 처음에는 바로 쉘상에서 작업하지만, 조금 커질 것을 알고 a.sh 정도로 vim 스크립트로 편집을 하고 실행시키도록 한다.

메모리가 증가되는 순간의 URL을 몇 개 수집한다. 수 분을 기다린다. 화면은 그저 URL과 메모리 사용량만 계속 올라간다.

아뿔싸, 접속한 서버가 오토스케일 정책에 따라 반납되면서 접속이 끊긴다. 뭐야 이거, a.sh 다시작성하기 귀찮은데... 아, 1년만에 들어온 생각인데...

터미널 버퍼를 위로 올려 봐도 vim으로 작업한 것은 남아 있지 않다. 그저 화면에 스크립트의 결과물만 장황하게 넘어간다.

당황하지 말고, terminal에는 vim, less 등이 사용 될 땐 alternative screen으로 작성된다는 것을 알고 있다. ansi terminal이 제공하는 매우 오래된 기능이다.

"ansi show alternative screen" 로 검색을 한다. 

ESC [ Ps ;...; Ps h             Set Mode
ESC [ Ps ;...; Ps l             Reset Mode
      Ps = 4            (A)     Insert Mode
           20           (A)     ‘Automatic Linefeed’ Mode.
           34                   Normal Cursor Visibility
           ?1           (V)     Application Cursor Keys
           ?3           (V)     Change Terminal Width to 132 columns
           ?5           (V)     Reverse Video
           ?6           (V)     ‘Origin’ Mode
           ?7           (V)     ‘Wrap’ Mode
           ?9                   X10 mouse tracking
           ?25          (V)     Visible Cursor
           ?47                  Alternate Screen (old xterm code)
           ?1000        (V)     VT200 mouse tracking
           ?1047                Alternate Screen (new xterm code)
           ?1049                Alternate Screen (new xterm code)

Set은 h로 끝나며, Ps로는 "?47"이면 되겠다. 내 로컬 console은 zsh이니까 몇가지 escape 처리해서 echo 해주면 되겠다. 될까?

echo -e \\e\[\?47h

으허허 보인다. 반납된 서버에 잠시 삽질했던 코드를 다시 치고 싶지 않은 그 임시 코드가 보인다.

#!/bin/bash

P=$(ps -ef | grep guni | grep python2 | sort -k2 -n | awk '($3 != 1) {print $2}' | head -1)
strace -tt -s 100 -p $P 2>&1 | stdbuf -o0 egrep "recvfrom\(.*(GET|POST)" | stdbuf -o0 awk '{print substr($3,2), $4}' &
PID1=$!
while true; do ps -efl | grep gunicorn | grep -w $P | awk '{print $10}'; sleep .5; done &
PID2=$!

trap "kill $PID1 $PID2; exit" SIGINT
while true
do
        if kill -0 $P; then
                sleep 1
                continue
        fi
        kill $PID1 $PID2
        exit
done

귀찮다... 이 시간에 이거 할 게 아닌데.

Network Card의 PPS를 측정하는 아주 간단한 스크립트

linux, NIC eth0 의 PPS를 1초마다 측정한다고 가정하면,

#!/bin/bash

while true
do
    echo $(cat /sys/class/net/eth0/statistics/tx_packets) $(cat /sys/class/net/eth0/statistics/rx_packets)
    sleep 1
done | awk '{DT=$1-TPREV; DR=$2-RPREV; if(TPREV) print strftime("%Y-%m-%d %H:%M:%S", systime()), "TX:", DT, "RX:", DR; TPREV=$1; RPREV=$2; }'

 

GS(링크)를 끌어다가 즐겨찾기에 떨어 뜨려 저장 후, 구글 검색 결과 창에서 누르면 주소를 정리해주는 북마클릿이다. 해당 내용은 아래와 같다. 만약 링크가 나오지 않는다면, 아무 북마크나 만들고 아래 내용으로 주소를 복사/붙여넣기를 하여 만든다.

javascript:location.href=location.href.split('?').map(x=>x.split('&').filter(a=>a.startsWith('http')||(['q','hl','tbm','start'].indexOf(a.split('=')[0])!=-1))).map(b=>b.join('&')).filter( a => a).join('?')

구글 검색을 한 다음 나오는 페이지를 공유하고 싶으나, 그 URL에는 불필요한 정보가 실려 있다. 예를 들어 "real user monitoring"이라는 키워드를 검색한다고하면,

https://www.google.com/search?q=real+user+monitoring&oq=real+user+monitoring&aqs=chrome..9i57j69i64j69i60.475j0j9&sourceid=chrome&ie=UTF-8

real user monitor가 q에 있으며 그 외에 다른 내용이 추가 되어 있다. oq 는 old query 인 것 같고, aqs, sourceid, ie 등등이 있지만, 내가 넣은 키워드는 아니다.

https://www.google.com/search?q=real+user+monitoring

위 정도면 충분하지 않은가?

https://www.google.com/search?q=real+user+monitoring&newwindow=1&sxsrf=LeKk03jPSDAZORglcmjxCXz04IBrb_OLA:1601697916568&ei=Ph3X7KYIs77wQOb2ZeQBg&start=10&sa=N&ved=ahUKEwiyxuraxZfsAhXOfXAKHZvsBWIQ8tMDegQIHRA2&biw=1664&bih=856

이것은 두번째 페이지를 선택했을때 나오는 URL이다. 여기에서는 의미 있는 인자로는 start=10으로 보인다. 또한 이미지 검색탭도 중요한 공유지점이므로 한 번 더 확인해 보면,

https://www.google.com/search?q=real+user+monitoring&newwindow=1&hl=ko&sxsrf=LeKk03rjnn29_rlZpdwCneoGI-E2l1abA:1601698337343&source=lnms&tbm=isch&sa=X&ved=ahUKEwic0ryjx5fsAhUQBogKHausBGsQ_AUoAXoECB0QAw&biw=1664&bih=856

tbm=isch 가 그 역할을 하는 것으로 보여진다. 따라서, 위 URL을 정리하는 간단한 스크립트를 만들수 있고, 북마크에서 해당 자바스크립트가 실행되는 URL로 만들어 두면 유용할 것이다. 단계는 다음과 같다.

  1. "?"로 나눈다.
  2. 나눈 각각을 "&"로 나눈다.
  3. 나눈 각각이 http로 시작하면 남긴다.
  4. 나눈 각각을 '='으로 나눈 첫번째 단어가 'q', 'hl', 'tbm', 'start' 이면 남긴다.
  5. 남은 각각을 역순으로 합친다.
location.href.split('?').map(
    x => x.split('&').filter(
        a => a.startsWith('http') || ( ['q','hl','tbm','start'].indexOf( a.split('=')[0] ) !=-1 )
    )
).map( b => b.join('&')).filter( a => a ).join('?')

이렇게 정리되는 주소로 재설정하는 것으로 마무리하여 북마클릿을 만든다.

객체의 생성이란 무엇인가, 플라톤의 이데아처럼 어딘가에 존재하는 것으로부터 현실에 필요한 속성을 부여하며 만들어지는 것인가, 아니면 옆에 있는 다른 객체를 복사하여 속성을 원하는 대로 고쳐쓰는 것인가?

개발자가 철학적인 사고를 하기 쉬운 것들은 이런 사고가 설계에 도움이 되기 때문아닌가?

c/c++의 struct, class를 생각하면, 텅빈 설계도 같은 것에서 메모리를 차지하면 만들어지는 것은 플라톤의 이데아 같은 것이 생각나고, prototype 객체를 우선만들고 복사한 다음 속성을 변경하여 쓰는 Javascript 같은 것은 후자와 같은 모양 아닐까?

몇 년 된 간단한 생각을 일단 적어 둠.

과거에 BASIC을 배울 땐 서브루틴과 함수가 분리되어 있었다. 포트란도 그러했다. 그러다가 C 언어를 배울 땐, return 유무에 따라 성질이 달라질 뿐 모두 함수로 통합이 됐다. 그러다가 JavaScript를 배울 땐, _객체_로서 함수 개념을 배우게 된다. 이 말은 함수와 자료가 통합되었다라고 말할 수 있다.

Javascript가 구현하는 함수객체는

function func1 ( ) {
  /* code */
}
  • func1()으로 쓰면 { ... } 안의 내용이 실행된다.
  • func1.abc = "def" 로 쓰면 func1를 객체 취급하여 'abc'라는 키를 추가한다.

이런 발상의 전환이 통합의 구체적인 모습이다.

  • func1.apply
  • func1.call
  • func1.bind

이런 함수는 func1 이라는 함수 객체가 만들어질 때 추가되는 속성이라고 생각하면된다.

짧은생각

Javascript와 python에서 제공하는 배열/리스트에서 특정 값의 위치를 찾는 비슷한 명령어로 각각 indexOf, index 라는 메소드가 있다. 이 둘은 찾지 못했을 때의 특성이 다른데,

  • Javascript: -1 을 return 한다.
  • Python: ValueError 예외를 발생한다.

언어를 다루다보면 이 둘은 묘한 선택의 순간을 만드는데, 전자는 매번 return 값을 확인해야하고, 후자는 앞 뒤로 예외 처리 구문을 넣어야한다.

Javascript/Java/C++ 계열에 익숙하다보면 정상적인 처리를 해야하는 상황을 예외를 발생시켜 점프하는 것에 약하지만 부담을 느낀다. 스택을 되돌리는 백그라운드 작업에 원하는 모든 리소스 해제는 잘 일어나는 것일까랄지, 단순히 값 하나만 비교하면 되는데 굳이 예외를 위해 백그라운드로 일어나는 작업을 컴파일러에게 맡겨야하나라든지 아무래도 부담스럽다.

Python 쪽에서는 없는 인덱스로 -1을 return 하는 순간 list[-1] 접근하는 것은 마지막 원소에 접근하는 방법이 되므로 아무래도 오류가 아닌 값처럼 인식될 수 있다. Javascript에서도 Array.slice(음수값)으로 python의 음수값 처럼 사용가능하지만 어쨌거나 기본적인 인덱스 연산으로는 접근이 불가능하므로 -1을 return 해도 큰 문제는 없다. python의 인덱스 연산은 -1에 대해 정상 작동하기 때문에 예외를 일으키는 방법밖엔 선택지가 없어 보인다.

추신

이 생각은

 50 function get_attrs(filter) {
 ...
 60   var attrs = [ 
 61       {name:"charset-configured", value: "utf-8"},
 62       {name:"charset-supported", value: "utf-8"},
          ....
 70   ];
 ...
 85   return attrs.filter(function (attr) {
 86     return ~filter.indexOf(attr.name)
 87   })
 88 }

이런 코드를 읽다가 86줄의 -1 에 대한 negate (~) bit operation을 보다가 꼬리를 문 생각임.

python dictionary 의 update 함수는 return 값으로 None을 되돌린다. 이거 dict 자신을 return 하면, copy와 더불어 더 간단한 method-chain으로 쉽게 쓸 수 있는 일이 많았을텐데, 왜 None인것이야?

요샌 일부러 마크다운편집이 가능하면 그걸 써보려고 노력한다. 오래 알아왔지만, 글 쓰는 일이 줄어들어 마크업이 할 일이 줄어 드니 영 느리다. 그래서 간단한 의식의 흐름대로 기술을 할 때, 쓰는 나와 읽는 당신의 관계에서 단순히 글의 연속으로 전달되는 것이 아닌, 강약이 전달되려면 강조 혹은 병렬적 정보전달은 필요한 것 아닌가 싶다.

머라고 주절 주절 쓰는 것은 사실 마크다운 문법을 테스트하기 위함이지 그 ^이상^도

이하

도 아니다. 어떤 문법은 것은 지원되지 않을 수도 있음을 고려해서 테스트해 본다.

문법을 익히는데 쓰는 글은

  1. 의미가 없어도 상관없고,
  2. 표현이 거칠거나
  3. 맥락을 뛰어 넘는 전개

가 이루어지기도 한다.

그래도 글의 모양을 갖추려면,

  • 가독성이 고려되어야하고
  • 적당한 길이어야하며
  • 무엇보다 독자가 실망하는 일이 없어야한다.

당신은 문법 연습을 하는 것이지, 실제 의미 있는 글을 쓰는 것이 아니기 때문이다.

오늘은 여기까지 연습하자.

npm, yarn을 설치했다면, bootstrap theme 변경을 위한 초간단 테스트를 할 수가 있다.

#!/bin/bash  
mkdir bootstrap-customizing  
cd bootstrap-customizing  
yarn init -y  
yarn add bootstrap node-sass  
mkdir -p public/css  
yarn node-sass --recursive --output public/css \  
  --source-map true --source-map-contents true \  
  node_modules/bootstrap/scss

bootstrap은 scss로 관리되고 있으므로 yarn(npm)으로 받아다가 바로 컴파일 해 볼 수가 있다. 위의 예는 directory 만들고, 빠르게 node package 를 초기화한 다음, bootstrap과 node-sass 컴파일러를 받은 후 directory to directory로 scss 파일을 css로 바꾸는 방법이다.

+ Recent posts