Ordering and coloring signposts

My team and I have watched all of the WWDC 2018 & 2019 session on signposts and custom instruments, and scoured the internet for documentation and examples... but the resources out there on instruments are thin.


It appears from WWDC videos that it should be easy to arrange (other than alphabetical) and add colors to signposts in instruments with either a instruments setup, or a little XML... but without needing Clips. Is this true? Any example code (or just configuration)?

Replies

I color my points of interest using a custom instrument

<?xml version="1.0" encoding="UTF-8" ?>
<!-- Instruments Developer Help: https://help.apple.com/instruments/developer/mac/current/ -->
<package>
    <id>com.robertmryan.CustomInterval</id>
    <version>0.2</version>
    <title>Custom Points of Interest</title>
    <owner>
        <name>Robert Ryan</name>
    </owner>

    <import-schema>os-signpost</import-schema>

    <!-- See https://help.apple.com/instruments/developer/mac/current/#/dev536412616 -->
    <os-signpost-point-schema>
        <id>custom-point-schema</id>
        <title>Points</title>
        <owner>
            <name>Robert Ryan</name>
        </owner>
        <purpose>Provide mechanism for multicolored events posted by `os_signpost`; The string generated by `os_signpost` must be in form of "Label:%d,Concept:%{public}@", where "Label" is string that will control what text appears in the event, and "Concept" is one of the strings listed in https://help.apple.com/instruments/developer/mac/current/#/dev66257045 that dictates the color of the interval. No spaces after the commas within this string.</purpose>
        <note>That message must use that printf-style format, not embedding the values in the format string literal.</note>

        <!-- you can constrain this to a particular subsystem if you'd like:
        <subsystem>"com.domain.MyApp"</subsystem>
        -->
        <category>"Interval"</category>
        <name>?name</name>

        <pattern>
            <message>"Label:" ?label ",Concept:" ?concept</message>
        </pattern>

        <column>
            <mnemonic>name</mnemonic>
            <title>Name</title>
            <type>string</type>
            <expression>?name</expression>
        </column>
        <column>
            <mnemonic>label</mnemonic>
            <title>Label</title>
            <type>string</type>
            <expression>?label</expression>
        </column>
        <column>
            <mnemonic>concept</mnemonic>
            <title>Concept</title>
            <type>event-concept</type>
            <expression>?concept</expression>
        </column>
    </os-signpost-point-schema>

    <os-signpost-interval-schema>
        <id>custom-interval-schema</id>
        <title>Intervals</title>
        <owner>
            <name>Robert Ryan</name>
        </owner>
        <purpose>Provide mechanism for multicolored intervals posted by `os_signpost`; The string generated by `os_signpost` must be in form of "Label:%d,Concept:%{public}@", where "Label" is string that will control what text appears in the interval, and "Concept" is one of the strings listed in https://help.apple.com/instruments/developer/mac/current/#/dev66257045 that dictates the color of the interval. No spaces after the commas within this string.</purpose>
        <note>That message must use that printf-style format, not embedding the values in the format string literal.</note>

        <!-- you can constrain this to a particular subsystem if you'd like:
        <subsystem>"com.domain.MyApp"</subsystem>
        -->
        <category>"Interval"</category>
        <name>?name</name>

        <start-pattern>
            <message>"Label:" ?label ",Concept:" ?concept</message>
        </start-pattern>

        <column>
            <mnemonic>name</mnemonic>
            <title>Name</title>
            <type>string</type>
            <expression>?name</expression>
        </column>
        <column>
            <mnemonic>label</mnemonic>
            <title>Label</title>
            <type>string</type>
            <expression>?label</expression>
        </column>
        <column>
            <mnemonic>concept</mnemonic>
            <title>Concept</title>
            <type>event-concept</type>
            <expression>?concept</expression>
        </column>
    </os-signpost-interval-schema>

    <instrument>
        <id>com.robertmryan.CustomInterval.instrument</id>
        <title>Custom Points of Interest</title>
        <category>Behavior</category>
        <purpose>Provide multi-colored intervals as dictated by the "event-concept" parsed from the `start-pattern` string.</purpose>
        <icon>Points of Interest</icon>
        <limitations></limitations>
        <create-table>
            <id>custom-interval-table</id>
            <schema-ref>custom-interval-schema</schema-ref>
        </create-table>
        <create-table>
            <id>custom-point-table</id>
            <schema-ref>custom-point-schema</schema-ref>
        </create-table>

        <graph>
            <title>Custom Interval Graph</title>
            <lane>
                <title>Points</title>
                <table-ref>custom-point-table</table-ref>
                <plot-template>
                    <instance-by>name</instance-by>
                    <label-format>%s</label-format>
                    <value-from>name</value-from>
                    <color-from>concept</color-from>
                    <label-from>label</label-from>
                </plot-template>
            </lane>
            <lane>
                <title>Intervals</title>
                <table-ref>custom-interval-table</table-ref>
                <plot-template>
                    <instance-by>name</instance-by>
                    <label-format>%s</label-format>
                    <value-from>name</value-from>
                    <color-from>concept</color-from>
                    <label-from>label</label-from>
                    <qualified-by>layout-qualifier</qualified-by>
                </plot-template>
            </lane>
        </graph>

        <list>
            <title>Custom Regions of Interest</title>
            <table-ref>custom-interval-table</table-ref>
            <column>name</column>
            <column>label</column>
            <column>concept</column>
            <column>start</column>
            <column>duration</column>
        </list>

        <list>
            <title>Custom Points of Interest</title>
            <table-ref>custom-point-table</table-ref>
            <column>name</column>
            <column>label</column>
            <column>concept</column>
        </list>
    </instrument>
</package>

Then you can just do the appropriate os_signpost calls. Personally, I use a helper swift class to make sure that the category is correct and the format of the strings is correct. E.g.

import Foundation
import os.log

// MARK: - CustomPointsOfInterestLog

/// Custom Points of Interest Log
///
/// This allows logging of events and intervals to a custom “Points of Interest” tool in Instruments.
///
/// Needless to say, this assumes that you have installed the custom Points of Interest tool in Instrumewnts.

class CustomPointsOfInterestLog {
    fileprivate let log: OSLog

    init(subsystem: String) {
        log = OSLog(subsystem: subsystem, category: "Interval")
    }

    func event(name: StaticString = "Points", label: String, concept: EventConcept = .debug) {
        os_signpost(.event, log: log, name: name, InstrumentsInterval.formatString, label, concept.rawValue)
    }

    func interval<T>(name: StaticString = "Intervals", label: String, concept: EventConcept = .debug, block: () throws -> T) rethrows -> T {
        let interval = InstrumentsInterval(name: name, label: label, concept: concept, log: self)

        interval.begin()
        defer { interval.end() }
        return try block()
    }
}

// MARK: - EventConcept

extension CustomPointsOfInterestLog {
    /// EventConcept enumeration
    ///
    /// This is used to dictate the color of the intervals in our custom instrument.
    /// See [Event Concept Engineering Type](https://help.apple.com/instruments/developer/mac/current/#/dev66257045).

    enum EventConcept: String {
        case success = "Success"
        case failure = "Failure"

        case fault = "Fault"
        case critical = "Critical"
        case error = "Error"
        case debug = "Debug"
        case pedantic = "Pedantic"
        case info = "Info"

        case signpost = "Signpost"

        case veryLow = "Very Low"
        case low = "Low"
        case moderate = "Moderate"
        case high = "High"

        case red = "Red"
        case orange = "Orange"
        case blue = "Blue"
        case purple = "Purple"
        case green = "Green"
    }
}

// MARK: - InstrumentsInterval

/// Interval to be shown in custom instrument when profiling app

struct InstrumentsInterval {
    fileprivate static let formatString: StaticString = "Label:%{public}@,Concept:%{public}@"

    let name: StaticString
    let label: String
    let concept: CustomPointsOfInterestLog.EventConcept
    let log: CustomPointsOfInterestLog
    let id: OSSignpostID

    init(name: StaticString, label: String, concept: CustomPointsOfInterestLog.EventConcept = .debug, log: CustomPointsOfInterestLog) {
        self.name = name
        self.concept = concept
        self.label = label
        self.log = log
        self.id = OSSignpostID(log: log.log)
    }

    /// Manually begin an interval
    func begin() {
        os_signpost(.begin, log: log.log, name: name, signpostID: id, Self.formatString, label, concept.rawValue)
    }

    /// Manually end an interval
    func end() {
        os_signpost(.end, log: log.log, name: name, signpostID: id)
    }

    /// Manually emit an event
    func event() {
        os_signpost(.event, log: log.log, name: name, signpostID: id, Self.formatString, label, concept.rawValue)
    }
}

And then I can do logging like so:

private let log = CustomPointsOfInterestLog(subsystem: "Example")

class Example {
    func synchronousTask() {
        log.interval(name: "Example", label: #function, concept: .green) {
            Thread.sleep(forTimeInterval: 2)
        }
    }
}

And I can see my colored output: