TOPコマンド結果をUSERで抽出してCSV出力する

# はじめに
様々な理由から、LinuxのTOPコマンドでのパフォーマンス測定を余儀なくされている同志の方々に送ります。

# 目的
LinuxのTOPコマンドをバッチモードで出力したファイルを、
python3を使ってcsvファイルに整形します。

```:top.csv（出力例）
timestamp,PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,COMMAND
10:00:00,1000,root,20,0,160000,2000,1640,R,10.0,0.2,0:00.02,top
10:00:00,3400,httpd,20,0,150000,2000,1700,S,0.0,0.3,0:07.98,nginx:
```

### 用意するもの
TOPのバッチモードで出力したファイルを用意します

```shell:TOPコマンドファイル
top -b -d 20 -c > top_org.log
```

```:top_org.log
top - 10:00:00 up 1 days, 44 min,  2 users,  load average: 0.00, 0.01, 0.01
Tasks: 100 total,   1 running, 99 sleeping,   0 stopped,   1 zombie
%Cpu(s):  2.0 us,  4.0 sy,  0.0 ni, 80.0 id,  5.0 wa,  0.0 hi,  2.0 si,  0.0 st
KiB Mem :  1000000 total,    60000 free,   700000 used,   200000 buff/cache
KiB Swap:  2000000 total,    90000 free,  2000000 used.    70000 avail Mem 

PID   USER     PR   NI VIRT     RES    SHR  S  %CPU %MEM  TIME+    COMMAND
1000 root      20   0  160000   2000   1640 R  10.0  0.2   0:00.02 top -b -d 20 -c
4500 apache    20   0  440000   1000      8 S   0.0  0.1   0:00.01 /usr/sbin/httpd
17000 mysql     20   0 1130000   7000      0 S   0.0  0.7  20:00.00 /usr/sbin/mysqld
    2 root      20   0       0      0      0 S   0.0  0.0   0:00.00 [kthreadd]
    4 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 [kworker/0:0H]
    6 root      20   0       0      0      0 S   0.0  0.0   0:00.00 [ksoftirqd/0]
・・・（以下略）
```


# お急ぎの方へ
本記事末尾に、分割されていないソースコードがあります。そちらを参照下さい。
その際、以下の部分だけは変更して下さい。
### 毎回変更する部分
・抽出対象のUSER
・「TOPコマンドファイル」の場所
・「TOPコマンドファイルを整形したCSVファイル」の場所
を以下の部分で設定しています。
希望条件や、環境に合わせて変更してください。

```python:.py
'''
設定情報
'''
# 抽出するUSER（未設定だと全USERで抽出。,区切りで設定）
user_array =['root'] 

# カレントDir取得
current_dir = os.getcwd()
# inputファイル名(Full PATH)
input_file_name=f"{current_dir}\\before\\top_org.log"
# outputファイル名(Full PATH)
output_file_name=f"{current_dir}\\after\\top_csv.csv"
```

# ソースコード
### 解説
1. 必要情報の設定
2. TOPコマンドファイルの読み込み
3. 先頭カラムに使うtimestampを抽出
4. 対象USERのプロセスを抽出
5. CSVファイル出力

という順番で進んでいきます。
　

####  1. 必要情報の設定
抽出対象となるUSERや、TOPコマンドファイルが配置している場所の指定を行います。

```python:.py
# -*- coding: utf-8 
import re
import os
import csv

'''
設定情報
'''
# 抽出するUSER（未設定だと全USERで抽出。,区切りで設定）
user_array =['root'] 

# カレントDir取得
current_dir = os.getcwd()
# inputファイル名(Full PATH)
input_file_name=f"{current_dir}\\before\\top.log"
# outputファイル名(Full PATH)
output_file_name=f"{current_dir}\\after\\top.log"

# 1回のTOPコマンド結果の中でプロセス行の始まりは何行目か
process_row_start = 8
# TOPコマンド結果でUSERカラムの位置は何カラム目か
user_column = 2
# TOPコマンド結果でCOMMANDカラムの位置は何カラム目か
command_column = 12

```
　
#### 2. TOPコマンドファイルの読み込み

```python:.py
    # inputファイル読み込み
    f = open(f"{input_file_name}", "r")
    toplog_lines = f.readlines()
    f.close()
```
inputファイル（TOPコマンドファイル）を、１行ずつ読み込んで変数`toplog_lines`に格納します。


#### 3. 先頭カラムに使うtimestampを抽出
```python:.py
    # timestamp正規表現
    r_top_timestamp = re.compile("top - ([0-9:]+)+")

    timestamp_list = []
    roop_cnt = 0
    for toplog_line in toplog_lines :
        # 正規表現でtimestamp一致したらTOPコマンドファイルの行番号を行番号を配列に入れる
        if r_top_timestamp.search(toplog_line) != None:
            timestamp_list.append(roop_cnt)
        roop_cnt += 1
```
ここでは、timestamp（`top - 10:00:00 up 1 days, 44 min,  2 users,  load average: 0.00, 0.01, 0.01`）が、
TOPコマンドファイルの何行目に入っているのか確認し、`timestamp_list`に行番号を格納しています。


#### 4. 対象USERのプロセスを抽出
まずは、プロセス抽出ループの中で必要な変数を定義します。

```python:.py
# TOPコマンドでプロセス行の始まりの位置
process_row_start = 8
# TOPコマンドでプロセス行のUSERカラムの位置
user_column = 2
# TOPコマンドでプロセス行のCOMMANDカラムの位置
command_column = 12
# 1回のTOPコマンド行数カウント用変数
rows_count = 0
# timestamp格納用変数
tmp_timestamp = ''
# csvに出力する文字列を格納するための配列（tmp）
tmp_output_csv_list = []
# csvに出力する文字列を格納するための配列（実際の書き出しに利用）
output_csv_list = []
```
　
次はTOPコマンドファイルを１行ずつループします。
以下を実施しています。
・TOPコマンド実施時間（タイムスタンプ）を抽出
・プロセスを１カラムずつ確認し抽出対象USERであれば、csv出力対象とする

```python:.py
    for toplog_line in toplog_lines :
        # 1回のTOP行数カウント +1
        rows_count +=1
        # 行末尾の改行を削除
        toplog_line = toplog_line.rstrip()

        # 改行のみの場合、次の行へ
        if not toplog_line :
            continue

        # タイムスタンプ行の場合、リストに追加し、次の行へ
        if r_top_timestamp.search(toplog_line) != None:
            print(toplog_line)
            tmp_timestamp = r_top_timestamp.search(toplog_line).group(1)
            # 1回のTOPコマンド結果、行数カウント初期化
            rows_count = 1
            continue

        # プロセス行の場合、Userが一致すれば抽出する
        if rows_count >= process_row_start:
            column_number = 0
            row_data = toplog_line.split(" ")
            # タイムスタンプを設定
            tmp_output_csv_list = [tmp_timestamp]

            # 行末まで繰り返す
            for column_data in row_data:
                if column_data =="":
                    # 空白の場合次のカラムへ
                    continue
                column_number += 1

                # COMMANDカラムまでのデータをtmpリストに入れる
                if column_number <= command_column :
                    tmp_output_csv_list.append(column_data)
                else :
                    continue

                # 抽出対象レコードかをチェックする
                # 抽出対象USER、または、user_array指定なしのとき抽出対象レコード選定
                if column_number == user_column :
                    user_key_flg = True
                    for key_user in user_array:
                        # 抽出対象USER、または、user_array指定なしのとき抽出対象レコード
                        if ( str(column_data) == key_user ):
                            user_key_flg = True
                            break
                        else:
                            user_key_flg = False

                    if user_key_flg == True :
                        pass
                    else:
                        break
            # for-else ：条件を満たしたときのみ、CSV抽出リストに追加する
            else:
                output_csv_list.append(tmp_output_csv_list)

```

#### 5. CSVファイル出力
csv抽出リスト `output_csv_list` の中身をcsvに出力します。

```python:.py
    # CSV書き出し
    csv_header = ['timestamp','USER','PR','NI','VIRT','RES','SHR','%CPU','%MEM','TIME+','COMMAND']
    with open(f'{output_file_name}','w') as f:
        csv_writer = csv.writer( f, delimiter = ',', lineterminator = '\n') 
        csv_writer.writerow(csv_header)
        csv_writer.writerows(output_csv_list)   
```
`csv_writer = csv.writer( f, delimiter = ',', lineterminator = '\n') `の、
`delimiter = ','`　を、
`delimiter = '\t'` にすれば、tsvファイル（タブ区切りファイル）にもなります。
お好みでどうぞ。

## ソースコードまとめ
```python:.py
'''
Topコマンドのプロセス行をUSERで抽出しcsvファイルに出力する
timestamp,USER,PR,NI,VIRT,RES,SHR,%CPU,%MEM,TIME+,COMMAND
'''
import re
import os
import csv

'''
設定情報
'''
# 抽出するUSER（未設定だと全USERで抽出。,区切りで設定）
user_array =['apache','httpd']

# カレントDir取得
current_dir = os.getcwd()
# inputファイル名(Full PATH)
input_file_name=f"{current_dir}\\before\\top.log"
# outputファイル名(Full PATH)
output_file_name=f"{current_dir}\\after\\top.log"


# TOPコマンドでプロセス行の始まりの位置
process_row_start = 8
# TOPコマンドでプロセス行のUSERカラムの位置
user_column = 2
# TOPコマンドでプロセス行のCOMMANDカラムの位置
command_column = 12

# timestamp正規表現
r_top_timestamp = re.compile("top - ([0-9:]+)+")

######## main 処理 #############
if __name__ == '__main__' :
    '''----------------------
       toplogファイル読み込み
    ----------------------'''
    # inputファイル読み込み
    f = open(f"{input_file_name}", "r")
    toplog_lines = f.readlines()
    f.close()

    '''----------------------
       timestamp行の抽出
    ----------------------'''
    timestamp_list = []
    roop_cnt = 0
    for toplog_line in toplog_lines :
        # 正規表現でtimestamp一致したら行番号を配列に入れる
        if r_top_timestamp.search(toplog_line) != None:
            timestamp_list.append(roop_cnt)
        roop_cnt += 1

    '''--------------------------
       対象Userのプロセスを抽出
    --------------------------'''
    rows_count = 0
    tmp_timestamp = ''
    tmp_output_csv_list = []
    output_csv_list = []

    for toplog_line in toplog_lines :
        # 1回のTOP行数カウント +1
        rows_count +=1
        # 行末尾の改行を削除
        toplog_line = toplog_line.rstrip()

        # 改行のみの場合次の行へ
        if not toplog_line :
            continue

        # タイムスタンプ行の場合、リストに追加する
        if r_top_timestamp.search(toplog_line) != None:
            print(toplog_line)
            tmp_timestamp = r_top_timestamp.search(toplog_line).group(1)
            # 1回のTOPコマンド結果、行数カウント初期化
            rows_count = 1
            continue

        # プロセス行の場合、Userが一致すれば抽出する
        if rows_count >= process_row_start:
            column_number = 0
            row_data = toplog_line.split(" ")
            # タイムスタンプを設定
            tmp_output_csv_list = [tmp_timestamp]

            # 行末まで繰り返す
            for column_data in row_data:
                if column_data =="":
                    # 空白の場合次のカラムへ
                    continue
                column_number += 1

                # COMMANDカラムまでのデータをtmpリストに入れる
                if column_number <= command_column :
                    tmp_output_csv_list.append(column_data)
                else :
                    continue

                # 抽出対象レコードかをチェックする
                # 抽出対象USER、または、user_array指定なしのとき抽出対象レコード
                if column_number == user_column :
                    user_key_flg = True
                    for key_user in user_array:
                        # 抽出対象USER、または、user_array指定なしのとき抽出対象レコード
                        if ( str(column_data) == key_user ):
                            user_key_flg = True
                            break
                        else:
                            user_key_flg = False

                    if user_key_flg == True :
                        pass
                    else:
                        break
            # for文をbreak以外で抜けたらCSV抽出リストに追加する
            else:
                output_csv_list.append(tmp_output_csv_list)


    '''--------------------------
       ファイル書き込み
    --------------------------'''
    # CSV書き出し
    csv_header = ['timestamp','USER','PR','NI','VIRT','RES','SHR','%CPU','%MEM','TIME+','COMMAND']
    with open(f'{output_file_name}','w') as f:
        csv_writer = csv.writer( f, delimiter = ',', lineterminator = '\n') 
        csv_writer.writerow(csv_header)
        csv_writer.writerows(output_csv_list)
```
2020/07/09 `# -*- coding: utf-8` はpython3では非推奨でしたので削除しました。
参考記事：https://qiita.com/KEINOS/items/6efc1147b917d7811b5b


以上です。


## 感想
ツールもなく、TOPコマンド結果だけ渡されて、「まとめといて」と言われる方々の負担が減りますように。