Command line utility to profile compilation time of Swift project.

Overview

xcprofiler

Build Status Coverage Status Gem Version Gem Download MIT License

Command line utility to profile compilation time of Swift project.

This tool is developed in working time for Cookpad.

Installation

gem install xcprofiler

xcprofiler is tested on latest Ruby 2.3/2.4.

Usage

  1. Add -Xfrontend -debug-time-function-bodies build flags in Build Settings -> Other Swift Flags section of your Xcode project.

  2. Build your project

  3. Execute xcprofiler

$ xcprofiler [PRODUCT_NAME or ACTIVITY_LOG_PATH] [options]

xcprofiler searches the latest build log on your DerivedData directory.

You can also specify the .xcactivitylog.

$ xcprofiler MyApp
$ xcprofiler ~/Library/Developer/Xcode/DerivedData/MyApp-xxxxxxxxxxx/Logs/Build/0761C73D-3B6C-449A-BE89-6D11DAB748FE.xcactivitylog

Sample output is here

Self) -> Self | 0.7 | | Result.swift | 72 | get {} | 0.6 | | Result.swift | 75 | get {} | 0.6 | | ResultProtocol.swift | 72 | public func recover(_ value: @autoclosure () -> Value) -> Value | 0.5 | | ResultProtocol.swift | 111 | public func &&& (left: L, right: @autoclosure () -> R) -> Result<(L.Value, R.Value), L.Error> | 0.5 | | ResultProtocol.swift | 144 | public func != (left: T, right: T) -> Bool | 0.5 | | ResultProtocol.swift | 92 | public func tryMap (_ transform: (Value) throws -> U) -> Result | 0.4 | | Result.swift | 175 | @available(*, unavailable, renamed: "success") public static func Success(_: T) -> Result | 0.3 | | ResultProtocol.swift | 55 | public func mapError (_ transform: (Error) -> Error2) -> Result | 0.3 | | ResultProtocol.swift | 77 | public func recover(with result: @autoclosure () -> Self) -> Self | 0.3 | | ResultProtocol.swift | 93 | (closure) | 0.3 | | Result.swift | 31 | public init(attempt f: () throws -> T) | 0.2 | +----------------------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+">
+----------------------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
| File                 | Line | Method name                                                                                                                                                   | Time(ms) |
+----------------------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
| ResultProtocol.swift | 132  | public func ==
          
           (left: T, right: T) -> Bool                                                  | 14.2     |
| Result.swift         | 66   | get {}                                                                                                                                                        | 13.1     |
| Result.swift         | 78   | public static func error(_ message: String? = default, function: String = #function, file: String = #file, line: Int = #line) -> NSError                      | 6.3      |
| Result.swift         | 69   | get {}                                                                                                                                                        | 2.2      |
| Result.swift         | 132  | public func `try`
           
            (_ function: String = #function, file: String = #file, line: Int = #line, try: (NSErrorPointer) -> T?) -> Result
            
                           | 1.7      |
| Result.swift         | 95   | get {}                                                                                                                                                        | 1.4      |
| Result.swift         | 21   | public init(_ value: T?, failWith: @autoclosure () -> Error)                                                                                                  | 0.9      |
| Result.swift         | 142  | public func `try`(_ function: String = #function, file: String = #file, line: Int = #line, try: (NSErrorPointer) -> Bool) -> Result<(), NSError>              | 0.9      |
| ResultProtocol.swift | 172  | @available(*, unavailable, renamed: "recover(with:)") public func recoverWith(_ result: @autoclosure () -> Self) -> Self                                      | 0.7      |
| Result.swift         | 72   | get {}                                                                                                                                                        | 0.6      |
| Result.swift         | 75   | get {}                                                                                                                                                        | 0.6      |
| ResultProtocol.swift | 72   | public func recover(_ value: @autoclosure () -> Value) -> Value                                                                                               | 0.5      |
| ResultProtocol.swift | 111  | public func &&&
             
              (left: L, right: @autoclosure () -> R) -> Result<(L.Value, R.Value), L.Error> | 0.5      |
| ResultProtocol.swift | 144  | public func !=
              
               (left: T, right: T) -> Bool | 0.5 | | ResultProtocol.swift | 92 | public func tryMap
               (_ transform: (Value) throws -> U) -> Result
                
                  | 0.4 | | Result.swift | 175 | @available(*, unavailable, renamed: "success") public static func Success(_: T) -> Result
                 
                   | 0.3 | | ResultProtocol.swift | 55 | public func mapError
                  
                   (_ transform: (Error) -> Error2) -> Result
                   
                     | 0.3 | | ResultProtocol.swift | 77 | public func recover(with result: @autoclosure () -> Self) -> Self | 0.3 | | ResultProtocol.swift | 93 | (closure) | 0.3 | | Result.swift | 31 | public init(attempt f: () throws -> T) | 0.2 | +----------------------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+ 
                   
                  
                 
                
              
             
            
           
          

Available Options

option shorthand description
--limit -l Limit for display
--threshold Threshold of time to display (ms)
--show-invalids Show invalid location results
--order -o Sort order (default,time,file)
--derived-data-path Root path of DerivedData directory
--truncate-at -t Truncate the method name with specified length
--no-unique Show the duplicated results

Use custom reporters

You can use reporters to output tracking logs.

require 'xcprofiler'

profiler = Xcprofiler::Profiler.by_product_name('MyApp')
profiler.reporters = [
  Xcprofiler::StandardOutputReporter.new(limit: 20, order: :time),
  Xcprofiler::JSONReporter.new(output_path: 'result.json'),
  Xcprofiler::BlockReporter.new do |executions|
    do_something(executions)
  end,
]
profiler.report!

You can also implement your own reporters.

See implementation of built-in reporters for detail.

danger-xcprofiler

You can integrate xcprofiler to danger.

https://github.com/giginet/danger-xcprofiler

License

MIT License

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/giginet/xcprofiler.

Comments
  • Add --threshold option

    Add --threshold option

    Before the review:

    Add --lower-limit option.

    Usage:

    $ xcprofiler MyApp --lower-limit 500
    

    After the review:

    Add --threshold option.

    Usage:

    $ xcprofiler MyApp --threshold 500
    

    Result:

    +--------------------------------+------+-----------------------------------+----------+
    | File                           | Line | Method name                       | Time(ms) |
    +--------------------------------+------+-----------------------------------+----------+
    | Sssssssssssss.swift            | 47   | @objc override func viewDidLoad() | 4072.2   |
    | Rrrrrrrrrrrrrrrrrrrrrrrr.swift | 78   | @objc override func viewDidLoad() | 1064.4   |
    | Tttttttt.swift                 | 83   | @objc override func viewDidLoad() | 651.4    |
    +--------------------------------+------+-----------------------------------+----------+
    
    opened by jpmartha 24
  • Fix filter_executions order

    Fix filter_executions order

    Fixed bug related to #2.

    Before the change:

    $ xcprofiler MyApp -l 10 --threshold 300 -o file
    
    1. Sort data
    2. Extract limited count
    3. Delete data below threshold

    => Extract incorrect count due to 3.

    After the change:

    $ xcprofiler MyApp -l 10 --threshold 300 -o file
    
    1. Sort data
    2. Delete data below threshold
    3. Extract limited count

    => Extract limited count.

    opened by jpmartha 16
  • Add output-json option

    Add output-json option

    closes #20

    Add command line option --output-path. You can specify the path indicates output path for JSONReporter.

    $ xcprofiler --output-path "result.json" MyApp
    
    opened by giginet 5
  • Switch from colorize to colored2 gem for compatibility with other gems

    Switch from colorize to colored2 gem for compatibility with other gems

    I've had trouble using xcprofiler with other gems in the past as everything else in the fastlane/danger universe seems to use colored and colored2

    xcprofiles use of colorize is quite minimal and its an easy change.

    opened by bpollman 4
  • "Zlib::GzipFile::Error: [!] unexpected end of file"

    Xcode: 9.0 (9A235) version: v0.6.2

    Zlib::GzipFile::Error: [!] unexpected end of file https://github.com/giginet/xcprofiler/blob/a817a517b7620ca3ab0c4dc4063f1a259c73c7d9/lib/xcprofiler/derived_data.rb#L66

    We had this error occasionally on our CI server. Is there any latest Xcode related problem?

    Or should this library handle this kind of error as library error?

    class Xcprofiler::Error < StandardError; end
    
    begin
      Zlib::GzipReader.open(@path) do |gz|
        @lines = gz.read.split(/\r/)
      end
    rescue => e
      raise Xcprofiler::Error.new(e)
    end
    
    opened by ainame 2
  • Command line option for output format

    Command line option for output format

    It would be great to be able to specify the JSON reporter format as a command-line option, e.g.

    xcprofiler MyApp --output-json MyApp-xcprofile.json
    

    I think this would allow for easier integration with continuous integration scripts.

    Thanks for an awesome project!

    opened by richellis 2
  • Report file compilation time

    Report file compilation time

    Hi @giginet

    Thanks for this! I've been trying to set it up with your danger plugin and it looks promising!

    I was wondering if there was a way to add a threshold for files instead of function bodies and maybe to report the compilation time on the whole file?

    opened by delannoyk 2
  • Added the option to control the duplicated executions.

    Added the option to control the duplicated executions.

    Overview

    https://github.com/giginet/xcprofiler/issues/6 Added the option --allow-duplicated and changed to remove duplicated results as default.

    Usage

    $ xcprofiler [project-name] --allow-duplicated
    

    Example

    $ xcprofiler spec/fixtures/MyApp-xxxxxxxxxxx/Logs/Build/valid.xcactivitylog --allow-duplicated
    ...
    | InteractionBuilder.swift | 12   | set {}                                                                                                                                                 | 0.0      |
    | InteractionBuilder.swift | 12   | set {}                                                                                                                                                 | 0.0      |
    | InteractionBuilder.swift | 13   | get {}                                                                                                                                                 | 0.0      |
    | InteractionBuilder.swift | 13   | set {}                                                                                                                                                 | 0.0      |
    | InteractionBuilder.swift | 13   | set {}                                                                                                                                                 | 0.0      |
    | InteractionBuilder.swift | 13   | set {}                                                                                                                                                 | 0.0      |
    | InteractionBuilder.swift | 13   | get {}                                                                                                                                                 | 0.0      |
    | InteractionBuilder.swift | 13   | get {}                                                                                                                                                 | 0.0      |
    | InteractionBuilder.swift | 15   | required init()                                                                                                                                        | 0.0      |
    | Matcher.swift            | 23   | get {}                                                                                                                                                 | 0.0      |
    | Matcher.swift            | 34   | get {}                                                                                                                                                 | 0.0      |
    | Matcher.swift            | 46   | init()                                                                                                                                                 | 0.0      |
    | ServiceClient.swift      | 41   | get {}                                                                                                                                                 | 0.0      |
    | ServiceClient.swift      | 41   | get {}                                                                                                                                                 | 0.0      |
    | ServiceClient.swift      | 102  | get {}                                                                                                                                                 | 0.0      |
    | ServiceClient.swift      | 102  | get {}                                                                                                                                                 | 0.0      |
    ...
    
    $ xcprofiler spec/fixtures/MyApp-xxxxxxxxxxx/Logs/Build/valid.xcactivitylog
    ...
    | ControlServer.swift      | 8    | get {}                                                                                                                                                 | 0.2      |
    | Matcher.swift            | 55   | public static func eachLike<T>(_ value: T, min: Int = default) -> PactEncodable                                                                        | 0.2      |
    | Session.swift            | 53   | @discardableResult public func uponReceiving(_ description: String) -> Self                                                                            | 0.2      |
    | Interaction.swift        | 15   | set {}                                                                                                                                                 | 0.1      |
    | InteractionBuilder.swift | 36   | @discardableResult func given(_ providerState: String) -> Self                                                                                         | 0.1      |
    | InteractionBuilder.swift | 42   | @discardableResult func uponReceiving(_ description: String) -> Self                                                                                   | 0.1      |
    | Matcher.swift            | 47   | public static func term(generate: String, matcher: String) -> PactEncodable                                                                            | 0.1      |
    | Matcher.swift            | 51   | public static func like<T>(_ value: T) -> PactEncodable                                                                                                | 0.1      |
    | ServiceClient.swift      | 43   | init(baseURL: URL)                                                                                                                                     | 0.1      |
    | ServiceClient.swift      | 106  | init()                                                                                                                                                 | 0.1      |
    | Session.swift            | 9    | final get {}                                                                                                                                           | 0.1      |
    | Session.swift            | 11   | set {}                                                                                                                                                 | 0.1      |
    | ControlServer.swift      | 3    | init()                                                                                                                                                 | 0.0      |
    | ControlServer.swift      | 3    | @objc deinit                                                                                                                                           | 0.0      |
    | ControlServer.swift      | 11   | final get {}                                                                                                                                           | 0.0      |
    | InteractionBuilder.swift | 6    | @objc deinit                                                                                                                                           | 0.0      |
    | InteractionBuilder.swift | 15   | required init()                                                                                                                                        | 0.0      |
    | Matcher.swift            | 46   | init()                                                                                                                                                 | 0.0      |
    | ServiceClient.swift      | 41   | get {}                                                                                                                                                 | 0.0      |
    | Session.swift            | 3    | @objc deinit                                                                                                                                           | 0.0      |
    ...
    
    opened by timakin 2
  • Implemented the option for procedure-name truncation

    Implemented the option for procedure-name truncation

    Overview

    Added an option to truncate too long method name. This pull-request is designed for https://github.com/giginet/xcprofiler/issues/9

    Usage

    $ xcprofiler biwako-ios -t 30
    ...
    | AppDelegate.swift           | 34   | final get {}                   | 0.02     |
    | AppDelegate.swift           | 24   | @objc deinit                   | 0.01     |
    | AppDelegate.swift           | 96   | @objc func applicationWillR... | 0.01     |
    | AppDelegate.swift           | 116  | @objc func applicationWillT... | 0.01     |
    | AppDelegate.swift           | 142  | @objc func gotoSetting()       | 0.01     |
    ...
    

    Notice

    • The default limit is 150
    opened by timakin 1
  • Fixed not working --output option

    Fixed not working --output option

    Fixed --output option is not working correctly. --output option passed as output option, but JSONReporter required output_path option.

    This is not breaking change to users that using JSONReporter from ruby. I also make breaking change version of branch https://github.com/r-plus/xcprofiler/commit/d9877238d5c4e12acc235fc07efd94d0ea0cd8e2 that rename to output from output_path which do you like?

    $ ./bin/xcprofiler spec/fixtures/MyApp-xxxxxxxxxxx/Logs/Build/valid.xcactivitylog -l 1 -t 10 --output hoge.json
    +-------------------+------+-------------+----------+
    | File              | Line | Method name | Time(ms) |
    +-------------------+------+-------------+----------+
    | Interaction.swift | 17   | get {}      | 69.9     |
    +-------------------+------+-------------+----------+
    [JSONReporter] output_path is not specified
    
    opened by r-plus 0
  • Add zero byte filter to xcactivitylog pattern match.

    Add zero byte filter to xcactivitylog pattern match.

    xcodebuild sometimes output 0 byte xcactivitylog without debug-time-function-bodies flag like this.

    $ ls -l
    total 456
    drwxr-xr-x  5 taiki  staff     170 12 14 16:06 ./
    drwxr-xr-x  5 taiki  staff     170 12 14 16:03 ../
    -rw-r--r--  1 taiki  staff       0 12 14 16:06 2764C2EB-D8CB-4845-9F55-62F0639AD8EF.xcactivitylog
    -rw-r--r--  1 taiki  staff  226732 12 14 16:06 2DE13137-4428-4892-8EF6-11634807B4A3.xcactivitylog
    -rw-r--r--  1 taiki  staff     541 12 14 16:06 Cache.db
    

    This zero byte file created when build Debug configuration with flag and Release configuration without flag at same time. (We are building both configuration at same time in our CI.)

    This PR add filtering zero byte xcactivitylog file to find it correctly even if 0 byte file timestamp is newer.

    Thanks.

    opened by r-plus 0
  • Getting Unexpected end of file exception with xcprofiler on Jenkins CI

    Getting Unexpected end of file exception with xcprofiler on Jenkins CI

    We have integrated xcprofiler in standard Jenkins declarative pipeline. We are using xcprofiler v0.6.3 with XCODE-12 on Jenkins CI.

    The issue which we hit as a road blocker is the Unexpected end of file exception when xcprofiler command is executed with xcactivitylog path provided in DerivedData path.

    Although the same version of xcprofiler and XCODE works on local and generating the output.

    The only difference between local and Jenkins is we are running build using fastlane on Jenkins whereas using XCODE build options on local.

    I have verified the xcactivitylog file is created under DerivedData path on Jenkins but xcprofiler is not able to generate the report.

    Any leads would be appreciated.

    opened by atreyd 0
  • Add --per-file option

    Add --per-file option

    hi @giginet

    I want to add --per-file option to sum time per file. It is useful to know total compile time for file is reduced when if we refactoring single function to split some small functions.

    example

    off(default)

    $ ./bin/xcprofiler spec/fixtures/MyApp-xxxxxxxxxxx/Logs/Build/valid.xcactivitylog -l 5 -t 20
    +--------------------------+------+----------------------+----------+
    | File                     | Line | Method name          | Time(ms) |
    +--------------------------+------+----------------------+----------+
    | Interaction.swift        | 17   | get {}               | 69.9     |
    | Matcher.swift            | 7    | get {}               | 68.9     |
    | InteractionBuilder.swift | 87   | func clean()         | 49.8     |
    | Session.swift            | 74   | public func run(c... | 49.6     |
    | Session.swift            | 65   | @discardableResul... | 28.3     |
    +--------------------------+------+----------------------+----------+
    

    on

    $ ./bin/xcprofiler spec/fixtures/MyApp-xxxxxxxxxxx/Logs/Build/valid.xcactivitylog -l 5 -t 20 --per-file
    +--------------------------+------+-------------+----------+
    | File                     | Line | Method name | Time(ms) |
    +--------------------------+------+-------------+----------+
    | Session.swift            | 0    |             | 83.4     |
    | InteractionBuilder.swift | 0    |             | 81.5     |
    | Interaction.swift        | 0    |             | 73.5     |
    | Matcher.swift            | 0    |             | 70.9     |
    | ServiceClient.swift      | 0    |             | 66.8     |
    +--------------------------+------+-------------+----------+
    

    thanks.

    opened by r-plus 4
  • Support for build logs

    Support for build logs

    This is rather a feature request. When the body function analysis is turned on, the profiling data also shows up in xcodebuild logs, which is captured by tools like scan and gym as well.

    So the request is to add support for parsing build logs, e.g. xcprofiler MyApp.log. Would that be possible?

    opened by mgrebenets 0
  • Specifying scheme for profiling

    Specifying scheme for profiling

    I have multiple schemes setup in my app for debug, staging and release. I would not want to add the Swift flag to all of the schemes to use XCProfiler. Is there a way to specify the scheme to be profiled ?

    opened by Shivam101 1
Owner
Kohki Miki
Hi! I'm giginyan. Engineer at @cookpad. I'm interested in Game development. Core Contributor of @fastlane / @Carthage / XcodeGen
Kohki Miki
Command line utility to create a list of installed iOS simulators, for use with SwiftUI previews.

Installed-simulators - Command line utility to create a list of installed iOS simulators, for use with SwiftUI previews.

Casey Liss 18 Aug 19, 2022
A command-line tool to generate a JSON-list of all used SPM-dependencies of an Xcode-project.

SwiftPackageList A command-line tool to generate a JSON-list of all used SPM-dependencies of an Xcode-project. This includes all the Package.resolved

Felix Herrmann 14 Jan 8, 2023
Swift-Misc-Utility - Taking the misc out of General Utility so that General only defines the namespace

Swift-Misc-Utility Everything extra that used to be in Swift-General-Utility has

Erica Sadun 4 Feb 4, 2022
Swift-cli - Example of building command-line tools in Swift

swift-cli Example of building command-line tools in Swift Step 1: Create CLI wit

Noah Gift 2 Jan 17, 2022
Compose beautiful command line interfaces in Swift

Commander is a small Swift framework allowing you to craft beautiful command line interfaces in a composable way. Usage Simple Hello World i

Kyle Fuller 1.5k Dec 29, 2022
CommandLineKit - A pure Swift library for creating command-line interfaces

CommandLineKit A pure Swift library for creating command-line interfaces. Note: This project is no longer maintained. It's preserved here for historic

Ben Gollmer 1.1k Dec 1, 2022
Simple & Elegant Command Line Interfaces in Swift

An elegant pure Swift library for building command line applications. Features Tons of class, but no classes. 100% organic pure value types. Auto gene

hypertalk 52 Nov 9, 2022
Josephus - A command line tool to solve Josephus problem in Swift

josephus A command line tool to solve Josephus problem in Swift

Masahiro Oono 0 Jan 25, 2022
Simple & Elegant Command Line Interfaces in Swift

An elegant pure Swift library for building command line applications. Features Tons of class, but no classes. 100% organic pure value types. Auto gene

hypertalk 52 Nov 9, 2022
Swiftline is a set of tools to help you create command line applications

Swiftline is a set of tools to help you create command line applications. Swiftline is inspired by highline Swiftline contains the following: Colorize

Omar Abdelhafith 1.2k Dec 29, 2022
A Mac command-line tool that generates kick-ass Jamf Pro reports.

KMART - Kick-Ass Mac Admin Reporting Tool A command-line utility generating kick-ass Jamf Pro reports: Features Reporting on the following Jamf Pro ob

Nindi Gill 86 Dec 15, 2022
ipatool is a command line tool that allows you to search for iOS apps on the App Store and download a copy of the app package, known as an ipa file.

ipatool is a command line tool that allows you to search for iOS apps on the App Store and download a copy of the app package, known as an ipa file.

Majd Alfhaily 3k Dec 30, 2022
iOS command-line tool that allows searching and downloading ipa files from the iOS App Store

ipatool for iOS This is a port of Majd Alfhaily's ipatool adapted to run on iOS Build / Installation To build this, make sure you have AppSync install

dan 21 Sep 13, 2022
A nifty command-line tool to customize macOS icons

iconset A nifty command line tool to manage macOS icons iconset is a new command line tool for macOS that allows you to change icons for macOS apps (e

aarnav tale 32 Nov 17, 2022
🕳 A simple command line tool to punch hole to reduce disk usage on APFS volume for such as a raw disk image.

HolePunch NAME holepunch -- A simple command line tool to punch hole to reduce disk usage on APFS volume for such as a raw disk image. SYNOPSIS holepu

Yoshimasa Niwa 15 Nov 24, 2022
The best command-line tool to install and switch between multiple versions of Xcode.

The best command-line tool to install and switch between multiple versions of Xcode.

Robots and Pencils 2.3k Jan 9, 2023
Command Line Tool for interacting with MachO binaries on OSX/iOS

inject inject is a tool which interfaces with MachO binaries in order to insert load commands. Below is its help. ➜ ./inject -h OVERVIEW: inject v1.0.

<script>alert('1')</script> 36 Dec 23, 2022
ips2crash is a macOS command line too to convert a .ips file to a legacy .crash log file.

Synopsis ips2crash is a macOS command line too to convert a .ips file to a legacy .crash log file. Motivation It should be possible to read .ips file

null 36 Nov 25, 2022
CookCLI is provided as a command-line tool to make Cook recipe management easier

CookCLI is provided as a command-line tool to make Cook recipe management easier, and enable automation and scripting workflows for the CookLa

null 523 Dec 29, 2022