diff --git a/src/Managers/Process.vala b/src/Managers/Process.vala index db527f839..84df398d0 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 { @@ -42,6 +45,9 @@ public class Monitor.Process : GLib.Object { // Contains info about io public ProcessIO io; + // Contains info about GPU usage + public ProcessDRM drm; + // Contains status info public ProcessStatus stat; @@ -61,29 +67,33 @@ 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; + 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 (); - 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 (); // 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 (); last_total = 0; + drm = new ProcessDRM (_pid, _update_interval); + io = {}; stat = {}; stat.pid = _pid; + update_interval = _update_interval; // getting uid GTop.ProcUid proc_uid; @@ -101,8 +111,9 @@ public class Monitor.Process : GLib.Object { exists = parse_stat () && read_cmdline (); get_children_pids (); get_usage (0, 1); - } + gpu_percentage = 0; + } // Updates the process to get latest information // Returns if the update was successful @@ -110,6 +121,8 @@ public class Monitor.Process : GLib.Object { exists = parse_stat (); if (exists) { get_usage (cpu_total, cpu_last_total); + drm.update (); + gpu_percentage = drm.gpu_percentage; parse_io (); parse_statm (); get_open_files (); @@ -279,9 +292,9 @@ public class Monitor.Process : GLib.Object { return true; } - /** - * Reads the /proc/%pid%/cmdline file and updates from the information contained therein. - */ +/** + * 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)); @@ -301,6 +314,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/ProcessDRM.vala b/src/Managers/ProcessDRM.vala new file mode 100644 index 000000000..a3b9e2c40 --- /dev/null +++ b/src/Managers/ProcessDRM.vala @@ -0,0 +1,115 @@ +/* + * 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 from the last time it was read + */ + private uint64 last_engine_render; + private uint64 last_engine_gfx; + + + public double gpu_percentage { get; private set; } + + 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 { + 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]) { + 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": + update_engine (splitted_line[1], ref last_engine_render); + 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 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) { + 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; + } + +} 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; 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); 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',