97 lines
3.7 KiB
Swift
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)"
|
|
}
|
|
}
|
|
}
|