I'm very specific on this one. I need to know if the device has a CPU which has heterogeneous cores like ARM's big.LITTLE technology, for instance, a set of 4 ARM Cortex-A53 + another set of 4 more powerfull ARM Cortex-A72, totaling 8 cores, basically 2 processors in the same chip. The processors model does not really matter.
What I'm considering is to read scaling_max_freq
of all cores and group those with different max frequencies (and then compare them) but I noticed that in some devices, the path to any core that's not cpu0 is actually a symlink to /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
That is, if I try to read cpu3's scaling_max_freq it will be a link to cpu0's scaling_max_freq. I wonder if in this case I can consider we're not running in a heterogeneous.
public final class CPU {
// To be formatted with specific core number
private static final String CPU_DIR = "/sys/devices/system/cpu/cpu%d";
private static final String CPUFREQ_DIR = CPU_DIR + "/cpufreq";
public static final String SCALING_MAX_FREQ = CPUFREQ_DIR + "/scaling_max_freq";
private static final String DEFAULT_FREQS = "200000 400000 800000 1200000";
private CPU() {
}
// Here I'd replace 0 with (other) core number
@NonNull
public static synchronized String[] getAvailFreqs() {
String[] split;
String freqs = FileUtils.readFile(format(SCALING_AVAIL_FREQS, 0), DEFAULT_FREQS);
try {
split = freqs.split(" ");
} catch (Exception e) {
split = DEFAULT_FREQS.split(" ");
}
return split;
}
// Here I'd replace 0 with (other) core number
public static synchronized int getMaxFreq() {
try {
return Integer.parseInt(FileUtils.readFile(format(SCALING_MAX_FREQ, 0), "1200000"));
} catch (Exception ignored){}
return 1200000;
}
private static String format(String format, Object arg) {
return String.format(Locale.US, format, arg);
}
}
public final class FileUtils {
private FileUtils() {
}
public static String readFile(String pathname, String defaultOutput) {
return baseReadSingleLineFile(new File(pathname), defaultOutput);
}
public static String readFile(File file, String defaultOutput) {
return baseReadSingleLineFile(file, defaultOutput);
}
// Async
private static String baseReadSingleLineFile(File file, String defaultOutput) {
String ret = defaultOutput;
Thread thread = new Thread(() -> {
if (file.isFile() || file.exists()) {
if (file.canRead()) {
try {
FileInputStream inputStream = new FileInputStream(file);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line = reader.readLine(); // Fisrt line
reader.close();
inputStream.close();
ret = line;
} catch (Exception ignored) {}
} else
// Uses cat command
ret = RootUtils.readFile(file, defaultOutput);
}
});
thread.start();
// 3 seconds timeout
long endTimeMillis = System.currentTimeMillis() + 3000;
while (thread.isAlive())
if (System.currentTimeMillis() > endTimeMillis)
return defaultOutput;
return ret;
}
}
Here's my current approach in Kotlin:
class CpuManager {
// GOTO: val clusters: List<CpuCluster>
companion object {
private const val CPU_DIR = "/sys/devices/system/cpu/cpu%d"
private const val CPUFREQ_DIR = "$CPU_DIR/cpufreq"
const val SCALING_CUR_FREQ = "$CPUFREQ_DIR/scaling_cur_freq"
const val SCALING_MAX_FREQ = "$CPUFREQ_DIR/scaling_max_freq"
const val SCALING_MIN_FREQ = "$CPUFREQ_DIR/scaling_min_freq"
const val SCALING_AVAIL_FREQS = "$CPUFREQ_DIR/scaling_available_frequencies"
private const val DEFAULT_FREQS = "200000 400000 800000 1200000"
}
private fun getAvailFreqs(cpuCore: Int = 0) : Array<String> {
val freqs = FileUtils.readFile(format(SCALING_AVAIL_FREQS, cpuCore), DEFAULT_FREQS)
return try {
freqs.split(" ").dropLastWhile { it.isEmpty() }.toTypedArray()
} catch (e: Exception) {
DEFAULT_FREQS.split(" ").dropLastWhile { it.isEmpty() }.toTypedArray()
}
}
@JvmOverloads
fun getMaxFreq(cpuCore: Int = 0): Int {
return try {
FileUtils.readFile(format(SCALING_MAX_FREQ, cpuCore), "1200000").toInt()
} catch (ignored: Exception) {
1200000
}
}
private fun format(format: String, arg: Any): String {
return String.format(Locale.US, format, arg)
}
val cpuCount: Int
get() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return Runtime.getRuntime().availableProcessors()
}
class CpuFilter : FileFilter {
override fun accept(pathname: File): Boolean {
return Pattern.matches("cpu[0-11]+", pathname.name)
}
}
return try {
val dir = File("/sys/devices/system/cpu/")
val files = dir.listFiles(CpuFilter())
files.size
} catch (e: Exception) {
1
}
}
val clusters: List<CpuCluster>
get() {
val cpuCount = this.cpuCount
val clustersList = mutableListOf<CpuCluster>()
val cpuCores = mutableListOf<CpuCore>()
for (i in (0 until cpuCount)) {
val cpuCore = CpuCore(coreNum = i)
cpuCore.availFreqs = getAvailFreqs(i)
//cpuCore.availGovs = getAvailGovs(i)
//cpuCore.governorTunables = getGovernorTunables(cpuCore.currentGov, cpuCore.coreNum)
cpuCores.add(cpuCore)
}
val allFreqs = mutableListOf<Array<Int>>()
for (cpu in 0 until cpuCount) {
val availCpuFreqs = cpuCores[cpu].availFreqs.toIntArray() // Extension function below
availCpuFreqs.sortWith(Comparator { o1, o2 -> o1.compareTo(o2) })
allFreqs.add(availCpuFreqs)
}
val maxFreqs = mutableListOf<Int>()
allFreqs.forEach { freqList ->
val coreMax = freqList[freqList.size - 1]
maxFreqs.add(coreMax)
}
val firstMaxFreq = allFreqs.first().last()
// Here is the logic I suggested
val distinctMaxFreqs = mutableListOf<Int>()
distinctMaxFreqs.add(firstMaxFreq)
maxFreqs.forEach {
if (it != firstMaxFreq && !distinctMaxFreqs.contains(it)) {
distinctMaxFreqs.add(it)
}
}
val clustersCount = distinctMaxFreqs.size
if (clustersCount == 1) {
clustersList.add(CpuCluster(cpuCores))
} else {
distinctMaxFreqs.forEach { maxFreq ->
val cpuClusterCores = mutableListOf<CpuCore>()
cpuCores.forEach {
if (it.maxFreq == maxFreq) {
cpuClusterCores.add(it)
}
}
clustersList.add(CpuCluster(cpuClusterCores))
}
}
return clustersList
}
data class CpuCluster(val cpuCores: List<CpuCore>) {
val totalCpuCount: Int
get() {
return cpuCores.size
}
val range: IntRange
get() {
return if (cpuCores.isNullOrEmpty()) {
0..0
} else {
IntRange(cpuCores.first().coreNum, cpuCores.last().coreNum)
}
}
}
data class CpuCore(val coreNum: Int = 0, var availFreqs: Array<String> = DEFAULT_FREQS.split(" ").toTypedArray(), var availGovs: Array<String> = DEFAULT_GOVERNORS.split(" ").toTypedArray()) {
var governorTunables: ArrayList<GovernorTunable>? = null
val currentGov: String
get() {
return FileUtils.readFile(
"/sys/devices/system/cpu/cpu$coreNum/cpufreq/scaling_governor",
"interactive")
}
val currentFreq: Int
get() {
return try {
FileUtils.readFile(
"/sys/devices/system/cpu/cpu$coreNum/cpufreq/scaling_cur_freq",
"800000")
.toInt()
} catch (e: Exception) { 800000 }
}
val minFreq: Int
get() {
return try {
availFreqs.sortWith(java.util.Comparator { o1, o2 -> o1.toInt().compareTo(o2.toInt()) })
availFreqs.first().toInt()
} catch (e: Exception) { 400000 }
}
val maxFreq: Int
get() {
return try {
availFreqs.sortWith(java.util.Comparator { o1, o2 -> o1.toInt().compareTo(o2.toInt()) })
availFreqs.last().toInt()
} catch (e: Exception) { 800000 }
}
}
}
private fun Array<String>.toIntArray(): Array<Int> {
val list = mutableListOf<Int>()
this.forEach { list.add(it.toInt()) }
return list.toTypedArray()
}
val cpuManager = CpuManager()
val clusters: List<CpuCluster> = cpuManager.clusters
if (clusters.size > 1) {
// Heterogeneous computing architecture
}