6
votes

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

2 Answers

0
votes

You could parse the results from $ cat /proc/cpuinfo, described in "model name":

processor   : 0
[...]
model name  : Intel(R) Core(TM) i5 CPU       M 560  @ 2.67GHz
[...]

processor   : 1
[...]

Note that "cpu MHz" describes the current frequency, not the maximum.

References:

https://unix.stackexchange.com/questions/87522/why-do-cpuinfo-cur-freq-and-proc-cpuinfo-report-different-numbers

https://unix.stackexchange.com/questions/146051/number-of-processors-in-proc-cpuinfo

EDIT: in case your OS doesn't return the model name, BogoMips could be used as a comparison unit (although it's not recommended by Wikipedia in the description below). At least you can use it to identify that you have an heterogeneous architecture.

BogoMips (from "bogus" and MIPS) is an unscientific measurement of CPU speed made by the Linux kernel when it boots to calibrate an internal busy-loop. An often-quoted definition of the term is "the number of million times per second a processor can do absolutely nothing".

BogoMips is a value that can be used to verify whether the processor in question is in the proper range of similar processors, i.e. BogoMips represents a processor's clock frequency as well as the potentially present CPU cache. It is not usable for performance comparisons among different CPUs.

Here you can find the complete list of BogoMips ratings.

0
votes

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 
}