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.
CPU class
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); } }
FileUtils class
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; } }
Advertisement
Answer
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() }
So now I can:
val cpuManager = CpuManager() val clusters: List<CpuCluster> = cpuManager.clusters if (clusters.size > 1) { // Heterogeneous computing architecture }