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/series b/vppbld/patches/series index 4c3a30ea..144ab34a 100644 --- a/vppbld/patches/series +++ b/vppbld/patches/series @@ -23,3 +23,6 @@ # 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 +