Skip to content

Commit

Permalink
Trace: process with cpu, memory, threads, wps
Browse files Browse the repository at this point in the history
  • Loading branch information
ikhvorost committed Dec 27, 2023
1 parent df90594 commit 8524cea
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 10 deletions.
5 changes: 4 additions & 1 deletion Sources/DLog/LogProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ public class LogProtocol: NSObject {
public func trace(_ message: @escaping @autoclosure () -> LogMessage = "", file: String = #file, function: String = #function, line: UInt = #line) -> String? {
let traceInfo = TraceInfo()
let metadata: () -> [Metadata] = {
[self.metadata.data, traceMetadata(function: function, traceInfo: traceInfo, traceConfig: self.config.traceConfig)]
[
self.metadata.data,
traceMetadata(function: function, traceInfo: traceInfo, traceConfig: self.config.traceConfig)
]
}
return logger.log(message: message, type: .trace, category: category, config: config, scope: _scope, metadata: metadata(), file: file, function: function, line: line)
}
Expand Down
85 changes: 85 additions & 0 deletions Sources/DLog/Mach.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// Mach.swift
//
// Created by Iurii Khvorost <iurii.khvorost@gmail.com> on 2023/12/18.
// Copyright © 2023 Iurii Khvorost. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

import Foundation

struct TaskInfo {

private static func taskInfo<T>(_ info: T, _ flavor: Int32) -> T {
var info = info
var count = mach_msg_type_number_t(MemoryLayout<T>.size / MemoryLayout<natural_t>.size)
_ = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
task_info(mach_task_self_, task_flavor_t(flavor), $0, &count)
}
}
return info
}

static var power: task_power_info { taskInfo(task_power_info(), TASK_POWER_INFO) }
static var power_v2: task_power_info_v2 { taskInfo(task_power_info_v2(), TASK_POWER_INFO_V2) }
static var vm: task_vm_info { taskInfo(task_vm_info(), TASK_VM_INFO) }
}


struct ThreadInfo {

private static func threadInfo<T>(_ thread: thread_act_t, _ info: T, _ flavor: Int32) -> T {
var info = info
var count = mach_msg_type_number_t(MemoryLayout<T>.size / MemoryLayout<natural_t>.size)
_ = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
thread_info(thread, thread_flavor_t(flavor), $0, &count)
}
}
return info
}

static func basic(thread: thread_act_t) -> thread_basic_info {
threadInfo(thread, thread_basic_info(), THREAD_BASIC_INFO)
}
}

func threadsInfo() -> (cpuUsage: Int32, threadsCount: UInt32) {
var thread_list: thread_act_array_t?
var thread_count: mach_msg_type_number_t = 0
guard task_threads(mach_task_self_, &thread_list, &thread_count) == KERN_SUCCESS, let threads = thread_list else {
return (0, 0)
}

defer {
let size = MemoryLayout<thread_t>.size * Int(thread_count)
vm_deallocate(mach_task_self_, vm_address_t(bitPattern: threads), vm_size_t(size))
}

var cpu_usage: Int32 = 0
for i in 0..<Int(thread_count) {
let basic = ThreadInfo.basic(thread: threads[i])
if basic.flags & TH_FLAGS_IDLE == 0 {
cpu_usage += basic.cpu_usage
}
}
return (cpu_usage * 100 / TH_USAGE_SCALE, thread_count)
}
54 changes: 49 additions & 5 deletions Sources/DLog/TraceProcess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,29 @@ public struct ProcessOptions: OptionSet {
self.rawValue = rawValue
}

/// CPU usage (%).
public static let cpu = Self(0)

/// Global unique identifier for the process.
public static let guid = Self(0)
public static let guid = Self(1)

/// Memory usage (MB).
public static let memory = Self(2)

/// The name of the process.
public static let name = Self(1)
public static let name = Self(3)

/// The identifier of the process.
public static let pid = Self(2)
public static let pid = Self(4)

/// Threads count.
public static let threads = Self(5)

/// Wakeups per second (WPS).
public static let wps = Self(6)

/// Compact: `.name` and `.pid`.
public static let compact: Self = [.name, .pid]
/// Compact: `.cpu`, `.memory`, `.pid` and `.threads`
public static let compact: Self = [.cpu, .memory, .pid, .threads]
}

/// Contains configuration values regarding to a process info.
Expand All @@ -56,11 +68,43 @@ public struct ProcessConfig {
public var options: ProcessOptions = .compact
}

fileprivate class Power {

static let shared = Power()

private var timer: Timer?

private(set) var wakeupsTotalCount: UInt64 = 0
private(set) var wakeupsPerSecond: UInt64 = 0

// mWh
private(set) var energyTotal = 0.0
private(set) var energyPerSecond = 0.0

private init() {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
let wakeups = TaskInfo.power.task_interrupt_wakeups
self.wakeupsPerSecond = wakeups - self.wakeupsTotalCount
self.wakeupsTotalCount = wakeups

// nJ / 3600 = nWh
let mWh = Double(TaskInfo.power_v2.task_energy) / (3600 * 1000000)
self.energyPerSecond = mWh - self.energyTotal
self.energyTotal = mWh
}
timer?.fire()
}
}

func processMetadata(processInfo: ProcessInfo, config: ProcessConfig) -> Metadata {
let items: [(ProcessOptions, String, () -> Any)] = [
(.cpu, "cpu", { "\(threadsInfo().cpuUsage)%" }),
(.guid, "guid", { processInfo.globallyUniqueString }),
(.memory, "memory", { "\(TaskInfo.vm.phys_footprint / (1024 * 1024))MB"}),
(.name, "name", { processInfo.processName }),
(.pid, "pid", { processInfo.processIdentifier }),
(.threads, "threads", { threadsInfo().threadsCount }),
(.wps, "wps", { Power.shared.wakeupsPerSecond }),
]
return Metadata.metadata(from: items, options: config.options)
}
8 changes: 4 additions & 4 deletions Tests/DLogTests/DLogTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,7 @@ final class IntervalTests: XCTestCase {
}?.match(#"> signpost$"#) == true)
}

func test_IntervalConfigAll() {
func test_Interval_Config_All() {
var config = LogConfig()
config.intervalConfig.options = .all

Expand Down Expand Up @@ -1206,7 +1206,7 @@ final class TraceTests: XCTestCase {
var config = LogConfig()
config.traceConfig.options = .process
let logger = DLog(config: config)
XCTAssert(logger.trace()?.match(#"\{process:\{name:[^,]+,pid:\d+\}\}"#) == true)
XCTAssert(logger.trace()?.match(#"\{process:\{cpu:\d+%,memory:\d+MB,pid:\d+,threads:\d+\}\}"#) == true)
}

func test_trace_process_all() {
Expand All @@ -1215,7 +1215,7 @@ final class TraceTests: XCTestCase {
config.traceConfig.processConfig.options = .all

let logger = DLog(config: config)
XCTAssert(logger.trace()?.match(#"\{process:\{guid:[^,]+,name:[^,]+,pid:\d+\}\}"#) == true)
XCTAssert(logger.trace()?.match(#"\{process:\{cpu:\d+%,guid:[^,]+,memory:\d+MB,name:[^,]+,pid:\d+,threads:\d+,wps:\d+\}\}"#) == true)
}

func test_trace_func_only() {
Expand Down Expand Up @@ -1352,6 +1352,6 @@ final class TraceTests: XCTestCase {
config.traceConfig.options = .all
let logger = DLog(config: config)
let text = logger.trace()
XCTAssert(text?.match(#"\#(Location) \{func:test_trace_config_all,process:\{name:[^,]+,pid:\d+\},queue:com\.apple\.main-thread,stack:\[\{symbols:DLogTests\.TraceTests\.test_trace_config_all\(\) -> \(\)\}"#) == true)
XCTAssert(text?.match(#"\#(Location) \{func:test_trace_config_all,process:\{cpu:\d+%,memory:\d+MB,pid:\d+,threads:\d+\},queue:com\.apple\.main-thread,stack:\[\{symbols:DLogTests\.TraceTests\.test_trace_config_all\(\) -> \(\)\}"#) == true)
}
}

0 comments on commit 8524cea

Please sign in to comment.