エクセル操作の様にbashを使う話 


作業ログ  

2024/06/01  

# エクセル操作の様にbashを使う話  
  
## 目的  
- 脱エクセル依存。エクセル一本足打法。エクセル以外でも簡単な集計できるように。  
- bashと親しむ。  
- 時短。  
- 冪等性あり、複数回集計でも楽になる。  
  
## 想定状況  
- tomcatのログから処理速度の平均を求めたい。  
- 一つ何十㎆もあるログを分析したい。  
- Windowsのエディタで開こうとしたら凄く時間かかった、メモリ不足で開けなかった。  
  
## 概要  
centosにtomcatを起動し、適当にアクセスしてログを生成。  
集計をbashで行ってみる。  
最後グラフにするところはエディタ操作で加工、エクセルのグラフ機能で。  


## 試験環境準備  
以下のVagrantfileで、tomcat起動まで実施する。  

:::note info
vagrantの説明が必要な場合は以下をご確認ください。
:::

https://qiita.com/kubo4ka/items/b731e9255688653b6fb6

:::note info
centos8のdnfがもうできなそうなので、boxをbento/centos-stream-9に変更してみます。
:::

```
mkdir bashtst
cd bashtst\
vagrant box add bento/centos-stream-9
vagrant init bento/centos-stream-9
```
Vagrantfileを以下の内容で更新し、vagrant up。

```ruby  
# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.provider "virtualbox" do |vb|
    vb.customize ["modifyvm", :id, "--vrde", "off"]
  end
  config.vm.define :centosstr9 do | centosstr9 |
    config.vm.boot_timeout = 600
    if Vagrant.has_plugin?("vagrant-vbguest")
      config.vbguest.no_remote = true
      config.vbguest.auto_update = false
    end
    centosstr9.vm.box = "bento/centos-stream-9"
    centosstr9.vm.hostname = "centosstr9"
    centosstr9.vm.network "private_network", ip: "192.168.55.101", :netmask => "255.255.255.0"
    centosstr9.vm.provision "shell", inline: <<-SHELL1

      # download and install tomcat.
      whoami ; echo "whoami exit with : $?"
      pwd ; echo "pwd exit with : $?"
      timedatectl set-timezone Asia/Tokyo
      date
      # sed -i -e "s/^mirrorlist/#mirrorlist/g" -e "s@^#baseurl=http://mirror@baseurl=http://vault@g" /etc/yum.repos.d/CentOS-*repo ; echo "exit with : $?"
      echo "dnf update. This may take a few minutes... please wait..."
      dnf -qy update ; echo "dnf -qy update exit with : $?"
      date
      sleep 5
      # sed -i -e "s/^mirrorlist/#mirrorlist/g" -e "s@^#baseurl=http://mirror@baseurl=http://vault@g" /etc/yum.repos.d/CentOS-*repo ; echo "exit with : $?"
      # sleep 5
      # echo "repo edit end"
      dnf -qy install java-11-openjdk-devel > /dev/null ; echo "dnf -qy install java-11-openjdk-devel exit with : $?"
      dnf -qy install httpd > /dev/null ; echo "dnf -qy install httpd exit with : $?"
      curl -O https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.89/bin/apache-tomcat-9.0.89.tar.gz > /dev/null 2>&1 ; echo "curl exit with : $?"
      ls -l `pwd`/*
      file *.gz
      tar xzf apache-tomcat-9.0.89.tar.gz -C /opt/
      useradd -s /sbin/nologin tomcat ; echo "useradd exit with : $?"
      chown -R tomcat:tomcat /opt/apache-tomcat-9.0.89
      ln -s /opt/apache-tomcat-9.0.89 /opt/tomcat
      chown -R tomcat:tomcat /opt/tomcat
      cat << EOS >> /etc/profile
### JAVA Setting Start
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk
export PATH=$PATH:$JAVA_HOME/bin
export CATALINA_HOME=/opt/tomcat
export PATH=$PATH:$CATALINA_HOME/bin
### JAVA Setting End
EOS

      cat << EOS > /etc/systemd/system/tomcat.service
[Unit]
Description=Apache Tomcat
After=network.target

[Service]
User=tomcat
Group=tomcat
Type=oneshot
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOS

      sudo sed -i -e "s/^SELINUX=enforcing$/SELINUX=disabled/g" /etc/selinux/config
      sudo sed -i 's/%b/%D/g' /opt/tomcat/conf/server.xml
      chmod 755 /etc/systemd/system/tomcat.service
      systemctl daemon-reload ; echo "systemctl daemon-reload exit with : $?"
      systemctl enable tomcat.service > /dev/null 2>&1 ; echo "systemctl enable tomcat.service exit with : $?"
      systemctl start tomcat.service ; echo "systemctl start tomcat.service exit with : $?"
      systemctl is-active tomcat.service ; echo "systemctl is-active tomcat.service exit with : $?"
      sleep 10
      curl -s localhost:8080 -o /dev/null -w 'result:%{http_code} ' ; echo "curl -s localhost:8080 exit with : $?"
      cat /opt/tomcat/logs/localhost_access_log.*
      echo 'centosstr9tomcat9 provision over.'

      date
      for i in `seq 1 10` ; do curl -s localhost:8080 -o /dev/null -w 'result:%{http_code} '; echo loop:${i} ; done
      date

SHELL1

  end
end
```

<details><summary>centos8の時のVagrantfile</summary>

```ruby  
# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.define :centos8 do | centos8 |
    config.vm.boot_timeout = 600
    if Vagrant.has_plugin?("vagrant-vbguest")
      config.vbguest.no_remote = true
      config.vbguest.auto_update = false
    end
    centos8.vm.box = "bento/centos-8.1"
    centos8.vm.hostname = "centos8"
    centos8.vm.network "private_network", ip: "192.168.55.101", :netmask => "255.255.255.0"
    centos8.vm.provision "shell", inline: <<-SHELL1

      # download and install tomcat.
      whoami
      timedatectl set-timezone Asia/Tokyo
      date
      sed -i -e "s/^mirrorlist/#mirrorlist/g" -e "s@^#baseurl=http://mirror@baseurl=http://vault@g" /etc/yum.repos.d/CentOS-*repo ; echo "exit with : $?"
      echo "dnf update. This may take a few minutes... please wait..."
      dnf -qy update ; echo "exit with : $?"
      date
      sleep 5
      sed -i -e "s/^mirrorlist/#mirrorlist/g" -e "s@^#baseurl=http://mirror@baseurl=http://vault@g" /etc/yum.repos.d/CentOS-*repo ; echo "exit with : $?"
      sleep 5
      echo "repo edit end"
      dnf -qy install java-11-openjdk-devel > /dev/null ; echo "exit with : $?"
      dnf -qy install httpd > /dev/null ; echo "exit with : $?"
      curl -O https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.89/bin/apache-tomcat-9.0.89.tar.gz
      tar xzf apache-tomcat-9.0.89.tar.gz -C /opt/
      useradd -s /sbin/nologin tomcat
      chown -R tomcat:tomcat /opt/apache-tomcat-9.0.89
      ln -s /opt/apache-tomcat-9.0.89 /opt/tomcat
      chown -R tomcat:tomcat /opt/tomcat
      cat << EOS >> /etc/profile
### JAVA Setting Start
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk
export PATH=$PATH:$JAVA_HOME/bin
export CATALINA_HOME=/opt/tomcat
export PATH=$PATH:$CATALINA_HOME/bin
### JAVA Setting End
EOS

      cat << EOS > /etc/systemd/system/tomcat.service
[Unit]
Description=Apache Tomcat
After=network.target

[Service]
User=tomcat
Group=tomcat
Type=oneshot
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOS

      sudo sed -i -e "s/^SELINUX=enforcing$/SELINUX=disabled/g" /etc/selinux/config
      sudo sed -i 's/%b/%D/g' /opt/tomcat/conf/server.xml
      chmod 755 /etc/systemd/system/tomcat.service
      systemctl daemon-reload ; echo "exit with : $?"
      systemctl enable tomcat.service ; echo "exit with : $?"
      systemctl start tomcat.service ; echo "exit with : $?"
      systemctl is-active tomcat.service ; echo "exit with : $?"
      sleep 10
      curl -s localhost:8080 -o /dev/null -w 'result:%{http_code} ' ; echo "exit with : $?"
      cat /opt/tomcat/logs/localhost_access_log.*
      echo 'centos8tomcat9 provision over.'

      date
      for i in `seq 1 10` ; do curl -s localhost:8080 -o /dev/null -w 'result:%{http_code} '; echo loop:${i} ; done
      date

SHELL1

  end
end

```

</details>

## 負荷発生  
ApacheBenchでログを稼ぎます。  
- 負荷はお手元の環境の様子見ながら調整願います。

```
su -
ab -n 1000000 -c 1000 http://localhost:8080/
```

## 簡単な集計  
```
cut -d " " -f 4,10 /opt/tomcat/logs/localhost_access_log.*.txt | cut -d ":" -f 2-4 | tail

cut -d " " -f 4,10 /opt/tomcat/logs/localhost_access_log.*.txt | cut -d ":" -f 2-4 | sort -nk 2 | tail

cut -d " " -f 4,10 /opt/tomcat/logs/localhost_access_log.*.txt | cut -d ":" -f 2-4 | awk '{m+=$2} END{print "avg "m/NR" ms";}'
```
## 違うURLへの負荷発生  
```
ab -n 1000000 -c 1000 http://localhost:8080/examples/
ab -n 1000000 -c 1000 http://localhost:8080/manager/
ab -n 1000000 -c 1000 http://localhost:8080/host-manager/
ab -n 1000000 -c 1000 http://localhost:8080/docs/
ab -n 1000000 -c 1000 http://localhost:8080/NG/
```
## 簡単な集計  
```
grep "/examples/" /opt/tomcat/logs/localhost_access_log.*.txt | cut -d " " -f 4,10 | cut -d ":" -f 2-4 | awk '{m+=$2} END{print "avg "m/NR" ms";}'

grep "/docs/" /opt/tomcat/logs/localhost_access_log.*.txt | cut -d " " -f 4,10 | cut -d ":" -f 2-4 | awk 'BEGIN {sum=0} {sum+=$2} END{print "avg "sum/NR" ms";}'

grep -v -e "/examples/" /opt/tomcat/logs/localhost_access_log.*.txt | wc -l
```
## 簡単な説明

ここまでで、サーバ上げて、
何かtomcat上げて、
何か大量にアクセスさせて、
何か大量にログ吐かせて、
で、この何十MB、何百MBもあるファイルを、、
Windows持っていってエクセルで解析だぁ！！
とはなりませんよね？

- そもそも、
  - 大容量テキストファイルはLinux上で処理した方が速くて楽だと思っています。
  - bashでは基本的にファイル内容確認はlessしましょう。
  - NW転送するならIO考慮してtarなりtar.gzしましょう。
  - どうしても分割したかったらsplitしましょう。
  - 内容見るなら初手はcatじゃなくてhead、tailしましょう。
  - 何行あるかはwc -lで確認しましょう。
  - gzなどでも、いちいち展開はせず、zcatなどとless、head、tail、wc -lを組み合わせましょう。
  - テキストなのかバイナリなのか何の形式なのか良く分からなかったらfileを使いましょう。

- エクセルの列選択の操作の様に、
  - cutを活用しましょう。
```
echo "range cut test" ; cat /opt/tomcat/logs/localhost_access_log.* | cut -d " " -f 1-9 | head
echo "field cut test" ; cat /opt/tomcat/logs/localhost_access_log.* | cut -d " " -f 6,7 | head
```

- エクセルのフィルタ操作の様に、
  - grep、sort、uniqを活用しましょう。
  - それぞれのコマンドの挙動を把握しましょう。
  - コマンド結果をパイプ「❘」で繋いで繋いで繋いで、意図する形になるように試行錯誤しましょう。
    - 特定文字列を含む、含まないと言うエクセルのフィルタ操作のように、grep、grep -vを活用しましょう。
```
grep "examples" /opt/tomcat/logs/localhost_access_log.`date +%Y-%m-%d --date '1 day ago'`* | wc -l
grep -v -e "NG" /opt/tomcat/logs/localhost_access_log.* | wc -l
```  
-
  -
    - 昇順、降順並べ替えと言うエクセルのフィルタ操作のように、sort、sort -r、sort -nを活用しましょう。

```
echo "sort test asc" ; cat /opt/tomcat/logs/localhost_access_log.* | cut -d " " -f 7 | sort | head
echo "sort test desc" ; cat /opt/tomcat/logs/localhost_access_log.* | cut -d " " -f 7 | sort -r | head
```
-
  -
    - 重複排除、重複件数カウントと言うエクセルの操作のように、uniq、uniq -cを活用しましょう。

```
echo "uniq test" ; cat /opt/tomcat/logs/localhost_access_log.* | cut -d " " -f 7 | sort | uniq
echo "uniq count test" ; cat /opt/tomcat/logs/localhost_access_log.* | cut -d " " -f 7 | sort -r | uniq -c
echo "uniq count sort desc as number test" ; cat /opt/tomcat/logs/localhost_access_log.* | cut -d " " -f 7 | sort -r | uniq -c | sort -nr
```

- エクセルの一括置換の操作の様に、
  - sedを活用しましょう。

```
echo "sort test desc, sed replace all" ; cat /opt/tomcat/logs/localhost_access_log.* | cut -d " " -f 7 | sort -r | sed "s/\///g" | head
```

- エクセルのavg関数の様に、
  - awkを活用しましょう。

```
echo "    count lines all logs" ; cat /opt/tomcat/logs/localhost_access_log.* | wc -l
echo "    speed sum all logs" ; cat /opt/tomcat/logs/localhost_access_log.* | cut -d " " -f 10 | awk '{sum+=$1} END {print(sum)}'
echo "    speed avg all logs" ; cat /opt/tomcat/logs/localhost_access_log.* | cut -d " " -f 10 | awk '{sum+=$1} END {print(sum/NR)}'
# bc <<< "scale=2; 2669604 / 9030039" | xargs printf "%.2f\n" # chk example
```

- エクセルのマクロ機能の様に、
  - ttlを活用しましょう。

```
HOSTADOR = 'localhost'
USERNAME = 'vagrant'
PASSWORD = 'vagrant'
WINDOWTITLE = 'login ttl test'

COMMAND = HOSTADOR
strconcat COMMAND ':2222 /ssh /2 /auth=password /user='
strconcat COMMAND USERNAME
strconcat COMMAND ' /passwd='
strconcat COMMAND PASSWORD

connect COMMAND

wait USERNAME
  settitle WINDOWTITLE
  sendln 'echo "ttl login ok"'
end
```

  - ttlではクオートのエスケープに気を付けましょう。

```
HOSTADOR = 'localhost'
USERNAME = 'vagrant'
PASSWORD = 'vagrant'
WINDOWTITLE = 'chk_tps'

COMMAND = HOSTADOR
strconcat COMMAND ':2222 /ssh /2 /auth=password /user='
strconcat COMMAND USERNAME
strconcat COMMAND ' /passwd='
strconcat COMMAND PASSWORD

connect COMMAND

wait USERNAME
  settitle WINDOWTITLE
  sendln 'su -'

wait 'Password'
  sendln PASSWORD

wait 'root'
  sendln 'echo "    tps today to examples top 10" ; grep "examples" /opt/tomcat/logs/localhost_access_log.`date +%Y-%m-%d`* | cut -d " " -f 4 | cut -d "/" -f 3 | cut -d ":" -f 2-4 | sort | uniq -c | sort -nr | head'

wait 'root'
  sendln 'echo "    tps yesterday to manager top 10" ; grep "manager" /opt/tomcat/logs/localhost_access_log.`date +%Y-%m-%d --date ' #$27 '1 day ago' #$27 '`* | cut -d " " -f 4 | cut -d "/" -f 3 | cut -d ":" -f 2-4 | sort | uniq -c | sort -nr | head'

wait 'root'
  sendln 'echo "    tps all logs to manager top 10" ; grep "manager" /opt/tomcat/logs/localhost_access_log* | sed "s/\[/tps@/g" | cut -d " " -f 4 | sort | uniq -c | sort -nr | head'

end
```

  - その他色々ttlの断片を試してみましょう。watchは便利です。

```
wait 'root'
  sendln 'echo "    count url all logs" ; cat /opt/tomcat/logs/localhost_access_log.* | cut -d " " -f 7 | sort | uniq -c'
```

```
wait 'root'
  sendln 'echo "    count target lines" ; cat /opt/tomcat/logs/localhost_access_log.* | wc -l'

wait 'root'
  sendln 'echo "    speed histogram all logs 10ms order" ; cat /opt/tomcat/logs/localhost_access_log.* | cut -d " " -f 10 | sort -nr | awk ' #$27 '{printf("%d\n", $1 / 10)}' #$27 ' |awk ' #$27 '{printf("%d\n", $1 * 10)}' #$27 ' | uniq -c'
```

```
wait 'root'
  sendln 'watch -n5 ' #$27 'echo "    tps now" ; cat /opt/tomcat/logs/localhost_access_log.`date +%Y-%m-%d`* | cut -d " " -f 4 | cut -d "/" -f 3 | cut -d ":" -f 2-4 | sort | uniq -c | sort -nr | head' #$27 ''
```

- 他にも色々探してみましょう。
  - 複数連続したスペースを一つにまとめるならsedを使ったり。
  - tps集計、処理速度1000msごと集計など、同じように対応可能です。  
  - grep結果をパイプ「❘」でlessに渡すのも便利です。検索とかvi操作ができるのでlessは安全で便利です。  

- 最終的にはエクセルに必要な列として、タイムスタンプとtpsや処理時間だけを貼り付けて、グラフにして可視化したりしましょう。エクセル説明は略。

- 毎回全部エクセルでやるより、ttlにしてbashで集計とすると、変化を追いやすく、冪等性もあり、メリットは大きいと思います。
- ワンライナーの書き方によってはsortなど要求リソースが多くなるかもなので、作成の際はtopを別窓でかけておいて、自分のttlの影響確認はしておきましょう。

以上