测量所有目标(包括依赖目标)的 xcodebuild 持续时间
Measuring xcodebuild durations for all targets (including dependent ones)
是否可以测量单个 xcodebuild
命令构建每个不同目标所消耗的时间?
假设我有一个目标,它取决于一些 cocoapod
:pod1
和 pod2
。
我使用 xcodebuild
构建我的目标。我可以测量总时间。
我需要测量分别花费在 pod1
、pod2
和我的目标
上的时间
我试图在 xcodebuild
的输出中找到答案,但没有找到。
提前致谢!
我最终编写了一个自定义 ruby
脚本来修改我的 xcodeproj
和 Pods.xcodeproj
的每个目标。此脚本添加两个 build phase
s,将目标名称和当前时间戳记录到输出文件中。一个 build phase
首先执行,另一个最后执行。后来我在一个单独的脚本中简单地从另一个时间戳中减去一个时间戳。
这是脚本的结果:
输出文件将如下所示(排序后)
Alamofire end: 1510929112.3409
Alamofire start: 1510929110.2161
AlamofireImage end: 1510929113.6925
AlamofireImage start: 1510929112.5205
输出文件的路径(屏幕截图上的/a/ci_automation/metrics/performance-metrics/a.txt
)无论如何都没有硬编码。相反,您将它作为 ruby
脚本的参数传递,如下所示:
$ruby prepare-for-target-build-time-profiling.rb ${PWD}/output.txt
请注意,此脚本需要 cocoapods 1.3.1
(可能 1.3
)。
这是 ruby
脚本:ruby prepare-for-target-build-time-profiling.rb
#!/usr/bin/env ruby
require 'xcodeproj'
require 'cocoapods'
require 'fileutils'
def inject_build_time_profiling_build_phases(project_path)
project = Xcodeproj::Project.open(project_path)
log_time_before_build_phase_name = '[Prefix placeholder] Log time before build'.freeze
log_time_after_build_phase_name = '[Prefix placeholder] Log time after build'.freeze
puts "Patching project at path: #{project_path}"
puts
project.targets.each do |target|
puts "Target: #{target.name}"
first_build_phase = create_leading_build_phase(target, log_time_before_build_phase_name)
last_build_phase = create_trailing_build_phase(target, log_time_after_build_phase_name)
puts
end
project.save
puts "Finished patching project at path: #{project_path}"
puts
end
def create_leading_build_phase(target, build_phase_name)
remove_existing_build_phase(target, build_phase_name)
build_phase = create_build_phase(target, build_phase_name)
shift_build_phase_leftwards(target, build_phase)
is_build_phase_leading = true
inject_shell_code_into_build_phase(target, build_phase, is_build_phase_leading)
return build_phase
end
def create_trailing_build_phase(target, build_phase_name)
remove_existing_build_phase(target, build_phase_name)
build_phase = create_build_phase(target, build_phase_name)
is_build_phase_leading = false
inject_shell_code_into_build_phase(target, build_phase, is_build_phase_leading)
return build_phase
end
def remove_existing_build_phase(target, build_phase_name)
existing_build_phase = target.shell_script_build_phases.find do |build_phase|
build_phase.name.end_with?(build_phase_name)
# We use `end_with` instead of `==`, because `cocoapods` adds its `[CP]` prefix to a `build_phase_name`
end
if !existing_build_phase.nil?
puts "deleting build phase #{existing_build_phase.name}"
target.build_phases.delete(existing_build_phase)
end
end
def create_build_phase(target, build_phase_name)
puts "creating build phase: #{build_phase_name}"
build_phase = Pod::Installer::UserProjectIntegrator::TargetIntegrator
.create_or_update_shell_script_build_phase(target, build_phase_name)
return build_phase
end
def shift_build_phase_leftwards(target, build_phase)
puts "moving build phase leftwards: #{build_phase.name}"
target.build_phases.unshift(build_phase).uniq! unless target.build_phases.first == build_phase
end
def inject_shell_code_into_build_phase(target, build_phase, is_build_phase_leading)
start_or_end = is_build_phase_leading ? "start" : "end"
build_phase.shell_script = <<-SH.strip_heredoc
timestamp=`echo "scale=4; $(gdate +%s%N/1000000000)" | bc`
echo "#{target.name} #{start_or_end}: ${timestamp}" >> #{$build_time_logs_output_file}
SH
end
def parse_arguments
$build_time_logs_output_file = ARGV[0]
if $build_time_logs_output_file.to_s.empty? || ! $build_time_logs_output_file.start_with?("/")
puts "Error: you should pass a full path to a output file as an script's argument. Example:"
puts "$ruby prepare-for-target-build-time-profiling.rb /path/to/script/output.txt"
puts
exit 1
end
end
def print_arguments
puts "Arguments:"
puts "Output path: #{$build_time_logs_output_file}"
puts
end
def clean_up_before_script
if File.exist?($build_time_logs_output_file)
FileUtils.rm($build_time_logs_output_file)
end
build_time_logs_output_folder = File.dirname($build_time_logs_output_file)
unless File.directory?(build_time_logs_output_folder)
FileUtils.mkdir_p(build_time_logs_output_folder)
end
end
def main
parse_arguments
print_arguments
clean_up_before_script
inject_build_time_profiling_build_phases("path/to/project.xcodeproj")
inject_build_time_profiling_build_phases("path/to/pods/project.xcodeproj")
end
# arguments:
$build_time_logs_output_file
main
是否可以测量单个 xcodebuild
命令构建每个不同目标所消耗的时间?
假设我有一个目标,它取决于一些 cocoapod
:pod1
和 pod2
。
我使用 xcodebuild
构建我的目标。我可以测量总时间。
我需要测量分别花费在 pod1
、pod2
和我的目标
我试图在 xcodebuild
的输出中找到答案,但没有找到。
提前致谢!
我最终编写了一个自定义 ruby
脚本来修改我的 xcodeproj
和 Pods.xcodeproj
的每个目标。此脚本添加两个 build phase
s,将目标名称和当前时间戳记录到输出文件中。一个 build phase
首先执行,另一个最后执行。后来我在一个单独的脚本中简单地从另一个时间戳中减去一个时间戳。
这是脚本的结果:
输出文件将如下所示(排序后)
Alamofire end: 1510929112.3409
Alamofire start: 1510929110.2161
AlamofireImage end: 1510929113.6925
AlamofireImage start: 1510929112.5205
输出文件的路径(屏幕截图上的/a/ci_automation/metrics/performance-metrics/a.txt
)无论如何都没有硬编码。相反,您将它作为 ruby
脚本的参数传递,如下所示:
$ruby prepare-for-target-build-time-profiling.rb ${PWD}/output.txt
请注意,此脚本需要 cocoapods 1.3.1
(可能 1.3
)。
这是 ruby
脚本:ruby prepare-for-target-build-time-profiling.rb
#!/usr/bin/env ruby
require 'xcodeproj'
require 'cocoapods'
require 'fileutils'
def inject_build_time_profiling_build_phases(project_path)
project = Xcodeproj::Project.open(project_path)
log_time_before_build_phase_name = '[Prefix placeholder] Log time before build'.freeze
log_time_after_build_phase_name = '[Prefix placeholder] Log time after build'.freeze
puts "Patching project at path: #{project_path}"
puts
project.targets.each do |target|
puts "Target: #{target.name}"
first_build_phase = create_leading_build_phase(target, log_time_before_build_phase_name)
last_build_phase = create_trailing_build_phase(target, log_time_after_build_phase_name)
puts
end
project.save
puts "Finished patching project at path: #{project_path}"
puts
end
def create_leading_build_phase(target, build_phase_name)
remove_existing_build_phase(target, build_phase_name)
build_phase = create_build_phase(target, build_phase_name)
shift_build_phase_leftwards(target, build_phase)
is_build_phase_leading = true
inject_shell_code_into_build_phase(target, build_phase, is_build_phase_leading)
return build_phase
end
def create_trailing_build_phase(target, build_phase_name)
remove_existing_build_phase(target, build_phase_name)
build_phase = create_build_phase(target, build_phase_name)
is_build_phase_leading = false
inject_shell_code_into_build_phase(target, build_phase, is_build_phase_leading)
return build_phase
end
def remove_existing_build_phase(target, build_phase_name)
existing_build_phase = target.shell_script_build_phases.find do |build_phase|
build_phase.name.end_with?(build_phase_name)
# We use `end_with` instead of `==`, because `cocoapods` adds its `[CP]` prefix to a `build_phase_name`
end
if !existing_build_phase.nil?
puts "deleting build phase #{existing_build_phase.name}"
target.build_phases.delete(existing_build_phase)
end
end
def create_build_phase(target, build_phase_name)
puts "creating build phase: #{build_phase_name}"
build_phase = Pod::Installer::UserProjectIntegrator::TargetIntegrator
.create_or_update_shell_script_build_phase(target, build_phase_name)
return build_phase
end
def shift_build_phase_leftwards(target, build_phase)
puts "moving build phase leftwards: #{build_phase.name}"
target.build_phases.unshift(build_phase).uniq! unless target.build_phases.first == build_phase
end
def inject_shell_code_into_build_phase(target, build_phase, is_build_phase_leading)
start_or_end = is_build_phase_leading ? "start" : "end"
build_phase.shell_script = <<-SH.strip_heredoc
timestamp=`echo "scale=4; $(gdate +%s%N/1000000000)" | bc`
echo "#{target.name} #{start_or_end}: ${timestamp}" >> #{$build_time_logs_output_file}
SH
end
def parse_arguments
$build_time_logs_output_file = ARGV[0]
if $build_time_logs_output_file.to_s.empty? || ! $build_time_logs_output_file.start_with?("/")
puts "Error: you should pass a full path to a output file as an script's argument. Example:"
puts "$ruby prepare-for-target-build-time-profiling.rb /path/to/script/output.txt"
puts
exit 1
end
end
def print_arguments
puts "Arguments:"
puts "Output path: #{$build_time_logs_output_file}"
puts
end
def clean_up_before_script
if File.exist?($build_time_logs_output_file)
FileUtils.rm($build_time_logs_output_file)
end
build_time_logs_output_folder = File.dirname($build_time_logs_output_file)
unless File.directory?(build_time_logs_output_folder)
FileUtils.mkdir_p(build_time_logs_output_folder)
end
end
def main
parse_arguments
print_arguments
clean_up_before_script
inject_build_time_profiling_build_phases("path/to/project.xcodeproj")
inject_build_time_profiling_build_phases("path/to/pods/project.xcodeproj")
end
# arguments:
$build_time_logs_output_file
main