From 0a3cd3d75cb01f0e928f3a6d80234b1bebe3e575 Mon Sep 17 00:00:00 2001 From: stsdc <6031763+stsdc@users.noreply.github.com> Date: Sun, 24 Dec 2023 20:41:28 +0100 Subject: [PATCH] Create ProcessWorkaround class and use it if in Flatpak env --- flatpak/workaround/process_monitor.py | 10 +- src/Managers/Process.vala | 77 ++------- src/Managers/ProcessManager.vala | 2 +- src/Managers/ProcessProvider.vala | 7 + src/Managers/ProcessWorkaround.vala | 226 ++++++++++++++++++++++++++ src/meson.build | 2 + 6 files changed, 259 insertions(+), 65 deletions(-) create mode 100644 src/Managers/ProcessWorkaround.vala diff --git a/flatpak/workaround/process_monitor.py b/flatpak/workaround/process_monitor.py index 7d6c03d5..a8feafdb 100644 --- a/flatpak/workaround/process_monitor.py +++ b/flatpak/workaround/process_monitor.py @@ -44,9 +44,13 @@ def GetProcesses(self, name, sender=None, conn=None): "io": "", "children": "" } - with open(f'/proc/{pid}/cmdline', 'rb') as file: - process["cmdline"] = (file.read().decode('utf-8', 'ignore').replace('\0', ' ')) - + try: + with open(f'/proc/{pid}/cmdline', 'rb') as file: + process["cmdline"] = (file.read().decode('utf-8', 'ignore').replace('\0', ' ')) + except FileNotFoundError as err: + process["cmdline"] = None + continue + with open(f'/proc/{pid}/stat', 'rb') as file: process["stat"] = (file.read().decode('utf-8', 'ignore').replace('\0', ' ')) diff --git a/src/Managers/Process.vala b/src/Managers/Process.vala index e6e1a1e2..bd6342d0 100644 --- a/src/Managers/Process.vala +++ b/src/Managers/Process.vala @@ -6,10 +6,10 @@ public class Monitor.Process : GLib.Object { // Whether or not the PID leads to something - public bool exists { get; private set; } + public bool exists { get; protected set; } // Full command from cmdline file - public string command { get; private set; } + public string command { get; protected set; } // If process is an installed app, this will contain its name, // otherwise it is just a trimmed command @@ -51,17 +51,17 @@ public class Monitor.Process : GLib.Object { * * Will be 0 on first update. */ - public double cpu_percentage { get; private set; } + public double cpu_percentage { get; protected set; } - private uint64 cpu_last_used; + protected 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; } + public uint64 mem_usage { get; protected set; } + public double mem_percentage { get; protected set; } - private uint64 last_total; + protected uint64 last_total; - const int HISTORY_BUFFER_SIZE = 30; + protected const int HISTORY_BUFFER_SIZE = 30; public Gee.ArrayList cpu_percentage_history = new Gee.ArrayList (); public Gee.ArrayList mem_percentage_history = new Gee.ArrayList (); @@ -95,7 +95,7 @@ public class Monitor.Process : GLib.Object { get_usage (0, 1); } - private int get_uid () { + protected int get_uid () { if (ProcessUtils.is_flatpak_env ()) { var process_provider = ProcessProvider.get_default (); string ? status = process_provider.pids_status.get (this.stat.pid); @@ -139,7 +139,7 @@ public class Monitor.Process : GLib.Object { return false; } - private bool get_children_pids () { + protected bool get_children_pids () { string ? children_content = ProcessUtils.read_file ("/proc/%d/task/%d/children".printf (stat.pid, stat.pid)); if (children_content == "" || children_content == null) { return false; @@ -152,52 +152,7 @@ public class Monitor.Process : GLib.Object { return true; } - private bool parse_io_workaround () { - var process_provider = ProcessProvider.get_default (); - string ? io_stats = process_provider.pids_io.get (this.stat.pid); - - if (io_stats == "") return false; - - foreach (string line in io_stats.split ("\n")) { - if (line == "") continue; - var splitted_line = line.split (":"); - switch (splitted_line[0]) { - case "wchar": - io.wchar = uint64.parse (splitted_line[1]); - break; - case "rchar": - io.rchar = uint64.parse (splitted_line[1]); - break; - case "syscr": - io.syscr = uint64.parse (splitted_line[1]); - break; - case "syscw": - io.syscw = uint64.parse (splitted_line[1]); - break; - case "read_bytes": - io.read_bytes = uint64.parse (splitted_line[1]); - break; - case "write_bytes": - io.write_bytes = uint64.parse (splitted_line[1]); - break; - case "cancelled_write_bytes": - io.cancelled_write_bytes = uint64.parse (splitted_line[1]); - break; - default: - warning ("Unknown value in /proc/%d/io", stat.pid); - break; - } - } - - return true; - } - - private bool parse_io () { - - if (ProcessUtils.is_flatpak_env ()) { - return parse_io_workaround (); - } - + protected bool parse_io () { var io_file = File.new_for_path ("/proc/%d/io".printf (stat.pid)); if (!io_file.query_exists ()) { @@ -250,7 +205,7 @@ public class Monitor.Process : GLib.Object { } // Reads the /proc/%pid%/stat file and updates the process with the information therein. - private bool parse_stat () { + protected bool parse_stat () { string ? stat_contents; if (ProcessUtils.is_flatpak_env ()) { var process_provider = ProcessProvider.get_default (); @@ -293,7 +248,7 @@ public class Monitor.Process : GLib.Object { return true; } - private bool parse_statm () { + protected bool parse_statm () { string ? statm_contents; if (ProcessUtils.is_flatpak_env ()) { var process_provider = ProcessProvider.get_default (); @@ -316,7 +271,7 @@ public class Monitor.Process : GLib.Object { return true; } - private bool get_open_files () { + protected bool get_open_files () { // try { // string directory = "/proc/%d/fd".printf (stat.pid); // Dir dir = Dir.open (directory, 0); @@ -343,7 +298,7 @@ public class Monitor.Process : GLib.Object { /** * Reads the /proc/%pid%/cmdline file and updates from the information contained therein. */ - private bool read_cmdline () { + protected bool read_cmdline () { string ? cmdline; if (ProcessUtils.is_flatpak_env ()) { var process_provider = ProcessProvider.get_default (); @@ -368,7 +323,7 @@ public class Monitor.Process : GLib.Object { return true; } - private void get_usage (uint64 cpu_total, uint64 cpu_last_total) { + protected void get_usage (uint64 cpu_total, uint64 cpu_last_total) { // Get CPU usage by process GTop.ProcTime proc_time; GTop.get_proc_time (out proc_time, stat.pid); diff --git a/src/Managers/ProcessManager.vala b/src/Managers/ProcessManager.vala index 8ef55611..cbf6ce34 100644 --- a/src/Managers/ProcessManager.vala +++ b/src/Managers/ProcessManager.vala @@ -250,7 +250,7 @@ namespace Monitor { */ private Process ? add_process (int pid, bool lazy_signal = false) { // create the process - var process = new Process (pid); + var process = ProcessProvider.get_default ().create_process (pid); if (!process.exists) { return null; diff --git a/src/Managers/ProcessProvider.vala b/src/Managers/ProcessProvider.vala index 75ef4cb9..039b78fb 100644 --- a/src/Managers/ProcessProvider.vala +++ b/src/Managers/ProcessProvider.vala @@ -53,6 +53,13 @@ namespace Monitor { return pids; } + public Process create_process (int pid) { + if (ProcessUtils.is_flatpak_env ()) { + return new ProcessWorkaround (pid); + } + return new Process (pid); + } + private bool process_line (IOChannel channel, IOCondition condition, GLib.List _pids) { if (condition == IOCondition.HUP) { // debug ("%s: The fd has been closed.\n", stream_name); diff --git a/src/Managers/ProcessWorkaround.vala b/src/Managers/ProcessWorkaround.vala new file mode 100644 index 00000000..7a196d52 --- /dev/null +++ b/src/Managers/ProcessWorkaround.vala @@ -0,0 +1,226 @@ +public class Monitor.ProcessWorkaround : Monitor.Process { + + // Construct a new process + public ProcessWorkaround (int _pid) { + base (_pid); + } + + private new int get_uid () { + var process_provider = ProcessProvider.get_default (); + string ? status = process_provider.pids_status.get (this.stat.pid); + var status_line = status.split ("\n"); + return int.parse (status_line[8].split ("\t")[1]); + } + + // Kills the process + public new bool kill () { + // Sends a kill signal that cannot be ignored + if (Posix.kill (stat.pid, Posix.Signal.KILL) == 0) { + return true; + } + return false; + } + + // Ends the process + public new bool end () { + // Sends a terminate signal + if (Posix.kill (stat.pid, Posix.Signal.TERM) == 0) { + return true; + } + return false; + } + + private new bool get_children_pids () { + var process_provider = ProcessProvider.get_default (); + string ? children_content = process_provider.pids_children.get (this.stat.pid); + + var splitted_children_pids = children_content.split (" "); + foreach (var child in splitted_children_pids) { + this.children.add (int.parse (child)); + } + return true; + } + + private new bool parse_io () { + var process_provider = ProcessProvider.get_default (); + string ? io_stats = process_provider.pids_io.get (this.stat.pid); + + if (io_stats == "") return false; + + foreach (string line in io_stats.split ("\n")) { + if (line == "") continue; + var splitted_line = line.split (":"); + switch (splitted_line[0]) { + case "wchar": + io.wchar = uint64.parse (splitted_line[1]); + break; + case "rchar": + io.rchar = uint64.parse (splitted_line[1]); + break; + case "syscr": + io.syscr = uint64.parse (splitted_line[1]); + break; + case "syscw": + io.syscw = uint64.parse (splitted_line[1]); + break; + case "read_bytes": + io.read_bytes = uint64.parse (splitted_line[1]); + break; + case "write_bytes": + io.write_bytes = uint64.parse (splitted_line[1]); + break; + case "cancelled_write_bytes": + io.cancelled_write_bytes = uint64.parse (splitted_line[1]); + break; + default: + warning ("Unknown value in /proc/%d/io", stat.pid); + break; + } + } + + return true; + } + + // Reads the /proc/%pid%/stat file and updates the process with the information therein. + private new bool parse_stat () { + var process_provider = ProcessProvider.get_default (); + string ? stat_contents = process_provider.pids_stat.get (this.stat.pid); + + if (stat_contents == null) return false; + + // debug (stat_contents); + + // Split the contents into an array and parse each value that we care about + + // But first we have to extract the command name, since it might include spaces + // First find the command in stat file. It is inside `(command)` + + /* *INDENT-OFF* */ + Regex regex = /\((.*?)\)/; // vala-lint=space-before-paren, + /* *INDENT-ON* */ + + MatchInfo match_info; + regex.match (stat_contents, 0, out match_info); + string matched_command = match_info.fetch (0); + + // Remove command from stat_contents + stat_contents = stat_contents.replace (matched_command, ""); + + // split the string and extract some stats + var splitted_stat = stat_contents.split (" "); + stat.comm = matched_command[1 : -1]; + + stat.state = splitted_stat[2]; + stat.ppid = int.parse (splitted_stat[3]); + stat.pgrp = int.parse (splitted_stat[4]); + stat.priority = int.parse (splitted_stat[17]); + stat.nice = int.parse (splitted_stat[18]); + stat.num_threads = int.parse (splitted_stat[19]); + + return true; + } + + private new bool parse_statm () { + var process_provider = ProcessProvider.get_default (); + string ? statm_contents = process_provider.pids_stat.get (this.stat.pid); + + if (statm_contents == null) return false; + + var splitted_statm = statm_contents.split (" "); + statm.size = int.parse (splitted_statm[0]); + statm.resident = int.parse (splitted_statm[1]); + statm.shared = int.parse (splitted_statm[2]); + statm.trs = int.parse (splitted_statm[3]); + statm.lrs = int.parse (splitted_statm[4]); + statm.drs = int.parse (splitted_statm[5]); + statm.dt = int.parse (splitted_statm[6]); + + return true; + } + + private new bool get_open_files () { + // try { + // string directory = "/proc/%d/fd".printf (stat.pid); + // Dir dir = Dir.open (directory, 0); + // string ? name = null; + // while ((name = dir.read_name ()) != null) { + // string path = Path.build_filename (directory, name); + + // if (FileUtils.test (path, FileTest.IS_SYMLINK)) { + // string real_path = FileUtils.read_link (path); + //// debug(content); + // open_files_paths.add (real_path); + // } + // } + // } 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 new bool read_cmdline () { + var process_provider = ProcessProvider.get_default (); + string ? cmdline = process_provider.pids_cmdline.get (this.stat.pid); + + + if (cmdline == null) { + return false; + } + + if (cmdline.length <= 0) { + // if cmdline has 0 length we look into stat file + // useful for kworker processes + command = stat.comm; + return true; + } + + command = cmdline; + + return true; + } + + private new void get_usage (uint64 cpu_total, uint64 cpu_last_total) { + // Get CPU usage by process + GTop.ProcTime proc_time; + GTop.get_proc_time (out proc_time, stat.pid); + cpu_percentage = 100 * ((double) (proc_time.rtime - cpu_last_used)) / (cpu_total - cpu_last_total); + cpu_last_used = proc_time.rtime; + + // Making CPU history + if (cpu_percentage_history.size == HISTORY_BUFFER_SIZE) { + cpu_percentage_history.remove_at (0); + } + cpu_percentage_history.add (cpu_percentage); + + // Get memory usage by process + GTop.Memory mem; + GTop.get_mem (out mem); + + GTop.ProcMem proc_mem; + GTop.get_proc_mem (out proc_mem, stat.pid); + mem_usage = (proc_mem.resident - proc_mem.share) / 1024; // in KiB + + // also if it is using X Window Server + if (Gdk.Display.get_default () is Gdk.X11.Display) { + Wnck.ResourceUsage resu = Wnck.ResourceUsage.pid_read (Gdk.Display.get_default (), stat.pid); + mem_usage += (resu.total_bytes_estimate / 1024); + } + + var total_installed_memory = (double) mem.total / 1024; + mem_percentage = (mem_usage / total_installed_memory) * 100; + + // Making RAM history + if (mem_percentage_history.size == HISTORY_BUFFER_SIZE) { + mem_percentage_history.remove_at (0); + } + mem_percentage_history.add (mem_percentage); + } + +} diff --git a/src/meson.build b/src/meson.build index 670dc56c..ec117034 100644 --- a/src/meson.build +++ b/src/meson.build @@ -58,6 +58,8 @@ source_app_files = [ 'Managers/ContainerManager.vala', 'Managers/Container.vala', 'Managers/ProcessProvider.vala', + 'Managers/ProcessWorkaround.vala', + # 'Managers/IProcess.vala', # Services 'Services/Shortcuts.vala',