ResourceController.swift 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. //
  2. // Copyright 2026 Aarav Ravindra Kharade
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import CSystem
  17. // ── Resource Controller ───────────────────────────────────────────
  18. /// Manages CPU and memory resources for the ArkOS runtime.
  19. ///
  20. /// CPU gating: All background work is dispatched through a concurrent
  21. /// queue to prevent runaway thread creation. The queue width matches
  22. /// the number of available CPU cores detected at startup.
  23. ///
  24. /// Memory monitoring: Reads the process RSS from getrusage(2) and
  25. /// compares against the configured cap (default 2 GB).
  26. public struct ResourceController {
  27. /// Execute a unit of work on a new detached pthread.
  28. public static func executeOnCorePool(_ work: @escaping () -> Void) {
  29. // Box the closure into a heap-allocated context
  30. let ctx = UnsafeMutablePointer<(() -> Void)>.allocate(capacity: 1)
  31. ctx.initialize(to: work)
  32. var thread: pthread_t?
  33. let result = pthread_create(&thread, nil, { arg -> UnsafeMutableRawPointer? in
  34. let fn = arg!.assumingMemoryBound(to: (() -> Void).self)
  35. fn.pointee()
  36. fn.deinitialize(count: 1)
  37. fn.deallocate()
  38. return nil
  39. }, ctx)
  40. if result == 0 {
  41. if let t = thread {
  42. pthread_detach(t)
  43. }
  44. } else {
  45. print("ERROR: pthread_create failed with \(result)")
  46. ctx.deinitialize(count: 1)
  47. ctx.deallocate()
  48. }
  49. }
  50. /// Returns the current process peak RSS in bytes.
  51. /// On Linux, getrusage(2) reports ru_maxrss in kilobytes.
  52. public static func checkMemoryUsage() -> Int64 {
  53. #if canImport(Glibc) || canImport(CSystem)
  54. var usage = rusage()
  55. if getrusage(RUSAGE_SELF, &usage) == 0 {
  56. return Int64(usage.ru_maxrss) * 1024
  57. }
  58. #endif
  59. return 0
  60. }
  61. /// Returns the number of online CPU cores by reading /proc/cpuinfo.
  62. public static func getCPUCoreCount() -> Int {
  63. #if canImport(Glibc) || canImport(CSystem)
  64. let count = sysconf(Int32(_SC_NPROCESSORS_ONLN))
  65. if count > 0 { return count }
  66. #endif
  67. return 1
  68. }
  69. /// Returns total system RAM in bytes by reading /proc/meminfo.
  70. public static func getTotalMemory() -> Int64 {
  71. if let data = readFileContents("/proc/meminfo") {
  72. for line in data.split(separator: "\n") {
  73. if line.hasPrefix("MemTotal:") {
  74. let parts = line.split(separator: " ").compactMap { Int64($0) }
  75. if let kb = parts.first {
  76. return kb * 1024
  77. }
  78. }
  79. }
  80. }
  81. return 0
  82. }
  83. /// Returns available system RAM in bytes by reading /proc/meminfo.
  84. public static func getAvailableMemory() -> Int64 {
  85. if let data = readFileContents("/proc/meminfo") {
  86. for line in data.split(separator: "\n") {
  87. if line.hasPrefix("MemAvailable:") {
  88. let parts = line.split(separator: " ").compactMap { Int64($0) }
  89. if let kb = parts.first {
  90. return kb * 1024
  91. }
  92. }
  93. }
  94. }
  95. return 0
  96. }
  97. /// Returns used memory in bytes (total - available).
  98. public static func getUsedMemory() -> Int64 {
  99. let total = getTotalMemory()
  100. let available = getAvailableMemory()
  101. return max(0, total - available)
  102. }
  103. /// Returns the CPU usage as a percentage (0-100).
  104. /// Reads /proc/stat twice with a 100ms interval and computes the delta.
  105. public static func getCPUUsage() -> Double {
  106. func readCPUStat() -> (idle: Int64, total: Int64)? {
  107. guard let data = readFileContents("/proc/stat") else { return nil }
  108. let lines = data.split(separator: "\n")
  109. guard let cpuLine = lines.first(where: { $0.hasPrefix("cpu ") }) else { return nil }
  110. let fields = cpuLine.split(separator: " ").dropFirst().compactMap { Int64($0) }
  111. guard fields.count >= 4 else { return nil }
  112. let idle = fields[3] + (fields.count > 4 ? fields[4] : 0) // idle + iowait
  113. let total = fields.reduce(0, +)
  114. return (idle, total)
  115. }
  116. guard let first = readCPUStat() else { return 0.0 }
  117. usleep(100_000) // 100ms
  118. guard let second = readCPUStat() else { return 0.0 }
  119. let deltaIdle = second.idle - first.idle
  120. let deltaTotal = second.total - first.total
  121. guard deltaTotal > 0 else { return 0.0 }
  122. return Double(deltaTotal - deltaIdle) / Double(deltaTotal) * 100.0
  123. }
  124. }