From: Pravin Pathak <[email protected]>

Add dynamic flow migration to automatically rebalance flows across
worker ports when an atomic queue's destination port becomes congested.

When consecutive enqueue attempts to a worker's private atomic ring fail
and exceed SLB_MIGRATION_LOAD_THRESHOLD, migration is initiated. The
PMD inspects the overloaded ring and selects the lightest active flow
not currently in migration cooldown.

To preserve strict per-flow event ordering, migration completes only
after all previously scheduled events for the source port have been
returned (q_enqueued == q_returned). The flow affinity is then remapped
to the least-loaded worker port, and any matching events retained in the
hold buffer are reassigned.

Signed-off-by: Pravin Pathak <[email protected]>
---
 drivers/event/slb/slb_evdev.c |  16 ++-
 drivers/event/slb/slb_evdev.h |  20 ++-
 drivers/event/slb/slb_event.c | 252 ++++++++++++++++++++++++++++++++--
 3 files changed, 273 insertions(+), 15 deletions(-)

diff --git a/drivers/event/slb/slb_evdev.c b/drivers/event/slb/slb_evdev.c
index be0b3630f6..1916b7faa4 100644
--- a/drivers/event/slb/slb_evdev.c
+++ b/drivers/event/slb/slb_evdev.c
@@ -74,8 +74,13 @@ slb_port_setup(struct rte_eventdev *dev, uint8_t port_id,
                .total_dequeues = 0,
                .empty_dequeues = 0,
                .pending_releases = 0,
+               .is_loaded = false,
                .single_linked_q = NULL,
                .single_linked_ring = NULL,
+               .flow_migrations = 0,
+               .migrations_in = 0,
+               .migrations_out = 0,
+               .last_migrated_time = 0,
        };
 
        port->remap_input_q = false;
@@ -208,8 +213,12 @@ slb_queue_setup(struct rte_eventdev *dev, uint8_t queue_id,
                queue->atomic_state->next_port_rr = 0;
                rte_spinlock_init(&queue->atomic_state->refill_lock);
                queue->atomic_state->hold_count = 0;
+               queue->atomic_state->migration.in_progress = false;
+               queue->atomic_state->migrations_count = 0;
                memset(queue->atomic_state->port_stats, 0,
                       sizeof(queue->atomic_state->port_stats));
+               memset(queue->atomic_state->flow_last_migrated, 0,
+                      sizeof(queue->atomic_state->flow_last_migrated));
        } else {
                queue->atomic_state = NULL;
        }
@@ -547,12 +556,16 @@ slb_stop(struct rte_eventdev *dev)
                struct slb_queue *q = &slb->queues[qid];
 
                if (q->schedule_type == RTE_SCHED_TYPE_ATOMIC && 
q->atomic_state != NULL) {
-                       for (int f = 0; f < SLB_ATOMIC_FLOWS; f++)
+                       for (int f = 0; f < SLB_ATOMIC_FLOWS; f++) {
                                rte_atomic_store_explicit(
                                        
&q->atomic_state->flow_map.flows[f].port_id,
                                        -1, rte_memory_order_relaxed);
+                               q->atomic_state->flow_last_migrated[f] = 0;
+                       }
+                       q->atomic_state->migration.in_progress = false;
 
                        for (uint16_t pid = 0; pid < slb->num_ports; pid++) {
+                               q->atomic_state->port_stats[pid].load_counter = 
0;
                                if (q->per_port_rings[pid] != NULL)
                                        slb_drain_ring(dev_id,
                                                       q->per_port_rings[pid],
@@ -568,6 +581,7 @@ slb_stop(struct rte_eventdev *dev)
                struct slb_port *port = &slb->ports[i];
                port->inflight_credits = 0;
                port->credits_drained = false;
+               port->is_loaded = false;
                for (int p = 0; p < SLB_MAX_PRIORITIES; p++)
                        port->next_q[p] = 0;
        }
diff --git a/drivers/event/slb/slb_evdev.h b/drivers/event/slb/slb_evdev.h
index 7a70d5c94c..b1a6de8606 100644
--- a/drivers/event/slb/slb_evdev.h
+++ b/drivers/event/slb/slb_evdev.h
@@ -87,6 +87,8 @@ struct slb_reorder_entry {
 #define SLB_ATOMIC_FLOWS 2048
 #define SLB_ATOMIC_FLOWS_MASK (SLB_ATOMIC_FLOWS - 1)
 #define SLB_HOLD_BUFFER_SIZE (SLB_MAX_PORT_DEQUEUE_DEPTH * 64)
+#define SLB_MIGRATION_LOAD_THRESHOLD 16
+#define SLB_MIGRATION_COOLDOWN_MS 500
 
 /* Per-flow entry for atomic scheduling affinity */
 struct slb_flow {
@@ -100,17 +102,28 @@ struct slb_flow_map {
 
 /* Per-port counters for an atomic queue */
 struct __rte_cache_aligned slb_atomic_port_stats {
+       uint32_t load_counter; /* Incremented when full, decremented when space 
available */
        uint32_t flow_count;   /* Number of active flows mapped to this port */
        uint64_t q_enqueued;   /* Events scheduled into this port's atomic 
port-Q */
        uint64_t q_returned;   /* Events returned (FORWARD/RELEASE) by this 
port */
 };
 
+/* Active migration state for an atomic queue */
+struct slb_migration_state {
+       bool in_progress;
+       uint8_t port_id;       /* Source port being drained */
+       uint32_t flow_idx;     /* Flow hash index being migrated */
+};
+
 /* Dynamically allocated state for atomic queues */
 struct slb_atomic_state {
        struct slb_flow_map flow_map;
        uint32_t next_port_rr;
        rte_spinlock_t refill_lock;
        uint16_t hold_count;
+       struct slb_migration_state migration;
+       uint64_t migrations_count;
+       uint64_t flow_last_migrated[SLB_ATOMIC_FLOWS];
        alignas(RTE_CACHE_LINE_SIZE)
        struct slb_atomic_port_stats port_stats[SLB_MAX_PORTS];
        uint8_t hold_ports[SLB_HOLD_BUFFER_SIZE];
@@ -132,7 +145,8 @@ struct __rte_cache_aligned slb_port {
        bool implicit_release;
        bool credits_drained;
        bool remap_input_q;
-       uint8_t pad_port[4];
+       bool is_loaded; /* Port marked loaded for atomic flow migration */
+       uint8_t pad_port[3];
        struct rte_event_ring *single_linked_ring;
        struct slb_evdev *slb;
 
@@ -149,6 +163,10 @@ struct __rte_cache_aligned slb_port {
        uint16_t dequeue_depth;
        uint16_t enqueue_depth;
        struct slb_queue *single_linked_q;
+       uint64_t flow_migrations;
+       uint64_t migrations_in;
+       uint64_t migrations_out;
+       uint64_t last_migrated_time;
        uint64_t enqueue_calls;
        uint64_t new_enqueued;
        uint64_t forward_enqueued;
diff --git a/drivers/event/slb/slb_event.c b/drivers/event/slb/slb_event.c
index 22f40d72d4..3a0f7f6fde 100644
--- a/drivers/event/slb/slb_event.c
+++ b/drivers/event/slb/slb_event.c
@@ -3,6 +3,7 @@
  */
 
 #include <stdbool.h>
+#include <limits.h>
 #include <rte_bitops.h>
 #include <rte_cycles.h>
 #include <rte_ring_elem.h>
@@ -163,12 +164,204 @@ slb_flow_hash(uint32_t flow_id)
        return (flow_id * 2654435761U) & SLB_ATOMIC_FLOWS_MASK;
 }
 
+static int32_t
+slb_find_lightest_flow_on_port(struct slb_queue *queue, uint8_t port_id,
+                              uint64_t now, uint64_t cooldown_cycles)
+{
+       struct rte_event_ring *ering = queue->per_port_rings[port_id];
+       struct slb_atomic_state *astate = queue->atomic_state;
+       struct rte_ring *r;
+       uint32_t cons;
+       uint32_t prod;
+       uint32_t count;
+       uint16_t counts[SLB_ATOMIC_FLOWS];
+       int32_t best_flow_idx = -1;
+       uint32_t min_events = UINT32_MAX;
+
+       if (unlikely(ering == NULL || astate == NULL))
+               return -1;
+
+       if (astate->port_stats[port_id].flow_count <= 1)
+               return -1;
+
+       r = &ering->r;
+       cons = rte_atomic_load_explicit(&r->cons.tail, 
rte_memory_order_relaxed);
+       prod = rte_atomic_load_explicit(&r->prod.tail, 
rte_memory_order_relaxed);
+       count = prod - cons;
+
+       memset(counts, 0, sizeof(counts));
+       if (count > 0) {
+               struct rte_event *ring_events = (struct rte_event *)(r + 1);
+
+               for (uint32_t i = 0; i < count; i++) {
+                       uint32_t slot = (cons + i) & r->mask;
+                       uint32_t f_idx = 
slb_flow_hash(ring_events[slot].flow_id);
+
+                       counts[f_idx]++;
+               }
+       }
+
+       for (uint32_t f = 0; f < SLB_ATOMIC_FLOWS; f++) {
+               if (rte_atomic_load_explicit(&astate->flow_map.flows[f].port_id,
+                                            rte_memory_order_relaxed) == 
(int32_t)port_id) {
+                       if (astate->flow_last_migrated[f] != 0 &&
+                           (now - astate->flow_last_migrated[f]) < 
cooldown_cycles)
+                               continue;
+
+                       if (counts[f] < min_events) {
+                               min_events = counts[f];
+                               best_flow_idx = (int32_t)f;
+                       }
+               }
+       }
+
+       return best_flow_idx;
+}
+
+static void
+slb_check_and_trigger_migration(struct slb_evdev *slb, struct slb_queue *queue,
+                               struct slb_atomic_state *astate, uint8_t 
full_port_id)
+{
+       struct slb_atomic_port_stats *pstats = 
&astate->port_stats[full_port_id];
+       uint64_t now;
+       uint64_t hz;
+       uint64_t cooldown_cycles;
+       int32_t flow_to_migrate;
+
+       pstats->load_counter++;
+       if (pstats->load_counter < SLB_MIGRATION_LOAD_THRESHOLD)
+               return;
+
+       if (astate->migration.in_progress)
+               return;
+
+       if (slb->ports[full_port_id].is_loaded)
+               return;
+
+       if (pstats->flow_count <= 1)
+               return;
+
+       now = rte_get_timer_cycles();
+       hz = rte_get_timer_hz();
+       cooldown_cycles = (hz * SLB_MIGRATION_COOLDOWN_MS) / 1000;
+
+       if (slb->ports[full_port_id].last_migrated_time != 0 &&
+           (now - slb->ports[full_port_id].last_migrated_time) < 
cooldown_cycles)
+               return;
+
+       flow_to_migrate = slb_find_lightest_flow_on_port(
+               queue, full_port_id, now, cooldown_cycles);
+       if (flow_to_migrate < 0)
+               return;
+
+       pstats->load_counter = 0;
+       slb->ports[full_port_id].is_loaded = true;
+       astate->migration.in_progress = true;
+       astate->migration.port_id = full_port_id;
+       astate->migration.flow_idx = (uint32_t)flow_to_migrate;
+}
+
+static inline int
+slb_find_migration_target_port(struct slb_evdev *slb, struct slb_queue *queue,
+                              struct slb_atomic_state *astate, uint8_t 
mig_port,
+                              uint64_t now, uint64_t cooldown_cycles)
+{
+       uint64_t temp_mask = queue->serving_ports_mask;
+       int target_port = -1;
+       int min_flows = INT_MAX;
+
+       while (temp_mask != 0) {
+               uint8_t cand_port = rte_ctz64(temp_mask);
+               struct slb_port *cp = &slb->ports[cand_port];
+
+               temp_mask &= ~(1ULL << cand_port);
+               if (cand_port == mig_port || cp->is_loaded)
+                       continue;
+
+               if (cp->last_migrated_time == 0 ||
+                   (now - cp->last_migrated_time) >= cooldown_cycles) {
+                       if ((int)astate->port_stats[cand_port].flow_count < 
min_flows) {
+                               min_flows = 
(int)astate->port_stats[cand_port].flow_count;
+                               target_port = cand_port;
+                       }
+               } else if (target_port == -1) {
+                       target_port = cand_port;
+               }
+       }
+       return target_port;
+}
+
+static inline void
+slb_complete_flow_migration(struct slb_evdev *slb, struct slb_atomic_state 
*astate,
+                           uint8_t mig_port, int target_port, uint64_t now)
+{
+       uint32_t f_idx = astate->migration.flow_idx;
+
+       rte_atomic_store_explicit(&astate->flow_map.flows[f_idx].port_id,
+                                 target_port, rte_memory_order_release);
+
+       for (uint16_t h = 0; h < astate->hold_count; h++) {
+               if (astate->hold_ports[h] == mig_port &&
+                   slb_flow_hash(astate->hold_buffer[h].flow_id) == f_idx) {
+                       astate->hold_ports[h] = (uint8_t)target_port;
+               }
+       }
+
+       astate->flow_last_migrated[f_idx] = now;
+       slb->ports[mig_port].last_migrated_time = now;
+       slb->ports[target_port].last_migrated_time = now;
+
+       if (astate->port_stats[mig_port].flow_count > 0)
+               astate->port_stats[mig_port].flow_count--;
+       astate->port_stats[target_port].flow_count++;
+       astate->migrations_count++;
+
+       slb->ports[mig_port].flow_migrations++;
+       slb->ports[mig_port].migrations_out++;
+       slb->ports[target_port].migrations_in++;
+
+       astate->port_stats[mig_port].load_counter = 0;
+       slb->ports[mig_port].is_loaded = false;
+       astate->migration.in_progress = false;
+}
+
+static inline uint8_t
+slb_assign_unmapped_flow_port(struct slb_evdev *slb, struct slb_atomic_state 
*astate,
+                             uint64_t serving_mask, uint16_t num_serving_ports)
+{
+       uint32_t rr_counter = astate->next_port_rr++;
+       int port_offset = rr_counter % num_serving_ports;
+       uint64_t temp_mask = serving_mask;
+       uint8_t assigned_port = 0;
+
+       for (int j = 0; j <= port_offset; j++) {
+               assigned_port = rte_ctz64(temp_mask);
+               temp_mask &= (temp_mask - 1);
+       }
+
+       if (slb->ports[assigned_port].is_loaded) {
+               uint64_t cand_mask = serving_mask;
+
+               while (cand_mask != 0) {
+                       uint8_t cp = rte_ctz64(cand_mask);
+
+                       cand_mask &= (cand_mask - 1);
+                       if (!slb->ports[cp].is_loaded) {
+                               assigned_port = cp;
+                               break;
+                       }
+               }
+       }
+       return assigned_port;
+}
+
 /* If port queue is empty for an atomic queue, pull events from main atomic 
queue to fill
  * up port queues.
  */
 static void
-slb_atomic_refill(struct slb_port *port __rte_unused, struct slb_queue *queue)
+slb_atomic_refill(struct slb_port *port, struct slb_queue *queue)
 {
+       struct slb_evdev *slb = port->slb;
        struct rte_event master_events[SLB_MAX_PORT_DEQUEUE_DEPTH];
        uint16_t n_deq;
        uint16_t i;
@@ -183,6 +376,35 @@ slb_atomic_refill(struct slb_port *port __rte_unused, 
struct slb_queue *queue)
        if (!rte_spinlock_trylock(&astate->refill_lock))
                return;
 
+       /* 0. Detect migration in progress before hold buffer processing */
+       if (astate->migration.in_progress) {
+               uint8_t mig_port = astate->migration.port_id;
+               uint64_t enq_cnt = astate->port_stats[mig_port].q_enqueued;
+               uint64_t ret_cnt = astate->port_stats[mig_port].q_returned;
+
+               if (enq_cnt == ret_cnt) {
+                       uint64_t now = rte_get_timer_cycles();
+                       uint64_t hz = rte_get_timer_hz();
+                       uint64_t cooldown_cycles =
+                               (hz * SLB_MIGRATION_COOLDOWN_MS) / 1000;
+                       int target_port = slb_find_migration_target_port(
+                               slb, queue, astate, mig_port, now,
+                               cooldown_cycles);
+
+                       if (target_port != -1)
+                               slb_complete_flow_migration(
+                                       slb, astate, mig_port, target_port, 
now);
+                       else {
+                               astate->port_stats[mig_port].load_counter = 0;
+                               slb->ports[mig_port].is_loaded = false;
+                               astate->migration.in_progress = false;
+                       }
+               }
+       }
+
+       if (astate->migration.in_progress)
+               full_ports_mask |= (1ULL << astate->migration.port_id);
+
        /* 1. Process Hold Buffer first */
        if (astate->hold_count > 0) {
                uint16_t retained = 0;
@@ -216,6 +438,10 @@ slb_atomic_refill(struct slb_port *port __rte_unused, 
struct slb_queue *queue)
                                &astate->hold_buffer[i], burst_len, NULL);
                        astate->port_stats[dest_port_id].q_enqueued += enqueued;
 
+                       if (enqueued > 0 &&
+                           astate->port_stats[dest_port_id].load_counter > 0)
+                               astate->port_stats[dest_port_id].load_counter--;
+
                        if (enqueued < burst_len) {
                                uint16_t rem = burst_len - enqueued;
                                uint16_t start_idx = i + enqueued;
@@ -230,6 +456,9 @@ slb_atomic_refill(struct slb_port *port __rte_unused, 
struct slb_queue *queue)
                                                rem);
                                }
                                retained += rem;
+                               if (enqueued == 0)
+                                       slb_check_and_trigger_migration(
+                                               slb, queue, astate, 
dest_port_id);
                        }
 
                        i += burst_len;
@@ -274,17 +503,8 @@ slb_atomic_refill(struct slb_port *port __rte_unused, 
struct slb_queue *queue)
                                        rte_memory_order_acquire);
 
                        if (unlikely(dest_port_id == -1)) {
-                               uint32_t rr_counter = astate->next_port_rr++;
-                               int port_offset = rr_counter % 
num_serving_ports;
-                               uint64_t temp_mask = serving_mask;
-                               uint8_t assigned_port = 0;
-
-                               for (int j = 0; j <= port_offset; j++) {
-                                       assigned_port = rte_ctz64(temp_mask);
-                                       temp_mask &= (temp_mask - 1);
-                               }
-
-                               dest_port_id = assigned_port;
+                               dest_port_id = slb_assign_unmapped_flow_port(
+                                       slb, astate, serving_mask, 
num_serving_ports);
                                rte_atomic_store_explicit(
                                        
&astate->flow_map.flows[flow_idx].port_id,
                                        dest_port_id, rte_memory_order_release);
@@ -346,7 +566,10 @@ slb_atomic_refill(struct slb_port *port __rte_unused, 
struct slb_queue *queue)
                                queue->per_port_rings[p], 
&sorted_events[p_start], p_count, NULL);
                        astate->port_stats[p].q_enqueued += enqueued;
 
-                       if (enqueued < p_count) {
+                       if (enqueued == p_count) {
+                               if (unlikely(astate->port_stats[p].load_counter 
> 0))
+                                       astate->port_stats[p].load_counter--;
+                       } else {
                                uint16_t rem = p_count - enqueued;
                                uint16_t space = SLB_HOLD_BUFFER_SIZE - 
astate->hold_count;
                                uint16_t to_copy = RTE_MIN(rem, space);
@@ -359,6 +582,9 @@ slb_atomic_refill(struct slb_port *port __rte_unused, 
struct slb_queue *queue)
                                        
memset(&astate->hold_ports[astate->hold_count], p, to_copy);
                                        astate->hold_count += to_copy;
                                }
+                               if (enqueued == 0)
+                                       slb_check_and_trigger_migration(
+                                               slb, queue, astate, p);
                        }
                }
        }
-- 
2.53.0

Reply via email to