GitLab APIを利用したメトリクス取得について
はじめに
現代のソフトウェア開発において、効率的なプロジェクト管理や継続的インテグレーション・デリバリー(CI/CD)を実現するためのツールは数多く存在します。
これらのツールは、開発者の生産性を向上させ、チームの状態を可視化するために非常に便利です。
今回はメトリクス取得の練習として、GitLab※の情報を基にメトリクスの選定からGitLab APIを活用したツールの作成までを行い、とある課題に関するチームの状況を把握してみました。
※GitLabはソフトウェア開発のライフサイクル全体をサポートするためのオープンソースのDevOpsプラットフォームで、リポジトリ管理、継続的インテグレーション・デリバリー(CI/CD)、コードレビュー、バグ追跡、プロジェクト管理などの機能を一元的に提供します。
目標
過去私が参画したチームの課題として存在した、
「スプリント内の優先度順にバックログが完了せず、仕掛かり中のストーリーが複数並行して存在し、リソースのマージまでスプリント後半に集中してしまうことがある」
この課題を例として、その状況を把握できるようなメトリクスを選び、確認するツールの作成を目標とします。
前提
利用するツールや運営方法により、有効な情報の取得方法が変わるため、
今回は以下のルールに基づいて、スクラムチームが運営されていることを前提とします。
- Redmine※のかんばんツールでスプリント期間のストーリー、タスクを管理している。
- ライブラリ管理にGitLabを利用。グループ配下に複数のプロジェクトの構成で管理しており、
スプリント内で複数のプロジェクトを更新することがある。 - 実装タスクには対をなすGitLabのマージリクエストが存在し、実装タスクの着手時にマージリクエストを作成する。
- レビューを経て変更内容の承認がされるとマージリクエストが完了となり、redmineの実装タスクも完了となる。
※Redmineは、プロジェクト管理やバグ追跡、タスク管理を行うためのオープンソースのウェブベースのツールで、柔軟なカスタマイズ性と多機能なプラグインサポートを特徴としています。
メトリクスの選定
前提を踏まえると、マージリクエストの作成から完了(マージ済み)までの時間が長い場合、タスクが仕掛中となっている時間が長く、ストーリーも長く仕掛中になっていることが予想されます。
よって、今回は 「マージリクエストがマージ済みになるまでの平均時間」 をメトリクスとして採用します。
加えて、どのようなマージリクエストが時間がかかっているかを確認できるように、
「マージ済みになるまでの時間が長い、上位5つのマージリクエスト」 も合わせて出力するようにします。
メトリクスの算出方法
GitLabのマージリクエストには作成日(created_at)と更新日(updated_at)、ステータス(state)が含まれているため、ステータスが「マージ済み(merged)」となっているマージリクエストの更新日から作成日を引くことで、マージリクエストがマージされるまでの期間を算出できます。
算出の元となる情報は、GitLabが提供しているAPIを利用して取得することが可能です。
今回は、以下のマニュアルを参考にして必要な情報が取得できるAPIを呼び出すことにします。
GitLab 日本語マニュアル-APIリソース
例:マージ済みのマージリクエスト一覧
リクエスト例
GET https://gitlab.example.com/gitlab/api/v4/merge_requests?state=merged
レスポンス例
[
{
"id": 2,
"iid": 1,
"project_id": 3,
"title": "test",
"description": "test_description",
"state": "merged", # ステータス
~
"created_at": "2024-04-29T08:46:00Z", # 作成日
"updated_at": "2024-04-29T14:46:00Z", # 更新日
~
}
]
一つのマージリクエストだけであれば、APIを一回呼び出すだけでよいですが、今回は複数のマージリクエストの情報が必要となります。そこでPythonでAPI呼び出しとデータの整形、集計を行うプログラムを作成し、一度の実行で必要な情報を取得できるようにします。
実行環境
GitLab : 9.4.1※
Python : 3.9.6
IntelliJ IDEA : 2024.3.2.2(Community Edition)
※古いバージョンですが、既に構築済みで自由に利用できる環境であったため、こちらを利用しています。最新のバージョンのほうが新たな機能があり、必要な情報を取得しやすい可能性が高いです。
実装例
PythonでGitLabのAPIを呼び出し、結果を整形、集計し、
マージリクエストがマージ済みになるまでの平均時間を算出する実装例です。
- 環境に応じた定義を設定
import time
from datetime import datetime, timedelta
import requests
access_token = 'ABCDEFGHIJKELMN' # GitLabのプライベートトークンAPIキー(ご自分の環境のキーに書き換えてください。)
group_id = BBB # グループID(ご自分の環境のグループIDに書き換えてください。)
date_format = "%Y-%m-%dT%H:%M:%S.%f%z" # 日付フォーマット
gitlab_url = 'https://gitlab.example.com/gitlab/api/v4' # GitLab_url(ご自分の環境にURLに書き換えてください。)
created_after = '2025-03-10T00:00:00Z' # 取得期間指定(以後)
created_before = '2025-03-21T00:00:00Z' # 取得期間指定(以前)
headers = {'PRIVATE-TOKEN': access_token} # APIリクエストヘッダ
merge_request_counts = 0 # マージリクエスト数
merge_request_item_list = [] # プロジェクト横断のマージリクエスト項目リスト
project_name_index = 0 # マージリクエストリストのプロジェクト名のインデックス
merge_request_title_index = 1 # マージリクエストリストのマージリクエストタイトルのインデックス
date_diff_index = 2 # マージリクエストリストのマージリクエストのマージまでの期間のインデックス
GitLabのプライベートトークンAPIキーについては、こちらを参考にご確認ください。
グループIDについては、こちらのAPIを利用して確認できます。
2.グループ配下のプロジェクト配下のマージ済みのマージリクエスト一覧を取得。
各マージリクエストの更新日と作成日の差分を算出し、マージリクエスト項目のリストの追加と差分を集計する。
# グループ配下プロジェクト一覧取得APIURL
# 条件:グループIDが指定したID
group_projects_url = f'{gitlab_url}/groups/{group_id}/projects?per_page=100'
# グループ配下プロジェクト一覧取得APIURLに対してGETリクエストを送信
# レスポンスをjson形式で解析、プロジェクト一覧を取得
projects_response = requests.get(group_projects_url, headers=headers).json()
# 負荷軽減のため2秒待つ
time.sleep(2)
# マージまでの期間の総合計期間初期化
total_diff = timedelta()
# プロジェクト分処理を繰り返す
for project in projects_response:
project_id = project['id'] # プロジェクトID
project_name = project['name'] # プロジェクト名
# マージリクエスト一覧取得URL
# 条件:ステータスがマージ済(state=merged)、かつ、作成日が指定期間である(created_after~created_before)
merge_requests_url = f'{gitlab_url}/projects/{project_id}/merge_requests?state=merged&per_page=100&created_after={created_after}&created_before={created_before}'
# マージリクエスト一覧取得URLに対してGETリクエストを送信
# レスポンスをjson形式で解析、マージリクエスト一覧リストを取得
merge_requests = requests.get(merge_requests_url, headers=headers).json()
# 負荷軽減のため2秒待つ
time.sleep(2)
# マージリクエスト分処理を繰り返す
for merge_request in merge_requests:
# マージリクエスト数のインクリメント
merge_request_counts += 1
# マージリクエストタイトル
merge_request_title = merge_request['title']
# マージリクエストの作成日
merge_request_created_at = datetime.strptime(merge_request['created_at'], date_format)
# マージリクエストの更新日(最新)
merge_request_updated_at = datetime.strptime(merge_request['updated_at'], date_format)
# 作成されてからマージされるまでの時間
date_diff = merge_request_updated_at - merge_request_created_at
# マージリクエスト項目(プロジェクト名、マージリクエストタイトル、マージリクエストのマージまでの期間)を
# リストに追加
merge_request_item_list.append((project_name, merge_request_title, date_diff))
# 総差分合計に差分を加算
total_diff += date_diff
3.各マージリクエストの更新日と作成日の差分の合計からマージリクエスト数を割り、平均値を算出し、標準出力する。
# マージまでの期間の平均期間
average_diff = total_diff / merge_request_counts
# 結果出力(平均期間)
print(f'スプリント期間:{created_after}〜{created_before}')
print(f'マージリクエストのマージリクエスト数:{merge_request_counts}')
print(f'マージリクエストのマージまでの平均期間:{average_diff}')
print()
4.マージリクエストのリスト作成されてからマージされるまでの時間を基準に降順でソートし、
上位5つを標準出力する。
# ソートしてTop5を抽出
top_5_merge_request_item_list = sorted(merge_request_item_list, key=lambda x: x[date_diff_index], reverse=True)[:5]
# 結果出力(期間Top5)
print('■マージまでの期間が長いTop5ストーリー')
for merge_request_item in top_5_merge_request_item_list:
print(merge_request_item[project_name_index],
merge_request_item[merge_request_title_index], merge_request_item[date_diff_index])
出力例
IntelliJ IDEAから作成したプログラムを実行すると、
下記の結果が出力され、目的の情報を取得することができました。
■スプリント期間:2024-03-10T00:00:00Z〜2024-03-21T00:00:00Z'
■マージリクエストのマージリクエスト数:48'
■マージリクエストのマージまでの平均期間:15:14:45.333000'
■マージまでの期間が長いTop5ストーリー
auth-project ユーザー認証機能の追加 1 days, 14:36:45.813000
auth-project リファクタリングによる可読性向上 1 days, 05:30:51.430000
auth-project OTP認証サービスのJunit作成 1 days, 01:51:14.046000
notification-project お知らせ表示の実装 23:13:58.668000
notification-project お知らせ表示のJunit作成 23:07:15.044000
所感
メトリクスの選定からツール作成までを試験的に実施しましたが、選定とその取得方法のフィージビリティ確認に最も時間を要しました。当初はソフトウェア開発の代表的なメトリクスであるFour Keysの利用も検討しましたが、今すぐ利用できる環境やそこに残されたデータでは必要な情報が取得できないことが判明したため、断念しました。目的を達成するためには、利用するツールや残す情報を準備段階から十分に検討する必要があると実感しました。
今後は、ツールの導入検討から運用定着までも含めて検討したいと思います。
※Four Keysとは、ソフトウェア開発とデリバリーのパフォーマンスを評価するための4つの主要な指標、デプロイ頻度、変更リードタイム、変更失敗率、サービス復旧時間を指します。
注意点
なお、掲載したソースコードはサンプルになります。本ソースコードを使用することで発生するいかなる損害や不利益について、当社は一切の責任を負いませんので自己の責任においてご利用ください。
Discussion