Skip to content

Instantly share code, notes, and snippets.

@irori
Created April 12, 2016 06:47
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save irori/e5af3d2994f34b7a2a68ac596e5c9ceb to your computer and use it in GitHub Desktop.
Save irori/e5af3d2994f34b7a2a68ac596e5c9ceb to your computer and use it in GitHub Desktop.
#!/usr/bin/ruby
#
# Calculate "Time to First Meaningful Paint" from trace events
# usage: ttfmp.rb trace.json
require 'json'
class TraceAnalyzer
def initialize(trace)
@json = JSON.parse(IO.read(trace))
parse
end
def parse
@layouts = {} # timestamp -> layout counters
@paints = [] # timestamps
mainFrameID = nil
@json.each do |item|
args = item['args'] || {}
case item['name']
when 'navigationStart'
unless mainFrameID
mainFrameID = args['frame']
@navStartTime = item['ts']
end
when 'FrameView::performLayout'
if args['counters'] && args['frame'] == mainFrameID
@layouts[item['ts']] = args['counters']
end
when 'Paint'
if args['data'] && args['data']['frame'] == mainFrameID
@paints.push item['ts']
end
when 'firstContentfulPaint'
if args['frame'] == mainFrameID
@firstContentfulPaint = item['ts']
end
end
end
end
def firstMeaningfulPaint(heuristics = {})
layoutTime = nil
maxSoFar = 0
pending = 0
@layouts.each do |ts, counters|
next if counters['host'].empty? || counters['visibleHeight'] == 0
layouts = counters['LayoutObjectsThatHadNeverHadLayout'] || 0
if heuristics[:pageHeight]
significance = layouts / heightRatio(counters)
else
significance = layouts
end
if heuristics[:webFont] && counters['hasBlankText']
pending += significance
else
significance += pending
pending = 0
if significance > maxSoFar
maxSoFar = significance
layoutTime = ts
end
end
end
paintTime = @paints.find {|ts| ts > layoutTime}
(paintTime - @navStartTime) / 1000
end
def firstContentfulPaint
(@firstContentfulPaint - @navStartTime) / 1000
end
private
def heightRatio(counters)
([counters['contentsHeightBefore'].to_f / counters['visibleHeight'], 1].max +
[counters['contentsHeightAfter'].to_f / counters['visibleHeight'], 1].max) / 2
end
end
if $0 == __FILE__
ARGV.each do |fname|
trace = TraceAnalyzer.new(fname)
puts({"TTFCP" => trace.firstContentfulPaint,
"Basic" => trace.firstMeaningfulPaint(),
"PageHeight" => trace.firstMeaningfulPaint(pageHeight: true),
"WebFont" => trace.firstMeaningfulPaint(webFont: true),
"Full" => trace.firstMeaningfulPaint(pageHeight: true, webFont: true)
}.to_s)
end
end
@paulirish
Copy link

@paulirish
Copy link

These traces are dependent on new instrumentation a la https://codereview.chromium.org/1773633003

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment