Skip to content

Instantly share code, notes, and snippets.

@ghale
Last active June 20, 2022 13:30
Show Gist options
  • Save ghale/469530ef6c51cbfa5816719f4d52d9a9 to your computer and use it in GitHub Desktop.
Save ghale/469530ef6c51cbfa5816719f4d52d9a9 to your computer and use it in GitHub Desktop.
Captures OS X CPU throttling as a custom value/tag
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import java.nio.charset.Charset
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
/**
* This Gradle script captures the thermal stats as reported by the OS 'pmset' command,
* and adds these as a custom value.
*/
// Register our throttling measurement service with Gradle and initialize it immediately
def throttlingService = gradle.sharedServices.registerIfAbsent('thermalThrottling', ThermalThrottlingService) { }.get()
buildScan {
buildFinished {
if (throttlingService.hasData()) {
def averageThrottling = throttlingService.averageThrottling
buildScan.value 'CPU Thermal Throttling Average', averageThrottling as String
buildScan.value 'CPU Thermal Throttling Max', throttlingService.maxThrottling as String
if (averageThrottling < 100) {
buildScan.tag 'CPU_THROTTLED'
}
}
}
}
abstract class ThermalThrottlingService implements BuildService<BuildServiceParameters.None>, AutoCloseable {
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1)
final ArrayList<Integer> throttlingSamples = []
ThermalThrottlingService() {
if (macOs) {
final Runnable captureDataPoint = {
try {
def thermalOutput = execAndGetStdout('pmset', '-g', 'therm')
def speedLimit = thermalOutput.readLines().find { it.trim().startsWith('CPU_Speed_Limit') }.split('=')[1].trim()
throttlingSamples.add(speedLimit as int)
} catch (Throwable t) {
t.printStackTrace()
}
}
scheduler.scheduleAtFixedRate(captureDataPoint, 0, 5, TimeUnit.SECONDS)
} else {
println "Not running on MacOS - no thermal throttling data will be captured"
}
}
@Override
void close() throws Exception {
scheduler.shutdownNow()
}
Integer getAverageThrottling() {
return hasData() ? (throttlingSamples.sum()/throttlingSamples.size()) : -1
}
Integer getMaxThrottling() {
// min() is the "maximum" amount CPU was throttled
return hasData() ? throttlingSamples.min() : -1
}
boolean hasData() {
return !throttlingSamples.empty
}
static String execAndGetStdout(String... args) {
Process process = args.toList().execute()
try {
def standardText = process.inputStream.withStream { s -> s.getText(Charset.defaultCharset().name()) }
def ignore = process.errorStream.withStream { s -> s.getText(Charset.defaultCharset().name()) }
def finished = process.waitFor(10, TimeUnit.SECONDS)
finished && process.exitValue() == 0 ? trimAtEnd(standardText) : null
} finally {
process.destroyForcibly()
}
}
static String trimAtEnd(String str) {
('x' + str).trim().substring(1)
}
static boolean isMacOs() {
def osName = System.getProperty('os.name').toLowerCase()
return osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment