TM-SGNL-iOS/SignalServiceKit/Util/LocalDevice.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

97 lines
3.7 KiB
Swift

//
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
@objc
public class LocalDevice: NSObject {
public struct MemoryStatus {
public let fetchDate: Date
public let footprint: UInt64
public let peakFootprint: Int64
public let bytesRemaining: UInt64
public let mallocSize: Int64
public let mallocAllocations: Int64
fileprivate static func fetchCurrentStatus() -> MemoryStatus? {
let vmInfoExpectedSize = MemoryLayout<task_vm_info_data_t>.size / MemoryLayout<natural_t>.size
var vmInfo = task_vm_info_data_t()
var vmInfoSize = mach_msg_type_number_t(vmInfoExpectedSize)
let kern: kern_return_t = withUnsafeMutablePointer(to: &vmInfo) {
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
task_info(mach_task_self_,
task_flavor_t(TASK_VM_INFO),
$0,
&vmInfoSize)
}
}
guard kern == KERN_SUCCESS else {
let errorString = String(cString: mach_error_string(kern), encoding: .ascii) ?? "Unknown error"
owsFailDebug(errorString)
return nil
}
let mallocZone = malloc_default_zone()
var statistics = malloc_statistics_t()
malloc_zone_statistics(mallocZone, &statistics)
let result = MemoryStatus(
fetchDate: Date(),
footprint: vmInfo.phys_footprint,
peakFootprint: vmInfo.ledger_phys_footprint_peak,
bytesRemaining: vmInfo.limit_bytes_remaining,
mallocSize: Int64(statistics.size_in_use),
mallocAllocations: Int64(statistics.size_allocated)
)
return result
}
}
private static var _memoryStatus = AtomicOptional<MemoryStatus>(nil, lock: .sharedGlobal)
public static func currentMemoryStatus(forceUpdate: Bool = false) -> MemoryStatus? {
// If we don't have a cached status, we must fetch
guard let currentStatus = _memoryStatus.get() else {
return _memoryStatus.map { _ in MemoryStatus.fetchCurrentStatus() }
}
let cacheValidityPeriod: TimeInterval = 1.0
if forceUpdate || currentStatus.fetchDate.addingTimeInterval(cacheValidityPeriod).isBeforeNow {
return _memoryStatus.map { _ in MemoryStatus.fetchCurrentStatus() }
} else {
return currentStatus
}
}
@objc
public static var memoryUsageString: String {
// Since this string is intended to be logged, we should fetch a fresh status
guard let currentMemoryStatus = currentMemoryStatus(forceUpdate: true) else {
return "Unknown"
}
let currentFootprint = currentMemoryStatus.footprint
let freeBytes = currentMemoryStatus.bytesRemaining
let mallocUsage = currentMemoryStatus.mallocSize
let mallocAllocations = currentMemoryStatus.mallocAllocations
let warnThreshold: UInt64 = 5 * 1024 * 1024
let criticalThreshold = UInt64(1.5 * 1024 * 1024)
switch freeBytes {
case 0:
// The simulator doesn't report available bytes anymore.
return "\(currentFootprint)"
case ..<criticalThreshold:
return "\(currentFootprint) - CRITICAL - \(freeBytes) remaining — mallocUsage: \(mallocUsage) / \(mallocAllocations)"
case ..<warnThreshold:
return "\(currentFootprint) — WARNING - \(freeBytes) remaining — mallocUsage: \(mallocUsage) / \(mallocAllocations)"
default:
return "\(currentFootprint)"
}
}
}