• Stars
    star
    392
  • Rank 109,735 (Top 3 %)
  • Language
    Objective-C
  • License
    Apache License 2.0
  • Created almost 12 years ago
  • Updated over 11 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Instrumentation for Objective C for debugging and profiling

hookshot

Hooks for debugging and profiling Objective C code

hookshot uses Objective C runtime hooks to help you understand where your app is spending its time.

Dig into your application activity visually:

The snapshot server

or drill in on stats:

$ bin/profile.py
Using /Users/robbywalker/Library/Application Support/iPhone Simulator/6.0/Applications/C1FB13C1-C230-4435-8A10-F7BED5A6B475/Documents/profile-23274
message                                                                              calls     ownTime       avgOwn      maxOwn         total
AppDelegate.doSomethingExpensive                                                     27        562.433ms     20.8309ms   21.1630ms      562.433ms
UIWebView.webView:decidePolicyForNavigationAction:request:frame:decisionListener:    2         19.480ms      9.7400ms    19.4440ms      20.110ms
UIWebView._webViewCommonInitWithWebView:scalesPageToFit:shouldEnableReachability:    1         11.725ms      11.7250ms   11.7250ms      12.780ms
AppDelegate.application:didFinishLaunchingWithOptions:                               1         7.913ms       7.9130ms    7.9130ms       25.179ms
...

hookshot uses undocumented Apple APIs

We'll say it again: hookshot uses undocumented Apple APIs. This is necessary for profiling and debugging, but will get your application rejected during App Store review if hookshot makes it in to production code. Follow the installation instructions to ensure hookshot is properly configured to be one big NOOP for release builds.

What is it?

hookshot contains an instrumenting profiler. Contrary to its name, Apple's profiler in Instruments is actually a sampling profiler. We find both useful for different occasions. Specifically, we find hookshot most useful for:

  • Thread activity graphs with drill-in

  • Seeing accurate counts of calls

  • More precise per-call timings of messages

  • Measurement of messages with highly variable performance

hookshot also contains utilities for counting live instances of classes you care about and generic message instrumentation.

Installation

You can get hookshot in your project within about 5 minutes: step-by-step installation instructions

Profiling

hookshot provides a whitelist based instrumenting profiler. You can use it to time critical parts of your code and identify bottlenecks.

Once you've installed hookshot in your project and run it, you can start exploring your data.

Basic hooks

Early in your application's run, you can instrument any classes you want to measure performance of:

HOOKSHOT_PROFILE_CLASS([AppDelegate class]);
HOOKSHOT_PROFILE_CLASS([UIWebView class]);
HOOKSHOT_PROFILE_CLASS_EXCEPT([AnotherClass class],
    [NSValue valueWithPointer:@selector(someFrequentlyCalledSelector)],
    [NSValue valueWithPointer:@selector(anotherFrequentlyCalledSelector)])

You can omit messages you know are called frequently where you suspect profiling overhead will distort your results.

profile.py

/bin/profile.py will analyze your latest profile data dump and summarize it:

$ bin/profile.py
Using /Users/robbywalker/Library/Application Support/iPhone Simulator/6.0/Applications/C1FB13C1-C230-4435-8A10-F7BED5A6B475/Documents/profile-23274
message                                                                              calls     ownTime       avgOwn      maxOwn         total
AppDelegate.doSomethingExpensive                                                     27        562.433ms     20.8309ms   21.1630ms      562.433ms
UIWebView.webView:decidePolicyForNavigationAction:request:frame:decisionListener:    2         19.480ms      9.7400ms    19.4440ms      20.110ms
UIWebView._webViewCommonInitWithWebView:scalesPageToFit:shouldEnableReachability:    1         11.725ms      11.7250ms   11.7250ms      12.780ms
AppDelegate.application:didFinishLaunchingWithOptions:                               1         7.913ms       7.9130ms    7.9130ms       25.179ms
UIWebView.webView:didFinishLoadForFrame:                                             1         3.458ms       3.4580ms    3.4580ms       3.473ms
UIWebView._updateViewSettings                                                        3         2.327ms       0.7757ms    1.7870ms       2.430ms
AppDelegate.applicationDidBecomeActive:                                              2         1.825ms       0.9125ms    1.8250ms       522.812ms
UIWebView._updateRequest                                                             3         0.548ms       0.1827ms    0.5250ms       0.548ms
UIWebView.initWithCoder:                                                             1         0.448ms       0.4480ms    0.4480ms       14.090ms
UIWebView.webView:decidePolicyForMIMEType:request:frame:decisionListener:            1         0.335ms       0.3350ms    0.3350ms       0.335ms
UIWebView._edgeExpressionInContainer:vertical:max:                                   4         0.289ms       0.0722ms    0.0990ms       0.397ms
...

We can see here that our example expensive method is, in fact, expensive.

A summary of your command line options is always available with bin/profile.py -h but we'll cover the most interesting ones here.

Change which profile file to read

The summary is pulled from the latest profile file to be written by your Simulator. You can also run on a device and extract the Applications' data directory to your Desktop. The rest works automatically.

You can override the default smart-find functionality if you want:

bin/profile.py <pid>

will find a dump file (in the Simulator directory or recursively from your Desktop) called profile-<pid> and use that.

View thread activity visually

bin/profile.py --server

Once the server is running, open localhost:8020 to view a graphical representation of thread activity.

The snapshot server

Hovering over a bar will show what call it represents. Clicking on any given call will replace that call by the timings of any other instrumented methods it called.

We have found this extremely useful for finding subtle performance bugs caused by thread interactions. For example, it will become obvious if and when the main thread is blocking on other threads and why.

Change the sort order

You can sort profiled messages by --ownTime (the default), --calls count, --average time per call, --total time per call, or --max time for a single call.

For example:

$ bin/profile.py --total
Using /Users/robbywalker/Library/Application Support/iPhone Simulator/6.0/Applications/C1FB13C1-C230-4435-8A10-F7BED5A6B475/Documents/profile-23274
message                                                                              calls     ownTime       avgOwn      maxOwn         total
AppDelegate.doSomethingExpensive                                                     27        562.433ms     20.8309ms   21.1630ms      562.433ms
AppDelegate.applicationDidBecomeActive:                                              2         1.825ms       0.9125ms    1.8250ms       522.812ms
AppDelegate.application:didFinishLaunchingWithOptions:                               1         7.913ms       7.9130ms    7.9130ms       25.179ms
UIWebView.webView:decidePolicyForNavigationAction:request:frame:decisionListener:    2         19.480ms      9.7400ms    19.4440ms      20.110ms
UIWebView.initWithCoder:                                                             1         0.448ms       0.4480ms    0.4480ms       14.090ms
...

Filter what messages are shown

You can filter by thread:

$ bin/profile.py --thread WebThread
Using /Users/robbywalker/Library/Application Support/iPhone Simulator/6.0/Applications/C1FB13C1-C230-4435-8A10-F7BED5A6B475/Documents/profile-23274
message                                                                            calls     ownTime       avgOwn      maxOwn         total
UIWebView.webView:resource:canAuthenticateAgainstProtectionSpace:forDataSource:    3         0.055ms       0.0183ms    0.0280ms       0.055ms

by message regex:

$ bin/profile.py --message '.*did'
Using /Users/robbywalker/Library/Application Support/iPhone Simulator/6.0/Applications/C1FB13C1-C230-4435-8A10-F7BED5A6B475/Documents/profile-23274
message                                                                  calls     ownTime       avgOwn      maxOwn         total
AppDelegate.application:didFinishLaunchingWithOptions:                   1         7.913ms       7.9130ms    7.9130ms       25.179ms
UIWebView.webView:didFinishLoadForFrame:                                 1         3.458ms       3.4580ms    3.4580ms       3.473ms
UIWebView._didMoveFromWindow:toWindow:                                   1         0.212ms       0.2120ms    0.2120ms       0.261ms
UIWebView.nsis_valueOfVariable:didChangeInEngine:                        4         0.130ms       0.0325ms    0.0400ms       0.232ms
UIWebView.webView:resource:didFinishLoadingFromDataSource:               12        0.100ms       0.0083ms    0.0140ms       0.100ms
UIWebView.webView:didStartProvisionalLoadForFrame:                       1         0.062ms       0.0620ms    0.0620ms       0.587ms
UIWebView.view:didSetFrame:oldFrame:                                     2         0.056ms       0.0280ms    0.0370ms       2.029ms
UIWebView.webView:didFirstLayoutInFrame:                                 2         0.023ms       0.0115ms    0.0130ms       0.023ms
UIWebView.webView:didReceiveServerRedirectForProvisionalLoadForFrame:    1         0.018ms       0.0180ms    0.0180ms       0.026ms
UIWebView.webView:didClearWindowObject:forFrame:                         1         0.016ms       0.0160ms    0.0160ms       0.016ms
UIWebView.webView:didReceiveTitle:forFrame:                              1         0.015ms       0.0150ms    0.0150ms       0.015ms
UIWebView.webView:didCommitLoadForFrame:                                 1         0.010ms       0.0100ms    0.0100ms       0.010ms
UIWebView.didMoveToSuperview                                             1         0.004ms       0.0040ms    0.0040ms       0.004ms
UIWebView.didAddSubview:                                                 1         0.004ms       0.0040ms    0.0040ms       0.004ms
UIWebView.didMoveToWindow                                                1         0.003ms       0.0030ms    0.0030ms       0.003ms

or by class:

$ bin/profile.py --class 'AppDelegate'
Using /Users/robbywalker/Library/Application Support/iPhone Simulator/6.0/Applications/C1FB13C1-C230-4435-8A10-F7BED5A6B475/Documents/profile-23274
message                                                   calls     ownTime       avgOwn      maxOwn         total
AppDelegate.doSomethingExpensive                          27        562.433ms     20.8309ms   21.1630ms      562.433ms
AppDelegate.application:didFinishLaunchingWithOptions:    1         7.913ms       7.9130ms    7.9130ms       25.179ms
AppDelegate.applicationDidBecomeActive:                   2         1.825ms       0.9125ms    1.8250ms       522.812ms
AppDelegate.isKindOfClass:                                4         0.020ms       0.0050ms    0.0060ms       0.020ms
AppDelegate.allocWithZone:                                1         0.018ms       0.0180ms    0.0180ms       0.018ms
AppDelegate.setWindow:                                    1         0.010ms       0.0100ms    0.0100ms       0.010ms
AppDelegate.init                                          1         0.009ms       0.0090ms    0.0090ms       0.009ms
AppDelegate.window                                        2         0.006ms       0.0030ms    0.0030ms       0.006ms
AppDelegate.setViewController:                            1         0.005ms       0.0050ms    0.0050ms       0.005ms
AppDelegate.viewController                                1         0.004ms       0.0040ms    0.0040ms       0.004ms

Instance counting

You can use hookshot to track how many instances of a given class are live.

HOOKSHOT_COUNT_INSTANCES(cls);

It's best to install this hook in your AppDelegate initialize or in the initialize of the class you want to count instances of.

This will result in log messages like:

HookshotDemo[25220:11303] UIImage: 8 (after dealloc)

C++

You can also instance count for C++ classes, but only if they have no super class. You also can't configure this part at runtime - it'll be on when hookshot is built in (Debug builds) and off otherwise.

When you define your class, instead of

class ClassName {
  ...
}

write

HOOKSHOT_COUNTED_CPP_CLASS(X) {
  ...
}

In your implementation file, at the top, add:

HOOKSHOT_COUNTED_CPP_CLASS_IMPLEMENTATION_PREAMBLE(ClassName)

Generic Instrumentation

The above features are built on top of a generic capability for instance message instrumentation defined in Classes/CCInstanceMessageInstrumentation.h

We'd love to hear what (non-production!) uses you find for it.

Known Limitations

  • hookshot can not instrument messages with templated C++ response types. hookshot will automatically detect these messages and not instrument them, and warn you about them in logs.

  • hookshot crashes when instrumenting messages with varargs. hookshot cannot detect these messages and ignore them, so you will have to use PROFILE_CLASS_EXCEPT or PREVENT_INSTRUMENTATION to work around this.

Contributing

We know there is a lot more that can be done to build great tools so we can all write more performant applications.

We're always happy to receive pull requests!

License

Copyright 2012 The hookshot Authors.

Published under The Apache License, see LICENSE

More Repositories

1

scales

scales - Metrics for Python
Python
920
star
2

ocstyle

Objective-C style checker
Python
255
star
3

fast-python-pb

Fast Protocol Buffers in python (by using the C++ API)
Python
250
star
4

TheKitchenSync

A Tool Belt for iOS Concurrency
Objective-C
197
star
5

greplin-bloom-filter

Java implementation of a probabilistic set data structure
Java
143
star
6

hop

Script to hop to common directories and servers
Lua
112
star
7

greplin-lucene-utils

Some utilities for Lucene
Java
111
star
8

CueTableReloader

A really handy class that automatically figures out insertions, deletions, and reloads in UITableView based on unique item keys.
Objective-C
92
star
9

greplin-exception-catcher

Exception catcher that runs on Google App Engine
Python
74
star
10

greplin-tornado-ses

An asynchronous client for Amazon SES
Python
42
star
11

greplin-zookeeper-utils

Utilities for dealing with Apache Zookeeper
Java
41
star
12

greplin-nagios-utils

Utilities for monitoring with Nagios
Python
39
star
13

lucene-interval-fields

Lucene fields and queries for interval fields.
Java
37
star
14

greplin-tornado-sendgrid

A client for the Sendgrid API
Python
32
star
15

greplin-twisted-utils

Utilities for working with Twisted
Python
28
star
16

qc

QuickCheck for Python
Python
27
star
17

greplin-tornado-stripe

Tornado bindings for Stripe's API
Python
27
star
18

polarbear

OOM diagnostics for Java.
C++
21
star
19

greplin-tornado-mixpanel

A client for the Mixpanel API
Python
20
star
20

greplin-tornado-kissmetrics

A client for the Kissmetrics API
Python
17
star
21

evernote-python-api

Packaged version of latest Evernote Python API
Python
14
star
22

htmltotext

Fork of flaxcode htmltotext module
C++
13
star
23

jsnappy

Java implementation of the Snappy compression/decompression algorithm from Google.
Java
11
star
24

phpserialize

PHP style serialize and unserialize in Python
Python
6
star
25

hegemon

(java)script utilities
Java
5
star
26

skia

Fork of Google's Skia library
C++
4
star
27

pyiso8601

Forked version of pyiso8601 - http://code.google.com/p/pyiso8601
Python
3
star
28

eventlet

Fork of eventlet with patches from Greplin
Python
3
star
29

greplin-vobject

Greplin fork of vobject
Python
2
star
30

hegemon-example

Java
1
star