DPDK patches and discussions
 help / color / mirror / Atom feed
* [PATCH] service: add service maintenance callback
@ 2024-12-30 15:32 Piotr Krzewinski
  0 siblings, 0 replies; only message in thread
From: Piotr Krzewinski @ 2024-12-30 15:32 UTC (permalink / raw)
  To: harry.van.haaren; +Cc: dev, Piotr Krzewinski

Add option to register a callback running on service lcores
along regular services, which gets information about the service loop.
It enables doing maintenance work or power saving during periods when
all registered services are idling.

As an example application that is doing dequeue from multiple
event ports using single service lcore (e.g. using rte dispatcher library)
may want to wait for new events inside the maintenance callback when
there is no work available on ANY of the ports.
This is not possible using non zero dequeue timeout without increasing
latency of work that is scheduled to other event ports.

Signed-off-by: Piotr Krzewinski <piotr.krzewinski@ericsson.com>
---
 app/test/test_service_cores.c | 54 +++++++++++++++++++++++++++++++++++
 lib/eal/common/rte_service.c  | 48 ++++++++++++++++++++++++++-----
 lib/eal/include/rte_service.h | 33 +++++++++++++++++++++
 lib/eal/version.map           |  4 +++
 4 files changed, 132 insertions(+), 7 deletions(-)

diff --git a/app/test/test_service_cores.c b/app/test/test_service_cores.c
index 46ed9a1868..f4abbd2dfe 100644
--- a/app/test/test_service_cores.c
+++ b/app/test/test_service_cores.c
@@ -1011,6 +1011,59 @@ service_may_be_active(void)
 	return unregister_all();
 }
 
+static uint64_t busy_loops;
+static uint64_t idle_loops;
+
+static void maint_callback(bool work_done)
+{
+	if (work_done)
+		busy_loops++;
+	else
+		idle_loops++;
+}
+
+/* test registering and unregistering of service maint callback*/
+static int
+service_maintenance(void)
+{
+	const uint32_t sid = 0;
+	busy_loops = 0;
+	idle_loops = 0;
+	/* expected failure cases */
+	TEST_ASSERT_EQUAL(-EINVAL, rte_service_may_be_active(10000),
+			"Invalid service may be active check did not fail");
+
+	/* start the service */
+	TEST_ASSERT_EQUAL(0, rte_service_runstate_set(sid, 1),
+			"Starting valid service failed");
+	TEST_ASSERT_EQUAL(0, rte_service_lcore_add(slcore_id),
+			"Add service core failed when not in use before");
+	TEST_ASSERT_EQUAL(0, rte_service_map_lcore_set(sid, slcore_id, 1),
+			"Enabling valid service on valid core failed");
+	TEST_ASSERT_EQUAL(0, rte_service_maint_callback_register(maint_callback, slcore_id),
+			"Registering maintenance callback failed");
+	TEST_ASSERT_EQUAL(0, rte_service_lcore_start(slcore_id),
+			"Service core start after add failed");
+
+	/* ensures core really is running the service function */
+	TEST_ASSERT_EQUAL(1, service_lcore_running_check(),
+			"Service core expected to poll service but it didn't");
+
+	/* ensures service maintenance callback received information about work done */
+	TEST_ASSERT(busy_loops > 0, "No busy loops detected");
+	TEST_ASSERT(idle_loops > 0, "No idle loops detected");
+
+	/* stop the service, and wait for not-active with timeout */
+	TEST_ASSERT_EQUAL(0, rte_service_runstate_set(sid, 0),
+			"Error: Service stop returned non-zero");
+	TEST_ASSERT_EQUAL(0, service_ensure_stopped_with_timeout(sid),
+			"Error: Service not stopped after timeout period.");
+	TEST_ASSERT_EQUAL(0, rte_service_maint_callback_unregister(slcore_id),
+			"Unregistering service maintenance callback failed");
+
+	return unregister_all();
+}
+
 /* check service may be active when service is running on a second lcore */
 static int
 service_active_two_cores(void)
@@ -1071,6 +1124,7 @@ static struct unit_test_suite service_tests  = {
 		TEST_CASE_ST(dummy_register, NULL, service_mt_unsafe_poll),
 		TEST_CASE_ST(dummy_register, NULL, service_mt_safe_poll),
 		TEST_CASE_ST(dummy_register, NULL, service_may_be_active),
+		TEST_CASE_ST(dummy_register, NULL, service_maintenance),
 		TEST_CASE_ST(dummy_register, NULL, service_active_two_cores),
 		TEST_CASES_END() /**< NULL terminate unit test array */
 	}
diff --git a/lib/eal/common/rte_service.c b/lib/eal/common/rte_service.c
index dad3150df9..850f9a2fb5 100644
--- a/lib/eal/common/rte_service.c
+++ b/lib/eal/common/rte_service.c
@@ -74,6 +74,7 @@ struct __rte_cache_aligned core_state {
 	RTE_BITSET_DECLARE(service_active_on_lcore, RTE_SERVICE_NUM_MAX);
 	RTE_ATOMIC(uint64_t) loops;
 	RTE_ATOMIC(uint64_t) cycles;
+	rte_maint_func maint_callback;
 	struct service_stats service_stats[RTE_SERVICE_NUM_MAX];
 };
 
@@ -317,6 +318,30 @@ rte_service_component_runstate_set(uint32_t id, uint32_t runstate)
 	return 0;
 }
 
+int32_t
+rte_service_maint_callback_register(rte_maint_func callback, uint32_t lcore)
+{
+	struct core_state *cs =	RTE_LCORE_VAR_LCORE(lcore, lcore_states);
+	if (lcore >= RTE_MAX_LCORE || !cs->is_service_core
+			|| callback == NULL)
+		return -EINVAL;
+
+	cs->maint_callback = callback;
+	return 0;
+}
+
+int32_t
+rte_service_maint_callback_unregister(uint32_t lcore)
+{
+	struct core_state *cs =	RTE_LCORE_VAR_LCORE(lcore, lcore_states);
+	if (lcore >= RTE_MAX_LCORE || !cs->is_service_core
+			|| !cs->maint_callback)
+		return -EINVAL;
+
+	cs->maint_callback = NULL;
+	return 0;
+}
+
 int32_t
 rte_service_runstate_set(uint32_t id, uint32_t runstate)
 {
@@ -378,16 +403,17 @@ service_counter_add(RTE_ATOMIC(uint64_t) *counter, uint64_t operand)
 				  rte_memory_order_relaxed);
 }
 
-static inline void
+static inline int32_t
 service_runner_do_callback(struct rte_service_spec_impl *s,
 			   struct core_state *cs, uint32_t service_idx)
 {
 	rte_eal_trace_service_run_begin(service_idx, rte_lcore_id());
 	void *userdata = s->spec.callback_userdata;
+	int32_t rc;
 
 	if (service_stats_enabled(s)) {
 		uint64_t start = rte_rdtsc();
-		int rc = s->spec.callback(userdata);
+		rc = s->spec.callback(userdata);
 
 		struct service_stats *service_stats =
 			&cs->service_stats[service_idx];
@@ -407,9 +433,10 @@ service_runner_do_callback(struct rte_service_spec_impl *s,
 			service_counter_add(&service_stats->cycles, cycles);
 		}
 	} else {
-		s->spec.callback(userdata);
+		rc = s->spec.callback(userdata);
 	}
 	rte_eal_trace_service_run_end(service_idx, rte_lcore_id());
+	return rc;
 }
 
 
@@ -436,16 +463,17 @@ service_run(uint32_t i, struct core_state *cs, const uint64_t *mapped_services,
 
 	rte_bitset_set(cs->service_active_on_lcore, i);
 
+	int32_t ret;
 	if ((service_mt_safe(s) == 0) && (serialize_mt_unsafe == 1)) {
 		if (!rte_spinlock_trylock(&s->execute_lock))
 			return -EBUSY;
 
-		service_runner_do_callback(s, cs, i);
+		ret = service_runner_do_callback(s, cs, i);
 		rte_spinlock_unlock(&s->execute_lock);
 	} else
-		service_runner_do_callback(s, cs, i);
+		ret = service_runner_do_callback(s, cs, i);
 
-	return 0;
+	return ret;
 }
 
 int32_t
@@ -505,13 +533,19 @@ service_runner_func(void *arg)
 	 */
 	while (rte_atomic_load_explicit(&cs->runstate, rte_memory_order_acquire) ==
 			RUNSTATE_RUNNING) {
+		bool work_done = false;
 		ssize_t id;
 
 		RTE_BITSET_FOREACH_SET(id, cs->mapped_services, RTE_SERVICE_NUM_MAX) {
 			/* return value ignored as no change to code flow */
-			service_run(id, cs, cs->mapped_services, service_get(id), 1);
+			int32_t ret = service_run(id, cs, cs->mapped_services, service_get(id), 1);
+			if ((ret != -EAGAIN) && (ret != -EINVAL) && (ret != -ENOEXEC))
+				work_done = true;
 		}
 
+		if (cs->maint_callback != NULL)
+			cs->maint_callback(work_done);
+
 		rte_atomic_store_explicit(&cs->loops, cs->loops + 1, rte_memory_order_relaxed);
 	}
 
diff --git a/lib/eal/include/rte_service.h b/lib/eal/include/rte_service.h
index 1236fe2dc6..b7eafd4443 100644
--- a/lib/eal/include/rte_service.h
+++ b/lib/eal/include/rte_service.h
@@ -336,6 +336,39 @@ int32_t rte_service_lcore_reset_all(void);
  */
 int32_t rte_service_set_stats_enable(uint32_t id, int32_t enable);
 
+/**
+ * Signature of callback function to run after all services are done
+ *
+ * If registered, service framework will execute the callback
+ * indicating if any of the services run on the core was performing work.
+ * This may be used e.g. for maintenance or power saving when no work done.
+ */
+typedef void (*rte_maint_func)(bool work_done);
+
+/**
+ * Register maintenance callback service.
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * @param callback Function callback to register
+ * @retval 0 Successfully registered the callback.
+ *         -EINVAL Attempted to register an invalid callback or the
+ *          lcore is not registered as service lcore
+ */
+__rte_experimental
+int32_t rte_service_maint_callback_register(rte_maint_func callback, uint32_t lcore);
+
+/**
+ * Uregister maintenance callback service.
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * @param callback Function callback to register
+ * @retval 0 Successfully unregistered the callback.
+ *         -EINVAL Attempted to unregister a callback from a core which
+ *	   is not a service lcore or for which no maint callback was registered
+ */
+__rte_experimental
+int32_t rte_service_maint_callback_unregister(uint32_t lcore);
+
 /**
  * Retrieve the list of currently enabled service cores.
  *
diff --git a/lib/eal/version.map b/lib/eal/version.map
index a20c713eb1..e5355e956e 100644
--- a/lib/eal/version.map
+++ b/lib/eal/version.map
@@ -398,6 +398,10 @@ EXPERIMENTAL {
 	# added in 24.11
 	rte_bitset_to_str;
 	rte_lcore_var_alloc;
+
+	# added in 25.03
+	rte_service_maint_callback_register;
+	rte_service_maint_callback_unregister;
 };
 
 INTERNAL {
-- 
2.34.1


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2024-12-30 15:33 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-12-30 15:32 [PATCH] service: add service maintenance callback Piotr Krzewinski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).