From 865e04512f433b0ac24b1bacc14dc049126d57e8 Mon Sep 17 00:00:00 2001
From: Amin Ben Romdhane <amin.benromdhane@iopsys.eu>
Date: Tue, 8 Apr 2025 12:21:37 +0200
Subject: [PATCH] Add support to sort processes by 'PID', 'Memory' and
 'CPU_Time'

---
 src/processes.c | 217 ++++++++++++++++++++++++++++++++++++++++++++----
 src/sysmngr.c   |   3 +
 2 files changed, 204 insertions(+), 16 deletions(-)

diff --git a/src/processes.c b/src/processes.c
index 251287a..0690671 100644
--- a/src/processes.c
+++ b/src/processes.c
@@ -14,6 +14,7 @@
 
 #include <libbbfdm-api/bbfdm_api.h>
 
+#define MAX_PROCESS_ENTRIES BBF_MAX_OBJECT_INSTANCES
 #define DEFAULT_CPU_NAME "cpu"
 #define DEFAULT_CPU_POLL_INTERVAL "5"
 #define DEFAULT_CPU_NUM_SAMPLES "30"
@@ -24,6 +25,10 @@
 typedef struct process_entry {
 	struct list_head list;
 
+	unsigned long pid_raw;
+	unsigned long vsize_raw;
+	unsigned long cpu_time_raw;
+
 	char command[256];
 	char state[16];
 	char pid[8];
@@ -41,12 +46,20 @@ typedef struct jiffy_counts_t {
 	unsigned long long busy_time;
 } jiffy_counts_t;
 
+typedef enum {
+	SORT_BY_PID,
+	SORT_BY_MEMORY,
+	SORT_BY_CPU_TIME,
+	/* SORT_BY_CPU_Usage */
+} process_sorting_method_t;
+
 typedef struct process_ctx {
 	struct ubus_context *ubus_ctx;
 	struct uloop_timeout instance_timer;
 	struct list_head list;
 	int refresh_interval;
 	int max_entries;
+	process_sorting_method_t sorting_method;
 } process_ctx;
 
 typedef struct cpu_info {
@@ -72,9 +85,46 @@ typedef struct cpu_info {
 static process_ctx g_process_ctx = {0};
 static cpu_info_t g_cpu_info = {0};
 
+static char *ProcessSupportedSortingMethods[] = {"PID", "Memory", "CPU_Time", /* "CPU_Usage", */ NULL};
+
 /*************************************************************
 * COMMON FUNCTIONS
 **************************************************************/
+static process_sorting_method_t str_to_sorting_method(const char *str)
+{
+	if (!str)
+		return SORT_BY_PID;
+
+	if (strcasecmp(str, ProcessSupportedSortingMethods[0]) == 0)
+		return SORT_BY_PID;
+	else if (strcasecmp(str, ProcessSupportedSortingMethods[1]) == 0)
+		return SORT_BY_MEMORY;
+	else if (strcasecmp(str, ProcessSupportedSortingMethods[2]) == 0)
+		return SORT_BY_CPU_TIME;
+	/*
+	 else if (strcasecmp(str, ProcessSupportedSortingMethods[3]) == 0)
+		return SORT_BY_CPU_Usage;
+	*/
+	return SORT_BY_PID;
+}
+
+static const char *sorting_method_to_str(process_sorting_method_t key)
+{
+	switch (key) {
+		case SORT_BY_MEMORY:
+			return ProcessSupportedSortingMethods[1];
+		case SORT_BY_CPU_TIME:
+			return ProcessSupportedSortingMethods[2];
+		/*
+		case SORT_BY_CPU_Usage:
+			return ProcessSupportedSortingMethods[3];
+		*/
+		case SORT_BY_PID:
+		default:
+			return ProcessSupportedSortingMethods[0];
+	}
+}
+
 static void get_jif_val(jiffy_counts_t *p_jif)
 {
 	FILE *file = NULL;
@@ -221,23 +271,37 @@ static int filter_process_dirs(const struct dirent *entry)
 	return 1;
 }
 
-// Comparison function for scandir to sort numerically
-static int numeric_sort(const struct dirent **a, const struct dirent **b)
+static int compare_by_pid(const void *a, const void *b)
+{
+	const process_entry *pa = *(const process_entry **)a;
+	const process_entry *pb = *(const process_entry **)b;
+	return (int)(pb->pid_raw - pa->pid_raw);
+}
+
+static int compare_by_cpu_time(const void *a, const void *b)
+{
+	const process_entry *pa = *(const process_entry **)a;
+	const process_entry *pb = *(const process_entry **)b;
+	return (int)(pb->cpu_time_raw - pa->cpu_time_raw);
+}
+
+static int compare_by_memory(const void *a, const void *b)
 {
-	long num1 = strtol((*a)->d_name, NULL, 10);
-	long num2 = strtol((*b)->d_name, NULL, 10);
-	return num1 - num2;
+	const process_entry *pa = *(const process_entry **)a;
+	const process_entry *pb = *(const process_entry **)b;
+	return (int)(pb->vsize_raw - pa->vsize_raw);
 }
 
 static void init_process_list(void)
 {
 	struct dirent **namelist = NULL;
+	process_entry *process_entries[MAX_PROCESS_ENTRIES];
 	int process_num = 0;
 
 	BBFDM_INFO("Init process list");
 
 	// Scan '/proc' for numeric directories
-	int count = scandir("/proc", &namelist, filter_process_dirs, numeric_sort);
+	int count = scandir("/proc", &namelist, filter_process_dirs, NULL);
 	if (count < 0) {
 		BBFDM_ERR("Error getting process list");
 		return;
@@ -245,19 +309,19 @@ static void init_process_list(void)
 
 	BBFDM_DEBUG("Process Number: '%d'", count);
 
-	for (int i = count - 1; i >= 0; i--) {
+	for (int i = 0; i < count; i++) {
+
+		if (process_num >= MAX_PROCESS_ENTRIES) {
+			FREE(namelist[i]);
+			continue;
+		}
+
 		struct stat stats = {0};
 		char buf[1024], fstat[288], command[256], comm[32];
 		char bsize[32], cputime[32], priori[32], state;
 		unsigned long stime, utime, vsize;
 		int priority, n;
 
-		// Handle max_entries limits (negative means show all)
-		if (g_process_ctx.max_entries >= 0 && process_num >= g_process_ctx.max_entries) {
-			FREE(namelist[i]);
-			continue;
-		}
-
 		snprintf(fstat, sizeof(fstat), "/proc/%s/stat", namelist[i]->d_name);
 		if (stat(fstat, &stats)) {
 			FREE(namelist[i]);
@@ -320,8 +384,6 @@ static void init_process_list(void)
 			continue;
 		}
 
-		list_add_tail(&pentry->list, &g_process_ctx.list);
-
 		DM_STRNCPY(pentry->pid, namelist[i]->d_name, sizeof(pentry->pid));
 		DM_STRNCPY(pentry->command, command, sizeof(pentry->command));
 		DM_STRNCPY(pentry->size, bsize, sizeof(pentry->size));
@@ -329,12 +391,50 @@ static void init_process_list(void)
 		DM_STRNCPY(pentry->cputime, cputime, sizeof(pentry->cputime));
 		DM_STRNCPY(pentry->state, get_proc_state(state), sizeof(pentry->state));
 
-		process_num++;
+		// store raw values
+		pentry->cpu_time_raw = utime + stime;
+		pentry->vsize_raw = vsize;
+		pentry->pid_raw = strtoul(namelist[i]->d_name, NULL, 10);
+
+		process_entries[process_num++] = pentry;
 
 		FREE(namelist[i]);
 	}
 
 	FREE(namelist);
+
+	// Sort entries based on configuration
+	switch (g_process_ctx.sorting_method) {
+		case SORT_BY_MEMORY:
+			qsort(process_entries, process_num, sizeof(process_entry *), compare_by_memory);
+			break;
+		/*
+		case SORT_BY_CPU_Usage:
+			qsort(process_entries, process_num, sizeof(process_entry *), compare_by_cpu_usage);
+			break;
+		*/
+		case SORT_BY_CPU_TIME:
+			qsort(process_entries, process_num, sizeof(process_entry *), compare_by_cpu_time);
+			break;
+		case SORT_BY_PID:
+		default:
+			qsort(process_entries, process_num, sizeof(process_entry *), compare_by_pid);
+			break;
+	}
+
+	// Add sorted entries to the global list
+	int process_count = 0;
+	for (int i = 0; i < process_num; i++) {
+
+		// Handle max_entries limits (negative means show all)
+		if (g_process_ctx.max_entries >= 0 && process_count >= g_process_ctx.max_entries) {
+			FREE(process_entries[i]);
+			continue;
+		}
+
+		list_add_tail(&process_entries[i]->list, &g_process_ctx.list);
+		process_count++;
+	}
 }
 
 static void free_process_list(void)
@@ -358,6 +458,15 @@ static int get_maximum_process_entries(void)
 	return (int)strtol(buf, NULL, 10);
 }
 
+static process_sorting_method_t get_process_sorting_method(void)
+{
+	char buf[32] = {0};
+
+	BBFDM_UCI_GET("sysmngr", "process", "sorting_method", "PID", buf, sizeof(buf));
+
+	return str_to_sorting_method(buf);
+}
+
 static int get_instance_refresh_interval(void)
 {
 	char buf[8] = {0};
@@ -581,8 +690,16 @@ static void free_global_cpu_info(void)
 void sysmngr_process_init(struct ubus_context *ubus_ctx)
 {
 	g_process_ctx.ubus_ctx = ubus_ctx;
+
 	g_process_ctx.max_entries = get_maximum_process_entries();
+	BBFDM_DEBUG("Process Config: |Max Entries| |%d|", g_process_ctx.max_entries);
+
+	g_process_ctx.sorting_method = get_process_sorting_method();
+	BBFDM_DEBUG("Process Config: |Sorting Method| |%s|", sorting_method_to_str(g_process_ctx.sorting_method));
+
 	g_process_ctx.refresh_interval = get_instance_refresh_interval();
+	BBFDM_DEBUG("Process Config: |Refresh Interval| |%d|", g_process_ctx.refresh_interval);
+
 	g_process_ctx.instance_timer.cb = process_refresh_instance_timer;
 	INIT_LIST_HEAD(&g_process_ctx.list);
 
@@ -987,6 +1104,67 @@ static int set_DeviceInfoProcessStatusCPU_FilePath(char *refparam, struct dmctx
 	return 0;
 }
 
+#ifdef SYSMNGR_VENDOR_EXTENSIONS
+static int get_process_supported_sorting_methods(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
+{
+	char buf[64] = {0};
+	int pos = 0;
+
+	for (int i = 0; ProcessSupportedSortingMethods[i] != NULL; i++) {
+		pos += snprintf(&buf[pos], sizeof(buf) - pos, "%s%s",
+						(i > 0) ? "," : "",
+						ProcessSupportedSortingMethods[i]);
+	}
+
+	*value = dmstrdup(buf);
+	return 0;
+}
+
+static int get_process_current_sorting_method(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
+{
+	*value = dmuci_get_option_value_fallback_def("sysmngr", "process", "sorting_method", "PID");
+	return 0;
+}
+
+static int set_process_current_sorting_method(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
+{
+	switch (action)	{
+		case VALUECHECK:
+			if (bbfdm_validate_string(ctx, value, -1, -1, ProcessSupportedSortingMethods, NULL))
+				return FAULT_9007;
+			break;
+		case VALUESET:
+			dmuci_set_value("sysmngr", "process", "sorting_method", value);
+			break;
+	}
+	return 0;
+}
+
+static int get_max_process_entries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
+{
+	*value = dmuci_get_option_value_fallback_def("sysmngr", "process", "max_process_entries", "-1");
+	return 0;
+}
+
+static int set_max_process_entries(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
+{
+	char buf[16] = {0};
+
+	switch (action)	{
+		case VALUECHECK:
+			snprintf(buf, sizeof(buf), "%u", MAX_PROCESS_ENTRIES);
+
+			if (bbfdm_validate_int(ctx, value, RANGE_ARGS{{"-1",buf}}, 1))
+				return FAULT_9007;
+			break;
+		case VALUESET:
+			dmuci_set_value("sysmngr", "process", "max_process_entries", value);
+			break;
+	}
+	return 0;
+}
+#endif
+
 /*************************************************************
  * EVENTS
  *************************************************************/
@@ -1058,5 +1236,12 @@ DMLEAF tDeviceInfoProcessStatusParams[] = {
 {"CPUUsage", &DMREAD, DMT_UNINT, get_process_cpu_usage, NULL, BBFDM_BOTH},
 {"ProcessNumberOfEntries", &DMREAD, DMT_UNINT, get_process_number_of_entries, NULL, BBFDM_BOTH},
 {"CPUNumberOfEntries", &DMREAD, DMT_UNINT, get_DeviceInfoProcessStatus_CPUNumberOfEntries, NULL, BBFDM_BOTH},
+
+#ifdef SYSMNGR_VENDOR_EXTENSIONS
+{CUSTOM_PREFIX"ProcessSupportedSortingMethods", &DMREAD, DMT_STRING, get_process_supported_sorting_methods, NULL, BBFDM_BOTH},
+{CUSTOM_PREFIX"ProcessCurrentSortingMethod", &DMWRITE, DMT_STRING, get_process_current_sorting_method, set_process_current_sorting_method, BBFDM_BOTH},
+{CUSTOM_PREFIX"MaxProcessEntries", &DMWRITE, DMT_INT, get_max_process_entries, set_max_process_entries, BBFDM_BOTH},
+#endif
+
 {0}
 };
diff --git a/src/sysmngr.c b/src/sysmngr.c
index aba72a1..fef2630 100644
--- a/src/sysmngr.c
+++ b/src/sysmngr.c
@@ -54,6 +54,9 @@ static void config_reload_cb(struct ubus_context *ctx, struct ubus_event_handler
 #ifdef SYSMNGR_PROCESS_STATUS
 	sysmngr_cpu_clean();
 	sysmngr_cpu_init();
+
+	sysmngr_process_clean(ctx);
+	sysmngr_process_init(ctx);
 #endif
 
 #ifdef SYSMNGR_MEMORY_STATUS
-- 
GitLab