Rob Rob - 2 months ago 74
iOS Question

Alternative to DTSendSignalFlag to identify key events in Instruments?

There used to be a nice tool by which you could programmatically insert flags into Instruments (see Xcode Instruments trace comparison). This feature stopped working in iOS 7.

Has anyone succeeded in getting

DTSendSignalFlag
to work in iOS 7? Signal flags are (were?) a useful way to programmatically post flags in Instruments via code (really helpful when diagnosing complicated apps in Instruments), but I'm not seeing my programmatically created flags in Instruments when I run on the iOS 7 simulator (but it works when I have Xcode 5 build for the iOS 6 simulator).

Rob Rob
Answer

Rather than using flags, we can now use "Points of Interest". In iOS 10 and macOS 10.12, we can use kdebug_signpost. This is illustrated in WWDC 2016 video System Trace in Depth.

For those processes that take a discrete amount of time, I can use kdebug_signpost_start and kdebug_signpost_end.

kdebug_signpost_start(signPostCode, identifier, 0, 0, colorCoding)
// do something time consuming
kdebug_signpost_end(signPostCode, identifier, 0, 0, colorCoding)

To mark a single moment in time, we can just use kdebug_signpost:

kdebug_signpost(signPostCode, identifier, 0, 0, colorCoding)

The first parameter, the signPostCode, is just some unique numeric code that corresponds to a "signpost code name" that I'll use in Instruments. You can use whatever values you want (between 0 and 16383), but I use something that designates some functional category:

enum SignPost: UInt32 {   // some useful constants that I'll reference in Instruments
    case download = 0
    case parse = 1
    case done = 2
}

The rest of the parameters can be whatever Uint values you want, but in my example, I'll use the second parameter as a unique identifier to match up repeated start and end calls and I'll use the last parameter to color code my regions in Instruments (where 0 = blue, 1 = green, 2 = purple, 3 = orange, 4 = red).

Regardless, you can then profile the app in Instruments, click on the "+" button, and add "Points of Interest". By configuring the "Signpost Code Names" to match the numeric values I passed as the first parameter to my signposts, Instruments will actually translate those codes for me. Once I profile the app and I now have my points of interest clearly highlighted for me:

enter image description here

In this snapshot, I profiled seven download operations (in blue) and seven parse operations (in green), constrained to two at a time, respectively. But the details of this demo app are not critical.

The main issue is that I now have clear correspondence between events in my code and what I see in Instruments. And I can control-click on an entry in the list of signpost ranges and tell Instruments to "Set time filter", if I want, so that when I go back to my other instruments (allocations or time profiler or whatever), the inspection range is filtered down to the relevant points of interest in my app.


Note: I'm using kdebug_signpost introduced in iOS 10/macOS 10.12. The headers tell us that earlier OS versions could use syscall:

In previous versions of the operating system, applications could use:

syscall(SYS_kdebug_trace, APPSDBG_CODE(DBG_MACH_CHUD, <your event code>) | DBG_FUNC_<type>, arg1, arg2, arg3, arg4);

to record events that would be displayed by Instruments. syscall(2) is now deprecated and this interface replaces the above call.


It's not terribly relevant here, but this is the code that generated that above snapshot:

let downloadQueue = OperationQueue()
let parseQueue = OperationQueue()
downloadQueue.maxConcurrentOperationCount = 2
parseQueue.maxConcurrentOperationCount = 2

let completionOperation = BlockOperation {
    kdebug_signpost(SignPost.done.rawValue, 0, 0, 0, UInt(SignPost.done.rawValue))

    let alert = NSAlert()
    alert.messageText = "done"
    alert.runModal()
}

for index in downloadIndexes {
    let downloadOperation = BlockOperation {
        kdebug_signpost_start(SignPost.download.rawValue, UInt(index), 0, 0, UInt(SignPost.download.rawValue))
        self.performDownload(at: index) {
            kdebug_signpost_end(SignPost.download.rawValue, UInt(index), 0, 0, UInt(SignPost.download.rawValue))
        }
    }
    downloadQueue.addOperation(downloadOperation)

    let parseOperation = BlockOperation {
        kdebug_signpost_start(SignPost.parse.rawValue, UInt(index), 0, 0, UInt(SignPost.parse.rawValue))
        self.performParse(at: index) {
            kdebug_signpost_end(SignPost.parse.rawValue, UInt(index), 0, 0, UInt(SignPost.parse.rawValue))
        }
    }
    parseOperation.addDependency(downloadOperation)
    completionOperation.addDependency(parseOperation)
    parseQueue.addOperation(parseOperation)
}

OperationQueue.main.addOperation(completionOperation)
Comments