Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions src/Managers/Process.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this need to be public? I think we should lessen scope as possible for new codes.


Icon _icon;
public Icon icon {
get {
Expand All @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment goes here.


// Contains status info
public ProcessStatus stat;

Expand All @@ -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?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this comment mean? Is it TODO?


const int HISTORY_BUFFER_SIZE = 30;
public Gee.ArrayList<double ? > cpu_percentage_history = new Gee.ArrayList<double ? > ();
public Gee.ArrayList<double ? > mem_percentage_history = new Gee.ArrayList<double ? > ();
public Gee.ArrayList<double ?> cpu_percentage_history = new Gee.ArrayList<double ?> ();
public Gee.ArrayList<double ?> mem_percentage_history = new Gee.ArrayList<double ?> ();



// 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<string> ();

last_total = 0;

drm = new ProcessDRM (_pid, _update_interval);

io = {};
stat = {};
stat.pid = _pid;
update_interval = _update_interval;

// getting uid
GTop.ProcUid proc_uid;
Expand All @@ -101,15 +111,18 @@ 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
public bool update (uint64 cpu_total, uint64 cpu_last_total) {
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 ();
Expand Down Expand Up @@ -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.
*/
Comment on lines -282 to +297
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation comments for a method should have the same indentation with the method as the previous code does.

private bool read_cmdline () {
string ? cmdline = ProcessUtils.read_file ("/proc/%d/cmdline".printf (stat.pid));

Expand All @@ -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;
Expand Down
115 changes: 115 additions & 0 deletions src/Managers/ProcessDRM.vala
Original file line number Diff line number Diff line change
@@ -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<GLib.File ?> ();

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);
}
Comment on lines +56 to +60
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason you crush FileError.ACCES error? Same reason with the following commnet?

// if the error is not no access to file, because regular user

If so please explicit by adding a comment here too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, isn't it possible to write something like this? I commented without testing if this is grammatically correct though.

            if (!(err is FileError.ACCES)) {
                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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this is commented out?

break;
}
}
} catch (Error e) {
if (e.code != 14) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What 14 mean here?

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

}
3 changes: 2 additions & 1 deletion src/Managers/ProcessManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion src/Models/TreeViewModel.vala
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ public enum Monitor.Column {
NAME,
CPU,
MEMORY,
GPU,
PID,
CMD
CMD,
}

public class Monitor.TreeViewModel : Gtk.TreeStore {
Expand All @@ -25,6 +26,7 @@ public class Monitor.TreeViewModel : Gtk.TreeStore {
typeof (string),
typeof (double),
typeof (int64),
typeof (double),
typeof (int),
typeof (string),
});
Expand Down Expand Up @@ -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);
}
}
Expand Down
25 changes: 25 additions & 0 deletions src/Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Comment on lines +164 to +167
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (gpu_usage < 0.0)
((Gtk.CellRendererText)cell).text = Utils.NO_DATA;
else
((Gtk.CellRendererText)cell).text = "%.0f%%".printf (gpu_usage);
if (gpu_usage < 0.0) {
((Gtk.CellRendererText)cell).text = Utils.NO_DATA;
} else {
((Gtk.CellRendererText)cell).text = "%.0f%%".printf (gpu_usage);
}

Please always explicit brackets to prevent bugs in case we added additional lines here in the future.

See also https://docs.elementary.io/develop/writing-apps/code-style#vala:

On conditionals and loops, always use braces even if there's only one line of code:

if (my_var > 2) {
    print ("hello\n");
}

}

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);
Expand Down
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ source_app_files = [
'Managers/Process.vala',
'Managers/ProcessStructs.vala',
'Managers/ProcessUtils.vala',
'Managers/ProcessDRM.vala',

# Services
'Services/DBusServer.vala',
Expand Down