Skip to content

Commit 1acd575

Browse files
Server Stats Collection (#1834)
* Server Stats Collection Motivation: In order to implement the 'runServer' RPC on the WorkerService we need to capture a snapshot with the resorces usage for the server and compute the difference between 2 of these snapshots. Modifications: - Created the ServerStats struct that contains all stats needed in the QPS benchmarking from the server - Implemented the init() that collects all these stats - Implemented the function that computes the differences between 2 snapshots of ServerStats Result: We will be able to implement the `runServer` RPC.
1 parent f7dc1ae commit 1acd575

File tree

2 files changed

+134
-2
lines changed

2 files changed

+134
-2
lines changed

Package.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ let includeNIOSSL = ProcessInfo.processInfo.environment["GRPC_NO_NIO_SSL"] == ni
3232
let packageDependencies: [Package.Dependency] = [
3333
.package(
3434
url: "https://github.com/apple/swift-nio.git",
35-
from: "2.58.0"
35+
from: "2.64.0"
3636
),
3737
.package(
3838
url: "https://github.com/apple/swift-nio-http2.git",
@@ -132,6 +132,7 @@ extension Target.Dependency {
132132
package: "swift-nio-transport-services"
133133
)
134134
static let nioTestUtils: Self = .product(name: "NIOTestUtils", package: "swift-nio")
135+
static let nioFileSystem: Self = .product(name: "_NIOFileSystem", package: "swift-nio")
135136
static let logging: Self = .product(name: "Logging", package: "swift-log")
136137
static let protobuf: Self = .product(name: "SwiftProtobuf", package: "swift-protobuf")
137138
static let protobufPluginLibrary: Self = .product(
@@ -251,7 +252,8 @@ extension Target {
251252
dependencies: [
252253
.grpcCore,
253254
.grpcProtobuf,
254-
.nioCore
255+
.nioCore,
256+
.nioFileSystem
255257
]
256258
)
257259

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2024, gRPC Authors All rights reserved.
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+
17+
import Dispatch
18+
import NIOCore
19+
import NIOFileSystem
20+
21+
#if canImport(Darwin)
22+
import Darwin
23+
#elseif canImport(Musl)
24+
import Musl
25+
#elseif canImport(Glibc)
26+
import Glibc
27+
#else
28+
let badOS = { fatalError("unsupported OS") }()
29+
#endif
30+
31+
#if canImport(Darwin)
32+
private let OUR_RUSAGE_SELF: Int32 = RUSAGE_SELF
33+
#elseif canImport(Musl) || canImport(Glibc)
34+
private let OUR_RUSAGE_SELF: Int32 = RUSAGE_SELF.rawValue
35+
#endif
36+
37+
/// Current server stats.
38+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
39+
internal struct ServerStats: Sendable {
40+
var time: Double
41+
var userTime: Double
42+
var systemTime: Double
43+
var totalCPUTime: UInt64
44+
var idleCPUTime: UInt64
45+
46+
init(
47+
time: Double,
48+
userTime: Double,
49+
systemTime: Double,
50+
totalCPUTime: UInt64,
51+
idleCPUTime: UInt64
52+
) {
53+
self.time = time
54+
self.userTime = userTime
55+
self.systemTime = systemTime
56+
self.totalCPUTime = totalCPUTime
57+
self.idleCPUTime = idleCPUTime
58+
}
59+
60+
init() async throws {
61+
self.time = Double(DispatchTime.now().uptimeNanoseconds) * 1e-9
62+
var usage = rusage()
63+
if getrusage(OUR_RUSAGE_SELF, &usage) == 0 {
64+
// Adding the seconds with the microseconds transformed into seconds to get the
65+
// real number of seconds as a `Double`.
66+
self.userTime = Double(usage.ru_utime.tv_sec) + Double(usage.ru_utime.tv_usec) * 1e-6
67+
self.systemTime = Double(usage.ru_stime.tv_sec) + Double(usage.ru_stime.tv_usec) * 1e-6
68+
} else {
69+
self.userTime = 0
70+
self.systemTime = 0
71+
}
72+
let (totalCPUTime, idleCPUTime) = try await ServerStats.getTotalAndIdleCPUTime()
73+
self.totalCPUTime = totalCPUTime
74+
self.idleCPUTime = idleCPUTime
75+
}
76+
77+
internal func difference(to stats: ServerStats) -> ServerStats {
78+
return ServerStats(
79+
time: self.time - stats.time,
80+
userTime: self.userTime - stats.userTime,
81+
systemTime: self.systemTime - stats.systemTime,
82+
totalCPUTime: self.totalCPUTime - stats.totalCPUTime,
83+
idleCPUTime: self.idleCPUTime - stats.idleCPUTime
84+
)
85+
}
86+
87+
/// Computes the total and idle CPU time after extracting stats from the first line of '/proc/stat'.
88+
///
89+
/// The first line in '/proc/stat' file looks as follows:
90+
/// CPU [user] [nice] [system] [idle] [iowait] [irq] [softirq]
91+
/// The totalCPUTime is computed as follows:
92+
/// total = user + nice + system + idle
93+
private static func getTotalAndIdleCPUTime() async throws -> (
94+
totalCPUTime: UInt64, idleCPUTime: UInt64
95+
) {
96+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(Linux) || os(Android)
97+
let contents: ByteBuffer
98+
do {
99+
contents = try await ByteBuffer(
100+
contentsOf: "/proc/stat",
101+
maximumSizeAllowed: .kilobytes(20)
102+
)
103+
} catch {
104+
return (0, 0)
105+
}
106+
107+
let view = contents.readableBytesView
108+
guard let firstNewLineIndex = view.firstIndex(of: UInt8(ascii: "\n")) else {
109+
return (0, 0)
110+
}
111+
let firstLine = String(buffer: ByteBuffer(view[0 ... firstNewLineIndex]))
112+
113+
let lineComponents = firstLine.components(separatedBy: " ")
114+
if lineComponents.count < 5 || lineComponents[0] != "CPU" {
115+
return (0, 0)
116+
}
117+
118+
let CPUTime: [UInt64] = lineComponents[1 ... 4].compactMap { UInt64($0) }
119+
if CPUTime.count < 4 {
120+
return (0, 0)
121+
}
122+
123+
let totalCPUTime = CPUTime.reduce(0, +)
124+
return (totalCPUTime, CPUTime[3])
125+
126+
#else
127+
return (0, 0)
128+
#endif
129+
}
130+
}

0 commit comments

Comments
 (0)