2011年1月25日火曜日

topコマンドのログ解析(Python手習い) その2

その後OrderedDictというクラスがcollectionsモジュールにあることが判明したため、これを使ってもうちょっとマシなファイルハンドル操作を実装してみた。

ついでにファイルオープン時に致命的なバグがあったため、合わせて修正。

from collections import OrderedDict
from datetime import datetime, timedelta
from functools import reduce
from os.path import exists
from re import match, search, split
from sys import argv
from shutil import rmtree
from os import makedirs

MAX_FILE_HANDLE = 500
WORK_DIR = 'work'

if len(argv) != 3:
    print("\nUsage:\npython", argv[0], "logfile yyyy/mm/dd")
    exit()

rmtree(WORK_DIR, True)
if not exists(WORK_DIR):
    makedirs(WORK_DIR)

mydate = datetime.strptime(argv[2], '%Y/%m/%d')
mydateStr = mydate.strftime('%Y/%m/%d ')

try:
    f = open(argv[1])
    mem = open(WORK_DIR + "/mem.log", 'w')
    mem.write("time,mem av,mem used,mem free,mem shard,mem buff,mem actv, mem in_d,swap av,swap used,swap free,swap cached\n")
    processes = OrderedDict()
    maxtime = ""

    for line in f:
        line = line.strip()
        if match(r"^\d\d:\d\d:\d\d", line):
            if line[0:2] == "00" and timestamp[0:2] != "00":
                mydate = mydate + timedelta(1)
                mydateStr = mydate.strftime('%Y/%m/%d ')
            timestamp = line[0:8]
        elif match(r"^Mem", line):
            data = split(r"\s+", line)[1:9:2]
        elif match(r"^\d+k", line):
            data = data + split(r"\s+", line)[0:5:2]
        elif match(r"^Swap:", line):
            data = data + split(r"\s+", line)[1:8:2]
            prefix = mydateStr + timestamp
            result = reduce((lambda x,y: x + ',' + y.rstrip('k')), data, prefix)
            mem.write(result + "\n")
        elif search(r"java", line):
            data = split(r"\s+", line)
            pid = data[0]

            process = processes.get(pid)
            if process == None:
                while len(processes) >= MAX_FILE_HANDLE:
                    processes.popitem(last=False)[1].close()
                filename = WORK_DIR + "/Pid-" + pid + ".log"
                if exists(filename):
                    process = open(filename, 'a')
                else:
                    process = open(filename, 'w')
                    process.write("time,PID,USER,PRI,NI,SIZE,RSS,SHARE,STAT,%CPU,%MEM,TIME,CPU,COMMAND\n")
            else:
                del processes[pid]
            processes[pid] = process

            prefix = mydateStr + timestamp
            result = reduce((lambda x,y: x + ',' + y), data, prefix)
            process.write(result + "\n")
            if data[10] > maxtime:
                maxtime = data[10]
                maxpid = pid

    print("maxpid:", maxpid)

finally:
    if f != None:
        f.close()
    if mem != None:
        mem.close()
    map(lambda x: x.close(), processes)

キモは51~63行あたり。

OrderedDictは名が示すとおり、(key, value)セットの追加順序を記憶する辞書型である。ただし、一度追加した(key, value)は、valueの更新が行われても順序を入れ替えない。今回はLRUを実装しなければならないため、既に追加されている(key, value)にアクセスした場合は必ず一度削除してから再度追加するようにしている。

さらに、もし管理しているファイルハンドルがMAX_FILE_HANDLEを超えそうになったら、一番使われていないファイルハンドルを取得しクローズ処理を行なっている(54行目)。popitemメソッドは、引数がtrueの場合はLIFO、falseの場合はFIFOとして動作する。

前回バグっていたのを直したのは56~60行目あたり。前回はopen関数の第2引数を常に'w'と指定していたため、ファイルハンドルが増えたために一旦クローズしてしまったファイルについて、再度書きこみを行おうとした場合にクローズ前までのデータを消してしまっていた。オープン時にファイルが存在する場合は追記('a')するように修正。

150MB程度のログファイルの処理に、僕の非常に非力なマシン(PentiumM 1.2GHz 752MB RAM)で約2分。生成されるファイル数は4,000あまりというところ。

時間があったら、次はもう少しモジュール化してみたい。

0 件のコメント:

コメントを投稿