Skip to content

Commit

Permalink
feat: added stress tests for CPU efficiency and performance cores and…
Browse files Browse the repository at this point in the history
… GPU
  • Loading branch information
exelban committed Feb 4, 2025
1 parent e3e149a commit 11158df
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 1 deletion.
159 changes: 159 additions & 0 deletions Kit/helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Cocoa
import ServiceManagement
import UserNotifications
import WebKit
import Metal

public struct LaunchAtLogin {
private static let id = "\(Bundle.main.bundleIdentifier!).LaunchAtLogin"
Expand Down Expand Up @@ -1626,3 +1627,161 @@ public class VerticallyCenteredTextFieldCell: NSTextFieldCell {
self.attributedStringValue.draw(in: titleRect)
}
}

public class CPUeStressTest {
public var isRunning: Bool = false

private var workers: [DispatchWorkItem] = []
private let queue = DispatchQueue.global(qos: .background)

public init() {}

public func start() {
guard !self.isRunning else { return }
self.isRunning = true

let efficientCoreCount = ProcessInfo.processInfo.processorCount / 2
self.workers.removeAll()

for index in 0..<efficientCoreCount {
let worker = DispatchWorkItem { [weak self] in
self?.test(threadIndex: index)
}
self.workers.append(worker)
self.queue.async(execute: worker)
}
}

public func stop() {
self.isRunning = false
self.workers.forEach { $0.cancel() }
self.workers.removeAll()
}

private func test(threadIndex: Int) {
pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0)
while isRunning {
_ = sqrt(987654.321)
}
}
}

public class CPUpStressTest {
public var isRunning = false

private var workers: [DispatchWorkItem] = []
private let queue = DispatchQueue.global(qos: .userInteractive)

public init() {}

public func start() {
guard !self.isRunning else { return }
self.isRunning = true

let performanceCoreCount = ProcessInfo.processInfo.activeProcessorCount
self.workers.removeAll()

for index in 0..<performanceCoreCount {
let worker = DispatchWorkItem { [weak self] in
self?.test(threadIndex: index)
}
self.workers.append(worker)
self.queue.async(execute: worker)
}
}

public func stop() {
self.isRunning = false
self.workers.forEach { $0.cancel() }
self.workers.removeAll()
}

private func test(threadIndex: Int) {
pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0)
while isRunning {
_ = sqrt(987654.321)
}
}
}

public class GPUStressTest {
public var isRunning = false

private let device: MTLDevice
private let commandQueue: MTLCommandQueue
private let pipeline: MTLComputePipelineState
private let dataSize = 50_000_000 // Large data size for GPU workload
private let bufferA: MTLBuffer
private let bufferB: MTLBuffer
private let bufferC: MTLBuffer

public init?() {
guard let device = MTLCreateSystemDefaultDevice(), let queue = device.makeCommandQueue() else {
return nil
}

self.device = device
self.commandQueue = queue

let source = """
#include <metal_stdlib>
using namespace metal;
kernel void full_load_kernel(const device float* inA [[buffer(0)]],
const device float* inB [[buffer(1)]],
device float* outC [[buffer(2)]],
uint id [[thread_position_in_grid]]) {
outC[id] = (inA[id] * inB[id]) + sin(inA[id]) + cos(inB[id]) + tan(inA[id]) + log(inB[id]);
}
"""

do {
let library = try device.makeLibrary(source: source, options: nil)
let function = library.makeFunction(name: "full_load_kernel")!
self.pipeline = try device.makeComputePipelineState(function: function)
} catch {
return nil
}

self.bufferA = device.makeBuffer(length: self.dataSize * MemoryLayout<Float>.size, options: .storageModeShared)!
self.bufferB = device.makeBuffer(length: self.dataSize * MemoryLayout<Float>.size, options: .storageModeShared)!
self.bufferC = device.makeBuffer(length: self.dataSize * MemoryLayout<Float>.size, options: .storageModeShared)!

let dataA = [Float](repeating: 1.0, count: self.dataSize)
let dataB = [Float](repeating: 2.0, count: self.dataSize)
memcpy(self.bufferA.contents(), dataA, dataA.count * MemoryLayout<Float>.size)
memcpy(self.bufferB.contents(), dataB, dataB.count * MemoryLayout<Float>.size)
}

public func start() {
guard !self.isRunning else { return }
self.isRunning = true

DispatchQueue.global(qos: .userInitiated).async { [weak self] in
self?.test()
}
}

public func stop() {
self.isRunning = false
}

private func test() {
let threadGroupSize = MTLSize(width: 256, height: 1, depth: 1)
let gridSize = MTLSize(width: self.dataSize, height: 1, depth: 1)

while self.isRunning {
guard let commandBuffer = self.commandQueue.makeCommandBuffer(),
let commandEncoder = commandBuffer.makeComputeCommandEncoder() else {
break
}

commandEncoder.setComputePipelineState(self.pipeline)
commandEncoder.setBuffer(self.bufferA, offset: 0, index: 0)
commandEncoder.setBuffer(self.bufferB, offset: 0, index: 1)
commandEncoder.setBuffer(self.bufferC, offset: 0, index: 2)
commandEncoder.dispatchThreads(gridSize, threadsPerThreadgroup: threadGroupSize)
commandEncoder.endEncoding()
commandBuffer.commit()
}
}
}
51 changes: 50 additions & 1 deletion Stats/Views/AppSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,24 @@ class ApplicationSettings: NSStackView {

self.addArrangedSubview(scrollView)

NotificationCenter.default.addObserver(self, selector: #selector(toggleUninstallHelperButton), name: .fanHelperState, object: nil)
let CPUeButton = buttonView(#selector(self.toggleCPUeStressTest), text: localizedString("Run"))
let CPUpButton = buttonView(#selector(self.toggleCPUpStressTest), text: localizedString("Run"))
let GPUButton = buttonView(#selector(self.toggleGPUStressTest), text: localizedString("Run"))

self.CPUeButton = CPUeButton
self.CPUpButton = CPUpButton
self.GPUButton = GPUButton

var tests = [
PreferencesRow(localizedString("Efficiency cores"), component: CPUeButton),
PreferencesRow(localizedString("Performance cores"), component: CPUpButton)
]
if self.GPUTest != nil {
tests.append(PreferencesRow(localizedString("GPU"), component: GPUButton))
}
scrollView.stackView.addArrangedSubview(PreferencesSection(label: localizedString("Stress tests"), tests))

NotificationCenter.default.addObserver(self, selector: #selector(self.toggleUninstallHelperButton), name: .fanHelperState, object: nil)
}

required init?(coder: NSCoder) {
Expand Down Expand Up @@ -372,6 +389,38 @@ class ApplicationSettings: NSStackView {
@objc private func uninstallHelper() {
SMCHelper.shared.uninstall()
}

@objc private func toggleCPUeStressTest() {
if self.CPUeTest.isRunning {
self.CPUeTest.stop()
self.CPUeButton?.title = localizedString("Run")
} else {
self.CPUeTest.start()
self.CPUeButton?.title = localizedString("Stop")
}
}

@objc private func toggleCPUpStressTest() {
if self.CPUpTest.isRunning {
self.CPUpTest.stop()
self.CPUpButton?.title = localizedString("Run")
} else {
self.CPUpTest.start()
self.CPUpButton?.title = localizedString("Stop")
}
}

@objc private func toggleGPUStressTest() {
guard let test = self.GPUTest else { return }

if test.isRunning {
test.stop()
self.GPUButton?.title = localizedString("Run")
} else {
test.start()
self.GPUButton?.title = localizedString("Stop")
}
}
}

private class ModuleSelectorView: NSStackView {
Expand Down

0 comments on commit 11158df

Please sign in to comment.