Viewing is open to everyone. Downloading and starring need a GitHub sign-in.
Sign in with GitHub
CKey.swift
v0.1.0-build.85
Latest
Commit ede38f5
Sign in with GitHub to download. Each download is recorded with your GitHub username.
| 1 | @_exported import CKeyScanKit |
| 2 | import CKeyCodec |
| 3 | import CKeyTelemetry |
| 4 | import Foundation |
| 5 | |
| 6 | /// Telemetry modes. The numeric values are canonical across every CKey SDK |
| 7 | /// (docs/adoption-and-telemetry.md). |
| 8 | public enum CKeyTelemetryMode: Int, Sendable, CaseIterable, ExpressibleByIntegerLiteral { |
| 9 | /// 0: no counting, nothing sent. Switching to it purges every counter. |
| 10 | case off = 0 |
| 11 | /// 1 (default): local aggregate counting; batches are sent to openckey.org |
| 12 | /// at most once an hour, never on the scan path. |
| 13 | case on = 1 |
| 14 | /// 2: local aggregate counting only; nothing is ever sent. |
| 15 | case local = 2 |
| 16 | |
| 17 | /// Allows `CKey.telemetry = 0`. Only the literals 0, 1 and 2 are valid; any |
| 18 | /// other literal is a programming error and stops the program (it is |
| 19 | /// never silently treated as 1). For values known only at run time, use |
| 20 | /// `try CKey.setTelemetry(rawValue:)` or `CKeyTelemetryMode(rawValue:)`. |
| 21 | public init(integerLiteral value: Int) { |
| 22 | guard let mode = CKeyTelemetryMode(rawValue: value) else { |
| 23 | fatalError("CKey telemetry mode must be 0 (off), 1 (on) or 2 (local); got \(value)") |
| 24 | } |
| 25 | self = mode |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | /// Aggregate scan counters kept by the telemetry core (modes 1 and 2). |
| 30 | public struct CKeyTelemetryCounters: Equatable, Sendable { |
| 31 | public struct Histogram: Equatable, Sendable { |
| 32 | public let count: UInt64 |
| 33 | public let sum: UInt64 |
| 34 | /// Ten buckets with inclusive upper edges (see docs/adoption-and-telemetry.md). |
| 35 | public let buckets: [UInt64] |
| 36 | } |
| 37 | public let sessionsStarted: UInt64 |
| 38 | public let sessionsSucceeded: UInt64 |
| 39 | public let sessionsFailed: UInt64 |
| 40 | public let frames: UInt64 |
| 41 | /// Decodes per profile, keyed "p0", "p0_1", "p1", "p2", "p3". |
| 42 | public let decoded: [String: UInt64] |
| 43 | public let timeToDecodeMilliseconds: Histogram |
| 44 | public let frameMilliseconds: Histogram |
| 45 | } |
| 46 | |
| 47 | /// Package-level configuration. |
| 48 | /// |
| 49 | /// `CKey` is both this module's name and this type's name; inside an app, |
| 50 | /// `CKey.telemetry` refers to the type. |
| 51 | public enum CKey { |
| 52 | /// The core's software version (the repository's VERSION). |
| 53 | public static var version: String { CKeyCoreInfo.softwareVersion } |
| 54 | |
| 55 | /// The telemetry mode: `.on` (1, default), `.local` (2) or `.off` (0). |
| 56 | /// Opting out is one line: `CKey.telemetry = .off` (or `= 0`). |
| 57 | /// |
| 58 | /// Setting a mode applies the core's switching rules: `.off` purges all |
| 59 | /// counters; `.on` -> `.local` drops anything unsent (an in-flight batch's |
| 60 | /// result is ignored). The mode is process-wide and not persisted; set it |
| 61 | /// at launch, before scanning. |
| 62 | public static var telemetry: CKeyTelemetryMode { |
| 63 | get { |
| 64 | CKeyTelemetryRuntime.shared.start() |
| 65 | return CKeyTelemetryMode(rawValue: Int(ckey_telemetry_get_mode())) ?? .on |
| 66 | } |
| 67 | set { |
| 68 | CKeyTelemetryRuntime.shared.start() |
| 69 | // Every enum value is valid, so the core cannot refuse it. |
| 70 | _ = ckey_telemetry_set_mode(Int32(newValue.rawValue)) |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | /// Sets the telemetry mode from a number (0, 1 or 2), e.g. from a |
| 75 | /// configuration file. |
| 76 | /// - Throws: `CKeyError` with code `.invalidConfiguration` for any other |
| 77 | /// value; the mode is left unchanged (never silently reset to 1). |
| 78 | public static func setTelemetry(rawValue: Int) throws { |
| 79 | guard let mode = CKeyTelemetryMode(rawValue: rawValue) else { |
| 80 | throw CKeyError(.invalidConfiguration, |
| 81 | "telemetry mode must be 0 (off), 1 (on) or 2 (local); got \(rawValue)") |
| 82 | } |
| 83 | telemetry = mode |
| 84 | } |
| 85 | |
| 86 | /// The local aggregate totals since counting started (all zero in mode 0). |
| 87 | /// Available to the host in modes 1 and 2; nothing here identifies a |
| 88 | /// user, a device or a payload. |
| 89 | public static var telemetryCounters: CKeyTelemetryCounters { |
| 90 | var c = ckey_telemetry_counters() |
| 91 | _ = ckey_telemetry_snapshot(&c) |
| 92 | func histogram(_ h: ckey_telemetry_histogram) -> CKeyTelemetryCounters.Histogram { |
| 93 | let buckets = withUnsafeBytes(of: h.buckets) { Array($0.bindMemory(to: UInt64.self)) } |
| 94 | return .init(count: h.count, sum: h.sum, buckets: buckets) |
| 95 | } |
| 96 | let decoded = withUnsafeBytes(of: c.decoded) { Array($0.bindMemory(to: UInt64.self)) } |
| 97 | let keys = ["p0", "p0_1", "p1", "p2", "p3"] |
| 98 | var byProfile: [String: UInt64] = [:] |
| 99 | for (index, key) in keys.enumerated() where index < decoded.count { byProfile[key] = decoded[index] } |
| 100 | return CKeyTelemetryCounters(sessionsStarted: c.sessions_started, sessionsSucceeded: c.sessions_succeeded, |
| 101 | sessionsFailed: c.sessions_failed, frames: c.frames, decoded: byProfile, |
| 102 | timeToDecodeMilliseconds: histogram(c.time_to_decode_ms), |
| 103 | frameMilliseconds: histogram(c.frame_ms)) |
| 104 | } |
| 105 | } |