learning_ai_common_plat/packages/swift-diagnostics/Sources/ByteLystDiagnostics/Device/DeviceState.swift
saravanakumardb1 abcf817cb3 feat(swift-diagnostics): implement Phase 2.2 Swift Client SDK
New package ByteLystDiagnostics with:

- Core types: DiagnosticsSession, TraceSpan, LogEntry, Breadcrumb

- DiagnosticsClient: actor-based singleton with polling

- BreadcrumbTrail: ring buffer (max 100) for timeline

- NetworkInterceptor: URLProtocol-based HTTP capture

- DeviceState: battery, memory, storage, network, thermal

- 20+ XCTest unit tests

Features:

- configure()/start()/stop() lifecycle

- trace() async span wrapper

- log() with breadcrumb integration

- breadcrumb() manual timeline markers

- ETag-based config polling

- 30-second batch flush

Platforms: iOS 15+, macOS 13+, watchOS 8+, tvOS 15+
2026-03-03 09:28:11 -08:00

192 lines
6.1 KiB
Swift

import Foundation
import UIKit
#if os(iOS)
import SystemConfiguration
#endif
/// Device state collector
public struct DeviceStateCollector {
/// Collect current device state
public static func collect() -> DiagnosticsDeviceState {
#if os(iOS)
return DiagnosticsDeviceState(
memoryMB: getMemoryUsage(),
batteryLevel: getBatteryLevel(),
isCharging: getIsCharging(),
storageMB: getStorageUsage(),
networkType: getNetworkType(),
isOnline: getIsOnline(),
thermalState: getThermalState()
)
#elseif os(macOS)
return DiagnosticsDeviceState(
memoryMB: getMemoryUsage(),
batteryLevel: nil,
isCharging: nil,
storageMB: nil,
networkType: nil,
isOnline: getIsOnline(),
thermalState: nil
)
#else
return DiagnosticsDeviceState(
memoryMB: nil,
batteryLevel: nil,
isCharging: nil,
storageMB: nil,
networkType: nil,
isOnline: true,
thermalState: nil
)
#endif
}
#if os(iOS)
private static func getMemoryUsage() -> Int? {
var info = mach_task_basic_info()
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4
let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
}
}
guard kerr == KERN_SUCCESS else { return nil }
return Int(info.resident_size / 1024 / 1024)
}
private static func getBatteryLevel() -> Double? {
UIDevice.current.isBatteryMonitoringEnabled = true
return Double(UIDevice.current.batteryLevel)
}
private static func getIsCharging() -> Bool? {
UIDevice.current.isBatteryMonitoringEnabled = true
return UIDevice.current.batteryState == .charging
}
private static func getStorageUsage() -> Int? {
do {
let attributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory())
if let totalSize = attributes[.systemSize] as? NSNumber,
let freeSize = attributes[.systemFreeSize] as? NSNumber {
let usedSize = totalSize.int64Value - freeSize.int64Value
return Int(usedSize / 1024 / 1024)
}
} catch {
return nil
}
return nil
}
private static func getNetworkType() -> String? {
// Simplified - would need more complex reachability check for actual implementation
if getIsOnline() {
return "wifi" // Default assumption
}
return "offline"
}
private static func getThermalState() -> DiagnosticsThermalState? {
switch ProcessInfo.processInfo.thermalState {
case .nominal:
return .nominal
case .fair:
return .fair
case .serious:
return .serious
case .critical:
return .critical
@unknown default:
return nil
}
}
#endif
private static func getIsOnline() -> Bool {
#if os(iOS) || os(macOS)
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
zeroAddress.sin_family = sa_family_t(AF_INET)
guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
SCNetworkReachabilityCreateWithAddress(nil, $0)
}
}) else {
return false
}
var flags: SCNetworkReachabilityFlags = []
if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
return false
}
let isReachable = flags.contains(.reachable)
let needsConnection = flags.contains(.connectionRequired)
return isReachable && !needsConnection
#else
return true
#endif
}
}
// MARK: - Connectivity Monitoring
#if os(iOS) || os(macOS)
import SystemConfiguration
/// Monitor network connectivity changes
public final class ConnectivityMonitor {
private var reachability: SCNetworkReachability?
private var callback: ((Bool) -> Void)?
public init() {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
zeroAddress.sin_family = sa_family_t(AF_INET)
reachability = withUnsafePointer(to: &zeroAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
SCNetworkReachabilityCreateWithAddress(nil, $0)
}
}
}
public func startMonitoring(callback: @escaping (Bool) -> Void) {
self.callback = callback
guard let reachability = reachability else { return }
let context = SCNetworkReachabilityContext(
version: 0,
info: Unmanaged.passUnretained(self).toOpaque(),
retain: nil,
release: nil,
copyDescription: nil
)
SCNetworkReachabilitySetCallback(reachability, { (_, flags, info) in
guard let info = info else { return }
let monitor = Unmanaged<ConnectivityMonitor>.fromOpaque(info).takeUnretainedValue()
let isReachable = flags.contains(.reachable)
let needsConnection = flags.contains(.connectionRequired)
let isConnected = isReachable && !needsConnection
monitor.callback?(isConnected)
}, &context)
SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(), CFRunLoopMode.commonModes.rawValue)
}
public func stopMonitoring() {
guard let reachability = reachability else { return }
SCNetworkReachabilityUnscheduleFromRunLoop(reachability, CFRunLoopGetMain(), CFRunLoopMode.commonModes.rawValue)
}
}
#endif