過去のGitHub Actionsのbuild時間を取得して集計してグラフにする

継続的にメンテナンスするのではなくて、雑な使い捨てでいいならshellscriptとjq職人芸でいけるので頑張ってしまったけれど、継続的にやるならもっと違うもので書いた方がメンテナンスしやすいと思います。

細かい部分はいくらでも改善の余地があるとは思いますが、とりあえず動いたのでヨシ・・・!?

以前も多少似たような何か作ったけど、こういうの誰か既にもっと綺麗に作ってないんですかね。

xuwei-k.hatenablog.com

GitHub Actionsのログはデフォルトでは90日保存されてるはずなので、その程度の期間をなんとなく集計したいだけならば、こうやって後から集計するだけで十分ですね。 もちろん、yamlの内部の構造がすごく変わっていると集計が難しいか実質不可能になるリスクはありますが。

もっとしっかり計測したいならば、buildした時点で専用の場所に綺麗に記録して、他のもっとリッチな仕組みで集計や表示した方がいいでしょう。

下記の例では、あくまで特定のstepの時間だけ計測していますが、(例えば、Javaのsetupやcacheのdownloadを除いてcompile時間、あるいはtest時間だけ計測したい、など)

どこを集計したいのか?は場合によると思うので、そこはいい感じに頑張ってください。

#!/bin/bash

set -eu

rm -rf run_ids.txt

# loopする数は各自調整してください
for i in $(seq 1 10)
do
  echo "$i"
  # pull reqなど含めて全部集計したいならば、branch指定は無しだったり、代わりに他のfilterでもいいが
  # その場合 "成功したjob" などでどこかでfilterしないとうまく出なそう
  gh api "repos/ここにorganizationかuser名/ここにproject名/actions/runs?branch=計測したいbranch&per_page=100&page=${i}" > runs.json
  jq '.workflow_runs.[] | select(.path == ".github/workflows/計測したいCIのファイル.yml") | .id' < runs.json >> run_ids.txt
done

rm -rf times.txt

while read -r p; do
  if [ -e "${p}.json" ]
  then
    # 途中からリトライする場合など考えて、ファイル存在してたらAPI呼び出しスキップ
    echo "${p}.json exists"
  else
    echo "${p}.json not exists"
    gh api "repos/ここにorganizationかuser名/ここにproject名/actions/runs/${p}/jobs" > "${p}.json"
  fi
  jq '.jobs.[] | select(.name == "ここにjobの名前") | .steps.[] | select(.name | contains("計測したいstepを頑張ってfilter")) | (.completed_at | fromdate) - (.started_at | fromdate)' < "${p}.json" >> times.txt
done < run_ids.txt

# 数値の一覧欲しいならここまで

# ここからGitHubで直接みれる方式にformat

# このままだと順番逆なので
tail -r times.txt > times_reversed.txt

cat << EOM > chart.md
\`\`\`mermaid

xychart-beta
EOM

printf %s "  line [" >> chart.md

while read -r p; do
  # skipされてると0が混ざる場合があるので除外
  if [ "$p" != "0" ]
  then
    printf %s "$p" >> chart.md
    printf %s "," >> chart.md
  fi
done < times_reversed.txt

# loop内で付与した余計な最後の "," を削除
truncate -s-1 chart.md

echo "]" >> chart.md
echo '```' >> chart.md

これを例えばscalazで行う場合の例が以下で

#!/bin/bash

set -eu

rm -rf run_ids.txt

for i in $(seq 1 10)
do
  echo "$i"
  gh api "repos/scalaz/scalaz/actions/runs?branch=master&per_page=100&page=${i}" > runs.json
  jq '.workflow_runs.[] | select(.path == ".github/workflows/ci.yml") | .id' < runs.json >> run_ids.txt
  wc -l run_ids.txt
done

rm -rf times.txt

while read -r p; do
  if [ -e "${p}.json" ]
  then
    echo "${p}.json exists"
  else
    echo "${p}.json not exists"
    gh api "repos/scalaz/scalaz/actions/runs/${p}/jobs" > "${p}.json"
  fi
  jq '.jobs.[] | select(.name == "test (2, rootJVM, 8)") | .steps.[] | select(.name | contains("Test/compile")) | select(.name | contains("Run ./sbt")) | (.completed_at | fromdate) - (.started_at | fromdate)' < "${p}.json" >> times.txt
done < run_ids.txt

# ここから後ろ同じなので省略

すると、このようなものが出来上がります。

xychart-beta
  line [163,164,184,172,176,171,170,173,232,177,210,187,211,237,224,174,175,163,162,245,230,234,234,169,173,204,185,173,177,181,171,210,175,168,166,171,181,213,174,260,264,196,167,164,177,173,168,167,185,180,215,219,209,171,203,228,229,216,173,184,191,230,177,170,172,170,190,211,184,166,233,168,162,171,226,235,165,215,167,161,163,183,179,169,226,192,179,168,165,172,171,167,177,227,200,249,178,184,177,174,213,171,177,230,235,259,164,200,251,176,176,176,172,163,176,171,181,163,204,172,208,171,253,180,210,241,170,173,162,168,175,181,207,212,181,163,177,164,177,247,168,178,174,180,210,207,179,241,207,228,174,204,231,246,182,174,180,163,237,188,169,166,180,208,175,165,226,179,192,167,180,176,176,180,169,172,228,257,175,177,171,163,175,174,197,188,176,189,178,238,176,192,167,232,174,230,211,244,216,167,247,211,244,179,210,240,247,168,208,225,208,181,208,265,179,180,171,218,179,199,178,221,174,179,176,168,227,211,201,180,180,221,220,173,181,136,136,169,222,156,178,170,136,187,134,135,174,162,140,141,137,136,136,139,134,134,139,141,138,134,149,137,139,135,136,137,135,136,138,125,137,136,132,137,141,136,135,134,135,133,136,139,142,138,139,140,135,133,137,134,139,136,149,136,133,142,136,135,137,133,137,134,134,137,133,146,139,135,136,135,137,138,134,135,135,137,135,143,137,139,137,142,133,141,139,135,138,135,141,140,137,134,134,140,136,135,139,135,141,135,137,138,136,140,134,137,136,138,141,132,134,132,138,141,134,137,136,134,129,138,133,136,136,142,134,139,136,136,135,136,138,146,136,137,143,135,140,138,136,137,140,134,134,143,136,139,139,140,138,142,128,133,134,146,136,141,149,149,152,150,149,154,148,150,147,150,148,151,150,155,152,152,147,148,150,147,149,149,150,150,150,148,150,148,151,149,151,152,149,147,148,154,150,152,148,149,151,151,153,147,148,156,155,152,143,151,151,153,151,147,146,150,141,142,152,145,156,150,150,141,146,146,142,147,156,141,145,140,147,143,144,142,145,151,143,152,140,142,157,146,144,143,145,148,150,146,144,142,146,144,150,149]

少し前に書きましたが、GitHubはmermaidの形式を大体?全部?サポートしてるので

https://mermaid.js.org/syntax/xyChart.html

これをGitHubmarkdownが貼り付け可能な任意の場所に貼り付けると勝手に折れ線グラフにしてくれます。

あるいは、そもそもgithub actions上でこれを実行すれば、tokenも無料で自動で手に入るし GITHUB_STEP_SUMMARY に直接書き込めば表示や保存まで全自動で最高便利なので、(細かい部分を変えてローカルで試行錯誤やdebugするのではないなら)その方がよいでしょう。

scalaz-graph

1回、明らかに速くなって、その後若干遅くなったタイミングがあるけれど、なんだろう、何かコマンド変えたかな?変えたかも? (覚えてない。そんなに重要ではないので調べる気がない)