From: Dave Seddon <dave.seddon.ca@gmail.com>
To: netdev@vger.kernel.org
Cc: "David S . Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Stanislav Fomichev <sdf@fomichev.me>,
Tom Herbert <tom@herbertland.com>,
Willem de Bruijn <willemdebruijn.kernel@gmail.com>,
linux-kernel@vger.kernel.org,
Dave Seddon <dave.seddon.ca@gmail.com>
Subject: [PATCH net-next v1 08/11] net: flow_dissector: per-shape counters + /proc/net/flow_dissector_stats
Date: Wed, 15 Jul 2026 17:43:54 -0700 [thread overview]
Message-ID: <20260716004357.3652679-9-dave.seddon.ca@gmail.com> (raw)
In-Reply-To: <20260716004357.3652679-1-dave.seddon.ca@gmail.com>
The opt-in fast-path (this series) gates each byte-identical shape behind
its own net.flow_dissector.* sysctl. Operators need to see how traffic is
actually composed to decide which gates to enable — and that same signal
lets a policy layer (a userspace agent, or the auto controller
proposed in a separate RFC thread) enable/disable the gates
automatically instead of hand-tuning knobs.
Add per-cpu counters, keyed by a small enum flow_dissector_shape covering
the byte-identical shapes (eth_ip, vlan, qinq, pppoe, mpls, ipip, gre):
occurrences[] a shape handled by the SLOW path. Measured while its
gate is off, this approximates the eligible-fraction
signal: how much traffic that shape's fast body would
have handled. The slow counts sit at protocol
recognition points, which are a superset of fast-path
eligibility (a VLAN+ARP frame counts as vlan; an
options-bearing IPv4 counts as eth_ip), so eligible%
is an upper bound -- the exact hit rate is
fast_hits / (occurrences + fast_hits) once a gate is
on. Deliberately so: matching the fast path's full
eligibility conditions in the slow path would mean
re-implementing the fast path's checks there.
fast_hits[] the fast body actually ran for that shape (gate on).
dissects total dissects — the denominator.
Because the fast path returns before the slow-path classification, exactly
one of occurrences/fast_hits is incremented per shaped packet depending on
gate state, so (occurrences + fast_hits) / dissects is a gate-invariant
eligible fraction. Placement mirrors the two paths precisely: the slow
counts sit at each protocol's dissection point -- for eth_ip that is the
out: exit label, taken only on a top-level, non-encapsulated, TCP/UDP
terminal, so a packet counted as VLAN/IPIP/GRE is never also counted as
eth_ip; the fast counts sit at each shape's success terminal, so a
fall-through to the slow path is never double-counted.
Expose the summed counters read-only at /proc/net/flow_dissector_stats
(documented in Documentation/admin-guide/sysctl/net.rst),
one line per shape with occurrences, fast_hits, computed eligible%, and
the current gate state. Counters and the file are global (init_net),
matching the global gates; the file is visible in the init netns only,
so containers do not see it. Per-netns is a noted follow-up.
Dissects fully handled by an attached netns BPF flow dissector program
are deliberately not counted: the dissects increment sits after the BPF
hook's early return, so the file counts only dissects that reach the
in-kernel dissector. That keeps fast_hits/dissects an undiluted
hit-rate for the code these gates control — the signal an operator (or
the auto controller proposed in a separate RFC thread) acts on.
The counters are unconditional by design, not oversight: their whole
purpose is observing traffic composition while the gates are OFF, so a
"stats gate" would blind exactly the signal an operator (or the
auto-enable controller proposed separately) needs before enabling
anything.
Cost: one unconditional this_cpu_inc per dissect (the denominator)
plus one this_cpu_inc at the matched shape's classification point,
summed only on read. Measured on a CPU-bound pktgen soak this is
+0.74% dissector time (+0.44 sigma -- within run-to-run noise); see
the cover letter. The gates themselves are unaffected: an off gate
stays a not-taken static branch.
Assisted-by: Claude:claude-fable-5 sparse smatch
Signed-off-by: Dave Seddon <dave.seddon.ca@gmail.com>
---
Documentation/admin-guide/sysctl/net.rst | 39 ++++
include/net/flow_dissector.h | 17 ++
net/core/flow_dissector.c | 236 ++++++++++++++++++++---
3 files changed, 270 insertions(+), 22 deletions(-)
diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
index dbc819740db0..3b55b2528ba2 100644
--- a/Documentation/admin-guide/sysctl/net.rst
+++ b/Documentation/admin-guide/sysctl/net.rst
@@ -597,6 +597,45 @@ additional 4-byte key field read and a write to
Default: 0
+The ``/proc/net/flow_dissector_stats`` observability file
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Read-only. Reports, per byte-identical shape, the number of packets the
+slow path handled (``occurrences``), the number the fast body handled
+(``fast_hits``), the resulting eligible fraction, and the current gate
+state, plus a total dissect count. Because the fast path returns before
+the slow-path classification, exactly one of the two counters is
+incremented per shaped packet depending on gate state, so
+``(occurrences + fast_hits) / dissects`` is a gate-invariant measure of
+how much traffic each shape's fast body would handle -- the signal an
+operator uses to decide whether a gate is worth enabling. The slow-path
+counts sit at protocol recognition points, a superset of fast-path
+eligibility (a VLAN+ARP frame counts as vlan), so the eligible fraction
+is an upper bound; the exact hit rate once a gate is on is
+``fast_hits / (occurrences + fast_hits)``. The counters
+are per-cpu and summed on read; the per-packet cost is one increment on
+the already-hot classification path.
+
+An example snapshot, from a host that mostly sees plain Eth + IP traffic
+with some VLAN, before any gate has been enabled::
+
+ # cat /proc/net/flow_dissector_stats
+ shape occurrences fast_hits eligible% gate
+ eth_ip 1866909 0 75.71 off
+ vlan 598847 0 24.28 off
+ qinq 0 0 0.00 off
+ pppoe 0 0 0.00 off
+ mpls 0 0 0.00 off
+ ipip 0 0 0.00 off
+ gre 0 0 0.00 off
+ dissects: 2465765
+
+Read top-down: 76% of dissects are plain Eth + IP and 24% are
+VLAN-tagged, so ``eth_ip`` and ``vlan`` are the shapes worth enabling on
+this host; the other shapes are absent. Every gate is still off, so all
+the traffic sits under ``occurrences``; enabling a gate moves that
+shape's packets to ``fast_hits``.
+
3. /proc/sys/net/unix - Parameters for Unix domain sockets
----------------------------------------------------------
diff --git a/include/net/flow_dissector.h b/include/net/flow_dissector.h
index ba7226a42d19..53b16f7306c7 100644
--- a/include/net/flow_dissector.h
+++ b/include/net/flow_dissector.h
@@ -436,6 +436,23 @@ extern struct static_key_false flow_dissector_mpls_key;
extern struct static_key_false flow_dissector_ipip_key;
extern struct static_key_false flow_dissector_gre_key;
+/* Per-shape counters for /proc/net/flow_dissector_stats.
+ * occurrences[]: shape seen by the slow path (the eligible-fraction
+ * signal while its gate is off); fast_hits[]: fast path ran.
+ * Byte-identical shapes only; the behaviour-changing descents are
+ * never counted or auto-managed.
+ */
+enum flow_dissector_shape {
+ FLOW_DISSECTOR_SHAPE_ETH_IP,
+ FLOW_DISSECTOR_SHAPE_VLAN,
+ FLOW_DISSECTOR_SHAPE_QINQ,
+ FLOW_DISSECTOR_SHAPE_PPPOE,
+ FLOW_DISSECTOR_SHAPE_MPLS,
+ FLOW_DISSECTOR_SHAPE_IPIP,
+ FLOW_DISSECTOR_SHAPE_GRE,
+ FLOW_DISSECTOR_SHAPE__MAX,
+};
+
/* struct flow_keys_digest:
*
* This structure is used to hold a digest of the full flow keys. This is a
diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c
index b79c80612dee..2a4b9b2af834 100644
--- a/net/core/flow_dissector.c
+++ b/net/core/flow_dissector.c
@@ -36,6 +36,10 @@
#include <net/netfilter/nf_conntrack_labels.h>
#endif
#include <linux/bpf-netns.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/string_choices.h>
+#include <linux/math64.h>
/* Per-shape fast-path gates, one static_branch per shape so operators
* enable only what their deployment carries. All default off.
@@ -55,6 +59,35 @@ EXPORT_SYMBOL(flow_dissector_ipip_key);
DEFINE_STATIC_KEY_FALSE(flow_dissector_gre_key);
EXPORT_SYMBOL(flow_dissector_gre_key);
+/* Per-cpu, summed on read via /proc/net/flow_dissector_stats.
+ * occurrences[]: shape handled by the slow path (with the gate off,
+ * the shape's eligible-fraction signal); fast_hits[]: fast body ran;
+ * dissects: denominator.
+ */
+struct flow_dissector_stats {
+ u64 occurrences[FLOW_DISSECTOR_SHAPE__MAX];
+ u64 fast_hits[FLOW_DISSECTOR_SHAPE__MAX];
+ u64 dissects;
+};
+
+static DEFINE_PER_CPU(struct flow_dissector_stats, flow_dissector_pcpu_stats);
+
+static inline void flow_dissector_count_slow(enum flow_dissector_shape shape)
+{
+ this_cpu_inc(flow_dissector_pcpu_stats.occurrences[shape]);
+}
+
+static inline void flow_dissector_count_fast(enum flow_dissector_shape shape)
+{
+ this_cpu_inc(flow_dissector_pcpu_stats.fast_hits[shape]);
+}
+
+/* Counting map: eth_ip counts in the dispatcher (only when the leaf
+ * did not set ENCAP), vlan/qinq/pppoe/mpls in their helpers, ipip/gre
+ * in the inner-descent helpers. The slow path counts where it
+ * recognises each shape, gate on or off.
+ */
+
/* IPv4 version/IHL byte of an option-less header: version 4, IHL 5. */
#define FLOW_DIS_IPV4_VIHL_NOOPT 0x45
@@ -1354,6 +1387,7 @@ static bool flow_dissect_fast_vlan(const struct sk_buff *skb,
__be16 saved_tpid;
u16 tci_prio;
u16 tci_id;
+ bool ok;
if (vlan_depth >= 1 &&
!static_branch_unlikely(&flow_dissector_qinq_key))
@@ -1403,22 +1437,35 @@ static bool flow_dissect_fast_vlan(const struct sk_buff *skb,
switch (inner_proto) {
case htons(ETH_P_IP):
- return flow_dissect_fast_ipv4(skb, flow_dissector,
- target_container, data,
- nhoff, hlen);
+ ok = flow_dissect_fast_ipv4(skb, flow_dissector,
+ target_container, data,
+ nhoff, hlen);
+ break;
case htons(ETH_P_IPV6):
- return flow_dissect_fast_ipv6(skb, flow_dissector,
- target_container, data,
- nhoff, hlen);
+ ok = flow_dissect_fast_ipv6(skb, flow_dissector,
+ target_container, data,
+ nhoff, hlen);
+ break;
case htons(ETH_P_8021Q):
case htons(ETH_P_8021AD):
- return flow_dissect_fast_vlan(skb, flow_dissector,
- target_container, data,
- inner_proto, nhoff, hlen,
- vlan_depth + 1);
+ ok = flow_dissect_fast_vlan(skb, flow_dissector,
+ target_container, data,
+ inner_proto, nhoff, hlen,
+ vlan_depth + 1);
+ break;
default:
return false;
}
+
+ /* Count only on full success -- a miss defers and the slow path
+ * counts the occurrence. Depth 0 counts vlan, depth >= 1 qinq; a
+ * double-tagged hit counts both, as the slow path does.
+ */
+ if (ok)
+ flow_dissector_count_fast(vlan_depth == 0 ?
+ FLOW_DISSECTOR_SHAPE_VLAN :
+ FLOW_DISSECTOR_SHAPE_QINQ);
+ return ok;
}
/* PPPoE session (RFC 2516) + 2-byte PPP protocol: write
@@ -1439,6 +1486,7 @@ static bool flow_dissect_fast_pppoe(const struct sk_buff *skb,
} *hdr;
__be16 inner_eth_proto;
u16 ppp_proto;
+ bool ok;
if (unlikely(hlen - nhoff < (int)sizeof(*hdr)))
return false;
@@ -1474,12 +1522,17 @@ static bool flow_dissect_fast_pppoe(const struct sk_buff *skb,
nhoff += PPPOE_SES_HLEN;
if (inner_eth_proto == htons(ETH_P_IP))
- return flow_dissect_fast_ipv4(skb, flow_dissector,
- target_container, data,
- nhoff, hlen);
- return flow_dissect_fast_ipv6(skb, flow_dissector,
- target_container, data,
- nhoff, hlen);
+ ok = flow_dissect_fast_ipv4(skb, flow_dissector,
+ target_container, data,
+ nhoff, hlen);
+ else
+ ok = flow_dissect_fast_ipv6(skb, flow_dissector,
+ target_container, data,
+ nhoff, hlen);
+
+ if (ok)
+ flow_dissector_count_fast(FLOW_DISSECTOR_SHAPE_PPPOE);
+ return ok;
}
/* Single MPLS label (BoS=1): write FLOW_DISSECTOR_KEY_MPLS lse[0]
@@ -1546,6 +1599,7 @@ static bool flow_dissect_fast_mpls(const struct sk_buff *skb,
key_basic->ip_proto = 0;
}
+ flow_dissector_count_fast(FLOW_DISSECTOR_SHAPE_MPLS);
return true;
}
@@ -1584,6 +1638,7 @@ static bool flow_dissect_fast_ipip_inner(const struct sk_buff *skb,
target_container);
key_control->flags |= FLOW_DIS_ENCAPSULATION;
}
+ flow_dissector_count_fast(FLOW_DISSECTOR_SHAPE_IPIP);
return true;
}
@@ -1648,9 +1703,27 @@ static bool flow_dissect_fast_gre_inner(const struct sk_buff *skb,
target_container);
key_control->flags |= FLOW_DIS_ENCAPSULATION;
}
+ flow_dissector_count_fast(FLOW_DISSECTOR_SHAPE_GRE);
return true;
}
+/* Did a fast-path leaf stamp FLOW_DIS_ENCAPSULATION? The dispatcher
+ * counts eth_ip only for plain top-level IP; descents count under
+ * their own shape (UDP-tunnel descents not at all).
+ */
+static bool flow_dissect_fast_is_encap(struct flow_dissector *flow_dissector,
+ void *target_container)
+{
+ struct flow_dissector_key_control *key_control;
+
+ if (!dissector_uses_key(flow_dissector, FLOW_DISSECTOR_KEY_CONTROL))
+ return false;
+ key_control = skb_flow_dissector_target(flow_dissector,
+ FLOW_DISSECTOR_KEY_CONTROL,
+ target_container);
+ return key_control->flags & FLOW_DIS_ENCAPSULATION;
+}
+
/* Top-level dispatcher: eligibility check (only the two standard
* dissectors and flag subset) + per-proto switch with per-shape
* static_branch gating. Each case's branch is a forward not-taken JMP
@@ -1689,15 +1762,24 @@ static bool flow_dissect_fast(const struct sk_buff *skb,
case htons(ETH_P_IP):
if (!static_branch_unlikely(&flow_dissector_eth_ip_key))
return false;
- return flow_dissect_fast_ipv4(skb, flow_dissector,
- target_container, data,
- nhoff, hlen);
+ if (!flow_dissect_fast_ipv4(skb, flow_dissector,
+ target_container, data,
+ nhoff, hlen))
+ return false;
+ /* Plain top-level eth+IP only; descents count under their own shape. */
+ if (!flow_dissect_fast_is_encap(flow_dissector, target_container))
+ flow_dissector_count_fast(FLOW_DISSECTOR_SHAPE_ETH_IP);
+ return true;
case htons(ETH_P_IPV6):
if (!static_branch_unlikely(&flow_dissector_eth_ip_key))
return false;
- return flow_dissect_fast_ipv6(skb, flow_dissector,
- target_container, data,
- nhoff, hlen);
+ if (!flow_dissect_fast_ipv6(skb, flow_dissector,
+ target_container, data,
+ nhoff, hlen))
+ return false;
+ if (!flow_dissect_fast_is_encap(flow_dissector, target_container))
+ flow_dissector_count_fast(FLOW_DISSECTOR_SHAPE_ETH_IP);
+ return true;
case htons(ETH_P_PPP_SES):
if (!static_branch_unlikely(&flow_dissector_pppoe_key))
return false;
@@ -1751,6 +1833,8 @@ bool __skb_flow_dissect(const struct net *net,
bool mpls_el = false;
int mpls_lse = 0;
int num_hdrs = 0;
+ int nhoff_init = 0;
+ bool eth_ip_top = false;
u8 ip_proto = 0;
bool ret;
@@ -1859,6 +1943,8 @@ bool __skb_flow_dissect(const struct net *net,
/* Opt-in fast-path; flow_dissect_fast() does the eligibility check
* and per-shape gating.
*/
+ this_cpu_inc(flow_dissector_pcpu_stats.dissects);
+
if (flow_dissect_fast(skb, flow_dissector, target_container,
data, proto, nhoff, hlen, flags))
return true;
@@ -1892,6 +1978,8 @@ bool __skb_flow_dissect(const struct net *net,
key_num_of_vlans->num_of_vlans = 0;
}
+ nhoff_init = nhoff;
+
proto_again:
fdret = FLOW_DISSECT_RET_CONTINUE;
@@ -1906,6 +1994,10 @@ bool __skb_flow_dissect(const struct net *net,
break;
}
+ /* Top-level eth+IPv4: eth_ip shape candidate (confirmed at out:). */
+ if (nhoff == nhoff_init)
+ eth_ip_top = true;
+
nhoff += iph->ihl * 4;
ip_proto = iph->protocol;
@@ -1954,6 +2046,9 @@ bool __skb_flow_dissect(const struct net *net,
break;
}
+ if (nhoff == nhoff_init)
+ eth_ip_top = true;
+
ip_proto = iph->nexthdr;
nhoff += sizeof(struct ipv6hdr);
@@ -2027,8 +2122,12 @@ bool __skb_flow_dissect(const struct net *net,
if (dissector_vlan == FLOW_DISSECTOR_KEY_MAX) {
dissector_vlan = FLOW_DISSECTOR_KEY_VLAN;
+ /* First (outer) tag: the vlan fast-path shape. */
+ flow_dissector_count_slow(FLOW_DISSECTOR_SHAPE_VLAN);
} else if (dissector_vlan == FLOW_DISSECTOR_KEY_VLAN) {
dissector_vlan = FLOW_DISSECTOR_KEY_CVLAN;
+ /* Second tag: the qinq (double-tag) fast-path shape. */
+ flow_dissector_count_slow(FLOW_DISSECTOR_SHAPE_QINQ);
} else {
fdret = FLOW_DISSECT_RET_PROTO_AGAIN;
break;
@@ -2074,6 +2173,8 @@ bool __skb_flow_dissect(const struct net *net,
break;
}
+ flow_dissector_count_slow(FLOW_DISSECTOR_SHAPE_PPPOE);
+
/* PFC (compressed 1-byte protocol) frames are not processed.
* A compressed protocol field has the least significant bit of
* the most significant octet set, which will fail the following
@@ -2138,6 +2239,7 @@ bool __skb_flow_dissect(const struct net *net,
case htons(ETH_P_MPLS_UC):
case htons(ETH_P_MPLS_MC):
+ flow_dissector_count_slow(FLOW_DISSECTOR_SHAPE_MPLS);
fdret = __skb_flow_dissect_mpls(skb, flow_dissector,
target_container, data,
nhoff, hlen, mpls_lse,
@@ -2235,6 +2337,7 @@ bool __skb_flow_dissect(const struct net *net,
fdret = FLOW_DISSECT_RET_OUT_GOOD;
break;
}
+ flow_dissector_count_slow(FLOW_DISSECTOR_SHAPE_GRE);
fdret = __skb_flow_dissect_gre(skb, key_control, flow_dissector,
target_container, data,
@@ -2297,6 +2400,10 @@ bool __skb_flow_dissect(const struct net *net,
fdret = FLOW_DISSECT_RET_OUT_GOOD;
break;
}
+ /* Counted below the STOP check: a stop-flagged dissect is
+ * one the fast path could never have handled.
+ */
+ flow_dissector_count_slow(FLOW_DISSECTOR_SHAPE_IPIP);
proto = htons(ETH_P_IP);
@@ -2388,6 +2495,15 @@ bool __skb_flow_dissect(const struct net *net,
key_basic->n_proto = proto;
key_basic->ip_proto = ip_proto;
+ /* eth_ip shape: top-level eth+IP, TCP/UDP, no encap -- counted here
+ * because the fast path returns earlier; with the gate off this is
+ * the shape's eligible-fraction signal.
+ */
+ if (ret && eth_ip_top &&
+ !(key_control->flags & FLOW_DIS_ENCAPSULATION) &&
+ (ip_proto == IPPROTO_TCP || ip_proto == IPPROTO_UDP))
+ flow_dissector_count_slow(FLOW_DISSECTOR_SHAPE_ETH_IP);
+
return ret;
out_bad:
@@ -2883,11 +2999,87 @@ static struct ctl_table flow_dissector_sysctl_table[] = {
},
};
+static const char * const flow_dissector_shape_names[FLOW_DISSECTOR_SHAPE__MAX] = {
+ [FLOW_DISSECTOR_SHAPE_ETH_IP] = "eth_ip",
+ [FLOW_DISSECTOR_SHAPE_VLAN] = "vlan",
+ [FLOW_DISSECTOR_SHAPE_QINQ] = "qinq",
+ [FLOW_DISSECTOR_SHAPE_PPPOE] = "pppoe",
+ [FLOW_DISSECTOR_SHAPE_MPLS] = "mpls",
+ [FLOW_DISSECTOR_SHAPE_IPIP] = "ipip",
+ [FLOW_DISSECTOR_SHAPE_GRE] = "gre",
+};
+
+static bool flow_dissector_shape_gate(enum flow_dissector_shape shape)
+{
+ switch (shape) {
+ case FLOW_DISSECTOR_SHAPE_ETH_IP:
+ return static_key_enabled(&flow_dissector_eth_ip_key);
+ case FLOW_DISSECTOR_SHAPE_VLAN:
+ return static_key_enabled(&flow_dissector_vlan_key);
+ case FLOW_DISSECTOR_SHAPE_QINQ:
+ return static_key_enabled(&flow_dissector_qinq_key);
+ case FLOW_DISSECTOR_SHAPE_PPPOE:
+ return static_key_enabled(&flow_dissector_pppoe_key);
+ case FLOW_DISSECTOR_SHAPE_MPLS:
+ return static_key_enabled(&flow_dissector_mpls_key);
+ case FLOW_DISSECTOR_SHAPE_IPIP:
+ return static_key_enabled(&flow_dissector_ipip_key);
+ case FLOW_DISSECTOR_SHAPE_GRE:
+ return static_key_enabled(&flow_dissector_gre_key);
+ default:
+ return false;
+ }
+}
+
+/* /proc/net/flow_dissector_stats: per-shape occurrences (slow path),
+ * fast_hits, and eligible% = (occurrences + fast_hits) / dissects --
+ * the signal a policy layer thresholds against a per-shape
+ * break-even.
+ */
+static int flow_dissector_stats_show(struct seq_file *seq, void *v)
+{
+ u64 occ[FLOW_DISSECTOR_SHAPE__MAX] = {};
+ u64 fast[FLOW_DISSECTOR_SHAPE__MAX] = {};
+ u64 dissects = 0;
+ int cpu, i;
+
+ for_each_possible_cpu(cpu) {
+ const struct flow_dissector_stats *s =
+ per_cpu_ptr(&flow_dissector_pcpu_stats, cpu);
+
+ for (i = 0; i < FLOW_DISSECTOR_SHAPE__MAX; i++) {
+ occ[i] += READ_ONCE(s->occurrences[i]);
+ fast[i] += READ_ONCE(s->fast_hits[i]);
+ }
+ dissects += READ_ONCE(s->dissects);
+ }
+
+ seq_printf(seq, "%-8s %16s %16s %10s %5s\n",
+ "shape", "occurrences", "fast_hits", "eligible%", "gate");
+ for (i = 0; i < FLOW_DISSECTOR_SHAPE__MAX; i++) {
+ u64 total = occ[i] + fast[i];
+ u64 pct_x100 = dissects ? div64_u64(total * 10000, dissects) : 0;
+
+ seq_printf(seq, "%-8s %16llu %16llu %7llu.%02llu %5s\n",
+ flow_dissector_shape_names[i], occ[i], fast[i],
+ pct_x100 / 100, pct_x100 % 100,
+ str_on_off(flow_dissector_shape_gate(i)));
+ }
+ seq_printf(seq, "dissects: %llu\n", dissects);
+ return 0;
+}
+
static int __init flow_dissector_sysctl_init(void)
{
if (!register_net_sysctl(&init_net, "net/flow_dissector",
flow_dissector_sysctl_table))
return -ENOMEM;
+
+ /* Global (init_net) counters — matches the global gates; per-netns
+ * is a noted follow-up. A missing proc file is not fatal.
+ */
+ proc_create_single("flow_dissector_stats", 0444, init_net.proc_net,
+ flow_dissector_stats_show);
return 0;
}
late_initcall(flow_dissector_sysctl_init);
--
2.54.0
next prev parent reply other threads:[~2026-07-16 0:44 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-16 0:43 [PATCH net-next v1 00/11] net: flow_dissector: opt-in byte-identical fast paths for common shapes Dave Seddon
2026-07-16 0:43 ` [PATCH net-next v1 01/11] net: flow_dissector: gate BPF program lookup behind a static key Dave Seddon
2026-07-16 0:43 ` [PATCH net-next v1 02/11] net: flow_dissector: opt-in fast-path for eth + IPv{4,6} + {TCP,UDP} Dave Seddon
2026-07-16 0:43 ` [PATCH net-next v1 03/11] net: flow_dissector: add fast-path for VLAN and QinQ + IP + TCP/UDP Dave Seddon
2026-07-16 0:43 ` [PATCH net-next v1 04/11] net: flow_dissector: add fast-path for PPPoE session + IPv{4,6} " Dave Seddon
2026-07-16 0:43 ` [PATCH net-next v1 05/11] net: flow_dissector: add fast-path for single MPLS label + IP Dave Seddon
2026-07-16 0:43 ` [PATCH net-next v1 06/11] net: flow_dissector: add fast-path for IP-in-IP family (IPIP / 4in6 / 6in4) Dave Seddon
2026-07-16 0:43 ` [PATCH net-next v1 07/11] net: flow_dissector: add byte-identical fast-path for plain GRE inner Dave Seddon
2026-07-16 0:43 ` Dave Seddon [this message]
2026-07-16 0:43 ` [PATCH net-next v1 09/11] net: flow_dissector: bound fast-path tunnel recursion Dave Seddon
2026-07-16 0:43 ` [PATCH net-next v1 10/11] net: flow_dissector: add KUnit fast/slow path equivalence tests Dave Seddon
2026-07-16 0:43 ` [PATCH net-next v1 11/11] Documentation: networking: add flow_dissector overview and fast-path guide Dave Seddon
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260716004357.3652679-9-dave.seddon.ca@gmail.com \
--to=dave.seddon.ca@gmail.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=sdf@fomichev.me \
--cc=tom@herbertland.com \
--cc=willemdebruijn.kernel@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox