Intel RDT plugin: add support for groups of PIDs.
test_utils_time \
test_utils_vl_lookup \
test_libcollectd_network_parse \
- test_utils_config_cores
+ test_utils_config_cores \
+ test_utils_proc_pids
TESTS = $(check_PROGRAMS)
src/testing.h
test_utils_config_cores_LDADD = libplugin_mock.la
+test_utils_proc_pids_SOURCES = \
+ src/utils/proc_pids/proc_pids_test.c \
+ src/testing.h
+test_utils_proc_pids_LDADD = libplugin_mock.la
+
libavltree_la_SOURCES = \
src/utils/avltree/avltree.c \
src/utils/avltree/avltree.h
pkglib_LTLIBRARIES += intel_rdt.la
intel_rdt_la_SOURCES = \
src/intel_rdt.c \
+ src/utils/proc_pids/proc_pids.c \
+ src/utils/proc_pids/proc_pids.h \
src/utils/config_cores/config_cores.h \
src/utils/config_cores/config_cores.c
intel_rdt_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBPQOS_CPPFLAGS)
intel_rdt_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBPQOS_LDFLAGS)
intel_rdt_la_LIBADD = $(BUILD_WITH_LIBPQOS_LIBS)
+
+test_plugin_intel_rdt_SOURCES = \
+ src/intel_rdt_test.c \
+ src/utils/config_cores/config_cores.c \
+ src/utils/proc_pids/proc_pids.c \
+ src/daemon/configfile.c \
+ src/daemon/types_list.c
+test_plugin_intel_rdt_CPPFLAGS = $(AM_CPPFLAGS)
+test_plugin_intel_rdt_LDFLAGS = $(PLUGIN_LDFLAGS)
+test_plugin_intel_rdt_LDADD = liboconfig.la libplugin_mock.la
+check_PROGRAMS += test_plugin_intel_rdt
+TESTS += test_plugin_intel_rdt
endif
if BUILD_PLUGIN_INTERFACE
#<Plugin "intel_rdt">
# Cores "0-2"
+# Processes "sshd"
#</Plugin>
#<Plugin interface>
<Plugin "intel_rdt">
Cores "0-2" "3,4,6" "8-10,15"
+ Processes "sshd,qemu-system-x86" "bash"
</Plugin>
B<Options:>
=item B<Cores> I<cores groups>
-All events are reported on a per core basis. Monitoring of the events can be
-configured for group of cores (aggregated statistics). This field defines groups
-of cores on which to monitor supported events. The field is represented as list
-of strings with core group values. Each string represents a list of cores in a
-group. Allowed formats are:
+Monitoring of the events can be configured for group of cores
+(aggregated statistics). This field defines groups of cores on which to monitor
+supported events. The field is represented as list of strings with core group
+values. Each string represents a list of cores in a group. Allowed formats are:
0,1,2,3
0-10,20-18
1,3,5-8,10,0x10-12
If an empty string is provided as value for this field default cores
configuration is applied - a separate group is created for each core.
+=item B<Processes> I<process names groups>
+
+Monitoring of the events can be configured for group of processes
+(aggregated statistics). This field defines groups of processes on which to
+monitor supported events. The field is represented as list of strings with
+process names group values. Each string represents a list of processes in a
+group. Allowed format is:
+ sshd,bash,qemu
+
=back
B<Note:> By default global interval is used to retrieve statistics on monitored
/**
* collectd - src/intel_rdt.c
*
- * Copyright(c) 2016-2018 Intel Corporation. All rights reserved.
+ * Copyright(c) 2016-2019 Intel Corporation. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
*
* Authors:
* Serhiy Pshyk <serhiyx.pshyk@intel.com>
+ * Starzyk, Mateusz <mateuszx.starzyk@intel.com>
+ * Wojciech Andralojc <wojciechx.andralojc@intel.com>
+ * Michał Aleksiński <michalx.aleksinski@intel.com>
**/
#include "collectd.h"
#include "utils/common/common.h"
#include "utils/config_cores/config_cores.h"
-
+#include "utils/proc_pids/proc_pids.h"
#include <pqos.h>
#define RDT_PLUGIN "intel_rdt"
+/* libpqos v2.0 or newer is required for process monitoring*/
+#undef LIBPQOS2
+#if defined(PQOS_VERSION) && PQOS_VERSION >= 20000
+#define LIBPQOS2
+#endif
+
+#define RDT_PLUGIN "intel_rdt"
+
#define RDT_MAX_SOCKETS 8
#define RDT_MAX_SOCKET_CORES 64
#define RDT_MAX_CORES (RDT_MAX_SOCKET_CORES * RDT_MAX_SOCKETS)
+#ifdef LIBPQOS2
+/*
+ * Process name inside comm file is limited to 16 chars.
+ * More info here: http://man7.org/linux/man-pages/man5/proc.5.html
+ */
+#define RDT_MAX_NAMES_GROUPS 64
+#define RDT_PROC_PATH "/proc"
+#endif /* LIBPQOS2 */
+
typedef enum {
UNKNOWN = 0,
CONFIGURATION_ERROR,
} rdt_config_status;
+#ifdef LIBPQOS2
+struct rdt_name_group_s {
+ char *desc;
+ size_t num_names;
+ char **names;
+ proc_pids_t **proc_pids;
+ size_t monitored_pids_count;
+ enum pqos_mon_event events;
+};
+typedef struct rdt_name_group_s rdt_name_group_t;
+#endif /* LIBPQOS2 */
+
struct rdt_ctx_s {
core_groups_list_t cores;
enum pqos_mon_event events[RDT_MAX_CORES];
- struct pqos_mon_data *pgroups[RDT_MAX_CORES];
- size_t num_groups;
+ struct pqos_mon_data *pcgroups[RDT_MAX_CORES];
+#ifdef LIBPQOS2
+ rdt_name_group_t ngroups[RDT_MAX_NAMES_GROUPS];
+ struct pqos_mon_data *pngroups[RDT_MAX_NAMES_GROUPS];
+ size_t num_ngroups;
+ proc_pids_t **proc_pids;
+ size_t num_proc_pids;
+#endif /* LIBPQOS2 */
const struct pqos_cpuinfo *pqos_cpu;
const struct pqos_cap *pqos_cap;
const struct pqos_capability *cap_mon;
static rdt_config_status g_state = UNKNOWN;
+static int g_interface = -1;
+
+static void rdt_submit_derive(const char *cgroup, const char *type,
+ const char *type_instance, derive_t value) {
+ value_list_t vl = VALUE_LIST_INIT;
+
+ vl.values = &(value_t){.derive = value};
+ vl.values_len = 1;
+
+ sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
+ snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
+ sstrncpy(vl.type, type, sizeof(vl.type));
+ if (type_instance)
+ sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
+
+ plugin_dispatch_values(&vl);
+}
+
+static void rdt_submit_gauge(const char *cgroup, const char *type,
+ const char *type_instance, gauge_t value) {
+ value_list_t vl = VALUE_LIST_INIT;
+
+ vl.values = &(value_t){.gauge = value};
+ vl.values_len = 1;
+
+ sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
+ snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
+ sstrncpy(vl.type, type, sizeof(vl.type));
+ if (type_instance)
+ sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
+
+ plugin_dispatch_values(&vl);
+}
+
#if COLLECT_DEBUG
static void rdt_dump_cgroups(void) {
char cores[RDT_MAX_CORES * 4];
return;
DEBUG(RDT_PLUGIN ": Core Groups Dump");
- DEBUG(RDT_PLUGIN ": groups count: %" PRIsz, g_rdt->num_groups);
+ DEBUG(RDT_PLUGIN ": groups count: %" PRIsz, g_rdt->cores.num_cgroups);
- for (size_t i = 0; i < g_rdt->num_groups; i++) {
+ for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
core_group_t *cgroup = g_rdt->cores.cgroups + i;
memset(cores, 0, sizeof(cores));
return;
}
+#ifdef LIBPQOS2
+static void rdt_dump_ngroups(void) {
+
+ char names[DATA_MAX_NAME_LEN];
+
+ if (g_rdt == NULL)
+ return;
+
+ DEBUG(RDT_PLUGIN ": Process Names Groups Dump");
+ DEBUG(RDT_PLUGIN ": groups count: %" PRIsz, g_rdt->num_ngroups);
+
+ for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
+ memset(names, 0, sizeof(names));
+ for (size_t j = 0; j < g_rdt->ngroups[i].num_names; j++)
+ snprintf(names + strlen(names), sizeof(names) - strlen(names) - 1, " %s",
+ g_rdt->ngroups[i].names[j]);
+
+ DEBUG(RDT_PLUGIN ": group[%d]:", (int)i);
+ DEBUG(RDT_PLUGIN ": description: %s", g_rdt->ngroups[i].desc);
+ DEBUG(RDT_PLUGIN ": process names:%s", names);
+ DEBUG(RDT_PLUGIN ": events: 0x%X", g_rdt->ngroups[i].events);
+ }
+
+ return;
+}
+#endif /* LIBPQOS2 */
+
static inline double bytes_to_kb(const double bytes) { return bytes / 1024.0; }
static inline double bytes_to_mb(const double bytes) {
return bytes / (1024.0 * 1024.0);
}
-static void rdt_dump_data(void) {
+static void rdt_dump_cores_data(void) {
+/*
+ * CORE - monitored group of cores
+ * RMID - Resource Monitoring ID associated with the monitored group
+ * This is not available for monitoring with resource control
+ * LLC - last level cache occupancy
+ * MBL - local memory bandwidth
+ * MBR - remote memory bandwidth
+ */
+#ifdef LIBPQOS2
+ if (g_interface == PQOS_INTER_OS_RESCTRL_MON) {
+ DEBUG(RDT_PLUGIN ": CORE LLC[KB] MBL[MB] MBR[MB]");
+ } else {
+ DEBUG(RDT_PLUGIN ": CORE RMID LLC[KB] MBL[MB] MBR[MB]");
+ }
+#else
+ DEBUG(RDT_PLUGIN ": CORE RMID LLC[KB] MBL[MB] MBR[MB]");
+#endif /* LIBPQOS2 */
+
+ for (int i = 0; i < g_rdt->cores.num_cgroups; i++) {
+ const struct pqos_event_values *pv = &g_rdt->pcgroups[i]->values;
+
+ double llc = bytes_to_kb(pv->llc);
+ double mbr = bytes_to_mb(pv->mbm_remote_delta);
+ double mbl = bytes_to_mb(pv->mbm_local_delta);
+#ifdef LIBPQOS2
+ if (g_interface == PQOS_INTER_OS_RESCTRL_MON) {
+ DEBUG(RDT_PLUGIN ": [%s] %10.1f %10.1f %10.1f",
+ g_rdt->cores.cgroups[i].desc, llc, mbl, mbr);
+ } else {
+ DEBUG(RDT_PLUGIN ": [%s] %8u %10.1f %10.1f %10.1f",
+ g_rdt->cores.cgroups[i].desc, g_rdt->pcgroups[i]->poll_ctx[0].rmid,
+ llc, mbl, mbr);
+ }
+#else
+ DEBUG(RDT_PLUGIN ": [%s] %8u %10.1f %10.1f %10.1f",
+ g_rdt->cores.cgroups[i].desc, g_rdt->pcgroups[i]->poll_ctx[0].rmid,
+ llc, mbl, mbr);
+#endif /* LIBPQOS2 */
+ }
+}
+
+#ifdef LIBPQOS2
+static void rdt_dump_pids_data(void) {
/*
- * CORE - monitored group of cores
- * RMID - Resource Monitoring ID associated with the monitored group
+ * NAME - monitored group of processes
+ * PIDs - list of PID numbers in the NAME group
* LLC - last level cache occupancy
* MBL - local memory bandwidth
* MBR - remote memory bandwidth
*/
- DEBUG(" CORE RMID LLC[KB] MBL[MB] MBR[MB]");
- for (size_t i = 0; i < g_rdt->num_groups; i++) {
- const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
+
+ DEBUG(RDT_PLUGIN ": NAME PIDs");
+ char pids[DATA_MAX_NAME_LEN];
+ for (size_t i = 0; i < g_rdt->num_ngroups; ++i) {
+ memset(pids, 0, sizeof(pids));
+ for (size_t j = 0; j < g_rdt->ngroups[i].num_names; ++j) {
+ pids_list_t *list = g_rdt->ngroups[i].proc_pids[j]->curr;
+ for (size_t k = 0; k < list->size; k++)
+ snprintf(pids + strlen(pids), sizeof(pids) - strlen(pids) - 1, " %u",
+ list->pids[k]);
+ }
+ DEBUG(RDT_PLUGIN ": [%s] %s", g_rdt->ngroups[i].desc, pids);
+ }
+
+ DEBUG(RDT_PLUGIN ": NAME LLC[KB] MBL[MB] MBR[MB]");
+ for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
+
+ const struct pqos_event_values *pv = &g_rdt->pngroups[i]->values;
double llc = bytes_to_kb(pv->llc);
double mbr = bytes_to_mb(pv->mbm_remote_delta);
double mbl = bytes_to_mb(pv->mbm_local_delta);
- DEBUG(" [%s] %8u %10.1f %10.1f %10.1f", g_rdt->cores.cgroups[i].desc,
- g_rdt->pgroups[i]->poll_ctx[0].rmid, llc, mbl, mbr);
+ DEBUG(RDT_PLUGIN ": [%s] %10.1f %10.1f %10.1f", g_rdt->ngroups[i].desc,
+ llc, mbl, mbr);
+ }
+}
+#endif /* LIBPQOS2 */
+#endif /* COLLECT_DEBUG */
+
+#ifdef LIBPQOS2
+static int isdupstr(const char *names[], const size_t size, const char *name) {
+ for (size_t i = 0; i < size; i++)
+ if (strncmp(names[i], name, (size_t)MAX_PROC_NAME_LEN) == 0)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * NAME
+ * strlisttoarray
+ *
+ * DESCRIPTION
+ * Converts string representing list of strings into array of strings.
+ * Allowed format is:
+ * name,name1,name2,name3
+ *
+ * PARAMETERS
+ * `str_list' String representing list of strings.
+ * `names' Array to put extracted strings into.
+ * `names_num' Variable to put number of extracted strings.
+ *
+ * RETURN VALUE
+ * Number of elements placed into names.
+ */
+static int strlisttoarray(char *str_list, char ***names, size_t *names_num) {
+ char *saveptr = NULL;
+
+ if (str_list == NULL || names == NULL)
+ return -EINVAL;
+
+ if (strstr(str_list, ",,")) {
+ /* strtok ignores empty words between separators.
+ * This condition handles that by rejecting strings
+ * with consecutive seprators */
+ ERROR(RDT_PLUGIN ": Empty process name");
+ return -EINVAL;
+ }
+
+ for (;;) {
+ char *token = strtok_r(str_list, ",", &saveptr);
+ if (token == NULL)
+ break;
+
+ str_list = NULL;
+
+ while (isspace(*token))
+ token++;
+
+ if (*token == '\0')
+ continue;
+
+ if ((isdupstr((const char **)*names, *names_num, token))) {
+ ERROR(RDT_PLUGIN ": Duplicated process name \'%s\' in group \'%s\'",
+ token, str_list);
+ return -EINVAL;
+ } else {
+ if (0 != strarray_add(names, names_num, token)) {
+ ERROR(RDT_PLUGIN ": Error allocating process name string");
+ return -ENOMEM;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * NAME
+ * ngroup_cmp
+ *
+ * DESCRIPTION
+ * Function to compare names in two name groups.
+ *
+ * PARAMETERS
+ * `ng_a' Pointer to name group a.
+ * `ng_b' Pointer to name group b.
+ *
+ * RETURN VALUE
+ * 1 if both groups contain the same names
+ * 0 if none of their names match
+ * -1 if some but not all names match
+ */
+static int ngroup_cmp(const rdt_name_group_t *ng_a,
+ const rdt_name_group_t *ng_b) {
+ unsigned found = 0;
+
+ assert(ng_a != NULL);
+ assert(ng_b != NULL);
+
+ const size_t sz_a = (unsigned)ng_a->num_names;
+ const size_t sz_b = (unsigned)ng_b->num_names;
+ const char **tab_a = (const char **)ng_a->names;
+ const char **tab_b = (const char **)ng_b->names;
+
+ for (size_t i = 0; i < sz_a; i++) {
+ for (size_t j = 0; j < sz_b; j++)
+ if (strncmp(tab_a[i], tab_b[j], (size_t)MAX_PROC_NAME_LEN) == 0)
+ found++;
+ }
+ /* if no names are the same */
+ if (!found)
+ return 0;
+ /* if group contains same names */
+ if (sz_a == sz_b && sz_b == (size_t)found)
+ return 1;
+ /* if not all names are the same */
+ return -1;
+}
+
+/*
+ * NAME
+ * oconfig_to_ngroups
+ *
+ * DESCRIPTION
+ * Function to set the descriptions and names for each process names group.
+ * Takes a config option containing list of strings that are used to set
+ * process group values.
+ *
+ * PARAMETERS
+ * `item' Config option containing process names groups.
+ * `groups' Table of process name groups to set values in.
+ * `max_groups' Maximum number of process name groups allowed.
+ *
+ * RETURN VALUE
+ * On success, the number of name groups set up. On error, appropriate
+ * negative error value.
+ */
+static int oconfig_to_ngroups(const oconfig_item_t *item,
+ rdt_name_group_t *groups,
+ const size_t max_groups) {
+ int index = 0;
+
+ assert(groups != NULL);
+ assert(max_groups > 0);
+ assert(item != NULL);
+
+ for (int j = 0; j < item->values_num; j++) {
+ int ret;
+ char value[DATA_MAX_NAME_LEN];
+
+ if ((item->values[j].value.string == NULL) ||
+ (strlen(item->values[j].value.string) == 0)) {
+ ERROR(RDT_PLUGIN ": Error - empty group");
+ return -EINVAL;
+ }
+
+ sstrncpy(value, item->values[j].value.string, sizeof(value));
+
+ ret = strlisttoarray(value, &groups[index].names, &groups[index].num_names);
+ if (ret != 0 || groups[index].num_names == 0) {
+ ERROR(RDT_PLUGIN ": Error parsing process names group (%s)",
+ item->values[j].value.string);
+ return -EINVAL;
+ }
+
+ /* set group description info */
+ groups[index].desc = sstrdup(item->values[j].value.string);
+ if (groups[index].desc == NULL) {
+ ERROR(RDT_PLUGIN ": Error allocating name group description");
+ return -ENOMEM;
+ }
+
+ groups[index].proc_pids = NULL;
+ groups[index].monitored_pids_count = 0;
+
+ index++;
+
+ if (index >= (const int)max_groups) {
+ WARNING(RDT_PLUGIN ": Too many process names groups configured");
+ return index;
+ }
+ }
+
+ return index;
+}
+
+/*
+ * NAME
+ * rdt_free_ngroups
+ *
+ * DESCRIPTION
+ * Function to deallocate memory allocated for name groups.
+ *
+ * PARAMETERS
+ * `rdt' Pointer to rdt context
+ */
+static void rdt_free_ngroups(rdt_ctx_t *rdt) {
+ for (int i = 0; i < RDT_MAX_NAMES_GROUPS; i++) {
+ if (rdt->ngroups[i].desc)
+ DEBUG(RDT_PLUGIN ": Freeing pids \'%s\' group\'s data...",
+ rdt->ngroups[i].desc);
+ sfree(rdt->ngroups[i].desc);
+ strarray_free(rdt->ngroups[i].names, rdt->ngroups[i].num_names);
+
+ if (rdt->ngroups[i].proc_pids)
+ proc_pids_free(rdt->ngroups[i].proc_pids, rdt->ngroups[i].num_names);
+
+ rdt->ngroups[i].num_names = 0;
+ sfree(rdt->pngroups[i]);
+ }
+ if (rdt->proc_pids)
+ sfree(rdt->proc_pids);
+
+ rdt->num_ngroups = 0;
+}
+
+/*
+ * NAME
+ * rdt_config_ngroups
+ *
+ * DESCRIPTION
+ * Reads name groups configuration.
+ *
+ * PARAMETERS
+ * `rdt` Pointer to rdt context
+ * `item' Config option containing process names groups.
+ *
+ * RETURN VALUE
+ * 0 on success. Negative number on error.
+ */
+static int rdt_config_ngroups(rdt_ctx_t *rdt, const oconfig_item_t *item) {
+ int n = 0;
+ enum pqos_mon_event events = 0;
+
+ if (item == NULL) {
+ DEBUG(RDT_PLUGIN ": ngroups_config: Invalid argument.");
+ return -EINVAL;
+ }
+
+ DEBUG(RDT_PLUGIN ": Process names groups [%d]:", item->values_num);
+ for (int j = 0; j < item->values_num; j++) {
+ if (item->values[j].type != OCONFIG_TYPE_STRING) {
+ ERROR(RDT_PLUGIN
+ ": given process names group value is not a string [idx=%d]",
+ j);
+ return -EINVAL;
+ }
+ DEBUG(RDT_PLUGIN ": [%d]: %s", j, item->values[j].value.string);
+ }
+
+ n = oconfig_to_ngroups(item, rdt->ngroups, RDT_MAX_NAMES_GROUPS);
+ if (n < 0) {
+ rdt_free_ngroups(rdt);
+ ERROR(RDT_PLUGIN ": Error parsing process name groups configuration.");
+ return -EINVAL;
+ }
+
+ /* validate configured process name values */
+ for (int group_idx = 0; group_idx < n; group_idx++) {
+ DEBUG(RDT_PLUGIN ": checking group [%d]: %s", group_idx,
+ rdt->ngroups[group_idx].desc);
+ for (size_t name_idx = 0; name_idx < rdt->ngroups[group_idx].num_names;
+ name_idx++) {
+ DEBUG(RDT_PLUGIN ": checking process name [%zu]: %s", name_idx,
+ rdt->ngroups[group_idx].names[name_idx]);
+ if (!proc_pids_is_name_valid(rdt->ngroups[group_idx].names[name_idx])) {
+ ERROR(RDT_PLUGIN ": Process name group '%s' contains invalid name '%s'",
+ rdt->ngroups[group_idx].desc,
+ rdt->ngroups[group_idx].names[name_idx]);
+ rdt_free_ngroups(rdt);
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (n == 0) {
+ ERROR(RDT_PLUGIN ": Empty process name groups configured.");
+ return -EINVAL;
+ }
+
+ /* Get all available events on this platform */
+ for (unsigned i = 0; i < rdt->cap_mon->u.mon->num_events; i++)
+ events |= rdt->cap_mon->u.mon->events[i].type;
+
+ events &= ~(PQOS_PERF_EVENT_LLC_MISS);
+
+ DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events);
+
+ rdt->num_ngroups = n;
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < i; j++) {
+ int found = ngroup_cmp(&rdt->ngroups[j], &rdt->ngroups[i]);
+ if (found != 0) {
+ rdt_free_ngroups(rdt);
+ ERROR(RDT_PLUGIN
+ ": Cannot monitor same process name in different groups.");
+ return -EINVAL;
+ }
+ }
+
+ rdt->ngroups[i].events = events;
+ rdt->pngroups[i] = calloc(1, sizeof(*rdt->pngroups[i]));
+ if (rdt->pngroups[i] == NULL) {
+ rdt_free_ngroups(rdt);
+ ERROR(RDT_PLUGIN
+ ": Failed to allocate memory for process name monitoring data.");
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * NAME
+ * rdt_refresh_ngroup
+ *
+ * DESCRIPTION
+ * Refresh pids monitored by name group.
+ *
+ * PARAMETERS
+ * `ngroup` Pointer to name group.
+ * `group_mon_data' PQoS monitoring context.
+ *
+ * RETURN VALUE
+ * 0 on success. Negative number on error.
+ */
+static int rdt_refresh_ngroup(rdt_name_group_t *ngroup,
+ struct pqos_mon_data *group_mon_data) {
+
+ int result = 0;
+
+ if (NULL == ngroup)
+ return -1;
+
+ if (NULL == ngroup->proc_pids) {
+ ERROR(RDT_PLUGIN
+ ": rdt_refresh_ngroup: \'%s\' uninitialized process pids array.",
+ ngroup->desc);
+
+ return -1;
+ }
+
+ DEBUG(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\' process names group.",
+ ngroup->desc);
+
+ proc_pids_t **proc_pids = ngroup->proc_pids;
+ pids_list_t added_pids;
+ pids_list_t removed_pids;
+
+ memset(&added_pids, 0, sizeof(added_pids));
+ memset(&removed_pids, 0, sizeof(removed_pids));
+
+ for (size_t i = 0; i < ngroup->num_names; ++i) {
+ int diff_result = pids_list_diff(proc_pids[i], &added_pids, &removed_pids);
+ if (0 != diff_result) {
+ ERROR(RDT_PLUGIN
+ ": rdt_refresh_ngroup: \'%s\'. Error [%d] during PID diff.",
+ ngroup->desc, diff_result);
+ result = -1;
+ goto cleanup;
+ }
+ }
+
+ DEBUG(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\' process names group, added: "
+ "%u, removed: %u.",
+ ngroup->desc, (unsigned)added_pids.size, (unsigned)removed_pids.size);
+
+ if (added_pids.size > 0) {
+
+ /* no pids are monitored for this group yet: start monitoring */
+ if (0 == ngroup->monitored_pids_count) {
+
+ int start_result =
+ pqos_mon_start_pids(added_pids.size, added_pids.pids, ngroup->events,
+ (void *)ngroup->desc, group_mon_data);
+ if (PQOS_RETVAL_OK == start_result) {
+ ngroup->monitored_pids_count = added_pids.size;
+ } else {
+ ERROR(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\'. Error [%d] while "
+ "STARTING pids monitoring",
+ ngroup->desc, start_result);
+ result = -1;
+ goto pqos_error_recovery;
+ }
+
+ } else {
+
+ int add_result =
+ pqos_mon_add_pids(added_pids.size, added_pids.pids, group_mon_data);
+ if (PQOS_RETVAL_OK == add_result)
+ ngroup->monitored_pids_count += added_pids.size;
+ else {
+ ERROR(RDT_PLUGIN
+ ": rdt_refresh_ngroup: \'%s\'. Error [%d] while ADDING pids.",
+ ngroup->desc, add_result);
+ result = -1;
+ goto pqos_error_recovery;
+ }
+ }
+ }
+
+ if (removed_pids.size > 0) {
+
+ /* all pids are removed: stop monitoring */
+ if (removed_pids.size == ngroup->monitored_pids_count) {
+ /* all pids for this group are lost: stop monitoring */
+ int stop_result = pqos_mon_stop(group_mon_data);
+ if (PQOS_RETVAL_OK != stop_result) {
+ ERROR(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\'. Error [%d] while "
+ "STOPPING monitoring",
+ ngroup->desc, stop_result);
+ result = -1;
+ goto pqos_error_recovery;
+ }
+ ngroup->monitored_pids_count = 0;
+ } else {
+ int remove_result = pqos_mon_remove_pids(
+ removed_pids.size, removed_pids.pids, group_mon_data);
+ if (PQOS_RETVAL_OK == remove_result) {
+ ngroup->monitored_pids_count -= removed_pids.size;
+ } else {
+ ERROR(RDT_PLUGIN
+ ": rdt_refresh_ngroup: \'%s\'. Error [%d] while REMOVING pids.",
+ ngroup->desc, remove_result);
+ result = -1;
+ goto pqos_error_recovery;
+ }
+ }
}
+
+ goto cleanup;
+
+pqos_error_recovery:
+ /* Why?
+ * Resources might be temporary unavailable.
+ *
+ * How?
+ * Collectd will halt the reading thread for this
+ * plugin if it returns an error.
+ * Consecutive errors will be increasing the read period
+ * up to 1 day interval.
+ * On pqos error stop monitoring current group
+ * and reset the proc_pids array
+ * monitoring will be restarted on next collectd read cycle
+ */
+ DEBUG(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\' group RESET after error.",
+ ngroup->desc);
+ pqos_mon_stop(group_mon_data);
+ for (size_t i = 0; i < ngroup->num_names; ++i)
+ if (ngroup->proc_pids[i]->curr)
+ ngroup->proc_pids[i]->curr->size = 0;
+
+ ngroup->monitored_pids_count = 0;
+
+cleanup:
+ pids_list_clear(&added_pids);
+ pids_list_clear(&removed_pids);
+
+ return result;
}
+
+/*
+ * NAME
+ * read_pids_data
+ *
+ * DESCRIPTION
+ * Poll monitoring statistics for name groups
+ *
+ * RETURN VALUE
+ * 0 on success. Negative number on error.
+ */
+static int read_pids_data() {
+
+ if (0 == g_rdt->num_ngroups) {
+ DEBUG(RDT_PLUGIN ": read_pids_data: not configured - PIDs read skipped");
+ return 0;
+ }
+
+ DEBUG(RDT_PLUGIN ": read_pids_data: Scanning active groups");
+ struct pqos_mon_data *active_groups[RDT_MAX_NAMES_GROUPS] = {0};
+ size_t active_group_idx = 0;
+ for (size_t pngroups_idx = 0;
+ pngroups_idx < STATIC_ARRAY_SIZE(g_rdt->pngroups); ++pngroups_idx)
+ if (0 != g_rdt->ngroups[pngroups_idx].monitored_pids_count)
+ active_groups[active_group_idx++] = g_rdt->pngroups[pngroups_idx];
+
+ int ret = 0;
+
+ if (0 == active_group_idx) {
+ DEBUG(RDT_PLUGIN ": read_pids_data: no active groups - PIDs read skipped");
+ goto groups_refresh;
+ }
+
+ DEBUG(RDT_PLUGIN ": read_pids_data: PIDs data polling");
+
+ int poll_result = pqos_mon_poll(active_groups, active_group_idx);
+ if (poll_result != PQOS_RETVAL_OK) {
+ ERROR(RDT_PLUGIN ": read_pids_data: Failed to poll monitoring data for "
+ "pids. Error [%d].",
+ poll_result);
+ ret = -poll_result;
+ goto groups_refresh;
+ }
+
+ for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
+ enum pqos_mon_event mbm_events =
+ (PQOS_MON_EVENT_LMEM_BW | PQOS_MON_EVENT_TMEM_BW |
+ PQOS_MON_EVENT_RMEM_BW);
+
+ if (g_rdt->pngroups[i] == NULL ||
+ g_rdt->ngroups[i].monitored_pids_count == 0)
+ continue;
+
+ const struct pqos_event_values *pv = &g_rdt->pngroups[i]->values;
+
+ /* Submit only monitored events data */
+
+ if (g_rdt->ngroups[i].events & PQOS_MON_EVENT_L3_OCCUP)
+ rdt_submit_gauge(g_rdt->ngroups[i].desc, "bytes", "llc", pv->llc);
+
+ if (g_rdt->ngroups[i].events & PQOS_PERF_EVENT_IPC)
+ rdt_submit_gauge(g_rdt->ngroups[i].desc, "ipc", NULL, pv->ipc);
+
+ if (g_rdt->ngroups[i].events & mbm_events) {
+ rdt_submit_derive(g_rdt->ngroups[i].desc, "memory_bandwidth", "local",
+ pv->mbm_local_delta);
+ rdt_submit_derive(g_rdt->ngroups[i].desc, "memory_bandwidth", "remote",
+ pv->mbm_remote_delta);
+ }
+ }
+
+#if COLLECT_DEBUG
+ rdt_dump_pids_data();
#endif /* COLLECT_DEBUG */
+groups_refresh:
+ ret = proc_pids_update(RDT_PROC_PATH, g_rdt->proc_pids, g_rdt->num_proc_pids);
+ if (0 != ret) {
+ ERROR(RDT_PLUGIN ": Initial update of proc pids failed");
+ return ret;
+ }
+
+ for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
+ int refresh_result =
+ rdt_refresh_ngroup(&(g_rdt->ngroups[i]), g_rdt->pngroups[i]);
+
+ if (0 != refresh_result) {
+ ERROR(RDT_PLUGIN ": read_pids_data: NGroup %zu refresh failed. Error: %d",
+ i, refresh_result);
+ if (0 == ret) {
+ /* refresh error will be escalated only if there were no
+ * errors before.
+ */
+ ret = refresh_result;
+ }
+ }
+ }
+
+ assert(ret <= 0);
+ return ret;
+}
+
+/*
+ * NAME
+ * rdt_init_pids_monitoring
+ *
+ * DESCRIPTION
+ * Initialize pids monitoring for all name groups
+ */
+static void rdt_init_pids_monitoring() {
+ for (size_t group_idx = 0; group_idx < g_rdt->num_ngroups; group_idx++) {
+ /*
+ * Each group must have not-null proc_pids array.
+ * Initial refresh is not mandatory for proper
+ * PIDs statistics detection.
+ */
+ rdt_name_group_t *ng = &g_rdt->ngroups[group_idx];
+ int init_result =
+ proc_pids_init((const char **)ng->names, ng->num_names, &ng->proc_pids);
+ if (0 != init_result) {
+ ERROR(RDT_PLUGIN
+ ": Initialization of proc_pids for group %zu failed. Error: %d",
+ group_idx, init_result);
+ continue;
+ }
+
+ /* update global proc_pids table */
+ proc_pids_t **proc_pids = realloc(g_rdt->proc_pids,
+ (g_rdt->num_proc_pids + ng->num_names) *
+ sizeof(*g_rdt->proc_pids));
+ if (NULL == proc_pids) {
+ ERROR(RDT_PLUGIN ": Alloc error\n");
+ continue;
+ }
+
+ for (size_t i = 0; i < ng->num_names; i++)
+ proc_pids[g_rdt->num_proc_pids + i] = ng->proc_pids[i];
+
+ g_rdt->proc_pids = proc_pids;
+ g_rdt->num_proc_pids += ng->num_names;
+ }
+
+ if (g_rdt->num_ngroups > 0) {
+ int update_result =
+ proc_pids_update(RDT_PROC_PATH, g_rdt->proc_pids, g_rdt->num_proc_pids);
+ if (0 != update_result)
+ ERROR(RDT_PLUGIN ": Initial update of proc pids failed");
+ }
+
+ for (size_t group_idx = 0; group_idx < g_rdt->num_ngroups; group_idx++) {
+ int refresh_result = rdt_refresh_ngroup(&(g_rdt->ngroups[group_idx]),
+ g_rdt->pngroups[group_idx]);
+ if (0 != refresh_result)
+ ERROR(RDT_PLUGIN ": Initial refresh of group %zu failed. Error: %d",
+ group_idx, refresh_result);
+ }
+}
+#endif /* LIBPQOS2 */
+/*
+ * NAME
+ * rdt_free_cgroups
+ *
+ * DESCRIPTION
+ * Function to deallocate memory allocated for core groups.
+ */
static void rdt_free_cgroups(void) {
config_cores_cleanup(&g_rdt->cores);
for (int i = 0; i < RDT_MAX_CORES; i++) {
- sfree(g_rdt->pgroups[i]);
+ sfree(g_rdt->pcgroups[i]);
}
+ g_rdt->cores.num_cgroups = 0;
}
static int rdt_default_cgroups(void) {
unsigned num_cores = g_rdt->pqos_cpu->num_cores;
- g_rdt->cores.cgroups = calloc(num_cores, sizeof(*g_rdt->cores.cgroups));
+ g_rdt->cores.cgroups = calloc(num_cores, sizeof(*(g_rdt->cores.cgroups)));
if (g_rdt->cores.cgroups == NULL) {
ERROR(RDT_PLUGIN ": Error allocating core groups array");
return -ENOMEM;
g_rdt->pqos_cpu->num_cores);
DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events);
- g_rdt->num_groups = n;
- for (size_t i = 0; i < n; i++) {
- for (size_t j = 0; j < i; j++) {
+ g_rdt->cores.num_cgroups = n;
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < i; j++) {
int found = 0;
found = config_cores_cmp_cgroups(&g_rdt->cores.cgroups[j],
&g_rdt->cores.cgroups[i]);
}
g_rdt->events[i] = events;
- g_rdt->pgroups[i] = calloc(1, sizeof(*g_rdt->pgroups[i]));
- if (g_rdt->pgroups[i] == NULL) {
+ g_rdt->pcgroups[i] = calloc(1, sizeof(*g_rdt->pcgroups[i]));
+ if (g_rdt->pcgroups[i] == NULL) {
rdt_free_cgroups();
ERROR(RDT_PLUGIN ": Failed to allocate memory for monitoring data.");
return -ENOMEM;
struct pqos_config pqos = {.fd_log = -1,
.callback_log = rdt_pqos_log,
.context_log = NULL,
- .verbose = 0};
+ .verbose = 0,
+#ifdef LIBPQOS2
+ .interface = PQOS_INTER_OS_RESCTRL_MON};
+ DEBUG(RDT_PLUGIN ": Initializing PQoS with RESCTRL interface");
+#else
+ .interface = PQOS_INTER_MSR};
+ DEBUG(RDT_PLUGIN ": Initializing PQoS with MSR interface");
+#endif
ret = pqos_init(&pqos);
+ DEBUG(RDT_PLUGIN ": PQoS initialization result: [%d]", ret);
+
+#ifdef LIBPQOS2
+ if (ret == PQOS_RETVAL_INTER) {
+ pqos.interface = PQOS_INTER_MSR;
+ DEBUG(RDT_PLUGIN ": Initializing PQoS with MSR interface");
+ ret = pqos_init(&pqos);
+ DEBUG(RDT_PLUGIN ": PQoS initialization result: [%d]", ret);
+ }
+#endif
+
if (ret != PQOS_RETVAL_OK) {
ERROR(RDT_PLUGIN ": Error initializing PQoS library!");
goto rdt_preinit_error1;
}
+ g_interface = pqos.interface;
+
ret = pqos_cap_get(&g_rdt->pqos_cap, &g_rdt->pqos_cpu);
if (ret != PQOS_RETVAL_OK) {
ERROR(RDT_PLUGIN ": Error retrieving PQoS capabilities.");
pqos_fini();
rdt_preinit_error1:
-
sfree(g_rdt);
return -1;
reports a failure in configuration and
aborts
*/
- return (0);
+ return 0;
}
for (int i = 0; i < ci->children_num; i++) {
oconfig_item_t *child = ci->children + i;
- if (strcasecmp("Cores", child->key) == 0) {
- if (rdt_config_cgroups(child) != 0) {
+ if (strncasecmp("Cores", child->key, (size_t)strlen("Cores")) == 0) {
+ if (g_rdt->cores.num_cgroups > 0) {
+ ERROR(RDT_PLUGIN
+ ": Configuration parameter \"%s\" can be used only once.",
+ child->key);
g_state = CONFIGURATION_ERROR;
+ } else if (rdt_config_cgroups(child) != 0)
+ g_state = CONFIGURATION_ERROR;
+
+ if (g_state == CONFIGURATION_ERROR)
/* if we return -1 at this point collectd
reports a failure in configuration and
aborts
*/
- return (0);
- }
+ return 0;
#if COLLECT_DEBUG
rdt_dump_cgroups();
#endif /* COLLECT_DEBUG */
+ } else if (strncasecmp("Processes", child->key,
+ (size_t)strlen("Processes")) == 0) {
+#ifdef LIBPQOS2
+ if (g_interface != PQOS_INTER_OS_RESCTRL_MON) {
+ ERROR(RDT_PLUGIN ": Configuration parameter \"%s\" not supported. "
+ "Resctrl monitoring is needed for PIDs monitoring.",
+ child->key);
+ g_state = CONFIGURATION_ERROR;
+ }
+
+ else if (g_rdt->num_ngroups > 0) {
+ ERROR(RDT_PLUGIN
+ ": Configuration parameter \"%s\" can be used only once.",
+ child->key);
+ g_state = CONFIGURATION_ERROR;
+ }
+
+ else if (rdt_config_ngroups(g_rdt, child) != 0)
+ g_state = CONFIGURATION_ERROR;
+
+ if (g_state == CONFIGURATION_ERROR)
+ /* if we return -1 at this point collectd
+ reports a failure in configuration and
+ aborts
+ */
+ return 0;
+
+#if COLLECT_DEBUG
+ rdt_dump_ngroups();
+#endif /* COLLECT_DEBUG */
+#else /* !LIBPQOS2 */
+ ERROR(RDT_PLUGIN ": Configuration parameter \"%s\" not supported, please "
+ "recompile collectd with libpqos version 2.0 or newer.",
+ child->key);
+#endif /* LIBPQOS2 */
} else {
ERROR(RDT_PLUGIN ": Unknown configuration parameter \"%s\".", child->key);
}
return 0;
}
-static void rdt_submit_derive(const char *cgroup, const char *type,
- const char *type_instance, derive_t value) {
- value_list_t vl = VALUE_LIST_INIT;
-
- vl.values = &(value_t){.derive = value};
- vl.values_len = 1;
-
- sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
- snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
- sstrncpy(vl.type, type, sizeof(vl.type));
- if (type_instance)
- sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
-
- plugin_dispatch_values(&vl);
-}
-
-static void rdt_submit_gauge(const char *cgroup, const char *type,
- const char *type_instance, gauge_t value) {
- value_list_t vl = VALUE_LIST_INIT;
-
- vl.values = &(value_t){.gauge = value};
- vl.values_len = 1;
-
- sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
- snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
- sstrncpy(vl.type, type, sizeof(vl.type));
- if (type_instance)
- sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
-
- plugin_dispatch_values(&vl);
-}
-
-static int rdt_read(__attribute__((unused)) user_data_t *ud) {
- int ret;
+static int read_cores_data() {
- if (g_rdt == NULL) {
- ERROR(RDT_PLUGIN ": rdt_read: plugin not initialized.");
- return -EINVAL;
+ if (0 == g_rdt->cores.num_cgroups) {
+ DEBUG(RDT_PLUGIN ": read_cores_data: not configured - Cores read skipped");
+ return 0;
}
+ DEBUG(RDT_PLUGIN ": read_cores_data: Cores data poll");
- ret = pqos_mon_poll(&g_rdt->pgroups[0], (unsigned)g_rdt->num_groups);
+ int ret =
+ pqos_mon_poll(&g_rdt->pcgroups[0], (unsigned)g_rdt->cores.num_cgroups);
if (ret != PQOS_RETVAL_OK) {
- ERROR(RDT_PLUGIN ": Failed to poll monitoring data.");
+ ERROR(RDT_PLUGIN ": read_cores_data: Failed to poll monitoring data for "
+ "cores. Error [%d].",
+ ret);
return -1;
}
-#if COLLECT_DEBUG
- rdt_dump_data();
-#endif /* COLLECT_DEBUG */
-
- for (size_t i = 0; i < g_rdt->num_groups; i++) {
+ for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
core_group_t *cgroup = g_rdt->cores.cgroups + i;
-
enum pqos_mon_event mbm_events =
(PQOS_MON_EVENT_LMEM_BW | PQOS_MON_EVENT_TMEM_BW |
PQOS_MON_EVENT_RMEM_BW);
- const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
+ const struct pqos_event_values *pv = &g_rdt->pcgroups[i]->values;
/* Submit only monitored events data */
}
}
+#if COLLECT_DEBUG
+ rdt_dump_cores_data();
+#endif /* COLLECT_DEBUG */
+
return 0;
}
-static int rdt_init(void) {
- int ret;
+static int rdt_read(__attribute__((unused)) user_data_t *ud) {
- if (g_state == CONFIGURATION_ERROR)
- return -1;
+ if (g_rdt == NULL) {
+ ERROR(RDT_PLUGIN ": rdt_read: plugin not initialized.");
+ return -EINVAL;
+ }
- ret = rdt_preinit();
- if (ret != 0)
- return ret;
+ int cores_read_result = read_cores_data();
+
+#ifdef LIBPQOS2
+ int pids_read_result = read_pids_data();
+#endif /* LIBPQOS2 */
+
+ if (0 != cores_read_result)
+ return cores_read_result;
- /* Start monitoring */
- for (size_t i = 0; i < g_rdt->num_groups; i++) {
+#ifdef LIBPQOS2
+ if (0 != pids_read_result)
+ return pids_read_result;
+#endif /* LIBPQOS2 */
+
+ return 0;
+}
+
+static void rdt_init_cores_monitoring() {
+ for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
core_group_t *cg = g_rdt->cores.cgroups + i;
- ret = pqos_mon_start(cg->num_cores, cg->cores, g_rdt->events[i],
- (void *)cg->desc, g_rdt->pgroups[i]);
+ int mon_start_result =
+ pqos_mon_start(cg->num_cores, cg->cores, g_rdt->events[i],
+ (void *)cg->desc, g_rdt->pcgroups[i]);
+
+ if (mon_start_result != PQOS_RETVAL_OK)
+ ERROR(RDT_PLUGIN
+ ": Error starting cores monitoring group %s (pqos status=%d)",
+ cg->desc, mon_start_result);
+ }
+}
+
+static int rdt_init(void) {
- if (ret != PQOS_RETVAL_OK)
- ERROR(RDT_PLUGIN ": Error starting monitoring group %s (pqos status=%d)",
- cg->desc, ret);
+ if (g_state == CONFIGURATION_ERROR) {
+ if (g_rdt != NULL) {
+ if (g_rdt->cores.num_cgroups > 0)
+ rdt_free_cgroups();
+#ifdef LIBPQOS2
+ if (g_rdt->num_ngroups > 0)
+ rdt_free_ngroups(g_rdt);
+#endif
+ }
+ return -1;
}
+ int rdt_preinint_result = rdt_preinit();
+ if (rdt_preinint_result != 0)
+ return rdt_preinint_result;
+
+ rdt_init_cores_monitoring();
+#ifdef LIBPQOS2
+ rdt_init_pids_monitoring();
+#endif /* LIBPQOS2 */
+
return 0;
}
if (g_rdt == NULL)
return 0;
- /* Stop monitoring */
- for (size_t i = 0; i < g_rdt->num_groups; i++) {
- pqos_mon_stop(g_rdt->pgroups[i]);
+ /* Stop monitoring cores */
+ for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
+ pqos_mon_stop(g_rdt->pcgroups[i]);
}
+/* Stop pids monitoring */
+#ifdef LIBPQOS2
+ for (size_t i = 0; i < g_rdt->num_ngroups; i++)
+ pqos_mon_stop(g_rdt->pngroups[i]);
+#endif
+
ret = pqos_fini();
if (ret != PQOS_RETVAL_OK)
ERROR(RDT_PLUGIN ": Error shutting down PQoS library.");
-
rdt_free_cgroups();
+#ifdef LIBPQOS2
+ rdt_free_ngroups(g_rdt);
+#endif /* LIBPQOS2 */
sfree(g_rdt);
return 0;
--- /dev/null
+#include "intel_rdt.c" /* sic */
+#include "testing.h"
+
+/***************************************************************************
+ * PQOS mocks
+ */
+int pqos_mon_reset(void) { return 0; }
+int pqos_mon_assoc_get(const unsigned lcore, pqos_rmid_t *rmid) { return 0; }
+int pqos_mon_start(const unsigned num_cores, const unsigned *cores,
+ const enum pqos_mon_event event, void *context,
+ struct pqos_mon_data *group) {
+ return 0;
+}
+#if PQOS_VERSION >= 30000
+int pqos_mon_start_pids(const unsigned num_pids, const pid_t *pids,
+ const enum pqos_mon_event event, void *context,
+ struct pqos_mon_data *group) {
+ return 0;
+}
+int pqos_mon_add_pids(const unsigned num_pids, const pid_t *pids,
+ struct pqos_mon_data *group) {
+ return 0;
+}
+int pqos_mon_remove_pids(const unsigned num_pids, const pid_t *pids,
+ struct pqos_mon_data *group) {
+ return 0;
+}
+
+#else
+int pqos_mon_start_pid(const pid_t pids, const enum pqos_mon_event event,
+ void *context, struct pqos_mon_data *group) {
+ return 0;
+}
+#endif
+int pqos_mon_stop(struct pqos_mon_data *group) { return 0; }
+int pqos_mon_poll(struct pqos_mon_data **groups, const unsigned num_groups) {
+ return 0;
+}
+
+#if PQOS_VERSION >= 30000
+int pqos_alloc_reset(const enum pqos_cdp_config l3_cdp_cfg,
+ const enum pqos_cdp_config l2_cdp_cfg,
+ const enum pqos_mba_config mba_cfg) {
+ return 0;
+}
+#elif PQOS_VERSION >= 20000
+int pqos_alloc_reset(const enum pqos_cdp_config l3_cdp_cfg,
+ const enum pqos_cdp_config l2_cdp_cfg) {
+ return 0;
+}
+#else
+int pqos_alloc_reset(const enum pqos_cdp_config l3_cdp_cfg) { return 0; }
+#endif
+int pqos_alloc_assoc_set(const unsigned lcore, const unsigned class_id) {
+ return 0;
+}
+int pqos_alloc_assoc_get(const unsigned lcore, unsigned *class_id) { return 0; }
+int pqos_alloc_assoc_set_pid(const pid_t task, const unsigned class_id) {
+ return 0;
+}
+int pqos_alloc_assoc_get_pid(const pid_t task, unsigned *class_id) { return 0; }
+int pqos_alloc_assign(const unsigned technology, const unsigned *core_array,
+ const unsigned core_num, unsigned *class_id) {
+ return 0;
+}
+int pqos_alloc_release(const unsigned *core_array, const unsigned core_num) {
+ return 0;
+}
+int pqos_alloc_assign_pid(const unsigned technology, const pid_t *task_array,
+ const unsigned task_num, unsigned *class_id) {
+ return 0;
+}
+int pqos_alloc_release_pid(const pid_t *task_array, const unsigned task_num) {
+ return 0;
+}
+int pqos_init(const struct pqos_config *config) { return 0; }
+int pqos_fini(void) { return 0; }
+int pqos_cap_get_type(const struct pqos_cap *cap, const enum pqos_cap_type type,
+ const struct pqos_capability **cap_item) {
+ return 0;
+}
+int pqos_cap_get(const struct pqos_cap **cap, const struct pqos_cpuinfo **cpu) {
+ return 0;
+}
+
+#ifdef LIBPQOS2
+/***************************************************************************
+ * helper functions
+ */
+rdt_ctx_t *stub_rdt_setup() {
+
+ rdt_ctx_t *rdt = calloc(1, sizeof(*rdt));
+ struct pqos_cpuinfo *pqos_cpu = calloc(1, sizeof(*pqos_cpu));
+ struct pqos_cap *pqos_cap = calloc(1, sizeof(*pqos_cap));
+ struct pqos_cap_mon *mon = calloc(1, sizeof(*mon));
+ struct pqos_capability *cap_mon = calloc(1, sizeof(*cap_mon));
+
+ cap_mon->u.mon = mon;
+ rdt->pqos_cap = pqos_cap;
+ rdt->pqos_cpu = pqos_cpu;
+ rdt->cap_mon = cap_mon;
+
+ return rdt;
+}
+
+void stub_rdt_teardown(rdt_ctx_t *rdt) {
+ free(rdt->cap_mon->u.mon);
+ free((void *)rdt->cap_mon);
+ free((void *)rdt->pqos_cpu);
+ free((void *)rdt->pqos_cap);
+ free(rdt);
+}
+
+/***************************************************************************
+ * tests
+ */
+DEF_TEST(rdt_config_ngroups__one_process) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "proc1", .type = OCONFIG_TYPE_STRING},
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_STR(values[0].value.string, rdt->ngroups[0].desc);
+ EXPECT_EQ_INT(1, rdt->num_ngroups);
+
+ /* cleanup */
+ rdt_free_ngroups(rdt);
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__two_groups) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "proc11,proc12,proc13", .type = OCONFIG_TYPE_STRING},
+ {.value.string = "proc21,proc22,proc23", .type = OCONFIG_TYPE_STRING},
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(2, rdt->num_ngroups);
+ EXPECT_EQ_STR("proc11,proc12,proc13", rdt->ngroups[0].desc);
+ EXPECT_EQ_STR("proc21,proc22,proc23", rdt->ngroups[1].desc);
+ EXPECT_EQ_STR("proc11", rdt->ngroups[0].names[0]);
+ EXPECT_EQ_STR("proc12", rdt->ngroups[0].names[1]);
+ EXPECT_EQ_STR("proc13", rdt->ngroups[0].names[2]);
+ EXPECT_EQ_STR("proc21", rdt->ngroups[1].names[0]);
+ EXPECT_EQ_STR("proc22", rdt->ngroups[1].names[1]);
+ EXPECT_EQ_STR("proc23", rdt->ngroups[1].names[2]);
+
+ /* cleanup */
+ rdt_free_ngroups(rdt);
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__too_long_proc_name) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "_seventeen_chars_", .type = OCONFIG_TYPE_STRING},
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(-EINVAL, result);
+
+ /* cleanup */
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__duplicate_proc_name_between_groups) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "proc11,proc12,proc", .type = OCONFIG_TYPE_STRING},
+ {.value.string = "proc21,proc,proc23", .type = OCONFIG_TYPE_STRING},
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(-EINVAL, result);
+
+ /* cleanup */
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__duplicate_proc_name_in_group) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "proc11,proc,proc,proc14", .type = OCONFIG_TYPE_STRING},
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(-EINVAL, result);
+
+ /* cleanup */
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__empty_group) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "proc11,proc12,proc13", .type = OCONFIG_TYPE_STRING},
+ {.value.string = "", .type = OCONFIG_TYPE_STRING},
+
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(-EINVAL, result);
+
+ /* cleanup */
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__empty_proc_name) {
+ /* setup */
+ rdt_ctx_t *rdt = stub_rdt_setup();
+
+ oconfig_value_t values[] = {
+ {.value.string = "proc11,,proc13", .type = OCONFIG_TYPE_STRING},
+ };
+ oconfig_item_t config_item = {
+ .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+ };
+
+ /* check */
+ int result = rdt_config_ngroups(rdt, &config_item);
+ EXPECT_EQ_INT(-EINVAL, result);
+
+ /* cleanup */
+ stub_rdt_teardown(rdt);
+
+ return 0;
+}
+
+int main(void) {
+ RUN_TEST(rdt_config_ngroups__one_process);
+ RUN_TEST(rdt_config_ngroups__two_groups);
+ RUN_TEST(rdt_config_ngroups__too_long_proc_name);
+ RUN_TEST(rdt_config_ngroups__duplicate_proc_name_between_groups);
+ RUN_TEST(rdt_config_ngroups__duplicate_proc_name_in_group);
+ RUN_TEST(rdt_config_ngroups__empty_group);
+ RUN_TEST(rdt_config_ngroups__empty_proc_name);
+ END_TEST;
+}
+
+#else
+DEF_TEST(pqos12_test_stub) {
+ EXPECT_EQ_INT(0, 0);
+ return 0;
+}
+
+int main(void) {
+ RUN_TEST(pqos12_test_stub);
+ END_TEST;
+}
+#endif
--- /dev/null
+/**
+ * collectd - src/utils/proc_pids/proc_pids.c
+ *
+ * Copyright(c) 2018-2019 Intel Corporation. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ * Starzyk, Mateusz <mateuszx.starzyk@intel.com>
+ * Wojciech Andralojc <wojciechx.andralojc@intel.com>
+ * Michał Aleksiński <michalx.aleksinski@intel.com>
+ **/
+
+#include "collectd.h"
+#include "utils/common/common.h"
+#include "utils/proc_pids/proc_pids.h"
+
+#define UTIL_NAME "utils_proc_pids"
+
+void pids_list_free(pids_list_t *list) {
+ assert(list);
+
+ sfree(list->pids);
+ sfree(list);
+}
+
+int proc_pids_is_name_valid(const char *name) {
+
+ if (name != NULL) {
+ unsigned len = strlen(name);
+ if (len > 0 && len <= MAX_PROC_NAME_LEN)
+ return 1;
+ else {
+ DEBUG(UTIL_NAME
+ ": Process name \'%s\' is too long. Max supported len is %d chars.",
+ name, MAX_PROC_NAME_LEN);
+ }
+ }
+
+ return 0;
+}
+
+int pids_list_add_pid(pids_list_t *list, const pid_t pid) {
+ assert(list);
+
+ if (list->allocated == list->size) {
+ size_t new_allocated = list->allocated + 1 + list->allocated / 10;
+ pid_t *new_pids = realloc(list->pids, sizeof(pid_t) * new_allocated);
+
+ if (NULL == new_pids) {
+ ERROR(UTIL_NAME ": Alloc error\n");
+ return -1;
+ }
+
+ list->pids = new_pids;
+ list->allocated = new_allocated;
+ }
+
+ list->pids[list->size] = pid;
+ list->size++;
+
+ return 0;
+}
+
+int pids_list_add_list(pids_list_t *dst, pids_list_t *src) {
+ assert(dst);
+ assert(src);
+
+ if (dst->allocated < dst->size + src->size) {
+ pid_t *new_pids =
+ realloc(dst->pids, sizeof(pid_t) * (dst->size + src->size));
+
+ if (NULL == new_pids) {
+ ERROR(UTIL_NAME ": Alloc error\n");
+ return -1;
+ }
+
+ dst->allocated = dst->size + src->size;
+ dst->pids = new_pids;
+ }
+
+ memcpy(dst->pids + dst->size, src->pids, src->size * sizeof(*(src->pids)));
+ dst->size += src->size;
+
+ return 0;
+}
+
+int pids_list_clear(pids_list_t *list) {
+ assert(list);
+
+ if (list->pids != NULL)
+ sfree(list->pids);
+
+ list->size = 0;
+ list->allocated = 0;
+
+ return 0;
+}
+
+int pids_list_contains_pid(pids_list_t *list, const pid_t pid) {
+ assert(list);
+
+ for (int i = 0; i < list->size; i++)
+ if (list->pids[i] == pid)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * NAME
+ * read_proc_name
+ *
+ * DESCRIPTION
+ * Reads process name from given pid directory.
+ * Strips new-line character (\n).
+ *
+ * PARAMETERS
+ * `procfs_path' Path to systems proc directory (e.g. /proc)
+ * `pid_entry' Dirent for PID directory
+ * `name' Output buffer for process name, recommended proc_comm.
+ * `out_size' Output buffer size, recommended sizeof(proc_comm)
+ *
+ * RETURN VALUE
+ * On success, the number of read bytes (includes stripped \n).
+ * -1 on file open error
+*/
+static int read_proc_name(const char *procfs_path,
+ const struct dirent *pid_entry, char *name,
+ const size_t out_size) {
+ assert(pid_entry);
+ assert(name);
+ assert(out_size);
+ memset(name, 0, out_size);
+
+ const char *comm_file_name = "comm";
+
+ char *path = ssnprintf_alloc("%s/%s/%s", procfs_path, pid_entry->d_name,
+ comm_file_name);
+ if (path == NULL)
+ return -1;
+ FILE *f = fopen(path, "r");
+ if (f == NULL) {
+ ERROR(UTIL_NAME ": Failed to open comm file, error: %d\n", errno);
+ sfree(path);
+ return -1;
+ }
+ size_t read_length = fread(name, sizeof(char), out_size, f);
+ name[out_size - 1] = '\0';
+ fclose(f);
+ sfree(path);
+ /* strip new line ending */
+ char *newline = strchr(name, '\n');
+ if (newline) {
+ *newline = '\0';
+ }
+
+ return read_length;
+}
+
+/*
+ * NAME
+ * get_pid_number
+ *
+ * DESCRIPTION
+ * Gets pid number for given /proc/pid directory entry or
+ * returns error if input directory does not hold PID information.
+ *
+ * PARAMETERS
+ * `entry' Dirent for PID directory
+ * `pid' PID number to be filled
+ *
+ * RETURN VALUE
+ * 0 on success. -1 on error.
+ */
+static int get_pid_number(struct dirent *entry, pid_t *pid) {
+ char *tmp_end; /* used for strtoul error check*/
+
+ if (pid == NULL || entry == NULL)
+ return -1;
+
+ if (entry->d_type != DT_DIR)
+ return -1;
+
+ /* trying to get pid number from directory name*/
+ *pid = strtoul(entry->d_name, &tmp_end, 10);
+ if (*tmp_end != '\0') {
+ return -1; /* conversion failed, not proc-pid */
+ }
+ /* all checks passed, marking as success */
+ return 0;
+}
+
+int proc_pids_init(const char **procs_names_array,
+ const size_t procs_names_array_size,
+ proc_pids_t **proc_pids[]) {
+
+ proc_pids_t **proc_pids_array;
+ assert(proc_pids);
+ assert(NULL == *proc_pids);
+
+ /* Copy procs names to output array. Initialize pids list with NULL value. */
+ proc_pids_array = calloc(procs_names_array_size, sizeof(*proc_pids_array));
+
+ if (NULL == proc_pids_array)
+ return -1;
+
+ for (size_t i = 0; i < procs_names_array_size; ++i) {
+ proc_pids_array[i] = calloc(1, sizeof(**proc_pids_array));
+ if (NULL == proc_pids_array[i])
+ goto proc_pids_init_error;
+
+ sstrncpy(proc_pids_array[i]->process_name, procs_names_array[i],
+ STATIC_ARRAY_SIZE(proc_pids_array[i]->process_name));
+ proc_pids_array[i]->prev = NULL;
+ proc_pids_array[i]->curr = NULL;
+ }
+
+ *proc_pids = proc_pids_array;
+
+ return 0;
+proc_pids_init_error:
+ if (NULL != proc_pids_array) {
+ for (size_t i = 0; i < procs_names_array_size; ++i) {
+ free(proc_pids_array[i]);
+ }
+ free(proc_pids_array);
+ }
+ return -1;
+}
+
+static void swap_proc_pids(proc_pids_t **proc_pids, size_t proc_pids_num) {
+ for (size_t i = 0; i < proc_pids_num; i++) {
+ pids_list_t *swap = proc_pids[i]->prev;
+ proc_pids[i]->prev = proc_pids[i]->curr;
+ proc_pids[i]->curr = swap;
+ }
+}
+
+int proc_pids_update(const char *procfs_path, proc_pids_t **proc_pids,
+ size_t proc_pids_num) {
+ assert(procfs_path);
+ assert(proc_pids);
+
+ DIR *proc_dir = opendir(procfs_path);
+ if (proc_dir == NULL) {
+ ERROR(UTIL_NAME ": Could not open %s directory, error: %d", procfs_path,
+ errno);
+ return -1;
+ }
+
+ swap_proc_pids(proc_pids, proc_pids_num);
+
+ for (size_t i = 0; i < proc_pids_num; i++) {
+ if (NULL == proc_pids[i]->curr)
+ proc_pids[i]->curr = calloc(1, sizeof(*(proc_pids[i]->curr)));
+
+ if (NULL == proc_pids[i]->curr) {
+ ERROR(UTIL_NAME ": Alloc error\n");
+ goto update_error;
+ }
+
+ proc_pids[i]->curr->size = 0;
+ }
+
+ /* Go through procfs and find PIDS and their comms */
+ struct dirent *entry;
+ while ((entry = readdir(proc_dir)) != NULL) {
+ pid_t pid;
+ int pid_conversion = get_pid_number(entry, &pid);
+ if (pid_conversion < 0)
+ continue;
+
+ proc_comm_t comm;
+ int read_result =
+ read_proc_name(procfs_path, entry, comm, sizeof(proc_comm_t));
+ if (read_result <= 0)
+ continue;
+
+ /* Try to find comm in input procs array */
+ for (size_t i = 0; i < proc_pids_num; ++i) {
+ if (0 ==
+ strncmp(comm, proc_pids[i]->process_name, STATIC_ARRAY_SIZE(comm)))
+ pids_list_add_pid(proc_pids[i]->curr, pid);
+ }
+ }
+
+ int close_result = closedir(proc_dir);
+ if (0 != close_result) {
+ ERROR(UTIL_NAME ": failed to close /proc directory, error: %d", errno);
+ goto update_error;
+ }
+ return 0;
+
+update_error:
+ swap_proc_pids(proc_pids, proc_pids_num);
+ return -1;
+}
+
+int pids_list_diff(proc_pids_t *proc, pids_list_t *added,
+ pids_list_t *removed) {
+ assert(proc);
+ assert(added);
+ assert(removed);
+
+ added->size = 0;
+ removed->size = 0;
+
+ if (NULL == proc->prev || 0 == proc->prev->size) {
+ /* append all PIDs from curr to added*/
+ return pids_list_add_list(added, proc->curr);
+ } else if (NULL == proc->curr || 0 == proc->curr->size) {
+ /* append all PIDs from prev to removed*/
+ return pids_list_add_list(removed, proc->prev);
+ }
+
+ for (int i = 0; i < proc->prev->size; i++)
+ if (0 == pids_list_contains_pid(proc->curr, proc->prev->pids[i])) {
+ int add_result = pids_list_add_pid(removed, proc->prev->pids[i]);
+ if (add_result < 0)
+ return add_result;
+ }
+
+ for (int i = 0; i < proc->curr->size; i++)
+ if (0 == pids_list_contains_pid(proc->prev, proc->curr->pids[i])) {
+ int add_result = pids_list_add_pid(added, proc->curr->pids[i]);
+ if (add_result < 0)
+ return add_result;
+ }
+
+ return 0;
+}
+
+int proc_pids_free(proc_pids_t *proc_pids[], size_t proc_pids_num) {
+ for (size_t i = 0; i < proc_pids_num; i++) {
+ if (NULL != proc_pids[i]->curr)
+ pids_list_free(proc_pids[i]->curr);
+ if (NULL != proc_pids[i]->prev)
+ pids_list_free(proc_pids[i]->prev);
+ sfree(proc_pids[i]);
+ }
+ sfree(proc_pids);
+
+ return 0;
+}
--- /dev/null
+/**
+ * collectd - src/utils/proc_pids/proc_pids.h
+ *
+ * Copyright(c) 2018-2019 Intel Corporation. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ * Starzyk, Mateusz <mateuszx.starzyk@intel.com>
+ * Wojciech Andralojc <wojciechx.andralojc@intel.com>
+ * Michał Aleksiński <michalx.aleksinski@intel.com>
+ **/
+
+#include <dirent.h>
+#include <sys/types.h>
+
+/*
+ * Process name inside comm file is limited to 16 chars.
+ * More info here: http://man7.org/linux/man-pages/man5/proc.5.html
+ */
+#define MAX_PROC_NAME_LEN 16
+
+/* Helper typedef for process name array
+ * Extra 1 char is added for string null termination.
+ */
+typedef char proc_comm_t[MAX_PROC_NAME_LEN + 1];
+
+/* List of pids. */
+typedef struct pids_list_s {
+ pid_t *pids;
+ size_t size;
+ size_t allocated;
+} pids_list_t;
+
+/* Holds process name and list of pids assigned to that name */
+typedef struct proc_pids_s {
+ proc_comm_t process_name;
+ pids_list_t *prev;
+ pids_list_t *curr;
+} proc_pids_t;
+
+/*
+ * NAME
+ * pids_list_free
+ *
+ * DESCRIPTION
+ * Free all elements of given pids list
+ *
+ * PARAMETERS
+ * `list' Head of target pids_list.
+ */
+void pids_list_free(pids_list_t *list);
+
+/*
+ * NAME
+ * pids_list_add_pid
+ *
+ * DESCRIPTION
+ * Adds pid at the end of the pids array.
+ * Reallocates memory for new pid element, it is up to user to free it.
+ *
+ * PARAMETERS
+ * `list' Target pids_list.
+ * `pid' Pid to be added.
+ *
+ * RETURN VALUE
+ * On success, returns 0.
+ * -1 on memory allocation error.
+ */
+int pids_list_add_pid(pids_list_t *list, const pid_t pid);
+
+/*
+ * NAME
+ * pids_list_clear
+ *
+ * DESCRIPTION
+ * Remove all pids from the list
+ *
+ * PARAMETERS
+ * `list' Target pids_list.
+ *
+ * RETURN VALUE
+ * On success, return 0
+ */
+int pids_list_clear(pids_list_t *list);
+
+/*
+ * NAME
+ * pids_list_add_list
+ *
+ * DESCRIPTION
+ * Adds pids list at the end of the pids list.
+ * Allocates memory for new pid elements, it is up to user to free it.
+ *
+ * PARAMETERS
+ * `dst' Target PIDs list.
+ * `src' Source PIDs list.
+ *
+ * RETURN VALUE
+ * On success, returns 0.
+ * -1 on memory allocation error.
+ */
+int pids_list_add_list(pids_list_t *dst, pids_list_t *src);
+
+/*
+ * NAME
+ * pids_list_contains_pid
+ *
+ * DESCRIPTION
+ * Tests if pids list contains specific pid.
+ *
+ * PARAMETERS
+ * `list' pids_list to check.
+ * `pid' Pid to be searched for.
+ *
+ * RETURN VALUE
+ * If PID found in list, returns 1,
+ * Otherwise returns 0.
+ */
+int pids_list_contains_pid(pids_list_t *list, const pid_t pid);
+
+/*
+ * NAME
+ * pids_list_diff
+ *
+ * DESCRIPTION
+ * Searches for differences in two given lists
+ *
+ * PARAMETERS
+ * `proc' List of pids
+ * `added' New pids which appeared
+ * `removed' Result array storing pids which disappeared
+ * RETURN VALUE
+ * 0 on success. Negative number on error.
+ */
+int pids_list_diff(proc_pids_t *proc, pids_list_t *added, pids_list_t *removed);
+
+/*
+ * NAME
+ * proc_pids_is_name_valid
+ *
+ * DESCRIPTION
+ * Checks if given string is valid process name.
+ *
+ * PARAMETERS
+ * `name' null-terminated char array
+ *
+ * RETURN VALUE
+ * If given name is a valid process name, returns 1,
+ * Otherwise returns 0.
+ */
+int proc_pids_is_name_valid(const char *name);
+
+/*
+ * NAME
+ * proc_pids_init
+ *
+ * DESCRIPTION
+ * Helper function to properly initialize array of proc_pids.
+ * Allocates memory for proc_pids structs.
+ *
+ * PARAMETERS
+ * `procs_names_array' Array of null-terminated strings with
+ * process' names to be copied to new array
+ * `procs_names_array_size' procs_names_array element count
+ * `proc_pids' Address of pointer, under which new
+ * array of proc_pids will be allocated.
+ * Must be NULL.
+ * RETURN VALUE
+ * 0 on success. Negative number on error:
+ * -1: allocation error
+ */
+int proc_pids_init(const char **procs_names_array,
+ const size_t procs_names_array_size,
+ proc_pids_t **proc_pids[]);
+
+/*
+ * NAME
+ * proc_pids_update
+ *
+ * DESCRIPTION
+ * Updates PIDs matching processes's names.
+ * Searches all PID directories in /proc fs and updates current pids_list.
+ *
+ * PARAMETERS
+ * `procfs_path' Path to systems proc directory (e.g. /proc)
+ * `proc_pids' Array of proc_pids pointers to be updated.
+ * `proc_pids_num' proc_pids element count
+ *
+ * RETURN VALUE
+ * 0 on success. -1 on error.
+ */
+int proc_pids_update(const char *procfs_path, proc_pids_t *proc_pids[],
+ size_t proc_pids_num);
+
+/*
+ * NAME
+ * proc_pids_free
+ *
+ * DESCRIPTION
+ * Releses memory allocatd for proc_pids
+ *
+ * PARAMETERS
+ * `proc_pids' Array of proc_pids
+ * `proc_pids_num' proc_pids element count
+ *
+ * RETURN VALUE
+ * 0 on success. -1 on error.
+ */
+int proc_pids_free(proc_pids_t *proc_pids[], size_t proc_pids_num);
--- /dev/null
+#include "testing.h"
+#include "utils/proc_pids/proc_pids.c" /* sic */
+#include <sys/stat.h>
+
+/***************************************************************************
+ * helper functions
+ */
+
+typedef struct stub_proc_pid {
+ proc_comm_t comm;
+ pid_t pid;
+} stub_proc_pid_t;
+
+static const char *proc_fs = "/tmp/procfs_stub";
+
+/*
+ * NAME
+ * stub_procfs_setup
+ *
+ * DESCRIPTION
+ * Prepares testing environment by creating temporary
+ * PID/comm file structure.
+ *
+ * PARAMETERS
+ * `proc_pids_array' Array of stub_proc_pid_t structs. Represents
+ * which PIDs should hold given process name.
+ * `proc_pids_array_length' Element count of input array.
+ *
+ * RETURN VALUE
+ * 0 on success.
+ * -1 on base dir creation error.
+ * -2 on comm file creation error.
+ * -3 on comm file write error.
+ */
+int stub_procfs_setup(const stub_proc_pid_t *proc_pids_array,
+ const size_t proc_pids_array_length) {
+ if (mkdir(proc_fs, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0)
+ return -1;
+ char path[256];
+
+ for (size_t i = 0; i < proc_pids_array_length; ++i) {
+ memset(path, 0, sizeof(path));
+ snprintf(path, STATIC_ARRAY_SIZE(path), "%s/%d", proc_fs,
+ proc_pids_array[i].pid);
+ mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ strncat(path, "/comm", STATIC_ARRAY_SIZE(path) - strlen(path) - 1);
+
+ FILE *fp = fopen(path, "w");
+ if (!fp)
+ return -2;
+
+ size_t slen = strlen(proc_pids_array[i].comm);
+ size_t wlen = fwrite(proc_pids_array[i].comm, sizeof(char), slen, fp);
+ fclose(fp);
+
+ if (slen != wlen)
+ return -3;
+ }
+ return 0;
+}
+
+/*
+ * NAME
+ * stub_procfs_teardown
+ *
+ * DESCRIPTION
+ * Clears testing environment: removes stub proc files.
+ * NOTE - This function could be implemented by usage of nftw, but this
+ * would require #define _XOPEN_SOURCE 500, which
+ * messes up intel_rdt includes.
+ *
+ * RETURN VALUE
+ * system command result
+ */
+int stub_procfs_teardown() {
+ char cmd[256];
+ sstrncpy(cmd, "rm -rf ", STATIC_ARRAY_SIZE(cmd));
+ strncat(cmd, proc_fs, STATIC_ARRAY_SIZE(cmd) - strlen(cmd) - 1);
+ return system(cmd);
+}
+
+/* Max PID value. More info:
+ * http://web.archive.org/web/20111209081734/http://research.cs.wisc.edu/condor/condorg/linux_scalability.html
+ */
+#define MAX_PID 4194304
+#define MAX_PID_STR "4194304"
+
+/***************************************************************************
+ * tests
+ */
+DEF_TEST(proc_pids_init__on_nullptr) {
+ /* setup */
+ const char *procs_names_array[] = {"proc1", "proc2", "proc3"};
+ const size_t procs_names_array_size = STATIC_ARRAY_SIZE(procs_names_array);
+ proc_pids_t **proc_pids_array = NULL;
+
+ /* check */
+ int result = proc_pids_init(procs_names_array, procs_names_array_size,
+ &proc_pids_array);
+ EXPECT_EQ_INT(0, result);
+ for (size_t i = 0; i < procs_names_array_size; ++i)
+ EXPECT_EQ_STR(procs_names_array[i], proc_pids_array[i]->process_name);
+
+ /* cleanup */
+ proc_pids_free(proc_pids_array, procs_names_array_size);
+ return 0;
+}
+
+DEF_TEST(pid_list_add_pid__empty_list) {
+ /* setup */
+ pids_list_t *proc_pids_instance = calloc(1, sizeof(*proc_pids_instance));
+ pid_t pid = 1234;
+
+ /* check */
+ pids_list_add_pid(proc_pids_instance, pid);
+ EXPECT_EQ_INT(pid, proc_pids_instance->pids[0]);
+
+ /* cleanup */
+ pids_list_free(proc_pids_instance);
+ return 0;
+}
+
+DEF_TEST(pid_list_add_pid__non_empty_list) {
+ /* setup */
+ pids_list_t *proc_pids_instance = calloc(1, sizeof(*proc_pids_instance));
+ pid_t pids[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+
+ /* check */
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids); ++i)
+ pids_list_add_pid(proc_pids_instance, pids[i]);
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids); ++i) {
+ EXPECT_EQ_INT(pids[i], proc_pids_instance->pids[i]);
+ }
+
+ /* cleanup */
+ pids_list_free(proc_pids_instance);
+ return 0;
+}
+
+DEF_TEST(pids_list_add_pids_list__non_empty_lists) {
+ /* setup */
+ pid_t pids_array_1[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+ pid_t pids_array_2[] = {2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007};
+ pids_list_t *pids_list_1 = calloc(1, sizeof(*pids_list_1));
+ pids_list_t *pids_list_2 = calloc(1, sizeof(*pids_list_2));
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array_1); ++i) {
+ pids_list_add_pid(pids_list_1, pids_array_1[i]);
+ pids_list_add_pid(pids_list_2, pids_array_2[i]);
+ }
+
+ /* check */
+ int result = pids_list_add_list(pids_list_1, pids_list_2);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(STATIC_ARRAY_SIZE(pids_array_2) +
+ STATIC_ARRAY_SIZE(pids_array_1),
+ pids_list_1->size);
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array_1); ++i) {
+ EXPECT_EQ_INT(1, pids_list_contains_pid(pids_list_1, pids_array_1[i]));
+ EXPECT_EQ_INT(1, pids_list_contains_pid(pids_list_1, pids_array_2[i]));
+ }
+
+ /* setup */
+ pids_list_free(pids_list_1);
+ pids_list_free(pids_list_2);
+ return 0;
+}
+
+DEF_TEST(pids_list_add_pids_list__add_to_empty) {
+ /* setup */
+ pid_t pids_array[] = {2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007};
+ pids_list_t *pids_list_1 = calloc(1, sizeof(*pids_list_1));
+ pids_list_t *pids_list_2 = calloc(1, sizeof(*pids_list_2));
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array); ++i)
+ pids_list_add_pid(pids_list_2, pids_array[i]);
+
+ /* check */
+ int result = pids_list_add_list(pids_list_1, pids_list_2);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(STATIC_ARRAY_SIZE(pids_array), pids_list_1->size);
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array); ++i)
+ EXPECT_EQ_INT(1, pids_list_contains_pid(pids_list_1, pids_array[i]));
+
+ /* setup */
+ pids_list_free(pids_list_1);
+ pids_list_free(pids_list_2);
+ return 0;
+}
+
+DEF_TEST(get_pid_number__valid_dir) {
+ /* setup */
+ struct dirent d;
+ sstrncpy(d.d_name, MAX_PID_STR, STATIC_ARRAY_SIZE(d.d_name));
+ d.d_type = DT_DIR;
+ pid_t pid = 0;
+
+ /* check */
+ int pid_conversion = get_pid_number(&d, &pid);
+
+ EXPECT_EQ_INT(0, pid_conversion);
+ EXPECT_EQ_INT(MAX_PID, pid);
+
+ /* cleanup */
+ return 0;
+}
+
+DEF_TEST(get_pid_number__invalid_dir_name) {
+ /* setup */
+ struct dirent d;
+ sstrncpy(d.d_name, "invalid", STATIC_ARRAY_SIZE(d.d_name));
+ d.d_type = DT_DIR;
+ pid_t pid = 0;
+
+ /* check */
+ int pid_conversion = get_pid_number(&d, &pid);
+
+ EXPECT_EQ_INT(-1, pid_conversion);
+ EXPECT_EQ_INT(0, pid);
+
+ /* cleanup */
+ return 0;
+}
+
+DEF_TEST(read_proc_name__valid_name) {
+ /* setup */
+ stub_proc_pid_t pp_stubs[] = {{"proc1", MAX_PID}};
+ stub_procfs_setup(pp_stubs, STATIC_ARRAY_SIZE(pp_stubs));
+ struct dirent d;
+ sstrncpy(d.d_name, MAX_PID_STR, STATIC_ARRAY_SIZE(d.d_name));
+ d.d_type = DT_DIR;
+
+ /* check */
+ proc_comm_t comm;
+ int read_result = read_proc_name(proc_fs, &d, comm, STATIC_ARRAY_SIZE(comm));
+
+ EXPECT_EQ_INT(strlen(pp_stubs[0].comm), read_result);
+ EXPECT_EQ_STR(pp_stubs[0].comm, comm);
+
+ /* cleanup */
+ stub_procfs_teardown();
+ return 0;
+}
+
+DEF_TEST(read_proc_name__invalid_name) {
+ /* setup */
+ struct dirent d;
+ sstrncpy(d.d_name, MAX_PID_STR, STATIC_ARRAY_SIZE(d.d_name));
+ d.d_type = DT_DIR;
+
+ /* check */
+ proc_comm_t comm;
+ int read_result = read_proc_name(proc_fs, &d, comm, STATIC_ARRAY_SIZE(comm));
+
+ EXPECT_EQ_INT(-1, read_result);
+
+ /* cleanup */
+ return 0;
+}
+
+DEF_TEST(proc_pids_update__one_proc_many_pid) {
+ /* setup */
+ const char *proc_names[] = {"proc1"};
+ stub_proc_pid_t pp_stubs[] = {{"proc1", 1007},
+ {"proc1", 1008},
+ {"proc1", 1009},
+ {"proc2", 1010},
+ {"proc3", 1011}};
+ proc_pids_t **proc_pids = NULL;
+ int result;
+ stub_procfs_setup(pp_stubs, STATIC_ARRAY_SIZE(pp_stubs));
+
+ result =
+ proc_pids_init(proc_names, STATIC_ARRAY_SIZE(proc_names), &proc_pids);
+ EXPECT_EQ_INT(0, result);
+
+ /* check */
+ result = proc_pids_update(proc_fs, proc_pids, STATIC_ARRAY_SIZE(proc_names));
+ EXPECT_EQ_INT(0, result);
+
+ /* proc name check */
+ EXPECT_EQ_STR(proc_names[0], proc_pids[0]->process_name);
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pp_stubs); ++i) {
+ if (0 == strcmp(pp_stubs[i].comm, proc_names[0]))
+ /* check if proc struct has correct pids */
+ EXPECT_EQ_INT(pids_list_contains_pid(proc_pids[0]->curr, pp_stubs[i].pid),
+ 1);
+ else
+ /* check if proc struct has no incorrect pids */
+ EXPECT_EQ_INT(pids_list_contains_pid(proc_pids[0]->curr, pp_stubs[i].pid),
+ 0);
+ }
+
+ /* cleanup */
+ proc_pids_free(proc_pids, STATIC_ARRAY_SIZE(proc_names));
+ stub_procfs_teardown();
+ return 0;
+}
+
+DEF_TEST(proc_pids_update__many_proc_many_pid) {
+ /* setup */
+ const char *proc_names[] = {"proc1", "proc2", "proc3"};
+ stub_proc_pid_t pp_stubs[] = {
+ {"proc1", 1007}, {"proc1", 1008}, {"proc1", 1009}, {"proc2", 2007},
+ {"proc2", 2008}, {"proc2", 2009}, {"proc3", 3007}, {"proc3", 3008},
+ {"proc3", 3009}, {"proc4", 4007}, {"proc4", 4008}, {"proc4", 4009},
+ {"proc5", 5007}, {"proc5", 5008}, {"proc5", 5009}};
+ proc_pids_t **proc_pids = NULL;
+ int result;
+ stub_procfs_setup(pp_stubs, STATIC_ARRAY_SIZE(pp_stubs));
+
+ result =
+ proc_pids_init(proc_names, STATIC_ARRAY_SIZE(proc_names), &proc_pids);
+ EXPECT_EQ_INT(0, result);
+
+ /* check */
+ result = proc_pids_update(proc_fs, proc_pids, STATIC_ARRAY_SIZE(proc_names));
+ EXPECT_EQ_INT(0, result);
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(proc_names); ++i) {
+
+ /* proc name check */
+ EXPECT_EQ_STR(proc_names[i], proc_pids[i]->process_name);
+
+ for (size_t j = 0; j < STATIC_ARRAY_SIZE(pp_stubs); ++j) {
+ if (0 == strcmp(pp_stubs[j].comm, proc_names[i]))
+ /* check if proc struct has correct pids */
+ EXPECT_EQ_INT(
+ pids_list_contains_pid(proc_pids[i]->curr, pp_stubs[j].pid), 1);
+ else
+ /* check if proc struct has no incorrect pids */
+ EXPECT_EQ_INT(
+ pids_list_contains_pid(proc_pids[i]->curr, pp_stubs[j].pid), 0);
+ }
+ }
+
+ /* cleanup */
+ proc_pids_free(proc_pids, STATIC_ARRAY_SIZE(proc_names));
+ stub_procfs_teardown();
+ return 0;
+}
+
+DEF_TEST(pids_list_diff__all_changed) {
+ /* setup */
+ pid_t pids_array_before[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+ pid_t pids_array_after[] = {2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007};
+ proc_pids_t proc_pids;
+ pids_list_t curr;
+ pids_list_t prev;
+
+ prev.pids = pids_array_before;
+ prev.size = STATIC_ARRAY_SIZE(pids_array_before);
+ prev.allocated = prev.size;
+ curr.pids = pids_array_after;
+ curr.size = STATIC_ARRAY_SIZE(pids_array_after);
+ curr.allocated = curr.size;
+ proc_pids.curr = &curr;
+ proc_pids.prev = &prev;
+
+ pids_list_t *new_pids = calloc(1, sizeof(*new_pids));
+ pids_list_t *lost_pids = calloc(1, sizeof(*lost_pids));
+
+ /* check */
+ int result = pids_list_diff(&proc_pids, new_pids, lost_pids);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(STATIC_ARRAY_SIZE(pids_array_before), lost_pids->size);
+ EXPECT_EQ_INT(STATIC_ARRAY_SIZE(pids_array_after), new_pids->size);
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array_before); ++i) {
+ EXPECT_EQ_INT(1, pids_list_contains_pid(new_pids, pids_array_after[i]));
+ EXPECT_EQ_INT(1, pids_list_contains_pid(lost_pids, pids_array_before[i]));
+ }
+
+ /* cleanup */
+ pids_list_free(new_pids);
+ pids_list_free(lost_pids);
+
+ return 0;
+}
+
+DEF_TEST(pids_list_diff__nothing_changed) {
+ /* setup */
+ pid_t pids_array_before[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+ proc_pids_t proc_pids;
+ pids_list_t curr;
+ pids_list_t prev;
+
+ prev.pids = pids_array_before;
+ prev.size = STATIC_ARRAY_SIZE(pids_array_before);
+ prev.allocated = prev.size;
+ curr.pids = pids_array_before;
+ curr.size = STATIC_ARRAY_SIZE(pids_array_before);
+ curr.allocated = curr.size;
+ proc_pids.curr = &curr;
+ proc_pids.prev = &prev;
+
+ pids_list_t *new_pids = calloc(1, sizeof(*new_pids));
+ pids_list_t *lost_pids = calloc(1, sizeof(*lost_pids));
+
+ /* check */
+ int result = pids_list_diff(&proc_pids, new_pids, lost_pids);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(0, lost_pids->size);
+ EXPECT_EQ_INT(0, new_pids->size);
+
+ /* cleanup */
+ pids_list_free(lost_pids);
+ pids_list_free(new_pids);
+
+ return 0;
+}
+
+DEF_TEST(pids_list_diff__one_added) {
+ /* setup */
+ pid_t pids_array_before[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+ pid_t pids_array_after[] = {1000, 1001, 1002, 1003, 1004,
+ 1005, 1006, 1007, 1008};
+ proc_pids_t proc_pids;
+ pids_list_t curr;
+ pids_list_t prev;
+
+ prev.pids = pids_array_before;
+ prev.size = STATIC_ARRAY_SIZE(pids_array_before);
+ prev.allocated = prev.size;
+ curr.pids = pids_array_after;
+ curr.size = STATIC_ARRAY_SIZE(pids_array_after);
+ curr.allocated = curr.size;
+ proc_pids.curr = &curr;
+ proc_pids.prev = &prev;
+
+ pids_list_t *new_pids = calloc(1, sizeof(*new_pids));
+ pids_list_t *lost_pids = calloc(1, sizeof(*lost_pids));
+
+ /* check */
+ int result = pids_list_diff(&proc_pids, new_pids, lost_pids);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(0, lost_pids->size);
+ EXPECT_EQ_INT(1, new_pids->size);
+ EXPECT_EQ_INT(1008, new_pids->pids[0]);
+
+ /* cleanup */
+ pids_list_free(lost_pids);
+ pids_list_free(new_pids);
+
+ return 0;
+}
+
+DEF_TEST(pids_list_diff__one_removed) {
+ /* setup */
+ pid_t pids_array_before[] = {1000, 1001, 1002, 1003, 1004,
+ 1005, 1006, 1007, 1008};
+ pid_t pids_array_after[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+
+ proc_pids_t proc_pids;
+ pids_list_t curr;
+ pids_list_t prev;
+
+ prev.pids = pids_array_before;
+ prev.size = STATIC_ARRAY_SIZE(pids_array_before);
+ prev.allocated = prev.size;
+ curr.pids = pids_array_after;
+ curr.size = STATIC_ARRAY_SIZE(pids_array_after);
+ curr.allocated = curr.size;
+ proc_pids.curr = &curr;
+ proc_pids.prev = &prev;
+
+ pids_list_t *new_pids = calloc(1, sizeof(*new_pids));
+ pids_list_t *lost_pids = calloc(1, sizeof(*lost_pids));
+
+ /* check */
+ int result = pids_list_diff(&proc_pids, new_pids, lost_pids);
+ EXPECT_EQ_INT(0, result);
+ EXPECT_EQ_INT(0, new_pids->size);
+ EXPECT_EQ_INT(1, lost_pids->size);
+ EXPECT_EQ_INT(1008, lost_pids->pids[0]);
+
+ /* cleanup */
+ pids_list_free(lost_pids);
+ pids_list_free(new_pids);
+
+ return 0;
+}
+
+int main(void) {
+ stub_procfs_teardown();
+ RUN_TEST(proc_pids_init__on_nullptr);
+ RUN_TEST(pid_list_add_pid__empty_list);
+ RUN_TEST(pid_list_add_pid__non_empty_list);
+ RUN_TEST(pids_list_add_pids_list__non_empty_lists);
+ RUN_TEST(pids_list_add_pids_list__add_to_empty);
+ RUN_TEST(get_pid_number__valid_dir);
+ RUN_TEST(get_pid_number__invalid_dir_name);
+ RUN_TEST(read_proc_name__valid_name);
+ RUN_TEST(read_proc_name__invalid_name);
+ RUN_TEST(proc_pids_update__one_proc_many_pid);
+ RUN_TEST(proc_pids_update__many_proc_many_pid);
+ RUN_TEST(pids_list_diff__all_changed);
+ RUN_TEST(pids_list_diff__nothing_changed);
+ RUN_TEST(pids_list_diff__one_added);
+ RUN_TEST(pids_list_diff__one_removed);
+ stub_procfs_teardown();
+ END_TEST;
+}