Skip to content
Advertisement

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

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 
}
User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement