| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- /*
- * Copyright 2024, gRPC Authors All rights reserved.
- *
- * 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 Dispatch
- import NIOCore
- import NIOFileSystem
- #if canImport(Darwin)
- import Darwin
- #elseif canImport(Musl)
- import Musl
- #elseif canImport(Glibc)
- import Glibc
- #else
- let badOS = { fatalError("unsupported OS") }()
- #endif
- #if canImport(Darwin)
- private let OUR_RUSAGE_SELF: Int32 = RUSAGE_SELF
- #elseif canImport(Musl) || canImport(Glibc)
- private let OUR_RUSAGE_SELF: Int32 = RUSAGE_SELF.rawValue
- #endif
- /// Client resource usage stats.
- @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
- internal struct ClientStats: Sendable {
- var time: Double
- var userTime: Double
- var systemTime: Double
- init(
- time: Double,
- userTime: Double,
- systemTime: Double
- ) {
- self.time = time
- self.userTime = userTime
- self.systemTime = systemTime
- }
- init() {
- self.time = Double(DispatchTime.now().uptimeNanoseconds) * 1e-9
- if let usage = System.resourceUsage() {
- self.userTime = Double(usage.ru_utime.tv_sec) + Double(usage.ru_utime.tv_usec) * 1e-6
- self.systemTime = Double(usage.ru_stime.tv_sec) + Double(usage.ru_stime.tv_usec) * 1e-6
- } else {
- self.userTime = 0
- self.systemTime = 0
- }
- }
- internal func difference(to state: ClientStats) -> ClientStats {
- return ClientStats(
- time: self.time - state.time,
- userTime: self.userTime - state.userTime,
- systemTime: self.systemTime - state.systemTime
- )
- }
- }
- /// Server resource usage stats.
- @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
- internal struct ServerStats: Sendable {
- var time: Double
- var userTime: Double
- var systemTime: Double
- var totalCPUTime: UInt64
- var idleCPUTime: UInt64
- init(
- time: Double,
- userTime: Double,
- systemTime: Double,
- totalCPUTime: UInt64,
- idleCPUTime: UInt64
- ) {
- self.time = time
- self.userTime = userTime
- self.systemTime = systemTime
- self.totalCPUTime = totalCPUTime
- self.idleCPUTime = idleCPUTime
- }
- init() async throws {
- self.time = Double(DispatchTime.now().uptimeNanoseconds) * 1e-9
- if let usage = System.resourceUsage() {
- self.userTime = Double(usage.ru_utime.tv_sec) + Double(usage.ru_utime.tv_usec) * 1e-6
- self.systemTime = Double(usage.ru_stime.tv_sec) + Double(usage.ru_stime.tv_usec) * 1e-6
- } else {
- self.userTime = 0
- self.systemTime = 0
- }
- let (totalCPUTime, idleCPUTime) = try await ServerStats.getTotalAndIdleCPUTime()
- self.totalCPUTime = totalCPUTime
- self.idleCPUTime = idleCPUTime
- }
- internal func difference(to stats: ServerStats) -> ServerStats {
- return ServerStats(
- time: self.time - stats.time,
- userTime: self.userTime - stats.userTime,
- systemTime: self.systemTime - stats.systemTime,
- totalCPUTime: self.totalCPUTime - stats.totalCPUTime,
- idleCPUTime: self.idleCPUTime - stats.idleCPUTime
- )
- }
- /// Computes the total and idle CPU time after extracting stats from the first line of '/proc/stat'.
- ///
- /// The first line in '/proc/stat' file looks as follows:
- /// CPU [user] [nice] [system] [idle] [iowait] [irq] [softirq]
- /// The totalCPUTime is computed as follows:
- /// total = user + nice + system + idle
- private static func getTotalAndIdleCPUTime() async throws -> (
- totalCPUTime: UInt64, idleCPUTime: UInt64
- ) {
- #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(Linux) || os(Android)
- let contents: ByteBuffer
- do {
- contents = try await ByteBuffer(
- contentsOf: "/proc/stat",
- maximumSizeAllowed: .kilobytes(20)
- )
- } catch {
- return (0, 0)
- }
- let view = contents.readableBytesView
- guard let firstNewLineIndex = view.firstIndex(of: UInt8(ascii: "\n")) else {
- return (0, 0)
- }
- let firstLine = String(buffer: ByteBuffer(view[0 ... firstNewLineIndex]))
- let lineComponents = firstLine.components(separatedBy: " ")
- if lineComponents.count < 5 || lineComponents[0] != "CPU" {
- return (0, 0)
- }
- let CPUTime: [UInt64] = lineComponents[1 ... 4].compactMap { UInt64($0) }
- if CPUTime.count < 4 {
- return (0, 0)
- }
- let totalCPUTime = CPUTime.reduce(0, +)
- return (totalCPUTime, CPUTime[3])
- #else
- return (0, 0)
- #endif
- }
- }
- extension System {
- fileprivate static func resourceUsage() -> rusage? {
- var usage = rusage()
- if getrusage(OUR_RUSAGE_SELF, &usage) == 0 {
- return usage
- } else {
- return nil
- }
- }
- }
|