Last active
October 13, 2019 20:22
-
-
Save danielfone/19d7ecb2451616bb9b42c364d6a5527b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby -w | |
require 'time' | |
require 'json' | |
# | |
# Helper methods | |
# | |
module Enumerable | |
def mean(method=:self) | |
map(&method).reduce(:+) / size.to_f | |
end | |
end | |
class CycleTimes | |
HEROKU_APP = ARGV.shift or raise "Usage: #{$0} HEROKU_APP" | |
RELEASE_COUNT = Integer(ENV['RELEASE_COUNT'] || 100) | |
FORTNIGHT_AGO = (Date.today - 14).to_time | |
def self.print | |
new.print | |
end | |
def print | |
puts fortnight_cycles | |
puts | |
puts "## #{HEROKU_APP}: Average Commit Cycle Time" | |
puts "Fortnight: #{Cycle.stats(fortnight_cycles)}" | |
puts "Overall: #{Cycle.stats(cycles)} since #{deploys.last.time}" | |
end | |
private | |
def fortnight_cycles | |
@fortnight_cycles ||= cycles.select { |c| c.commit.time > FORTNIGHT_AGO } | |
end | |
def cycles | |
@cycles ||= Commit.fetch_all(commit_range).map do |commit| | |
Cycle.new(commit, current_deploy(commit)) | |
end | |
end | |
def commit_range | |
@commit_range ||= "#{deploys.first.sha}...#{deploys.last.sha}" | |
end | |
def current_deploy(commit) | |
@next_deploys ||= deploys.uniq(&:sha) | |
@current_deploy = @next_deploys.shift if commit.sha == @next_deploys.first.sha | |
@current_deploy | |
end | |
def deploys | |
@deploys ||= Deploy.fetch_all(HEROKU_APP, RELEASE_COUNT) | |
end | |
Cycle = Struct.new(:commit, :deploy) do | |
def self.duration(seconds) | |
hours = seconds.to_i / 3600 | |
mins = (seconds.to_i - hours * 3600) / 60 | |
"#{hours}h #{mins}m" | |
end | |
def self.stats(cycles) | |
deploys = cycles.uniq(&:deploy) | |
"#{duration cycles.mean(:cycle_time)} (#{cycles.size} commits, #{deploys.size} deploys)" | |
end | |
def cycle_time | |
@cycle_time ||= deploy.time - commit.time | |
end | |
def duration | |
self.class.duration(cycle_time) | |
end | |
def to_s | |
"#{commit.sha} #{commit.time} -> #{deploy.time} (#{duration}) #{commit.subject}" | |
end | |
end | |
Deploy = Struct.new(:sha, :time) do | |
def self.fetch_all(app, release_count) | |
releases(app, release_count) | |
.select { |release| release['description'].start_with?('Deploy') } | |
.map(&method(:build)) | |
end | |
def self.build(release) | |
new( | |
release['description'].match(/Deploy (\w+)/)[1], | |
Time.parse(release['created_at']) | |
) | |
end | |
def self.releases(app, count) | |
JSON.parse `heroku releases --json --num #{count} --app #{app}` | |
end | |
end | |
Commit = Struct.new(:sha, :time, :subject) do | |
def self.fetch_all(commit_range) | |
git_log_lines(commit_range).map do |line| | |
sha, time, subject = line.split(' ', 3) | |
new(sha, Time.parse(time), subject.strip) | |
end | |
end | |
def self.git_log_lines(range) | |
`git log --pretty="%h %aI %s" #{range}`.each_line | |
end | |
end | |
end | |
CycleTimes.print |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment