| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- //
- // Copyright 2026 Aarav Ravindra Kharade
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- import CSystem
- // ── Resource Controller ───────────────────────────────────────────
- /// Manages CPU and memory resources for the ArkOS runtime.
- ///
- /// CPU gating: All background work is dispatched through a concurrent
- /// queue to prevent runaway thread creation. The queue width matches
- /// the number of available CPU cores detected at startup.
- ///
- /// Memory monitoring: Reads the process RSS from getrusage(2) and
- /// compares against the configured cap (default 2 GB).
- public struct ResourceController {
- /// Execute a unit of work on a new detached pthread.
- public static func executeOnCorePool(_ work: @escaping () -> Void) {
- // Box the closure into a heap-allocated context
- let ctx = UnsafeMutablePointer<(() -> Void)>.allocate(capacity: 1)
- ctx.initialize(to: work)
- var thread: pthread_t?
- let result = pthread_create(&thread, nil, { arg -> UnsafeMutableRawPointer? in
- let fn = arg!.assumingMemoryBound(to: (() -> Void).self)
- fn.pointee()
- fn.deinitialize(count: 1)
- fn.deallocate()
- return nil
- }, ctx)
- if result == 0 {
- if let t = thread {
- pthread_detach(t)
- }
- } else {
- print("ERROR: pthread_create failed with \(result)")
- ctx.deinitialize(count: 1)
- ctx.deallocate()
- }
- }
- /// Returns the current process peak RSS in bytes.
- /// On Linux, getrusage(2) reports ru_maxrss in kilobytes.
- public static func checkMemoryUsage() -> Int64 {
- #if canImport(Glibc) || canImport(CSystem)
- var usage = rusage()
- if getrusage(RUSAGE_SELF, &usage) == 0 {
- return Int64(usage.ru_maxrss) * 1024
- }
- #endif
- return 0
- }
- /// Returns the number of online CPU cores by reading /proc/cpuinfo.
- public static func getCPUCoreCount() -> Int {
- #if canImport(Glibc) || canImport(CSystem)
- let count = sysconf(Int32(_SC_NPROCESSORS_ONLN))
- if count > 0 { return count }
- #endif
- return 1
- }
- /// Returns total system RAM in bytes by reading /proc/meminfo.
- public static func getTotalMemory() -> Int64 {
- if let data = readFileContents("/proc/meminfo") {
- for line in data.split(separator: "\n") {
- if line.hasPrefix("MemTotal:") {
- let parts = line.split(separator: " ").compactMap { Int64($0) }
- if let kb = parts.first {
- return kb * 1024
- }
- }
- }
- }
- return 0
- }
- /// Returns available system RAM in bytes by reading /proc/meminfo.
- public static func getAvailableMemory() -> Int64 {
- if let data = readFileContents("/proc/meminfo") {
- for line in data.split(separator: "\n") {
- if line.hasPrefix("MemAvailable:") {
- let parts = line.split(separator: " ").compactMap { Int64($0) }
- if let kb = parts.first {
- return kb * 1024
- }
- }
- }
- }
- return 0
- }
- /// Returns used memory in bytes (total - available).
- public static func getUsedMemory() -> Int64 {
- let total = getTotalMemory()
- let available = getAvailableMemory()
- return max(0, total - available)
- }
- /// Returns the CPU usage as a percentage (0-100).
- /// Reads /proc/stat twice with a 100ms interval and computes the delta.
- public static func getCPUUsage() -> Double {
- func readCPUStat() -> (idle: Int64, total: Int64)? {
- guard let data = readFileContents("/proc/stat") else { return nil }
- let lines = data.split(separator: "\n")
- guard let cpuLine = lines.first(where: { $0.hasPrefix("cpu ") }) else { return nil }
- let fields = cpuLine.split(separator: " ").dropFirst().compactMap { Int64($0) }
- guard fields.count >= 4 else { return nil }
- let idle = fields[3] + (fields.count > 4 ? fields[4] : 0) // idle + iowait
- let total = fields.reduce(0, +)
- return (idle, total)
- }
- guard let first = readCPUStat() else { return 0.0 }
- usleep(100_000) // 100ms
- guard let second = readCPUStat() else { return 0.0 }
- let deltaIdle = second.idle - first.idle
- let deltaTotal = second.total - first.total
- guard deltaTotal > 0 else { return 0.0 }
- return Double(deltaTotal - deltaIdle) / Double(deltaTotal) * 100.0
- }
- }
|