Created
May 1, 2014 01:33
-
-
Save crosebrugh/0d384154b6ba9859881a to your computer and use it in GitHub Desktop.
Analyze a .map file and output sizes of each pod, gem, the app itself, RubyMotion, iOS, as well the specific symbol sizes largest to smallest
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 | |
# analyze a .map file and output sizes of each pod, gem, the app itself, RubyMotion, iOS | |
# also output functions (as per the -n option) sorted by size (high to low) | |
# | |
# change *_PREFIX constants and/or path_regexes to fit your specifics | |
# | |
# requires that RubyMotion is patched in order to generate the mapfile: | |
# /Library/RubyMotion2.26/lib/motion/project/builder.rb | |
# | |
# 305a306,308 | |
# > mapfile = config.app_bundle(platform).sub(/[^.]+\z/,'map') | |
# > App.info 'Map', mapfile | |
# > linker_option << " -Wl,-map,#{mapfile.gsub(/\s/,'\ ')}" | |
require 'rubygems' | |
require 'optparse' | |
require 'fileutils' | |
require 'ruby-debug' | |
DEFAULT_MAPFILE="./build/iPhoneOS-7.1-Development/ulutu POS.map" | |
# can set this to anything. these prefix each function line from the mapfile | |
APP_PREFIX='A' | |
GEMS_PREFIX='G' | |
BUNDLER_GEMS_PREFIX='g' | |
BUILD_PREFIX='B' | |
RM_PREFIX='R' | |
XCODE_PREFIX='X' | |
def main | |
num_output_lines = 50 # default | |
optparse = OptionParser.new do |opts| | |
opts.banner = "Usage: #{$0} [options] [mapfile (defaults to #{DEFAULT_MAPFILE})]" | |
opts.on("-n [INTEGER]", OptionParser::DecimalInteger, | |
"Output top 'n' lines (default #{num_output_lines}, blank means all)") do |n| | |
num_output_lines = n | |
end | |
opts.on( '-h', '--help', 'Display this screen') do | |
puts opts | |
exit | |
end | |
end | |
optparse.parse! | |
mapfile = ARGV[0] || DEFAULT_MAPFILE | |
# make sure that when the match is good $1 returns the actual match (hence the parens surrounding the path) | |
# these are checked against the Symbols section of the mapfile in order | |
path_regexes = [ | |
[ Regexp.new("(/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.1.sdk)"), XCODE_PREFIX ], | |
[ Regexp.new("(#{`pwd`.strip})"), APP_PREFIX ], | |
[ Regexp.new("(/lib/ruby/gems/1.9.1/gems)"), GEMS_PREFIX ], | |
[ Regexp.new("(/lib/ruby/gems/1.9.1/bundler/gems)"), BUNDLER_GEMS_PREFIX ], | |
[ Regexp.new("(#{File.dirname(mapfile)})"), BUILD_PREFIX ], | |
[ Regexp.new("(/Library/RubyMotion[^\/]*)"), RM_PREFIX ], | |
] | |
puts "NOTE: Analyzing: #{mapfile}" | |
puts | |
puts "NOTE: Prefixes" | |
path_regexes.each do |re, prefix| | |
puts " '#{prefix}' = #{re.to_s}" | |
end | |
linenum = 0 | |
files = {} # idx => [ prefix, file ] | |
line_warns = { } # linenum => [ message, line ] | |
line_sizes = [] # [ size, func, prefix, file ] | |
segment_sizes = Hash.new(0) | |
File.open(mapfile,'r') do |fd| | |
while (line=fd.gets) | |
linenum += 1 | |
#puts line | |
begin | |
if (line =~ /\A\[\s*(\d+)\]\s+(.*)\Z/) | |
idx = $1 | |
file = $2 | |
file_prefix = nil | |
path_regexes.each do |re, prefix| | |
if (s_idx=(file =~ re)) | |
match_len = $1.length | |
if ((prefix == RM_PREFIX) && (file =~ /\(([^\)]*)\)/)) | |
file = $1 | |
else | |
file = file[(s_idx+match_len)..-1] | |
end | |
file_prefix = prefix | |
break | |
end | |
end | |
files[idx] = [ file_prefix, file || '' ] | |
#puts "#{linenum} >> '#{idx}' '#{file}'" | |
elsif (line =~ /\A0x[0-9A-F]+\s+0x([0-9A-F]+)\s+__([A-Z]+)\s+(.*)\Z/) | |
section = $3 | |
next if (section == '__text') | |
size = hex_to_i($1) | |
segment = "Segment: #{$2}" | |
segment_sizes[segment] += size | |
elsif (line =~ /\A0x[0-9A-F]+\s+0x([0-9A-F]+)\s+\[\s*(\d+)\]\s+(.*)\Z/) | |
size = hex_to_i($1) | |
idx = $2 | |
func = $3 | |
file = files[idx] | |
line_sizes << [ size, func, file[0], file[1] ] | |
#puts "#{linenum} << #{idx} #{size} #{func}" | |
end | |
rescue ArgumentError => e | |
line_warns[linenum] = [ e.message, line ] | |
end | |
end | |
end | |
line_sizes = line_sizes.sort { |a,b| b[0] <=> a[0] } | |
# gather into pods/gems/app/etc... | |
total_size = 0 | |
pod_sizes = Hash.new(0) # name => size | |
gem_sizes = Hash.new(0) # name => size | |
unknown_sizes = Hash.new(0) # name => size | |
app_size = 0 | |
rm_size = 0 | |
build_size = 0 | |
line_sizes.each.with_index do |(size, func, prefix, file), idx| | |
case prefix | |
when APP_PREFIX | |
case file | |
when /\A\/vendor\/git\/([^\/]*)/ | |
gem_sizes[$1] += size | |
when /\A\/pods\/([^\/]*)/ | |
pod_sizes[$1] += size | |
when /\A\/vendor\/Pods\/([^\/]*)/ | |
pod_sizes[$1] += size | |
when /\A\/app/ | |
app_size += size | |
end | |
when GEMS_PREFIX | |
case file | |
when /\A\/([^\/]*)/ | |
gem_sizes[$1] += size | |
end | |
when BUNDLER_GEMS_PREFIX | |
case file | |
when /\A\/([^\/]*)/ | |
gem_sizes[$1] += size | |
end | |
when BUILD_PREFIX | |
build_size += size | |
when RM_PREFIX | |
rm_size += size | |
when XCODE_PREFIX | |
build_size += size | |
end || (unknown_sizes[file] += size) | |
total_size += size | |
end | |
sizes = [ | |
[ rm_size, 'RubyMotion' ], | |
[ app_size, 'app' ], | |
[ build_size, 'iOS' ] | |
] | |
gem_sizes.sort_by { |k, v| v }.reverse.each do |k, v| | |
sizes << [ v, "Gem #{k}" ] | |
end | |
pod_sizes.sort_by { |k, v| v }.reverse.each do |k, v| | |
sizes << [ v, "Pod #{k}" ] | |
end | |
unknown_sizes.sort_by { |k, v| v }.reverse.each do |k, v| | |
sizes << [ v, k ] | |
end | |
segment_sizes.sort_by { |k, v| v }.reverse.each do |k, v| | |
total_size += v | |
sizes << [ v, k ] | |
end | |
sizes.sort! { |a,b| b[0] <=> a[0] } | |
puts | |
puts "NOTE: sizes (MB)" | |
puts " #{i_to_mb(total_size)} - TOTAL" | |
sizes.each do |v, str| | |
v = i_to_mb(v) | |
puts " #{v} - #{str}" if (v != "0.00") | |
end | |
# | |
puts | |
line_warns.each do |k,v| | |
puts "WARN: #{v[0]}" | |
puts " line #{k}: #{v[1]}" | |
end | |
# | |
unless (num_output_lines && (num_output_lines == 0)) | |
# | |
puts | |
puts "NOTE: All functions largest to smallest: 'size (in bytes)' 'prefix' 'file' 'function'" | |
line_sizes.each.with_index do |(size, func, prefix, file), idx| | |
size = sprintf("%9d",size) | |
puts("#{size} #{prefix} #{file} #{func}") if (!num_output_lines || (idx < num_output_lines)) | |
end | |
end | |
'' | |
end | |
def hex_to_i(hex) | |
[hex].pack('H*').unpack('L>')[0] | |
end | |
MB=1024.0*1024.0 | |
def i_to_mb(v) | |
r = sprintf("%8.2f",(v || 0).to_f/MB) | |
end | |
main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment