Search code examples
androidlinuxlinux-kernelcpuroot

Detect if running on a device with heterogeneous CPU architecture


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;
    }
}

Solution

  • 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 
    }