From ab8542e4e58d9acd05613ee81529406f13594df0 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Mon, 28 Sep 2026 12:19:01 -0400 Subject: [PATCH] mangoapp: show Adreno GPU and CPU stats Valve's mangoapp reads an Adreno GPU's load, clock and temperatures from the paths of its own hardware, and the CPU temperature from thermal zones named as mainline kernels name them. Every Adreno under Android keeps the GPU values in KGSL's sysfs, readable by the app, and Android kernels name the CPU sensors cpuss-N / cpu-N-M; the session binds each where mangoapp looks. Thermal zones are found by sensor name, since their numbering differs per device, and anything that cannot be found is left alone. --- .../launcher/runtime/LinuxRuntime.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java b/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java index 816d7c3ad..b04548b00 100644 --- a/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java +++ b/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java @@ -193,6 +193,8 @@ public final class LinuxRuntime { } } bindGpuNode(context, cmd); + bindAdrenoStats(cmd); + bindCpuTemps(cmd, root); if (extraBinds != null) { for (String spec : extraBinds) bind(cmd, spec); } @@ -244,6 +246,72 @@ public final class LinuxRuntime { bind(cmd, KGSL_DEVICE + ":/dev/dri/" + node); } + /** + * Valve's mangoapp (Deck mode's performance overlay) reads an Adreno GPU's load, clock and + * temperatures from where they are on Valve's own hardware; every Adreno under Android keeps + * them in KGSL's sysfs, readable by the app. Only the GPU temperature has no KGSL file on older + * kernels, and thermal zones are numbered per device, so a zone is found by its sensor's name, + * which differs between Snapdragon generations. A value that cannot be found is left alone. + */ + private static void bindAdrenoStats(List cmd) { + String kgsl = "/sys/class/kgsl/kgsl-3d0/"; + String gpuTemp = new File(kgsl + "temp").canRead() ? kgsl + "temp" + : thermalZone("gpuss-0", "gpuss-0-usr", "gpu0-usr", "gpu-usr", "gpu"); + String[][] stats = { + {kgsl + "gpu_busy_percentage", "/sys/kernel/debug/dri/0/perf_now"}, + {kgsl + "devfreq/cur_freq", "/sys/devices/platform/soc@0/3d00000.gpu/devfreq/3d00000.gpu/cur_freq"}, + {gpuTemp, "/sys/class/thermal/thermal_zone28/temp"}, + {gpuTemp, "/sys/class/thermal/thermal_zone26/temp"}, + {thermalZone("ddr", "ddr-usr", "ddr-thermal"), "/sys/class/thermal/thermal_zone22/temp"}, + }; + for (String[] stat : stats) { + if (stat[0] != null && new File(stat[0]).canRead()) bind(cmd, stat[0] + ":" + stat[1]); + } + } + + /** + * mangoapp's CPU temperature is the mean of the thermal zones named cpuN-thermal or + * cpuN-top-thermal, as mainline kernels name them; Android kernels name the same sensors + * cpuss-0, cpu-1-0 and the like, so those zones are shown under the mainline name. + */ + private static void bindCpuTemps(List cmd, File root) { + File name = new File(root, "etc/bannerlator/cpu-thermal-type"); + try { + if (!name.isFile()) Files.write(name.toPath(), "cpu0-thermal\n".getBytes(StandardCharsets.US_ASCII)); + } catch (IOException e) { + return; + } + for (java.util.Map.Entry zone : thermalZones().entrySet()) { + String type = zone.getKey(); + if (type.startsWith("cpu") && !type.matches("cpu\\d-(top-)?thermal")) { + bind(cmd, name.getPath() + ":" + new File(zone.getValue(), "type").getPath()); + } + } + } + + /** The temp file of the first thermal zone whose sensor has one of these names, in that order. */ + private static String thermalZone(String... types) { + java.util.Map zones = thermalZones(); + for (String type : types) { + if (zones.containsKey(type)) return new File(zones.get(type), "temp").getPath(); + } + return null; + } + + /** The device's thermal zones by their sensor's name. */ + private static java.util.Map thermalZones() { + java.util.Map byType = new java.util.HashMap<>(); + File[] zones = new File("/sys/class/thermal").listFiles((dir, n) -> n.startsWith("thermal_zone")); + if (zones == null) return byType; + for (File zone : zones) { + try (java.io.BufferedReader r = new java.io.BufferedReader(new java.io.FileReader(new File(zone, "type")))) { + byType.putIfAbsent(String.valueOf(r.readLine()).trim(), zone); + } catch (IOException ignored) { + } + } + return byType; + } + private static void bind(List cmd, String spec) { cmd.add("-b"); cmd.add(spec);