Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
os_activity_t for Swift 3
//
// Activity.swift
//
// Created by Zachary Waldowski on 8/21/16.
// Copyright © 2016 Zachary Waldowski. Licensed under MIT.
//
import os.activity
private final class LegacyActivityContext {
let dsoHandle: UnsafeRawPointer?
let description: UnsafePointer<CChar>
let flags: os_activity_flag_t
init(dsoHandle: UnsafeRawPointer?, description: UnsafePointer<CChar>, flags: os_activity_flag_t) {
self.dsoHandle = dsoHandle
self.description = description
self.flags = flags
}
}
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
@_silgen_name("_os_activity_create") private func _os_activity_create(_ dso: UnsafeRawPointer?, _ description: UnsafePointer<Int8>, _ parent : Unmanaged<AnyObject>?, _ flags: os_activity_flag_t) -> AnyObject!
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
@_silgen_name("os_activity_apply") private func _os_activity_apply(_ storage: AnyObject, _ block: @convention(block) () -> ())
@_silgen_name("_os_activity_initiate") private func __os_activity_initiate(_ dso: UnsafeRawPointer?, _ description: UnsafePointer<Int8>, _ flags: os_activity_flag_t, _ block: @convention(block) () -> ())
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
@_silgen_name("os_activity_scope_enter") private func _os_activity_scope_enter(_ storage: AnyObject, _ state: UnsafeMutablePointer<os_activity_scope_state_s>)
@_silgen_name("_os_activity_start") private func __os_activity_start(_ dso: UnsafeRawPointer?, _ description: UnsafePointer<Int8>, _ flags: os_activity_flag_t) -> UInt64
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
@_silgen_name("os_activity_scope_leave") private func _os_activity_scope_leave(_ state: UnsafeMutablePointer<os_activity_scope_state_s>)
@_silgen_name("os_activity_end") private func __os_activity_end(_ state: UInt64)
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
private let OS_ACTIVITY_NONE = unsafeBitCast(dlsym(UnsafeMutableRawPointer(bitPattern: -2), "_os_activity_none"), to: Unmanaged<AnyObject>.self)
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
private let OS_ACTIVITY_CURRENT = unsafeBitCast(dlsym(UnsafeMutableRawPointer(bitPattern: -2), "_os_activity_current"), to: Unmanaged<AnyObject>.self)
public struct Activity {
/// Support flags for OSActivity.
public struct Options: OptionSet {
public let rawValue: UInt32
public init(rawValue: UInt32) {
self.rawValue = rawValue
}
/// Detach a newly created activity from a parent activity, if any.
///
/// If passed in conjunction with a parent activity, the activity will
/// only note what activity "created" the new one, but will make the
/// new activity a top level activity. This allows seeing what
/// activity triggered work without actually relating the activities.
public static let detached = Options(rawValue: OS_ACTIVITY_FLAG_DETACHED.rawValue)
/// Will only create a new activity if none present.
///
/// If an activity ID is already present, a new activity will be
/// returned with the same underlying activity ID.
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
public static let ifNonePresent = Options(rawValue: OS_ACTIVITY_FLAG_IF_NONE_PRESENT.rawValue)
}
private let opaque: AnyObject
/// Creates an activity.
public init(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle, options: Options = []) {
self.opaque = description.withUTF8Buffer { (buf: UnsafeBufferPointer<UInt8>) -> AnyObject in
let str = unsafeBitCast(buf.baseAddress!, to: UnsafePointer<Int8>.self)
let flags = os_activity_flag_t(rawValue: options.rawValue)
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *), OS_ACTIVITY_OBJECT_API != 0 {
return _os_activity_create(dso, str, OS_ACTIVITY_CURRENT, flags)
} else {
return LegacyActivityContext(dsoHandle: dso, description: str, flags: flags)
}
}
}
private func active(execute body: @convention(block) () -> ()) {
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *), OS_ACTIVITY_OBJECT_API != 0 {
_os_activity_apply(opaque, body)
} else {
let context = opaque as! LegacyActivityContext
__os_activity_initiate(context.dsoHandle, context.description, context.flags, body)
}
}
/// Executes a function body within the context of the activity.
public func active<Return>(execute body: () throws -> Return) rethrows -> Return {
func impl(execute work: () throws -> Return, recover: (Error) throws -> Return) rethrows -> Return {
var result: Return?
var error: Error?
active {
do {
result = try work()
} catch let e {
error = e
}
}
if let e = error {
return try recover(e)
} else {
return result!
}
}
return try impl(execute: body, recover: { throw $0 })
}
/// Opaque structure created by `Activity.enter()` and restored using
/// `leave()`.
public struct Scope {
fileprivate var state = os_activity_scope_state_s()
fileprivate init() {}
/// Pops activity state to `self`.
public mutating func leave() {
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *), OS_ACTIVITY_OBJECT_API != 0 {
_os_activity_scope_leave(&state)
} else {
UnsafeRawPointer(bitPattern: Int(state.opaque.0)).map(Unmanaged<AnyObject>.fromOpaque)?.release()
__os_activity_end(state.opaque.1)
}
}
}
/// Changes the current execution context to the activity.
///
/// An activity can be created and applied to the current scope by doing:
///
/// var scope = OSActivity("my new activity").enter()
/// defer { scope.leave() }
/// ... do some work ...
///
public func enter() -> Scope {
var scope = Scope()
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *), OS_ACTIVITY_OBJECT_API != 0 {
_os_activity_scope_enter(opaque, &scope.state)
} else {
let context = opaque as! LegacyActivityContext
scope.state.opaque.0 = numericCast(Int(bitPattern: Unmanaged.passRetained(context).toOpaque()))
scope.state.opaque.1 = __os_activity_start(context.dsoHandle, context.description, context.flags)
}
return scope
}
/// Creates an activity.
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
public init(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle, parent: Activity, options: Options = []) {
self.opaque = description.withUTF8Buffer { (buf: UnsafeBufferPointer<UInt8>) -> AnyObject in
let str = unsafeBitCast(buf.baseAddress!, to: UnsafePointer<Int8>.self)
let flags = os_activity_flag_t(rawValue: options.rawValue)
return _os_activity_create(dso, str, Unmanaged.passRetained(parent.opaque), flags)
}
}
private init(_ opaque: AnyObject) {
self.opaque = opaque
}
/// An activity with no traits; as a parent, it is equivalent to a
/// detached activity.
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
public static var none: Activity {
return Activity(OS_ACTIVITY_NONE.takeUnretainedValue())
}
/// The running activity.
///
/// As a parent, the new activity is linked to the current activity, if one
/// is present. If no activity is present, it behaves the same as `.none`.
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
public static var current: Activity {
return Activity(OS_ACTIVITY_CURRENT.takeUnretainedValue())
}
/// Label an activity auto-generated by UI with a name that is useful for
/// debugging macro-level user actions.
///
/// This function should be called early within the scope of an `IBAction`,
/// before any sub-activities are created. The name provided will be shown
/// in tools in addition to the system-provided name. This API should only
/// be called once, and only on an activity created by the system. These
/// actions help determine workflow of the user in order to reproduce
/// problems that occur.
///
/// For example, a control press and/or menu item selection can be labeled:
///
/// OSActivity.labelUserAction("New mail message")
/// OSActivity.labelUserAction("Empty trash")
///
/// Where the underlying name will be "gesture:" or "menuSelect:".
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
public static func labelUserAction(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle) {
description.withUTF8Buffer { (buf: UnsafeBufferPointer<UInt8>) in
let str = unsafeBitCast(buf.baseAddress!, to: UnsafePointer<Int8>.self)
_os_activity_label_useraction(UnsafeMutableRawPointer(mutating: dso), str)
}
}
}
@dhoerl

This comment has been minimized.

Copy link

commented Nov 9, 2016

Thank you so much for doing this!!! Big huge help!

@lilyball

This comment has been minimized.

Copy link

commented Jan 10, 2017

<os/activity.h> stuffs the static strings into the section __TEXT,__oslogstring,cstring_literals. Your gist here doesn't do that (since there's no way to do this in Swift). Isn't that a problem?

@zwaldowski

This comment has been minimized.

Copy link
Owner Author

commented Jan 10, 2017

Putting it in a special section in ObjC guarantees a stable in-this-binary pointer; the OS uses this and the DSO handle to get a UUID for the binary. Not sure what that UUID is used for; it's LaunchServices-related. The relevant facility in libsystem_trace looks in __TEXT,__cstring and __TEXT,__const as well; StaticString seems to do the trick for Swift. The (half-assed) overlay done for os_log does the same thing.

@zwaldowski

This comment has been minimized.

Copy link
Owner Author

commented Jan 10, 2017

Note to self: rewrite to use dlsym instead of @_silgen_name.

@lilyball

This comment has been minimized.

Copy link

commented Jan 13, 2017

@zwaldowski Where's the (half-assed) overlay for os_log? I skimmed through your public gists but don't see anything.

@lilyball

This comment has been minimized.

Copy link

commented Jan 13, 2017

Whoa wait, the system frameworks actually include os_log APIs for Swift? I thought they didn't have that, like they don't have os_activity. That's interesting.

@kdawgwilk

This comment has been minimized.

Copy link

commented Aug 31, 2017

What is the answer fixing these warnings?

screen shot 2017-08-31 at 4 36 58 pm

@alemar11

This comment has been minimized.

Copy link

commented Sep 8, 2017

@kdawgwilk try this:
let str = buf.baseAddress!.withMemoryRebound(to: Int8.self, capacity: 8, { $0 })

@vgorloff

This comment has been minimized.

Copy link

commented May 6, 2018

Update for Xcode 9.4 / Swift 4.1:

import Foundation
import os.activity

// Bridging Obj-C variabled defined as c-macroses. See `activity.h` header.
private let OS_ACTIVITY_NONE = unsafeBitCast(dlsym(UnsafeMutableRawPointer(bitPattern: -2), "_os_activity_none"), to: OS_os_activity.self)
private let OS_ACTIVITY_CURRENT = unsafeBitCast(dlsym(UnsafeMutableRawPointer(bitPattern: -2), "_os_activity_current"), to: OS_os_activity.self)

public struct Activity {

   private let activity: OS_os_activity!

   public init(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle, options: Options = []) {
      activity = description.withUTF8Buffer {
         if let dso = UnsafeMutableRawPointer(mutating: dso), let address = $0.baseAddress {
            let str = UnsafeRawPointer(address).assumingMemoryBound(to: Int8.self)
            return _os_activity_create(dso, str, OS_ACTIVITY_CURRENT, os_activity_flag_t(rawValue: options.rawValue))
         } else {
            return nil
         }
      }
   }

   public init(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle, parent: Activity, options: Options = []) {
      let parentActivity: OS_os_activity = parent.activity != nil ? parent.activity : OS_ACTIVITY_CURRENT
      activity = description.withUTF8Buffer {
         if let dso = UnsafeMutableRawPointer(mutating: dso), let address = $0.baseAddress {
            let str = UnsafeRawPointer(address).assumingMemoryBound(to: Int8.self)
            return _os_activity_create(dso, str, parentActivity, os_activity_flag_t(rawValue: options.rawValue))
         } else {
            return nil
         }
      }
   }

   private init(_ activity: OS_os_activity) {
      self.activity = activity
   }

}

extension Activity {

   public static var none: Activity {
      return Activity(OS_ACTIVITY_NONE)
   }

   public static var current: Activity {
      return Activity(OS_ACTIVITY_CURRENT)
   }

   public static func initiate(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle, options: Options = [],
                               execute body: @convention(block) () -> ()) {
      description.withUTF8Buffer {
         if let dso = UnsafeMutableRawPointer(mutating: dso), let address = $0.baseAddress {
            let str = UnsafeRawPointer(address).assumingMemoryBound(to: Int8.self)
            _os_activity_initiate(dso, str, os_activity_flag_t(rawValue: options.rawValue), body)
         }
      }
   }

   public func apply(execute body: @convention(block) () -> ()) {
      if activity != nil {
         os_activity_apply(activity, body)
      }
   }

   public func enter() -> Scope {
      var scope = Scope()
      if activity != nil {
         os_activity_scope_enter(activity, &scope.state)
      }
      return scope
   }

   /**
    * Label an activity that is auto-generated by AppKit/UIKit with a name that is
    * useful for debugging macro-level user actions.  The API should be called
    * early within the scope of the IBAction and before any sub-activities are
    * created.
    * This API can only be called once and only on the activity created by AppKit/UIKit.
    */
   public static func labelUserAction(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle) {
      description.withUTF8Buffer {
         if let dso = UnsafeMutableRawPointer(mutating: dso), let address = $0.baseAddress {
            let str = UnsafeRawPointer(address).assumingMemoryBound(to: Int8.self)
            _os_activity_label_useraction(dso, str)
         }
      }
   }

}

extension Activity {

   public struct Options: OptionSet {

      public let rawValue: UInt32

      public init(rawValue: UInt32) {
         self.rawValue = rawValue
      }

      public static let `default` = Options(rawValue: OS_ACTIVITY_FLAG_DEFAULT.rawValue)
      public static let detached = Options(rawValue: OS_ACTIVITY_FLAG_DETACHED.rawValue)
      public static let ifNonePresent = Options(rawValue: OS_ACTIVITY_FLAG_IF_NONE_PRESENT.rawValue)
   }

   public struct Scope {

      fileprivate var state = os_activity_scope_state_s()

      public mutating func leave() {
         os_activity_scope_leave(&state)
      }
   }
}
@LinMaris

This comment has been minimized.

Copy link

commented Sep 25, 2018

@vgorloff What is OS_os_activity?

@sstadelman

This comment has been minimized.

Copy link

commented Nov 2, 2018

@zwaldowski (all), Do you occasionally get blocked by the error 'String' is not convertible to 'StaticString'?

EDIT: This seems to be the Swift compiler behavior, when a Return type is not specified when wrapping a function expecting a return. Makes sense.

Fix:

func applyMainExclusionPath() -> CGRect {
        // would be invoked from within Activity
        
        return Activity("apply main exclusion path").active { () -> CGRect in
@Oyvindkg

This comment has been minimized.

Copy link

commented Dec 18, 2018

I haven't been able to try this out yet, but it looks interesting.
Compared to the native API, the process of entering and leaving scopes is a bit verbose, and when someone forgets to leave it, I imagine it may cause problems.

To avoid the deferred leave boilerplate, would it be possible to define the scope as a class instead and leave the scope on deinit?

public class Scope {

    fileprivate var state = os_activity_scope_state_s()

    deinit {
        os_activity_scope_leave(&state)
    }
}
@OdNairy

This comment has been minimized.

Copy link

commented Jun 25, 2019

To avoid the deferred leave boilerplate, would it be possible to define the scope as a class instead and leave the scope on deinit?

public class Scope {

    fileprivate var state = os_activity_scope_state_s()

    deinit {
        os_activity_scope_leave(&state)
    }
}

This approach doesn't work on Swift 5.0. Unfortunately, we still have to dance around the defer { scope.leave() }.

@zwaldowski

This comment has been minimized.

Copy link
Owner Author

commented Jun 25, 2019

Re: class Scope, Swift ARC deinitialization isn't defined to happen at the end of scope like in C++. It's free to be deallocated (and thus end scope) as soon as you last use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.