1
/
5

【Tech Blog】開発速度・パフォーマンスを可視化する

※本ストーリーは当社運営の「GameWith Developer Blog」の転載になります。

開発速度・パフォーマンスを可視化する #GameWith #TechWith - GameWith Developer Blog
こんにちは。 GameWith のエンジニアの tiwu です! 今回は自分が所属する開発チームの開発速度・パフォーマンスを可視化してみたので、利用技術など解説していこうと思います! まず、簡単にチームの紹介をしたいと思います。 自分が所属しているチームは1週間のスプリント開発を、GitHub + ZenHub を利用して行っています。 起票された Issue は下記の図のように、起票から完了まで流れていきます。 ※IceBox, Sprint Backlog, Close は ZenHub の Pipe
https://tech.gamewith.co.jp/entry/2021/07/07/110351

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

はじめに                      

こんにちは。GameWith のエンジニアの tiwu です!

今回は自分が所属する開発チームの開発速度・パフォーマンスを可視化してみたので、利用技術など解説していこうと思います!

開発チームの紹介                  

まず、簡単にチームの紹介をしたいと思います。

自分が所属しているチームは1週間のスプリント開発を、GitHub + ZenHub を利用して行っています。

起票された Issue は下記の図のように、起票から完了まで流れていきます。

※IceBox, Sprint Backlog, Close は ZenHub の Pipeline 名です

※途中に In Progress などがありますが省略しています

月曜日に新規案件共有会があり、Issue が起票され IceBox に移動します。

スプリント中に対応が終わった Issue は Sprint Backlog から Close に移動させます。

計測                         

「LeanとDevOpsの科学」を参考に下記2つを計測していきたいと思います。

  • デプロイ頻度
  • 変更のリードタイム
LeanとDevOpsの科学[Accelerate] テクノロジーの戦略的活用が組織変革を加速する impress top gearシリーズ
AmazonでNicole Forsgren Ph.D., Jez Humble, Gene Kim, 武舎広幸, 武舎るみのLeanとDevOpsの科学[Accelerate] テクノロジーの戦略的活用が組織変革を加速する impress top gearシリーズ。アマゾンならポイント還元本が多数。一度購入いただいた電子書籍は、KindleおよびFire端末、スマートフォンやタブレットなど、様々な端末でもお楽しみいただけます。
https://www.amazon.co.jp/exec/obidos/ASIN/B07L2R3LTN/hatena-blog-22/

デプロイ頻度

スプリント中に対応が完了した Issue の数を計測しようと思います。

Issue != デプロイ数ではありますが、いったん Issue 数で定義しました。

スプリントを GitHub のマイルストーンを利用し管理しているので、マイルストーンに紐づくクローズされた Issue の数がデプロイ頻度となります。

変更のリードタイム

これは2段階に分けて計測をしていきます。

IceBox ~ Sprint Backlog

まずは IceBox ~ Sprint Backlog の移動時間です。

この時間が長ければ長いほど起票されてから開発着手まで時間がかかったことがわかります。

起票の日時は GitHub API を利用することで取得することが出来ます。

Sprint Backlog への移動日時は ZenHub API を利用することで取得することが出来ます。

Sprint Backlog ~ Close

次に Sprint Backlog ~ Close の移動時間です。

1週間スプリントで動いているため、この時間が1週間以上のタスクはスプリント内に終わらなかったことがわかります。

Close された日時は GitHub API を利用することで取得することが出来ます。

実装                         

GAS 経由で GitHub API, ZenHub API を利用しデータを取得し、SpreadSheet に保存します。

データの可視化は DataStudio を利用します。

GitHub API

まず、マイルストーンに紐づくクローズされた Issue を取得します。

Issues
application/vnd.github.VERSION.raw+json application/vnd.github.VERSION.text+json application/vnd.github.VERSION.html+json application/vnd.github.VERSION.full+json List issues assigned to the authenticated user across all visible repositories including own
https://docs.github.com/en/rest/reference/issues#list-repository-issues

Issue 取得 API のmilestone と state パラメーターを利用して取得します。

const response = await fetch('https://api.github.com/repos/${owner}/${repo}/issues?milestone=${milestone_number}&state=closed', {
  headers: {
    'Authorization': 'token ${token}'
  },
});

次に Issue の起票日とクローズ日ですが、これは Issue 取得 API のレスポンスに created_at, closed_at が含まれているためこれを利用します。

ZenHub API

ZenHub の Sprint Backlog への移動は、Events API を利用することで取得できます。

ZenHubIO/API
If you have any questions or feedback, contact support for support. The ZenHub Board for this repo has been sunset - all support inquiries should be submitted directly through the email above. You can also submit a feature request here.
https://github.com/ZenHubIO/API#get-issue-events
const response = await fetch('https://api.zenhub.io/p1/repositories/${repo_id}/issues/${issue_number}/events?access_token=${token}');

レスポンスは下記のようになっており(公式から)、type = transferIssue イベントの to_pipeline = Sprint Backlog となっている日時が今回計測に使う日時になります。

[
  {
    "user_id": 16717,
    "type": "estimateIssue",
    "created_at": "2015-12-11T19:43:22.296Z",
    "from_estimate": {
      "value": 8
    }
  },
  {
    "user_id": 16717,
    "type": "estimateIssue",
    "created_at": "2015-12-11T18:43:22.296Z",
    "from_estimate": {
      "value": 4
    },
    "to_estimate": {
      "value": 8
    }
  },
  {
    "user_id": 16717,
    "type": "estimateIssue",
    "created_at": "2015-12-11T13:43:22.296Z",
    "to_estimate": {
      "value": 4
    }
  },
  {
    "user_id": 16717,
    "type": "transferIssue",
    "created_at": "2015-12-11T12:43:22.296Z",
    "from_pipeline": {
      "name": "Backlog"
    },
    "to_pipeline": {
      "name": "In progress"
    },
    "workspace_id": "5d0a7a9741fd098f6b7f58ac"
  },
  {
    "user_id": 16717,
    "type": "transferIssue",
    "created_at": "2015-12-11T11:43:22.296Z",
    "to_pipeline": {
      "name": "Backlog"
    }
  }
]

GAS

GAS では下記のような関数を作りデータを取得し、Spreadsheet に書き込みをします。

function setData(milestone, sheetName) {
// シートの取得
const sheet = SpreadsheetApp.openById('XXX').getSheetByName(sheetName);
const lastRow = sheet.getLastRow();

// マイルストーンに紐づく Issue を取得
const response = UrlFetchApp.fetch('https://api.github.com/repos/${owner}/${repo}/issues?milestone=${milestone}&state=closed', {
headers: {
'Authorization': 'token XXX'
},
});
const issues = JSON.parse(response.getContentText());

issues.forEach((issue, index) => {
// issue 毎にイベント情報を取得
const eventsResponse = UrlFetchApp.fetch('https://api.zenhub.io/p1/repositories/${repo_id}/issues/${issue.number}/events?access_token=XXX');
const events = JSON.parse(eventsResponse.getContentText());

let springBacklogDate = null;
// 最新順に取得できるので、古い順からみる
for (const event of events.reverse()) {
if (event.type === 'transferIssue' && event.to_pipeline.name === 'Sprint Backlog') {
springBacklogDate = new Date(event.created_at);
break;
}
}

created_at = new Date(issue.created_at);
closed_at = new Date(issue.closed_at);

const row = lastRow + index + 1;
sheet.getRange(row, 1).setValue(issue.title);
// 起票日
sheet.getRange(row, 2).setValue(Utilities.formatDate(created_at, 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss'));
// Sprint Backlog 移動日
sheet.getRange(row, 3).setValue(Utilities.formatDate(springBacklogDate, 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss'));
// クローズ日
sheet.getRange(row, 4).setValue(Utilities.formatDate(closed_at, 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss'));
// IceBox ~ Sprint Backlog
sheet.getRange(row, 5).setValue(parseInt((springBacklogDate - created_at) / 1000)); // 秒
// Sprint Backlog ~ Close
sheet.getRange(row, 6).setValue(parseInt((closed_at - springBacklogDate) / 1000)); // 秒
});
}

GAS には JS のように fetch 関数はないため UrlFetchApp を利用して、外部の API を叩きます。

Class UrlFetchApp | Apps Script | Google Developers
"type": "thumb-down", "id": "missingTheInformationINeed", "label":"Missing the information I need" },{ "type": "thumb-down", "id": "tooComplicatedTooManySteps", "label":"Too complicated / too many steps" },{ "type": "thumb-down", "id": "outOfDate", "label
https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app

また、JSON で取得することは出来ないため、getContentText 後に JSON.parse をする必要があるます。

Class HTTPResponse | Apps Script | Google Developers
Send feedback This class allows users to access specific information on HTTP responses. See also Return the data inside this object as a blob converted to the specified content type. This method adds the appropriate extension to the filename-for example,
https://developers.google.com/apps-script/reference/url-fetch/http-response#getContentText()

DataStudio

DataStudio ではシンプルに棒グラフと折れ線グラフを作りました。

Issue 数は安定して 7.5 ほど対応できています(たまに20を超えることも)

案件の速度は平均を取ってみました。

IceBox ~ Sprint Backlog はたまに平均 20オーバーの時もあり、起票後着手まで時間がかかった案件があることがわかります。

Sprint Backlog ~ Close に関しては、7日を超えたり越えなかったり。

終わりに                       

今回は平均を取ってみましたが、中央値を取るべきかなどなど改善すべき箇所が他にもありそうです。

また、計測して終わりではなく BML ループのように学び・次に活かすループを回していこうと思います!

☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆

現在、GameWithでは「ゲームをより楽しめる世界を創る」というMissionの下、そんな世界を実現するべく仲間を募集しております。記事をご覧頂き、少しでもご興味を持って頂けましたら嬉しい限りです。

サーバサイドエンジニア | 株式会社GameWith
ゲームの情報・攻略サイト「GameWith」を中心とした弊社サービスにおける新規機能の開発や既存機能の改善、新規サービスの開発を担当していただきます。 ...
https://hrmos.co/pages/gamewith/jobs/0000100
株式会社GameWithでは一緒に働く仲間を募集しています
同じタグの記事
今週のランキング
株式会社GameWithからお誘い
この話題に共感したら、メンバーと話してみませんか?