From f7a79a6bfa4789153248edbd01839f4fbe1ccd4a Mon Sep 17 00:00:00 2001 From: Ritvik Uppal Date: Fri, 17 Jul 2026 07:20:04 -0700 Subject: [PATCH] sflow: per-port sample rate, direction, and VPP APIs Adds three stacked VPP patches: per-port sample rate (0012, with per-thread sampling counters), per-port direction (0013), and the per-interface sampling-rate/direction binary APIs (0014). Signed-off-by: Ritvik Uppal --- .../0012-sflow-per-port-sample-rate.patch | 259 ++++++++++++++++++ .../0013-sflow-per-port-direction.patch | 88 ++++++ vppbld/patches/0014-sflow-per-port-api.patch | 174 ++++++++++++ vppbld/patches/series | 7 + 4 files changed, 528 insertions(+) create mode 100644 vppbld/patches/0012-sflow-per-port-sample-rate.patch create mode 100644 vppbld/patches/0013-sflow-per-port-direction.patch create mode 100644 vppbld/patches/0014-sflow-per-port-api.patch diff --git a/vppbld/patches/0012-sflow-per-port-sample-rate.patch b/vppbld/patches/0012-sflow-per-port-sample-rate.patch new file mode 100644 index 00000000..b720d951 --- /dev/null +++ b/vppbld/patches/0012-sflow-per-port-sample-rate.patch @@ -0,0 +1,259 @@ +diff --git a/src/plugins/sflow/node.c b/src/plugins/sflow/node.c +index 3d5363166..3f45c97e2 100644 +--- a/src/plugins/sflow/node.c ++++ b/src/plugins/sflow/node.c +@@ -73,26 +73,35 @@ sflow_node_ingress_egress (vlib_main_t *vm, vlib_node_runtime_t *node, + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + ++ u32 dir = (sample_type == SFLOW_SAMPLETYPE_EGRESS) ? VLIB_TX : VLIB_RX; ++ u32 sw_if = vnet_buffer (vlib_get_buffer (vm, from[0]))->sw_if_index[dir]; ++ vnet_hw_interface_t *hw = vnet_get_sup_hw_interface(smp->vnet_main, sw_if); ++ sflow_per_interface_data_t *sfif = vec_elt_at_index(smp->per_interface_data, hw->hw_if_index); ++ + uword thread_index = os_get_thread_index (); + sflow_per_thread_data_t *sfwk = + vec_elt_at_index (smp->per_thread_data, thread_index); + +- /* note that sfwk->skip==1 means "take the next packet", +- so we never see sfwk->skip==0. */ ++ /* per-thread, per-interface sampling state: each thread keeps its own ++ skip/pool/seed so concurrent threads on the same interface don't race. */ ++ sflow_per_if_sampler_t *smpif = ++ vec_elt_at_index (sfwk->per_interface, hw->hw_if_index); + ++ /* note that smpif->skip==1 means "take the next packet", ++ so we never see smpif->skip==0. */ + u32 pkts = n_left_from; +- if (PREDICT_TRUE (sfwk->skip > pkts)) ++ if (PREDICT_TRUE (smpif->skip > pkts)) + { + /* skip the whole frame-vector */ +- sfwk->skip -= pkts; +- sfwk->pool += pkts; ++ smpif->skip -= pkts; ++ smpif->pool += pkts; + } + else + { +- while (pkts >= sfwk->skip) ++ while (pkts >= smpif->skip) + { + /* reach in to get the one we want. */ +- vlib_buffer_t *bN = vlib_get_buffer (vm, from[sfwk->skip - 1]); ++ vlib_buffer_t *bN = vlib_get_buffer (vm, from[smpif->skip - 1]); + + /* Sample this packet header. */ + u32 hdr = bN->current_length; +@@ -123,7 +132,7 @@ sflow_node_ingress_egress (vlib_main_t *vm, vlib_node_runtime_t *node, + + sflow_sample_t sample = { + .sample_type = sample_type, +- .samplingN = sfwk->smpN, ++ .samplingN = sfif->samplingN ? sfif->samplingN : smp->samplingN, + .input_if_index = if_index, + .output_if_index = if_index_out, + .sampled_packet_size = +@@ -151,14 +160,14 @@ sflow_node_ingress_egress (vlib_main_t *vm, vlib_node_runtime_t *node, + if (PREDICT_FALSE (!sflow_fifo_enqueue (&sfwk->fifo, &sample))) + sfwk->drop++; + +- pkts -= sfwk->skip; +- sfwk->pool += sfwk->skip; +- sfwk->skip = sflow_next_random_skip (sfwk); ++ pkts -= smpif->skip; ++ smpif->pool += smpif->skip; ++ smpif->skip = sflow_next_random_skip_if (smp, sfif, smpif); + } + /* We took a sample (or several) from this frame-vector, but now we are + skipping the rest. */ +- sfwk->skip -= pkts; +- sfwk->pool += pkts; ++ smpif->skip -= pkts; ++ smpif->pool += pkts; + } + + /* the rest of this is boilerplate code just to make sure +diff --git a/src/plugins/sflow/sflow.c b/src/plugins/sflow/sflow.c +index 89529d1ca..908fbc5e1 100644 +--- a/src/plugins/sflow/sflow.c ++++ b/src/plugins/sflow/sflow.c +@@ -515,11 +515,13 @@ read_node_counters (sflow_main_t *smp, sflow_err_ctrs_t *ctrs) + { + sflow_per_thread_data_t *sfwk = + vec_elt_at_index (smp->per_thread_data, thread_index); +- ctrs->counters[SFLOW_ERROR_PROCESSED] += sfwk->pool; + ctrs->counters[SFLOW_ERROR_SAMPLED] += sfwk->smpl; + ctrs->counters[SFLOW_ERROR_DROPPED] += sfwk->drop; + ctrs->counters[SFLOW_ERROR_DIPROCESSED] += sfwk->dsmp; + ctrs->counters[SFLOW_ERROR_DIDROPPED] += sfwk->ddrp; ++ /* pool is now tracked per-thread, per-interface */ ++ for (int i = 0; i < vec_len (sfwk->per_interface); i++) ++ ctrs->counters[SFLOW_ERROR_PROCESSED] += sfwk->per_interface[i].pool; + } + } + +@@ -641,6 +643,42 @@ sflow_set_worker_sampling_state (sflow_main_t *smp) + } + } + ++/* (Re)initialize the per-thread, per-interface sampling state for one ++ interface across all threads. Called from the control path only (interface ++ enable, per-port or global rate change) so the datapath never races on ++ skip/pool/seed. */ ++static void ++sflow_reset_interface_samplers (sflow_main_t *smp, u32 hw_if_index) ++{ ++ sflow_per_interface_data_t *sfif = ++ vec_elt_at_index (smp->per_interface_data, hw_if_index); ++ for (clib_thread_index_t thread_index = 0; ++ thread_index < smp->total_threads; thread_index++) ++ { ++ sflow_per_thread_data_t *sfwk = ++ vec_elt_at_index (smp->per_thread_data, thread_index); ++ vec_validate (sfwk->per_interface, hw_if_index); ++ sflow_per_if_sampler_t *smpif = ++ vec_elt_at_index (sfwk->per_interface, hw_if_index); ++ smpif->pool = 0; ++ /* distinct seed per (thread, interface) so threads don't correlate */ ++ smpif->seed = hw_if_index ^ (thread_index + 1); ++ smpif->skip = sflow_next_random_skip_if (smp, sfif, smpif); ++ } ++} ++ ++static void ++sflow_set_interface_sampling_rate (sflow_main_t *smp) ++{ ++ for (int i = 0; i < vec_len (smp->per_interface_data); i++) ++ { ++ sflow_per_interface_data_t *sfif = ++ vec_elt_at_index (smp->per_interface_data, i); ++ if (sfif && sfif->sflow_enabled && !sfif->samplingN) ++ sflow_reset_interface_samplers (smp, i); ++ } ++} ++ + static void + sflow_sampling_start (sflow_main_t *smp) + { +@@ -713,6 +751,7 @@ sflow_sampling_rate (sflow_main_t *smp, u32 samplingN) + // dynamic change of sampling rate + smp->samplingN = samplingN; + sflow_set_worker_sampling_state (smp); ++ sflow_set_interface_sampling_rate (smp); + } + else + { +@@ -854,14 +893,53 @@ sflow_enable_disable (sflow_main_t *smp, u32 sw_if_index, bool enable_disable) + sfif->hw_if_index = sw->hw_if_index; + sfif->polled = 0; + sfif->sflow_enabled = enable_disable; ++ ++ if (enable_disable) ++ { ++ /* make sure per-thread structures exist, then seed this ++ interface's per-thread samplers before the feature arc starts ++ delivering packets to the workers. */ ++ sflow_set_worker_sampling_state (smp); ++ sflow_reset_interface_samplers (smp, sw->hw_if_index); ++ } ++ + sflow_enable_disable_interface (smp, sfif); + smp->interfacesEnabled += (enable_disable) ? 1 : -1; + } +- + sflow_sampling_start_stop (smp); + return 0; + } + ++int ++sflow_set_sampling_rate_set (sflow_main_t *smp, u32 sw_if_index, u32 samplingN) ++{ ++ vnet_sw_interface_t *sw; ++ ++ /* Utterly wrong? */ ++ if (pool_is_free_index (smp->vnet_main->interface_main.sw_interfaces, ++ sw_if_index)) ++ return VNET_API_ERROR_INVALID_SW_IF_INDEX; ++ ++ /* Not a physical port? */ ++ sw = vnet_get_sw_interface (smp->vnet_main, sw_if_index); ++ if (sw->type != VNET_SW_INTERFACE_TYPE_HARDWARE) ++ return VNET_API_ERROR_INVALID_SW_IF_INDEX; ++ ++ SFLOW_DBG ("sw_if_index=%u, sup_sw_if_index=%u, hw_if_index=%u\n", sw->sw_if_index, sw->sup_sw_if_index, sw->hw_if_index); ++ ++ vec_validate (smp->per_interface_data, sw->hw_if_index); ++ sflow_per_interface_data_t *sfif = vec_elt_at_index (smp->per_interface_data, sw->hw_if_index); ++ ++ sfif->samplingN = samplingN; ++ /* re-seed the per-thread samplers for this interface with the new rate. ++ If sampling hasn't started yet the samplers are seeded on enable. */ ++ if (smp->total_threads) ++ sflow_reset_interface_samplers (smp, sw->hw_if_index); ++ ++ return 0; ++ ++} ++ + static clib_error_t * + sflow_sampling_rate_command_fn (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +diff --git a/src/plugins/sflow/sflow.h b/src/plugins/sflow/sflow.h +index 7e8c0469e..cf90d9cea 100644 +--- a/src/plugins/sflow/sflow.h ++++ b/src/plugins/sflow/sflow.h +@@ -162,6 +162,16 @@ sflow_drop_fifo_dequeue (sflow_drop_fifo_t *fifo, sflow_sample_t *sample) + return true; + } + ++/* per-thread, per-interface sampling counters. Kept per-thread so that ++ multiple worker threads sampling the same interface never share (and race ++ on) skip/pool/seed. Indexed by hw_if_index. */ ++typedef struct ++{ ++ u32 skip; ++ u32 pool; ++ u32 seed; ++} sflow_per_if_sampler_t; ++ + /* private to worker */ + typedef struct + { +@@ -173,6 +183,8 @@ typedef struct + u32 drop; + u32 dsmp; + u32 ddrp; ++ /* per-interface sampling state, indexed by hw_if_index */ ++ sflow_per_if_sampler_t *per_interface; + CLIB_CACHE_LINE_ALIGN_MARK (_fifo); + sflow_fifo_t fifo; + CLIB_CACHE_LINE_ALIGN_MARK (_drop_fifo); +@@ -246,4 +258,15 @@ sflow_next_random_skip (sflow_per_thread_data_t *sfwk) + return (random_u32 (&sfwk->seed) % lim) + 1; + } + ++static inline u32 ++sflow_next_random_skip_if (sflow_main_t *smp, sflow_per_interface_data_t *sfif, ++ sflow_per_if_sampler_t *smpif) ++{ ++ u32 effectiveN = sfif->samplingN ? sfif->samplingN : smp->samplingN; ++ if (effectiveN <= 1) ++ return 1; ++ u32 lim = (2 * effectiveN) - 1; ++ return (random_u32 (&smpif->seed) % lim) + 1; ++} ++ + #endif /* __included_sflow_h__ */ +diff --git a/src/plugins/sflow/sflow_common.h b/src/plugins/sflow/sflow_common.h +index 3e1021e4a..cd73302a8 100644 +--- a/src/plugins/sflow/sflow_common.h ++++ b/src/plugins/sflow/sflow_common.h +@@ -19,6 +19,7 @@ typedef struct + u32 linux_if_index; + u32 polled; + int sflow_enabled; ++ u32 samplingN; + } sflow_per_interface_data_t; + + /* mirror sflow_direction enum in sflow.api */ diff --git a/vppbld/patches/0013-sflow-per-port-direction.patch b/vppbld/patches/0013-sflow-per-port-direction.patch new file mode 100644 index 00000000..4e286501 --- /dev/null +++ b/vppbld/patches/0013-sflow-per-port-direction.patch @@ -0,0 +1,88 @@ +diff --git a/src/plugins/sflow/sflow.c b/src/plugins/sflow/sflow.c +index 5b7237c9c..f4283f608 100644 +--- a/src/plugins/sflow/sflow.c ++++ b/src/plugins/sflow/sflow.c +@@ -792,12 +792,14 @@ void + sflow_enable_disable_interface (sflow_main_t *smp, + sflow_per_interface_data_t *sfif) + { ++ u32 direction = sflow_direction_resolve (smp, sfif); ++ + bool ingress_on = +- sfif->sflow_enabled && (smp->samplingD == SFLOW_DIRN_INGRESS || +- smp->samplingD == SFLOW_DIRN_BOTH); ++ sfif->sflow_enabled && (direction == SFLOW_DIRN_INGRESS || ++ direction == SFLOW_DIRN_BOTH); + bool egress_on = + sfif->sflow_enabled && +- (smp->samplingD == SFLOW_DIRN_EGRESS || smp->samplingD == SFLOW_DIRN_BOTH); ++ (direction == SFLOW_DIRN_EGRESS || direction == SFLOW_DIRN_BOTH); + bool drop_on = sfif->sflow_enabled && smp->dropM; + bool ingress_enabled = (vnet_feature_is_enabled ("device-input", "sflow", + sfif->sw_if_index) == 1); +@@ -940,6 +942,37 @@ sflow_set_sampling_rate_set (sflow_main_t *smp, u32 sw_if_index, u32 samplingN) + + } + ++int ++sflow_set_direction_set (sflow_main_t *smp, u32 sw_if_index, u32 direction) ++{ ++ vnet_sw_interface_t *sw; ++ ++ /* Utterly wrong? */ ++ if (pool_is_free_index (smp->vnet_main->interface_main.sw_interfaces, ++ sw_if_index)) ++ return VNET_API_ERROR_INVALID_SW_IF_INDEX; ++ ++ /* Not a physical port? */ ++ sw = vnet_get_sw_interface (smp->vnet_main, sw_if_index); ++ if (sw->type != VNET_SW_INTERFACE_TYPE_HARDWARE) ++ return VNET_API_ERROR_INVALID_SW_IF_INDEX; ++ ++ if (direction > SFLOW_DIRN_BOTH) ++ return VNET_API_ERROR_INVALID_VALUE; ++ ++ SFLOW_DBG ("sw_if_index=%u, sup_sw_if_index=%u, hw_if_index=%u\n", ++ sw->sw_if_index, sw->sup_sw_if_index, sw->hw_if_index); ++ ++ vec_validate (smp->per_interface_data, sw->hw_if_index); ++ sflow_per_interface_data_t *sfif = ++ vec_elt_at_index (smp->per_interface_data, sw->hw_if_index); ++ ++ sfif->direction = direction; ++ sflow_enable_disable_interface (smp, sfif); ++ ++ return 0; ++} ++ + static clib_error_t * + sflow_sampling_rate_command_fn (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +diff --git a/src/plugins/sflow/sflow.h b/src/plugins/sflow/sflow.h +index cf90d9cea..e26896b1a 100644 +--- a/src/plugins/sflow/sflow.h ++++ b/src/plugins/sflow/sflow.h +@@ -269,4 +269,11 @@ sflow_next_random_skip_if (sflow_main_t *smp, sflow_per_interface_data_t *sfif, + return (random_u32 (&smpif->seed) % lim) + 1; + } + ++static inline u32 ++sflow_direction_resolve (sflow_main_t *smp, sflow_per_interface_data_t *sfif) ++{ ++ u32 effectiveD = sfif->direction ? sfif->direction : smp->samplingD; ++ return effectiveD; ++} ++ + #endif /* __included_sflow_h__ */ +diff --git a/src/plugins/sflow/sflow_common.h b/src/plugins/sflow/sflow_common.h +index cd73302a8..6055d04f1 100644 +--- a/src/plugins/sflow/sflow_common.h ++++ b/src/plugins/sflow/sflow_common.h +@@ -20,6 +20,7 @@ typedef struct + u32 polled; + int sflow_enabled; + u32 samplingN; ++ u32 direction; + } sflow_per_interface_data_t; + + /* mirror sflow_direction enum in sflow.api */ diff --git a/vppbld/patches/0014-sflow-per-port-api.patch b/vppbld/patches/0014-sflow-per-port-api.patch new file mode 100644 index 00000000..62adcb52 --- /dev/null +++ b/vppbld/patches/0014-sflow-per-port-api.patch @@ -0,0 +1,174 @@ +diff --git a/src/plugins/sflow/sflow.api b/src/plugins/sflow/sflow.api +index 57a478b62..c2fae8dd1 100644 +--- a/src/plugins/sflow/sflow.api ++++ b/src/plugins/sflow/sflow.api +@@ -282,3 +282,29 @@ define sflow_interface_details + u32 context; + vl_api_interface_index_t hw_if_index; + }; ++ ++/** \brief ++ @param client_index - opaque cookie to identify the sender ++ @param context - sender context, to match reply w/ request ++ @param sampling_N - A port's sampling rate ++ @param hw_if_index - hardware interface handle ++*/ ++autoreply define sflow_interface_sampling_rate_set { ++ u32 client_index; ++ u32 context; ++ u32 sampling_N [default=10000]; ++ vl_api_interface_index_t hw_if_index; ++}; ++ ++/** \brief ++ @param client_index - opaque cookie to identify the sender ++ @param context - sender context, to match reply w/ request ++ @param direction - A port's direction ++ @param hw_if_index - hardware interface handle ++*/ ++autoreply define sflow_interface_direction_set { ++ u32 client_index; ++ u32 context; ++ u32 direction; ++ vl_api_interface_index_t hw_if_index; ++}; +diff --git a/src/plugins/sflow/sflow.c b/src/plugins/sflow/sflow.c +index 6a8cb6af8..c19726256 100644 +--- a/src/plugins/sflow/sflow.c ++++ b/src/plugins/sflow/sflow.c +@@ -1396,6 +1396,32 @@ send_sflow_interface_details (vpe_api_main_t *am, vl_api_registration_t *reg, + vl_api_send_msg (reg, (u8 *) mp); + } + ++static void ++vl_api_sflow_interface_sampling_rate_set_t_handler ( ++ vl_api_sflow_interface_sampling_rate_set_t *mp) ++{ ++ vl_api_sflow_interface_sampling_rate_set_reply_t *rmp; ++ sflow_main_t *smp = &sflow_main; ++ int rv; ++ ++ rv = sflow_set_sampling_rate_set (smp, ntohl (mp->hw_if_index), ntohl (mp->sampling_N)); ++ ++ REPLY_MACRO (VL_API_SFLOW_INTERFACE_SAMPLING_RATE_SET_REPLY); ++} ++ ++static void ++vl_api_sflow_interface_direction_set_t_handler ( ++ vl_api_sflow_interface_direction_set_t *mp) ++{ ++ vl_api_sflow_interface_direction_set_reply_t *rmp; ++ sflow_main_t *smp = &sflow_main; ++ int rv; ++ ++ rv = sflow_set_direction_set (smp, ntohl (mp->hw_if_index), ntohl (mp->direction)); ++ ++ REPLY_MACRO (VL_API_SFLOW_INTERFACE_DIRECTION_SET_REPLY); ++} ++ + static void + vl_api_sflow_interface_dump_t_handler (vl_api_sflow_interface_dump_t *mp) + { +diff --git a/src/plugins/sflow/sflow_test.c b/src/plugins/sflow/sflow_test.c +index adeb53539..675bdd0ba 100644 +--- a/src/plugins/sflow/sflow_test.c ++++ b/src/plugins/sflow/sflow_test.c +@@ -318,6 +318,98 @@ api_sflow_direction_set (vat_main_t *vam) + return ret; + } + ++static int ++api_sflow_interface_sampling_rate_set (vat_main_t *vam) ++{ ++ unformat_input_t *i = vam->input; ++ u32 sampling_N = ~0; ++ u32 hw_if_index = ~0; ++ vl_api_sflow_interface_sampling_rate_set_t *mp; ++ int ret; ++ ++ /* Parse args required to build the message */ ++ while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) ++ { ++ if (unformat (i, "sampling_N %d", &sampling_N)) ++ ; ++ else if (unformat (i, "sw_if_index %d", &hw_if_index)) ++ ; ++ else ++ break; ++ } ++ ++ if (sampling_N == ~0) ++ { ++ errmsg ("missing sampling_N number \n"); ++ return -99; ++ } ++ if (hw_if_index == ~0) ++ { ++ errmsg ("missing interface \n"); ++ return -99; ++ } ++ ++ /* Construct the API message */ ++ M (SFLOW_INTERFACE_SAMPLING_RATE_SET, mp); ++ mp->sampling_N = ntohl (sampling_N); ++ mp->hw_if_index = ntohl (hw_if_index); ++ ++ /* send it... */ ++ S (mp); ++ ++ /* Wait for a reply... */ ++ W (ret); ++ return ret; ++} ++ ++static int ++api_sflow_interface_direction_set (vat_main_t *vam) ++{ ++ unformat_input_t *i = vam->input; ++ u32 direction = ~0; ++ u32 hw_if_index = ~0; ++ vl_api_sflow_interface_direction_set_t *mp; ++ int ret; ++ ++ /* Parse args required to build the message */ ++ while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) ++ { ++ if (unformat (i, "direction rx")) ++ direction = SFLOW_DIRN_INGRESS; ++ else if (unformat (i, "direction tx")) ++ direction = SFLOW_DIRN_EGRESS; ++ else if (unformat (i, "direction both")) ++ direction = SFLOW_DIRN_BOTH; ++ else if (unformat (i, "sw_if_index %d", &hw_if_index)) ++ ; ++ else ++ break; ++ } ++ ++ if (direction == ~0) ++ { ++ errmsg ("missing direction\n"); ++ return -99; ++ } ++ if (hw_if_index == ~0) ++ { ++ errmsg ("missing interface\n"); ++ return -99; ++ } ++ ++ /* Construct the API message */ ++ M (SFLOW_INTERFACE_DIRECTION_SET, mp); ++ mp->direction = ntohl (direction); ++ mp->hw_if_index = ntohl (hw_if_index); ++ ++ /* send it... */ ++ S (mp); ++ ++ /* Wait for a reply... */ ++ W (ret); ++ return ret; ++} ++ + static void + vl_api_sflow_drop_monitoring_get_reply_t_handler ( + vl_api_sflow_drop_monitoring_get_reply_t *mp) diff --git a/vppbld/patches/series b/vppbld/patches/series index 4c3a30ea..a7793375 100644 --- a/vppbld/patches/series +++ b/vppbld/patches/series @@ -23,3 +23,10 @@ # scope. Existing hash-eth-l34 and IP_FLOW_HASH_DEFAULT are byte-for-byte # unchanged. 0011-sonic-inner-aware-flow-hash.patch +# 12. sflow: native per-port sample rate +0012-sflow-per-port-sample-rate.patch +# 13. Adding per port direction +0013-sflow-per-port-direction.patch +# 14. Adding new VPP API handlers for per port and direction patches +0014-sflow-per-port-api.patch +