Skip to content

Instantly share code, notes, and snippets.

@lifey
Last active January 11, 2023 19:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lifey/05f97dcc1bbd88bf2c760b53ce0f5bbe to your computer and use it in GitHub Desktop.
Save lifey/05f97dcc1bbd88bf2c760b53ce0f5bbe to your computer and use it in GitHub Desktop.
import java.lang.management.ManagementFactory
import kotlin.math.abs
class CpuBurner(val timePeriodMs: Long, val totalCpu: Int) {
fun burnCpu() {
burnCpuInternal( totalCpu , 1 )
}
fun burnCpuInternal( remainingPercentage: Int, idx: Int) {
if (remainingPercentage <= 0) return
val currentThreadPercentage = Math.min(remainingPercentage, 100)
// we want it to be a new thread so we can account the cpu consumption and that it will not be limited by thread pool capacities
val thread = Thread({ SingleThreadedCpuBurner().burnCpuInOneThread(timePeriodMs, currentThreadPercentage) })
thread.setName("Burn CPU[${currentThreadPercentage}%/$totalCpu%]#${idx}")
thread.start()
if (remainingPercentage > 100) {
burnCpuInternal( remainingPercentage - 100, idx + 1)
}
thread.join()
}
}
class SingleThreadedCpuBurner {
private val CHUNK_SIZE_MS = 100L
private var totalSlept = 0L
private var totalPlanned = 0L
private var totalBurned = 0L
private var sleepSkew = 0L
private var blackHoleTraveler = System.currentTimeMillis()
fun burnCpuInOneThread(timePeriodMs: Long, percentage: Int) {
val threadCpuOnStart = getThreadCpuTimeNs()
val startTime = System.currentTimeMillis()
var periodMsLeft = timePeriodMs
while (periodMsLeft > 0) {
val chunk = Math.min(CHUNK_SIZE_MS, timePeriodMs)
burnPercentSingleIteration(chunk, percentage)
sleepSingleIteration(chunk,percentage)
periodMsLeft -= CHUNK_SIZE_MS
}
val threadCpuOnEnd = getThreadCpuTimeNs()
val endTime = System.currentTimeMillis()
val cpuConsumedMs = (threadCpuOnEnd - threadCpuOnStart).toMillis()
val actualPeriodMs = endTime - startTime
val throttling = 100.0 * (1 -
((cpuConsumedMs.toDouble()) / ((actualPeriodMs.toDouble()) * percentage.toDouble() / 100.0)))
println("${totalSlept.toMillis()} $totalPlanned ${totalBurned.toMillis()} Executed burn cpu for $actualPeriodMs ms at ${percentage}% blackhole traveler is $blackHoleTraveler CPU is ${cpuConsumedMs}ms estimated throttling ${String.format("%.2f", throttling)}%")
}
private fun getThreadCpuTimeNs(): Long {
val bean = ManagementFactory.getThreadMXBean()
val id = Thread.currentThread().id
return bean.getThreadCpuTime(id)
}
private fun burnPercentSingleIteration(periodMs: Long, percentage: Int) {
val burnPeriodMs = (periodMs * percentage) / 100
if (burnPeriodMs > 0) {
val startTime = System.nanoTime()
while (System.nanoTime() - startTime < burnPeriodMs.toNanos()) {
for (i in 1..100000) {
blackHoleTraveler = blackHoleForBurnCpu(blackHoleTraveler)
}
}
totalBurned += System.nanoTime() - startTime
}
}
private fun sleepSingleIteration(periodMs: Long, percentage: Int) {
val sleepPeriod = (periodMs * (100 - percentage)) / 100
val toSleep = sleepPeriod + sleepSkew.toMillis()
if (toSleep <= 0) {
sleepSkew += periodMs
return
}
val timestampOnStart = System.nanoTime()
Thread.sleep(toSleep)
val timePassed = System.nanoTime() - timestampOnStart
sleepSkew = (sleepSkew - sleepSkew.toMillis().toNanos()) + (toSleep.toNanos()) - timePassed
totalPlanned += sleepPeriod
totalSlept += timePassed
}
inline fun Long.toNanos() = this * 1000 * 1000
inline fun Long.toMillis() = this / 1000 / 1000
// the purpose of this method is to make sure that the JVM can't optimize code and eliminate it.
private fun blackHoleForBurnCpu(traveler: Long): Long = abs(traveler * 37 + 19)
}
fun main() {
for(i in 1..100) {
CpuBurner(10000, 20).burnCpu()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment