Skip to content

Instantly share code, notes, and snippets.

@jdudek

jdudek/README.md Secret

Last active January 9, 2019 16:00
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jdudek/8fd3cbdc577684039b0eac4d506b8b03 to your computer and use it in GitHub Desktop.
Save jdudek/8fd3cbdc577684039b0eac4d506b8b03 to your computer and use it in GitHub Desktop.
Heroku build time metrics

Deploy time metrics

See https://pilot.co/blog/how-to-start-fixing-your-heroku-deploy-time/.

Metrics

First, get Heroku OAuth token:

heroku plugins:install git@github.com:heroku/heroku-oauth.git
heroku authorizations:create -d 'Deployment metrics'

Set the env variable HEROKU_AUTH_TOKEN

export HEROKU_AUTH_TOKEN=…

or add it to your env management tool of choice (dotenv, figaro etc.)

After that, you’re able to run the Rake task:

bin/rake metrics:heroku
bin/rake metrics:heroku SINCE='23/05/2016 05:54:28'
require 'platform-api'
class Metrics::Heroku
def initialize(app_name)
@app_name = app_name
end
attr_reader :app_name
def api
options = {default_headers: {'Range' => 'started_at ..; order=desc'}}
@api ||= PlatformAPI.connect_oauth(ENV['HEROKU_AUTH_TOKEN'], options)
end
def builds
all_builds.select { |b| b.succeeded? }
end
def all_builds
raw_builds.map { |raw_build| Build.new(raw_build) }
end
def raw_builds
api.build.list(app_name).lazy
end
class Build < OpenStruct
def succeeded?
self.status == 'succeeded'
end
def created_at
super.to_time.utc
end
alias_method :started_at, :created_at
def updated_at
super.to_time.utc
end
alias_method :finished_at, :updated_at
def duration
(finished_at - started_at).to_f
end
def build_log
@build_log ||= BuildLog.new(Excon.get(output_stream_url).body) unless @build_log_failed
rescue Excon::Errors::Error
@build_log_failed = true
end
delegate :npm_install_time,
:bundler_time,
:webpack_time,
:sprockets_time,
:migrations_time,
:total_known_time,
to: :build_log, allow_nil: true
def unknown_time
duration - total_known_time if build_log
end
end
class BuildLog
attr_reader :log
def initialize(log)
@log = log
end
def npm_install_started_at
if log =~ /^\s+([a-zA-Z0-9: ]+) Starting npm install/
Time.parse($1)
end
end
def npm_install_finished_at
if log =~ /^\s+([a-zA-Z0-9: ]+) Finished npm install/
Time.parse($1)
end
end
def npm_install_time
if npm_install_started_at && npm_install_finished_at
npm_install_finished_at - npm_install_started_at
end
end
def bundler_time
scan(/Bundle completed \(([0-9.]+)s\)/).map(&:to_f).inject(&:+)
end
def webpack_time
scan(/Version: webpack [0-9.]+\s+Time: (\d+)ms/).map(&:to_i).inject(&:+).to_f / 1000
end
def sprockets_time
if webpack_time && assets_precompile_time
assets_precompile_time - webpack_time
else
assets_precompile_time
end
end
def assets_precompile_time
scan(/Asset precompilation completed \(([0-9.]+)s\)/).map(&:to_f).inject(&:+)
end
def migrations_time
scan(/migrated \(([0-9.]+)s\)/).map(&:to_f).inject(&:+) || 0
end
def total_known_time
[
npm_install_time,
bundler_time,
webpack_time,
sprockets_time,
migrations_time
].compact.inject(0.0, &:+)
end
private
def scan(regexp)
log.scan(regexp).map(&:first)
end
end
end
require 'csv'
require Rails.root.join('lib', 'metrics', 'heroku')
namespace :metrics do
desc 'Fetch deployment metrics from Heroku'
task :heroku do
since = ENV['SINCE'] && ENV['SINCE'].to_time(:utc)
CSV.open('heroku.csv', 'w') do |csv|
# Ensure we can preview the output with `tail -f heroku.csv`
csv.sync = true
csv << [
'app',
'created_at',
'duration',
'npm_install_time',
'bundler_time',
'webpack_time',
'sprockets_time',
'migrations_time',
'unknown_time',
]
# Insert Heroku app names here
%w(production-app staging-app).each do |app_name|
heroku = Metrics::Heroku.new(app_name)
heroku.builds.each do |build|
break if since && build.created_at <= since
csv << [
app_name,
build.created_at.strftime("%Y-%m-%d %H:%M:%S"),
build.duration,
build.npm_install_time,
build.bundler_time,
build.webpack_time,
build.sprockets_time,
build.migrations_time,
build.unknown_time,
]
print '.'
end
puts
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment