From cac24913cd61506197d9389a8cc208624fbfd68c Mon Sep 17 00:00:00 2001 From: stsdc <6031763+stsdc@users.noreply.github.com> Date: Fri, 31 Oct 2025 23:26:23 +0100 Subject: [PATCH 1/8] Introduce per process GPU usage --- src/Managers/Process.vala | 90 +++++++++++++++++-- src/Managers/ProcessStructs.vala | 6 ++ src/Models/TreeViewModel.vala | 5 +- .../ProcessTreeView/CPUProcessTreeView.vala | 25 ++++++ 4 files changed, 119 insertions(+), 7 deletions(-) diff --git a/src/Managers/Process.vala b/src/Managers/Process.vala index db527f839..1a4ed202a 100644 --- a/src/Managers/Process.vala +++ b/src/Managers/Process.vala @@ -42,6 +42,9 @@ public class Monitor.Process : GLib.Object { // Contains info about io public ProcessIO io; + // Contains info about GPU usage + public ProcessDRMDriver drm_driver; + // Contains status info public ProcessStatus stat; @@ -61,11 +64,13 @@ public class Monitor.Process : GLib.Object { private uint64 cpu_last_used; // Memory usage of the process, measured in KiB. - public uint64 mem_usage { get; private set; } public double mem_percentage { get; private set; } - private uint64 last_total; + private uint64 last_drm_driver_engine_render; + public double gpu_percentage { get; private set; } + + private uint64 last_total; // Obsolete? const int HISTORY_BUFFER_SIZE = 30; public Gee.ArrayList cpu_percentage_history = new Gee.ArrayList (); @@ -83,6 +88,7 @@ public class Monitor.Process : GLib.Object { io = {}; stat = {}; + drm_driver = {}; stat.pid = _pid; // getting uid @@ -101,8 +107,9 @@ public class Monitor.Process : GLib.Object { exists = parse_stat () && read_cmdline (); get_children_pids (); get_usage (0, 1); - } + get_gpu_usage (); + } // Updates the process to get latest information // Returns if the update was successful @@ -110,6 +117,7 @@ public class Monitor.Process : GLib.Object { exists = parse_stat (); if (exists) { get_usage (cpu_total, cpu_last_total); + get_gpu_usage (); parse_io (); parse_statm (); get_open_files (); @@ -279,9 +287,79 @@ public class Monitor.Process : GLib.Object { return true; } - /** - * Reads the /proc/%pid%/cmdline file and updates from the information contained therein. - */ + static bool is_drm_fd (int fd_dir_fd, string name) { + Posix.Stat stat; + int ret = Posix.fstatat (fd_dir_fd, name, out stat, 0); + return ret == 0 && (stat.st_mode & Posix.S_IFMT) == Posix.S_IFCHR && Posix.major (stat.st_rdev) == 226; + } + + private bool get_gpu_usage () { + string path_fdinfo = "/proc/%d/fdinfo".printf (stat.pid); + string path_fd = "/proc/%d/fd".printf (stat.pid); + + try { + Dir dir = Dir.open (path_fdinfo, 0); + string ? name = null; + while ((name = dir.read_name ()) != null) { + + // skip standard fds + if (name == "0" || name == "1" || name == "2") { + continue; + } + string path = Path.build_filename (path_fdinfo, name); + + int fd_dir_fd = Posix.open (path_fd, Posix.O_RDONLY | Posix.O_DIRECTORY, 0); + bool is_drm = is_drm_fd (fd_dir_fd, name); + Posix.close (fd_dir_fd); + + if (is_drm) { + var drm_file = File.new_for_path (path); + + try { + var dis = new DataInputStream (drm_file.read ()); + string ? line; + + while ((line = dis.read_line ()) != null) { + var splitted_line = line.split (":"); + switch (splitted_line[0]) { + case "drm-engine-render": + drm_driver.engine_render = uint64.parse (splitted_line[1].strip().split(" ")[0]); + if (last_drm_driver_engine_render != 0) { + gpu_percentage = 100 * ((double) (drm_driver.engine_render - last_drm_driver_engine_render)) / 2e9; // assuming 2 second interval + } + last_drm_driver_engine_render = drm_driver.engine_render; + debug("%s %s", this.application_name, gpu_percentage.to_string()); + break; + default: + // warning ("Unknown value in %s", path); + break; + } + } + } catch (Error e) { + if (e.code != 14) { + // if the error is not `no access to file`, because regular user + // TODO: remove `magic` number + + warning ("Can't read process io: '%s' %d", e.message, e.code); + } + return false; + } + break; + } + } + } catch (FileError err) { + if (err is FileError.ACCES) { + fd_permission_error (err.message); + } else { + warning (err.message); + } + } + return true; + } + +/** + * Reads the /proc/%pid%/cmdline file and updates from the information contained therein. + */ private bool read_cmdline () { string ? cmdline = ProcessUtils.read_file ("/proc/%d/cmdline".printf (stat.pid)); diff --git a/src/Managers/ProcessStructs.vala b/src/Managers/ProcessStructs.vala index 6b210b902..486b202d6 100644 --- a/src/Managers/ProcessStructs.vala +++ b/src/Managers/ProcessStructs.vala @@ -107,3 +107,9 @@ public struct Monitor.ProcessStatus { // The time the process started after system boot. public uint64 starttime; } + +public struct Monitor.ProcessDRMDriver { + // Time spent busy in nanoseconds by the + // render engine executing workloads + public uint64 engine_render; +} \ No newline at end of file diff --git a/src/Models/TreeViewModel.vala b/src/Models/TreeViewModel.vala index aecb1c760..344726674 100644 --- a/src/Models/TreeViewModel.vala +++ b/src/Models/TreeViewModel.vala @@ -8,8 +8,9 @@ public enum Monitor.Column { NAME, CPU, MEMORY, + GPU, PID, - CMD + CMD, } public class Monitor.TreeViewModel : Gtk.TreeStore { @@ -25,6 +26,7 @@ public class Monitor.TreeViewModel : Gtk.TreeStore { typeof (string), typeof (double), typeof (int64), + typeof (double), typeof (int), typeof (string), }); @@ -79,6 +81,7 @@ public class Monitor.TreeViewModel : Gtk.TreeStore { set (iter, Column.CPU, process.cpu_percentage, Column.MEMORY, process.mem_usage, + Column.GPU, process.gpu_percentage, -1); } } diff --git a/src/Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala b/src/Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala index 51d3c2ef2..70acbefdb 100644 --- a/src/Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala +++ b/src/Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala @@ -9,6 +9,7 @@ public class Monitor.CPUProcessTreeView : Gtk.TreeView { private Gtk.TreeViewColumn pid_column; private Gtk.TreeViewColumn cpu_column; private Gtk.TreeViewColumn memory_column; + private Gtk.TreeViewColumn gpu_column; public signal void process_selected (Process process); @@ -58,6 +59,17 @@ public class Monitor.CPUProcessTreeView : Gtk.TreeView { memory_column.set_sort_column_id (Column.MEMORY); insert_column (memory_column, -1); + // setup gpu column + var gpu_cell = new Gtk.CellRendererText (); + gpu_cell.xalign = 0.5f; + + gpu_column = new Gtk.TreeViewColumn.with_attributes (_("GPU"), gpu_cell); + gpu_column.expand = false; + gpu_column.set_cell_data_func (gpu_cell, gpu_usage_cell_layout); + gpu_column.alignment = 0.5f; + gpu_column.set_sort_column_id (Column.GPU); + insert_column (gpu_column, -1); + // setup PID column var pid_cell = new Gtk.CellRendererText (); pid_cell.xalign = 0.5f; @@ -142,6 +154,19 @@ public class Monitor.CPUProcessTreeView : Gtk.TreeView { ((Gtk.CellRendererText)cell).text = "%.1f %s".printf (memory_usage_double, units); } + public void gpu_usage_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) { + // grab the value that was store in the model and convert it down to a usable format + Value gpu_usage_value; + model.get_value (iter, Column.GPU, out gpu_usage_value); + double gpu_usage = gpu_usage_value.get_double (); + + // format the double into a string + if (gpu_usage < 0.0) + ((Gtk.CellRendererText)cell).text = Utils.NO_DATA; + else + ((Gtk.CellRendererText)cell).text = "%.0f%%".printf (gpu_usage); + } + private void pid_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) { Value pid_value; model.get_value (iter, Column.PID, out pid_value); From d5f9686cbcd1d32ecfcac20167af49bc8b15ccb1 Mon Sep 17 00:00:00 2001 From: stsdc <6031763+stsdc@users.noreply.github.com> Date: Sun, 2 Nov 2025 17:12:45 +0100 Subject: [PATCH 2/8] Add some comments --- src/Managers/Process.vala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Managers/Process.vala b/src/Managers/Process.vala index 1a4ed202a..abf129cb6 100644 --- a/src/Managers/Process.vala +++ b/src/Managers/Process.vala @@ -292,7 +292,9 @@ public class Monitor.Process : GLib.Object { int ret = Posix.fstatat (fd_dir_fd, name, out stat, 0); return ret == 0 && (stat.st_mode & Posix.S_IFMT) == Posix.S_IFCHR && Posix.major (stat.st_rdev) == 226; } - + // Reads the /proc/%pid%/fdinfo file, then check if the corresponding fd is a drm fd + // Next, parsing the fdinfo file to get time spent on gpu in ns + // Calculating delta from last time and dividing by time interval to get percentage private bool get_gpu_usage () { string path_fdinfo = "/proc/%d/fdinfo".printf (stat.pid); string path_fd = "/proc/%d/fd".printf (stat.pid); @@ -322,10 +324,12 @@ public class Monitor.Process : GLib.Object { while ((line = dis.read_line ()) != null) { var splitted_line = line.split (":"); switch (splitted_line[0]) { + // for i915 there is only drm-engine-render to check case "drm-engine-render": drm_driver.engine_render = uint64.parse (splitted_line[1].strip().split(" ")[0]); if (last_drm_driver_engine_render != 0) { - gpu_percentage = 100 * ((double) (drm_driver.engine_render - last_drm_driver_engine_render)) / 2e9; // assuming 2 second interval + var interval = 2; // @TODO: get actual interval + gpu_percentage = 100 * ((double) (drm_driver.engine_render - last_drm_driver_engine_render)) / (interval * 1e9); } last_drm_driver_engine_render = drm_driver.engine_render; debug("%s %s", this.application_name, gpu_percentage.to_string()); From 7a381797bae268049f2a4054f261e4a0b65df3bc Mon Sep 17 00:00:00 2001 From: stsdc <6031763+stsdc@users.noreply.github.com> Date: Sun, 2 Nov 2025 17:15:33 +0100 Subject: [PATCH 3/8] Fix lint --- src/Managers/Process.vala | 4 ++-- src/Managers/ProcessStructs.vala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Managers/Process.vala b/src/Managers/Process.vala index abf129cb6..4de50bbfa 100644 --- a/src/Managers/Process.vala +++ b/src/Managers/Process.vala @@ -326,13 +326,13 @@ public class Monitor.Process : GLib.Object { switch (splitted_line[0]) { // for i915 there is only drm-engine-render to check case "drm-engine-render": - drm_driver.engine_render = uint64.parse (splitted_line[1].strip().split(" ")[0]); + drm_driver.engine_render = uint64.parse (splitted_line[1].strip ().split (" ")[0]); if (last_drm_driver_engine_render != 0) { var interval = 2; // @TODO: get actual interval gpu_percentage = 100 * ((double) (drm_driver.engine_render - last_drm_driver_engine_render)) / (interval * 1e9); } last_drm_driver_engine_render = drm_driver.engine_render; - debug("%s %s", this.application_name, gpu_percentage.to_string()); + debug ("%s %s", this.application_name, gpu_percentage.to_string ()); break; default: // warning ("Unknown value in %s", path); diff --git a/src/Managers/ProcessStructs.vala b/src/Managers/ProcessStructs.vala index 486b202d6..598eb32ca 100644 --- a/src/Managers/ProcessStructs.vala +++ b/src/Managers/ProcessStructs.vala @@ -112,4 +112,4 @@ public struct Monitor.ProcessDRMDriver { // Time spent busy in nanoseconds by the // render engine executing workloads public uint64 engine_render; -} \ No newline at end of file +} From 5243cfc5a839ca813b9cde73d6f155ccc1f9ce9b Mon Sep 17 00:00:00 2001 From: stsdc <6031763+stsdc@users.noreply.github.com> Date: Thu, 6 Nov 2025 16:26:25 +0100 Subject: [PATCH 4/8] Get update_interval for the per process GPU usage calculation from the settings --- src/Managers/Process.vala | 22 ++++++++++++++-------- src/Managers/ProcessManager.vala | 3 ++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Managers/Process.vala b/src/Managers/Process.vala index 4de50bbfa..48dd5df20 100644 --- a/src/Managers/Process.vala +++ b/src/Managers/Process.vala @@ -25,6 +25,9 @@ public class Monitor.Process : GLib.Object { public string username = Utils.NO_DATA; + // Update interval in seconds is used to calculate GPU usage + public int update_interval; + Icon _icon; public Icon icon { get { @@ -79,7 +82,7 @@ public class Monitor.Process : GLib.Object { // Construct a new process - public Process (int _pid) { + public Process (int _pid, int _update_interval) { _icon = ProcessUtils.get_default_icon (); open_files_paths = new Gee.HashSet (); @@ -90,6 +93,7 @@ public class Monitor.Process : GLib.Object { stat = {}; drm_driver = {}; stat.pid = _pid; + update_interval = _update_interval; // getting uid GTop.ProcUid proc_uid; @@ -107,8 +111,7 @@ public class Monitor.Process : GLib.Object { exists = parse_stat () && read_cmdline (); get_children_pids (); get_usage (0, 1); - - get_gpu_usage (); + get_usage_gpu (); } // Updates the process to get latest information @@ -117,7 +120,7 @@ public class Monitor.Process : GLib.Object { exists = parse_stat (); if (exists) { get_usage (cpu_total, cpu_last_total); - get_gpu_usage (); + get_usage_gpu (); parse_io (); parse_statm (); get_open_files (); @@ -287,15 +290,18 @@ public class Monitor.Process : GLib.Object { return true; } + // Based on nvtop + // https://github.com/Syllo/nvtop/blob/4bf5db248d7aa7528f3a1ab7c94f504dff6834e4/src/extract_processinfo_fdinfo.c#L88 static bool is_drm_fd (int fd_dir_fd, string name) { Posix.Stat stat; int ret = Posix.fstatat (fd_dir_fd, name, out stat, 0); return ret == 0 && (stat.st_mode & Posix.S_IFMT) == Posix.S_IFCHR && Posix.major (stat.st_rdev) == 226; } + // Reads the /proc/%pid%/fdinfo file, then check if the corresponding fd is a drm fd // Next, parsing the fdinfo file to get time spent on gpu in ns // Calculating delta from last time and dividing by time interval to get percentage - private bool get_gpu_usage () { + private bool get_usage_gpu () { string path_fdinfo = "/proc/%d/fdinfo".printf (stat.pid); string path_fd = "/proc/%d/fd".printf (stat.pid); @@ -328,11 +334,10 @@ public class Monitor.Process : GLib.Object { case "drm-engine-render": drm_driver.engine_render = uint64.parse (splitted_line[1].strip ().split (" ")[0]); if (last_drm_driver_engine_render != 0) { - var interval = 2; // @TODO: get actual interval - gpu_percentage = 100 * ((double) (drm_driver.engine_render - last_drm_driver_engine_render)) / (interval * 1e9); + gpu_percentage = 100 * ((double) (drm_driver.engine_render - last_drm_driver_engine_render)) / (update_interval * 1e9); } last_drm_driver_engine_render = drm_driver.engine_render; - debug ("%s %s", this.application_name, gpu_percentage.to_string ()); + // debug ("%s %s", this.application_name, gpu_percentage.to_string ()); break; default: // warning ("Unknown value in %s", path); @@ -383,6 +388,7 @@ public class Monitor.Process : GLib.Object { return true; } + // @TODO: Divide into get_usage_cpu and get_usage_mem and write some tests private void get_usage (uint64 cpu_total, uint64 cpu_last_total) { // Get CPU usage by process GTop.ProcTime proc_time; diff --git a/src/Managers/ProcessManager.vala b/src/Managers/ProcessManager.vala index 69033027c..fb33c30f6 100644 --- a/src/Managers/ProcessManager.vala +++ b/src/Managers/ProcessManager.vala @@ -259,7 +259,8 @@ namespace Monitor { */ private Process ? add_process (int pid, bool lazy_signal = false) { // create the process - var process = new Process (pid); + int update_interval = MonitorApp.settings.get_int ("update-time"); + var process = new Process (pid, update_interval); if (!process.exists) { return null; From 2d82102f4b5c84354ebf7010f6d4b5e9f924b03a Mon Sep 17 00:00:00 2001 From: stsdc <6031763+stsdc@users.noreply.github.com> Date: Sun, 16 Nov 2025 01:07:41 +0100 Subject: [PATCH 5/8] Refactor get_usage_gpu() --- src/Managers/Process.vala | 77 +++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/src/Managers/Process.vala b/src/Managers/Process.vala index 48dd5df20..777bfb1b8 100644 --- a/src/Managers/Process.vala +++ b/src/Managers/Process.vala @@ -76,8 +76,8 @@ public class Monitor.Process : GLib.Object { private uint64 last_total; // Obsolete? const int HISTORY_BUFFER_SIZE = 30; - public Gee.ArrayList cpu_percentage_history = new Gee.ArrayList (); - public Gee.ArrayList mem_percentage_history = new Gee.ArrayList (); + public Gee.ArrayList cpu_percentage_history = new Gee.ArrayList (); + public Gee.ArrayList mem_percentage_history = new Gee.ArrayList (); @@ -305,9 +305,12 @@ public class Monitor.Process : GLib.Object { string path_fdinfo = "/proc/%d/fdinfo".printf (stat.pid); string path_fd = "/proc/%d/fd".printf (stat.pid); + var drm_files = new Gee.ArrayList (); + try { Dir dir = Dir.open (path_fdinfo, 0); string ? name = null; + while ((name = dir.read_name ()) != null) { // skip standard fds @@ -322,48 +325,52 @@ public class Monitor.Process : GLib.Object { if (is_drm) { var drm_file = File.new_for_path (path); - - try { - var dis = new DataInputStream (drm_file.read ()); - string ? line; - - while ((line = dis.read_line ()) != null) { - var splitted_line = line.split (":"); - switch (splitted_line[0]) { - // for i915 there is only drm-engine-render to check - case "drm-engine-render": - drm_driver.engine_render = uint64.parse (splitted_line[1].strip ().split (" ")[0]); - if (last_drm_driver_engine_render != 0) { - gpu_percentage = 100 * ((double) (drm_driver.engine_render - last_drm_driver_engine_render)) / (update_interval * 1e9); - } - last_drm_driver_engine_render = drm_driver.engine_render; - // debug ("%s %s", this.application_name, gpu_percentage.to_string ()); - break; - default: - // warning ("Unknown value in %s", path); - break; - } - } - } catch (Error e) { - if (e.code != 14) { - // if the error is not `no access to file`, because regular user - // TODO: remove `magic` number - - warning ("Can't read process io: '%s' %d", e.message, e.code); - } - return false; - } - break; + drm_files.add (drm_file); } } } catch (FileError err) { if (err is FileError.ACCES) { - fd_permission_error (err.message); + } else { warning (err.message); } } + + foreach (var drm_file in drm_files) { + try { + var dis = new DataInputStream (drm_file.read ()); + string ? line; + + while ((line = dis.read_line ()) != null) { + var splitted_line = line.split (":"); + switch (splitted_line[0]) { + // for i915 there is only drm-engine-render to check + case "drm-engine-render": + drm_driver.engine_render = uint64.parse (splitted_line[1].strip ().split (" ")[0]); + if (last_drm_driver_engine_render != 0) { + gpu_percentage = 100 * ((double) (drm_driver.engine_render - last_drm_driver_engine_render)) / (update_interval * 1e9); + } + last_drm_driver_engine_render = drm_driver.engine_render; + // debug ("%s %s", this.application_name, gpu_percentage.to_string ()); + break; + default: + // warning ("Unknown value in %s", path); + break; + } + } + } catch (Error e) { + if (e.code != 14) { + // if the error is not `no access to file`, because regular user + // TODO: remove `magic` number + + warning ("Can't read process io: '%s' %d", e.message, e.code); + } + return false; + } + break; + } return true; + } /** From 35202a7b724f0188e1092b99ca72c8c76a32bafa Mon Sep 17 00:00:00 2001 From: stsdc <6031763+stsdc@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:40:05 +0100 Subject: [PATCH 6/8] Refactor GPU usage handling by introducing ProcessDRM class --- src/Managers/Process.vala | 94 +++------------------------- src/Managers/ProcessDRM.vala | 102 +++++++++++++++++++++++++++++++ src/Managers/ProcessStructs.vala | 6 -- src/meson.build | 1 + 4 files changed, 110 insertions(+), 93 deletions(-) create mode 100644 src/Managers/ProcessDRM.vala diff --git a/src/Managers/Process.vala b/src/Managers/Process.vala index 777bfb1b8..0f0d1f6c7 100644 --- a/src/Managers/Process.vala +++ b/src/Managers/Process.vala @@ -46,7 +46,7 @@ public class Monitor.Process : GLib.Object { public ProcessIO io; // Contains info about GPU usage - public ProcessDRMDriver drm_driver; + public ProcessDRM drm; // Contains status info public ProcessStatus stat; @@ -89,9 +89,10 @@ public class Monitor.Process : GLib.Object { last_total = 0; + drm = new ProcessDRM (_pid, _update_interval); + io = {}; stat = {}; - drm_driver = {}; stat.pid = _pid; update_interval = _update_interval; @@ -111,7 +112,8 @@ public class Monitor.Process : GLib.Object { exists = parse_stat () && read_cmdline (); get_children_pids (); get_usage (0, 1); - get_usage_gpu (); + + gpu_percentage = 0; } // Updates the process to get latest information @@ -120,7 +122,8 @@ public class Monitor.Process : GLib.Object { exists = parse_stat (); if (exists) { get_usage (cpu_total, cpu_last_total); - get_usage_gpu (); + drm.update (); + gpu_percentage = drm.gpu_percentage; parse_io (); parse_statm (); get_open_files (); @@ -290,89 +293,6 @@ public class Monitor.Process : GLib.Object { return true; } - // Based on nvtop - // https://github.com/Syllo/nvtop/blob/4bf5db248d7aa7528f3a1ab7c94f504dff6834e4/src/extract_processinfo_fdinfo.c#L88 - static bool is_drm_fd (int fd_dir_fd, string name) { - Posix.Stat stat; - int ret = Posix.fstatat (fd_dir_fd, name, out stat, 0); - return ret == 0 && (stat.st_mode & Posix.S_IFMT) == Posix.S_IFCHR && Posix.major (stat.st_rdev) == 226; - } - - // Reads the /proc/%pid%/fdinfo file, then check if the corresponding fd is a drm fd - // Next, parsing the fdinfo file to get time spent on gpu in ns - // Calculating delta from last time and dividing by time interval to get percentage - private bool get_usage_gpu () { - string path_fdinfo = "/proc/%d/fdinfo".printf (stat.pid); - string path_fd = "/proc/%d/fd".printf (stat.pid); - - var drm_files = new Gee.ArrayList (); - - try { - Dir dir = Dir.open (path_fdinfo, 0); - string ? name = null; - - while ((name = dir.read_name ()) != null) { - - // skip standard fds - if (name == "0" || name == "1" || name == "2") { - continue; - } - string path = Path.build_filename (path_fdinfo, name); - - int fd_dir_fd = Posix.open (path_fd, Posix.O_RDONLY | Posix.O_DIRECTORY, 0); - bool is_drm = is_drm_fd (fd_dir_fd, name); - Posix.close (fd_dir_fd); - - if (is_drm) { - var drm_file = File.new_for_path (path); - drm_files.add (drm_file); - } - } - } catch (FileError err) { - if (err is FileError.ACCES) { - - } else { - warning (err.message); - } - } - - foreach (var drm_file in drm_files) { - try { - var dis = new DataInputStream (drm_file.read ()); - string ? line; - - while ((line = dis.read_line ()) != null) { - var splitted_line = line.split (":"); - switch (splitted_line[0]) { - // for i915 there is only drm-engine-render to check - case "drm-engine-render": - drm_driver.engine_render = uint64.parse (splitted_line[1].strip ().split (" ")[0]); - if (last_drm_driver_engine_render != 0) { - gpu_percentage = 100 * ((double) (drm_driver.engine_render - last_drm_driver_engine_render)) / (update_interval * 1e9); - } - last_drm_driver_engine_render = drm_driver.engine_render; - // debug ("%s %s", this.application_name, gpu_percentage.to_string ()); - break; - default: - // warning ("Unknown value in %s", path); - break; - } - } - } catch (Error e) { - if (e.code != 14) { - // if the error is not `no access to file`, because regular user - // TODO: remove `magic` number - - warning ("Can't read process io: '%s' %d", e.message, e.code); - } - return false; - } - break; - } - return true; - - } - /** * Reads the /proc/%pid%/cmdline file and updates from the information contained therein. */ diff --git a/src/Managers/ProcessDRM.vala b/src/Managers/ProcessDRM.vala new file mode 100644 index 000000000..4d4ff6023 --- /dev/null +++ b/src/Managers/ProcessDRM.vala @@ -0,0 +1,102 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io) + */ + +public class Monitor.ProcessDRM { + // Time spent busy in nanoseconds by the + // render engine executing workloads + public uint64 engine_render { get; private set; } + + public uint engine_gfx { get; private set; } + + public double gpu_percentage { get; private set; } + + private uint64 last_drm_driver_engine_render; + + private int pid; + private int update_interval; + + public ProcessDRM (int pid, int update_interval) { + this.pid = pid; + this.update_interval = update_interval; + } + + public void update () { + string path_fdinfo = "/proc/%d/fdinfo".printf (pid); + string path_fd = "/proc/%d/fd".printf (pid); + + var drm_files = new Gee.ArrayList (); + + try { + Dir dir = Dir.open (path_fdinfo, 0); + string ? name = null; + + while ((name = dir.read_name ()) != null) { + + // skip standard fds + if (name == "0" || name == "1" || name == "2") { + continue; + } + string path = Path.build_filename (path_fdinfo, name); + + int fd_dir_fd = Posix.open (path_fd, Posix.O_RDONLY | Posix.O_DIRECTORY, 0); + bool is_drm = is_drm_fd (fd_dir_fd, name); + Posix.close (fd_dir_fd); + + if (is_drm) { + var drm_file = File.new_for_path (path); + drm_files.add (drm_file); + } + } + } catch (FileError err) { + if (err is FileError.ACCES) { + + } else { + warning (err.message); + } + } + + foreach (var drm_file in drm_files) { + try { + var dis = new DataInputStream (drm_file.read ()); + string ? line; + + while ((line = dis.read_line ()) != null) { + var splitted_line = line.split (":"); + switch (splitted_line[0]) { + // for i915 there is only drm-engine-render to check + case "drm-engine-render": + this.engine_render = uint64.parse (splitted_line[1].strip ().split (" ")[0]); + if (last_drm_driver_engine_render != 0) { + gpu_percentage = 100 * ((double) (this.engine_render - last_drm_driver_engine_render)) / (update_interval * 1e9); + } + last_drm_driver_engine_render = this.engine_render; + // debug ("%s %s", this.application_name, gpu_percentage.to_string ()); + break; + default: + // warning ("Unknown value in %s", path); + break; + } + } + } catch (Error e) { + if (e.code != 14) { + // if the error is not `no access to file`, because regular user + // TODO: remove `magic` number + + warning ("Can't read process io: '%s' %d", e.message, e.code); + } + } + break; + } + } + + // Based on nvtop + // https://github.com/Syllo/nvtop/blob/4bf5db248d7aa7528f3a1ab7c94f504dff6834e4/src/extract_processinfo_fdinfo.c#L88 + static bool is_drm_fd (int fd_dir_fd, string name) { + Posix.Stat stat; + int ret = Posix.fstatat (fd_dir_fd, name, out stat, 0); + return ret == 0 && (stat.st_mode & Posix.S_IFMT) == Posix.S_IFCHR && Posix.major (stat.st_rdev) == 226; + } + +} \ No newline at end of file diff --git a/src/Managers/ProcessStructs.vala b/src/Managers/ProcessStructs.vala index 598eb32ca..6b210b902 100644 --- a/src/Managers/ProcessStructs.vala +++ b/src/Managers/ProcessStructs.vala @@ -107,9 +107,3 @@ public struct Monitor.ProcessStatus { // The time the process started after system boot. public uint64 starttime; } - -public struct Monitor.ProcessDRMDriver { - // Time spent busy in nanoseconds by the - // render engine executing workloads - public uint64 engine_render; -} diff --git a/src/meson.build b/src/meson.build index 41ff3c6cb..a50fe0f2c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -41,6 +41,7 @@ source_app_files = [ 'Managers/Process.vala', 'Managers/ProcessStructs.vala', 'Managers/ProcessUtils.vala', + 'Managers/ProcessDRM.vala', # Services 'Services/DBusServer.vala', From 54b9a86b80b2ad04a7c668660510203a197bb25e Mon Sep 17 00:00:00 2001 From: stsdc <6031763+stsdc@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:18:20 +0100 Subject: [PATCH 7/8] Refactor ProcessDRM --- src/Managers/ProcessDRM.vala | 41 ++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/Managers/ProcessDRM.vala b/src/Managers/ProcessDRM.vala index 4d4ff6023..a3b9e2c40 100644 --- a/src/Managers/ProcessDRM.vala +++ b/src/Managers/ProcessDRM.vala @@ -4,28 +4,31 @@ */ public class Monitor.ProcessDRM { - // Time spent busy in nanoseconds by the - // render engine executing workloads - public uint64 engine_render { get; private set; } + /** Time spent busy in nanoseconds by the render engine executing + * workloads from the last time it was read + */ + private uint64 last_engine_render; + private uint64 last_engine_gfx; - public uint engine_gfx { get; private set; } public double gpu_percentage { get; private set; } - private uint64 last_drm_driver_engine_render; - private int pid; private int update_interval; public ProcessDRM (int pid, int update_interval) { this.pid = pid; this.update_interval = update_interval; + + last_engine_render = 0; + last_engine_gfx = 0; } public void update () { string path_fdinfo = "/proc/%d/fdinfo".printf (pid); string path_fd = "/proc/%d/fd".printf (pid); + var drm_files = new Gee.ArrayList (); try { @@ -65,14 +68,12 @@ public class Monitor.ProcessDRM { while ((line = dis.read_line ()) != null) { var splitted_line = line.split (":"); switch (splitted_line[0]) { + case "drm-engine-gfx": + update_engine (splitted_line[1], ref last_engine_gfx); + break; // for i915 there is only drm-engine-render to check case "drm-engine-render": - this.engine_render = uint64.parse (splitted_line[1].strip ().split (" ")[0]); - if (last_drm_driver_engine_render != 0) { - gpu_percentage = 100 * ((double) (this.engine_render - last_drm_driver_engine_render)) / (update_interval * 1e9); - } - last_drm_driver_engine_render = this.engine_render; - // debug ("%s %s", this.application_name, gpu_percentage.to_string ()); + update_engine (splitted_line[1], ref last_engine_render); break; default: // warning ("Unknown value in %s", path); @@ -84,13 +85,25 @@ public class Monitor.ProcessDRM { // if the error is not `no access to file`, because regular user // TODO: remove `magic` number - warning ("Can't read process io: '%s' %d", e.message, e.code); + warning ("Can't read fdinfo: '%s' %d", e.message, e.code); } } break; } } + private void update_engine (string line, ref uint64 last_engine) { + var engine = uint64.parse (line.strip ().split (" ")[0]); + if (last_engine != 0) { + gpu_percentage = calculate_percentage (engine, last_engine, update_interval); + } + last_engine = engine; + } + + private static double calculate_percentage (uint64 engine, uint64 last_engine, int interval) { + return 100 * ((double) (engine - last_engine)) / (interval * 1e9); + } + // Based on nvtop // https://github.com/Syllo/nvtop/blob/4bf5db248d7aa7528f3a1ab7c94f504dff6834e4/src/extract_processinfo_fdinfo.c#L88 static bool is_drm_fd (int fd_dir_fd, string name) { @@ -99,4 +112,4 @@ public class Monitor.ProcessDRM { return ret == 0 && (stat.st_mode & Posix.S_IFMT) == Posix.S_IFCHR && Posix.major (stat.st_rdev) == 226; } -} \ No newline at end of file +} From 8bd4800fdaeabd7a7178d32f7e722d443b41d5f6 Mon Sep 17 00:00:00 2001 From: stsdc <6031763+stsdc@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:24:47 +0100 Subject: [PATCH 8/8] Remove leftovers --- src/Managers/Process.vala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Managers/Process.vala b/src/Managers/Process.vala index 0f0d1f6c7..84df398d0 100644 --- a/src/Managers/Process.vala +++ b/src/Managers/Process.vala @@ -70,7 +70,6 @@ public class Monitor.Process : GLib.Object { public uint64 mem_usage { get; private set; } public double mem_percentage { get; private set; } - private uint64 last_drm_driver_engine_render; public double gpu_percentage { get; private set; } private uint64 last_total; // Obsolete?