diff mbox series

[v3,net-next,1/2] net: page_pool: introduce ethtool stats

Message ID 628c0a6d9bdbc547c93fcd4ae2e84d08af7bc8e1.1649528984.git.lorenzo@kernel.org (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series net: mvneta: add support for page_pool_get_stats | expand

Checks

Context Check Description
netdev/tree_selection success Clearly marked for net-next
netdev/apply fail Patch does not apply to net-next

Commit Message

Lorenzo Bianconi April 9, 2022, 6:39 p.m. UTC
Introduce page_pool APIs to report stats through ethtool and reduce
duplicated code in each driver.

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 include/net/page_pool.h |  4 +++
 net/core/page_pool.c    | 64 ++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 67 insertions(+), 1 deletion(-)

Comments

Andrew Lunn April 11, 2022, 11:57 a.m. UTC | #1
> diff --git a/include/net/page_pool.h b/include/net/page_pool.h
> index ea5fb70e5101..94b2d666db03 100644
> --- a/include/net/page_pool.h
> +++ b/include/net/page_pool.h
> @@ -117,6 +117,10 @@ struct page_pool_stats {
>  	struct page_pool_recycle_stats recycle_stats;
>  };
>  
> +int page_pool_ethtool_stats_get_count(void);
> +u8 *page_pool_ethtool_stats_get_strings(u8 *data);
> +u64 *page_pool_ethtool_stats_get(u64 *data, struct page_pool_stats *stats);
> +
>  /*
>   * Drivers that wish to harvest page pool stats and report them to users
>   * (perhaps via ethtool, debugfs, or another mechanism) can allocate a

You could also add stub function here for when the page pool
statistics are disabled. We can then avoid all the messy #ifdef in the
drivers.

> +u64 *page_pool_ethtool_stats_get(u64 *data, struct page_pool_stats *stats)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
> +		*data++ = stats->alloc_stats.fast;
> +		*data++ = stats->alloc_stats.slow;
> +		*data++ = stats->alloc_stats.slow_high_order;
> +		*data++ = stats->alloc_stats.empty;
> +		*data++ = stats->alloc_stats.refill;
> +		*data++ = stats->alloc_stats.waive;
> +		*data++ = stats->recycle_stats.cached;
> +		*data++ = stats->recycle_stats.cache_full;
> +		*data++ = stats->recycle_stats.ring;
> +		*data++ = stats->recycle_stats.ring_full;
> +		*data++ = stats->recycle_stats.released_refcnt;
> +	}
> +
> +	return data;

What is the purpose of the loop?

     Andrew
Ilias Apalodimas April 11, 2022, 12:06 p.m. UTC | #2
Hi Lorenzo,

[...]

>
>         for_each_possible_cpu(cpu) {
>                 const struct page_pool_recycle_stats *pcpu =
> @@ -66,6 +87,47 @@ bool page_pool_get_stats(struct page_pool *pool,
>         return true;
>  }
>  EXPORT_SYMBOL(page_pool_get_stats);
> +
> +u8 *page_pool_ethtool_stats_get_strings(u8 *data)
> +{
> +       int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
> +               memcpy(data, pp_stats[i], ETH_GSTRING_LEN);
> +               data += ETH_GSTRING_LEN;
> +       }
> +
> +       return data;

Is there a point returning data here or can we make this a void?

> +}
> +EXPORT_SYMBOL(page_pool_ethtool_stats_get_strings);
> +
> +int page_pool_ethtool_stats_get_count(void)
> +{
> +       return ARRAY_SIZE(pp_stats);
> +}
> +EXPORT_SYMBOL(page_pool_ethtool_stats_get_count);
> +
> +u64 *page_pool_ethtool_stats_get(u64 *data, struct page_pool_stats *stats)
> +{
> +       int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
> +               *data++ = stats->alloc_stats.fast;
> +               *data++ = stats->alloc_stats.slow;
> +               *data++ = stats->alloc_stats.slow_high_order;
> +               *data++ = stats->alloc_stats.empty;
> +               *data++ = stats->alloc_stats.refill;
> +               *data++ = stats->alloc_stats.waive;
> +               *data++ = stats->recycle_stats.cached;
> +               *data++ = stats->recycle_stats.cache_full;
> +               *data++ = stats->recycle_stats.ring;
> +               *data++ = stats->recycle_stats.ring_full;
> +               *data++ = stats->recycle_stats.released_refcnt;
> +       }
> +
> +       return data;

Ditto

> +}
> +EXPORT_SYMBOL(page_pool_ethtool_stats_get);
>  #else
>  #define alloc_stat_inc(pool, __stat)
>  #define recycle_stat_inc(pool, __stat)
> --
> 2.35.1
>

Thanks
/Ilias
Lorenzo Bianconi April 11, 2022, 12:27 p.m. UTC | #3
> > diff --git a/include/net/page_pool.h b/include/net/page_pool.h
> > index ea5fb70e5101..94b2d666db03 100644
> > --- a/include/net/page_pool.h
> > +++ b/include/net/page_pool.h
> > @@ -117,6 +117,10 @@ struct page_pool_stats {
> >  	struct page_pool_recycle_stats recycle_stats;
> >  };
> >  
> > +int page_pool_ethtool_stats_get_count(void);
> > +u8 *page_pool_ethtool_stats_get_strings(u8 *data);
> > +u64 *page_pool_ethtool_stats_get(u64 *data, struct page_pool_stats *stats);
> > +
> >  /*
> >   * Drivers that wish to harvest page pool stats and report them to users
> >   * (perhaps via ethtool, debugfs, or another mechanism) can allocate a
> 
> You could also add stub function here for when the page pool
> statistics are disabled. We can then avoid all the messy #ifdef in the
> drivers.
> 
> > +u64 *page_pool_ethtool_stats_get(u64 *data, struct page_pool_stats *stats)
> > +{
> > +	int i;
> > +
> > +	for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
> > +		*data++ = stats->alloc_stats.fast;
> > +		*data++ = stats->alloc_stats.slow;
> > +		*data++ = stats->alloc_stats.slow_high_order;
> > +		*data++ = stats->alloc_stats.empty;
> > +		*data++ = stats->alloc_stats.refill;
> > +		*data++ = stats->alloc_stats.waive;
> > +		*data++ = stats->recycle_stats.cached;
> > +		*data++ = stats->recycle_stats.cache_full;
> > +		*data++ = stats->recycle_stats.ring;
> > +		*data++ = stats->recycle_stats.ring_full;
> > +		*data++ = stats->recycle_stats.released_refcnt;
> > +	}
> > +
> > +	return data;
> 
> What is the purpose of the loop?

ops sorry, you are right. I will fix it.

Regards,
Lorenzo

> 
>      Andrew
Lorenzo Bianconi April 11, 2022, 12:28 p.m. UTC | #4
> Hi Lorenzo,

Hi Ilias,

> 
> [...]
> 
> >
> >         for_each_possible_cpu(cpu) {
> >                 const struct page_pool_recycle_stats *pcpu =
> > @@ -66,6 +87,47 @@ bool page_pool_get_stats(struct page_pool *pool,
> >         return true;
> >  }
> >  EXPORT_SYMBOL(page_pool_get_stats);
> > +
> > +u8 *page_pool_ethtool_stats_get_strings(u8 *data)
> > +{
> > +       int i;
> > +
> > +       for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
> > +               memcpy(data, pp_stats[i], ETH_GSTRING_LEN);
> > +               data += ETH_GSTRING_LEN;
> > +       }
> > +
> > +       return data;
> 
> Is there a point returning data here or can we make this a void?

it is to add the capability to add more strings in the driver code after
running page_pool_ethtool_stats_get_strings.

> 
> > +}
> > +EXPORT_SYMBOL(page_pool_ethtool_stats_get_strings);
> > +
> > +int page_pool_ethtool_stats_get_count(void)
> > +{
> > +       return ARRAY_SIZE(pp_stats);
> > +}
> > +EXPORT_SYMBOL(page_pool_ethtool_stats_get_count);
> > +
> > +u64 *page_pool_ethtool_stats_get(u64 *data, struct page_pool_stats *stats)
> > +{
> > +       int i;
> > +
> > +       for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
> > +               *data++ = stats->alloc_stats.fast;
> > +               *data++ = stats->alloc_stats.slow;
> > +               *data++ = stats->alloc_stats.slow_high_order;
> > +               *data++ = stats->alloc_stats.empty;
> > +               *data++ = stats->alloc_stats.refill;
> > +               *data++ = stats->alloc_stats.waive;
> > +               *data++ = stats->recycle_stats.cached;
> > +               *data++ = stats->recycle_stats.cache_full;
> > +               *data++ = stats->recycle_stats.ring;
> > +               *data++ = stats->recycle_stats.ring_full;
> > +               *data++ = stats->recycle_stats.released_refcnt;
> > +       }
> > +
> > +       return data;
> 
> Ditto

same here.

Regards,
Lorenzo

> 
> > +}
> > +EXPORT_SYMBOL(page_pool_ethtool_stats_get);
> >  #else
> >  #define alloc_stat_inc(pool, __stat)
> >  #define recycle_stat_inc(pool, __stat)
> > --
> > 2.35.1
> >
> 
> Thanks
> /Ilias
Ilias Apalodimas April 11, 2022, 12:34 p.m. UTC | #5
On Mon, 11 Apr 2022 at 15:28, Lorenzo Bianconi <lorenzo@kernel.org> wrote:
>
> > Hi Lorenzo,
>
> Hi Ilias,
>
> >
> > [...]
> >
> > >
> > >         for_each_possible_cpu(cpu) {
> > >                 const struct page_pool_recycle_stats *pcpu =
> > > @@ -66,6 +87,47 @@ bool page_pool_get_stats(struct page_pool *pool,
> > >         return true;
> > >  }
> > >  EXPORT_SYMBOL(page_pool_get_stats);
> > > +
> > > +u8 *page_pool_ethtool_stats_get_strings(u8 *data)
> > > +{
> > > +       int i;
> > > +
> > > +       for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
> > > +               memcpy(data, pp_stats[i], ETH_GSTRING_LEN);
> > > +               data += ETH_GSTRING_LEN;
> > > +       }
> > > +
> > > +       return data;
> >
> > Is there a point returning data here or can we make this a void?
>
> it is to add the capability to add more strings in the driver code after
> running page_pool_ethtool_stats_get_strings.

But the current driver isn't using it.  I don't have too much
experience with how drivers consume ethtool stats, but would it make
more sense to return a length instead of a pointer? Maybe Andrew has
an idea.

Thanks
/Ilias
>
> >
> > > +}
> > > +EXPORT_SYMBOL(page_pool_ethtool_stats_get_strings);
> > > +
> > > +int page_pool_ethtool_stats_get_count(void)
> > > +{
> > > +       return ARRAY_SIZE(pp_stats);
> > > +}
> > > +EXPORT_SYMBOL(page_pool_ethtool_stats_get_count);
> > > +
> > > +u64 *page_pool_ethtool_stats_get(u64 *data, struct page_pool_stats *stats)
> > > +{
> > > +       int i;
> > > +
> > > +       for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
> > > +               *data++ = stats->alloc_stats.fast;
> > > +               *data++ = stats->alloc_stats.slow;
> > > +               *data++ = stats->alloc_stats.slow_high_order;
> > > +               *data++ = stats->alloc_stats.empty;
> > > +               *data++ = stats->alloc_stats.refill;
> > > +               *data++ = stats->alloc_stats.waive;
> > > +               *data++ = stats->recycle_stats.cached;
> > > +               *data++ = stats->recycle_stats.cache_full;
> > > +               *data++ = stats->recycle_stats.ring;
> > > +               *data++ = stats->recycle_stats.ring_full;
> > > +               *data++ = stats->recycle_stats.released_refcnt;
> > > +       }
> > > +
> > > +       return data;
> >
> > Ditto
>
> same here.
>
> Regards,
> Lorenzo
>
> >
> > > +}
> > > +EXPORT_SYMBOL(page_pool_ethtool_stats_get);
> > >  #else
> > >  #define alloc_stat_inc(pool, __stat)
> > >  #define recycle_stat_inc(pool, __stat)
> > > --
> > > 2.35.1
> > >
> >
> > Thanks
> > /Ilias
Lorenzo Bianconi April 11, 2022, 12:46 p.m. UTC | #6
On Apr 11, Ilias Apalodimas wrote:
> On Mon, 11 Apr 2022 at 15:28, Lorenzo Bianconi <lorenzo@kernel.org> wrote:
> >
> > > Hi Lorenzo,
> >
> > Hi Ilias,
> >
> > >
> > > [...]
> > >
> > > >
> > > >         for_each_possible_cpu(cpu) {
> > > >                 const struct page_pool_recycle_stats *pcpu =
> > > > @@ -66,6 +87,47 @@ bool page_pool_get_stats(struct page_pool *pool,
> > > >         return true;
> > > >  }
> > > >  EXPORT_SYMBOL(page_pool_get_stats);
> > > > +
> > > > +u8 *page_pool_ethtool_stats_get_strings(u8 *data)
> > > > +{
> > > > +       int i;
> > > > +
> > > > +       for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
> > > > +               memcpy(data, pp_stats[i], ETH_GSTRING_LEN);
> > > > +               data += ETH_GSTRING_LEN;
> > > > +       }
> > > > +
> > > > +       return data;
> > >
> > > Is there a point returning data here or can we make this a void?
> >
> > it is to add the capability to add more strings in the driver code after
> > running page_pool_ethtool_stats_get_strings.
> 
> But the current driver isn't using it.  I don't have too much
> experience with how drivers consume ethtool stats, but would it make
> more sense to return a length instead of a pointer? Maybe Andrew has
> an idea.

yes, but it is for future usage. Returning a pointer modified by a
local routine is a common approach in the kernel (not sure about ethtool)
and it seems straightforward to me but len is fine as well. Opinions?

Regards,
Lorenzo

> 
> Thanks
> /Ilias
> >
> > >
> > > > +}
> > > > +EXPORT_SYMBOL(page_pool_ethtool_stats_get_strings);
> > > > +
> > > > +int page_pool_ethtool_stats_get_count(void)
> > > > +{
> > > > +       return ARRAY_SIZE(pp_stats);
> > > > +}
> > > > +EXPORT_SYMBOL(page_pool_ethtool_stats_get_count);
> > > > +
> > > > +u64 *page_pool_ethtool_stats_get(u64 *data, struct page_pool_stats *stats)
> > > > +{
> > > > +       int i;
> > > > +
> > > > +       for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
> > > > +               *data++ = stats->alloc_stats.fast;
> > > > +               *data++ = stats->alloc_stats.slow;
> > > > +               *data++ = stats->alloc_stats.slow_high_order;
> > > > +               *data++ = stats->alloc_stats.empty;
> > > > +               *data++ = stats->alloc_stats.refill;
> > > > +               *data++ = stats->alloc_stats.waive;
> > > > +               *data++ = stats->recycle_stats.cached;
> > > > +               *data++ = stats->recycle_stats.cache_full;
> > > > +               *data++ = stats->recycle_stats.ring;
> > > > +               *data++ = stats->recycle_stats.ring_full;
> > > > +               *data++ = stats->recycle_stats.released_refcnt;
> > > > +       }
> > > > +
> > > > +       return data;
> > >
> > > Ditto
> >
> > same here.
> >
> > Regards,
> > Lorenzo
> >
> > >
> > > > +}
> > > > +EXPORT_SYMBOL(page_pool_ethtool_stats_get);
> > > >  #else
> > > >  #define alloc_stat_inc(pool, __stat)
> > > >  #define recycle_stat_inc(pool, __stat)
> > > > --
> > > > 2.35.1
> > > >
> > >
> > > Thanks
> > > /Ilias
Andrew Lunn April 11, 2022, 12:49 p.m. UTC | #7
On Mon, Apr 11, 2022 at 03:34:21PM +0300, Ilias Apalodimas wrote:
> On Mon, 11 Apr 2022 at 15:28, Lorenzo Bianconi <lorenzo@kernel.org> wrote:
> >
> > > Hi Lorenzo,
> >
> > Hi Ilias,
> >
> > >
> > > [...]
> > >
> > > >
> > > >         for_each_possible_cpu(cpu) {
> > > >                 const struct page_pool_recycle_stats *pcpu =
> > > > @@ -66,6 +87,47 @@ bool page_pool_get_stats(struct page_pool *pool,
> > > >         return true;
> > > >  }
> > > >  EXPORT_SYMBOL(page_pool_get_stats);
> > > > +
> > > > +u8 *page_pool_ethtool_stats_get_strings(u8 *data)
> > > > +{
> > > > +       int i;
> > > > +
> > > > +       for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
> > > > +               memcpy(data, pp_stats[i], ETH_GSTRING_LEN);
> > > > +               data += ETH_GSTRING_LEN;
> > > > +       }
> > > > +
> > > > +       return data;
> > >
> > > Is there a point returning data here or can we make this a void?
> >
> > it is to add the capability to add more strings in the driver code after
> > running page_pool_ethtool_stats_get_strings.
> 
> But the current driver isn't using it.

It could be you need it for the mlx5 driver, which puts the TLS
counters after the page pool counters. Or you could just move them to
the end. I don't think the order of statistics are ABI, just the
strings themselves..

> I don't have too much
> experience with how drivers consume ethtool stats, but would it make
> more sense to return a length instead of a pointer? Maybe Andrew has
> an idea.

Either is acceptable. Even if you do make it a void, the driver can
use the stats_get_count() and do the maths. But a length or a pointer
is simpler.

   Andrew
Ilias Apalodimas April 11, 2022, 12:51 p.m. UTC | #8
Hi Andrew,

On Mon, 11 Apr 2022 at 15:49, Andrew Lunn <andrew@lunn.ch> wrote:
>
> On Mon, Apr 11, 2022 at 03:34:21PM +0300, Ilias Apalodimas wrote:
> > On Mon, 11 Apr 2022 at 15:28, Lorenzo Bianconi <lorenzo@kernel.org> wrote:
> > >
> > > > Hi Lorenzo,
> > >
> > > Hi Ilias,
> > >
> > > >
> > > > [...]
> > > >
> > > > >
> > > > >         for_each_possible_cpu(cpu) {
> > > > >                 const struct page_pool_recycle_stats *pcpu =
> > > > > @@ -66,6 +87,47 @@ bool page_pool_get_stats(struct page_pool *pool,
> > > > >         return true;
> > > > >  }
> > > > >  EXPORT_SYMBOL(page_pool_get_stats);
> > > > > +
> > > > > +u8 *page_pool_ethtool_stats_get_strings(u8 *data)
> > > > > +{
> > > > > +       int i;
> > > > > +
> > > > > +       for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
> > > > > +               memcpy(data, pp_stats[i], ETH_GSTRING_LEN);
> > > > > +               data += ETH_GSTRING_LEN;
> > > > > +       }
> > > > > +
> > > > > +       return data;
> > > >
> > > > Is there a point returning data here or can we make this a void?
> > >
> > > it is to add the capability to add more strings in the driver code after
> > > running page_pool_ethtool_stats_get_strings.
> >
> > But the current driver isn't using it.
>
> It could be you need it for the mlx5 driver, which puts the TLS
> counters after the page pool counters. Or you could just move them to
> the end. I don't think the order of statistics are ABI, just the
> strings themselves..
>
> > I don't have too much
> > experience with how drivers consume ethtool stats, but would it make
> > more sense to return a length instead of a pointer? Maybe Andrew has
> > an idea.
>
> Either is acceptable. Even if you do make it a void, the driver can
> use the stats_get_count() and do the maths. But a length or a pointer
> is simpler.

Thanks, I am fine with either as well.   I was just wondering why we
need it since the mvneta driver wasn't using it. Let's leave it as is

/Ilias
>
>    Andrew
diff mbox series

Patch

diff --git a/include/net/page_pool.h b/include/net/page_pool.h
index ea5fb70e5101..94b2d666db03 100644
--- a/include/net/page_pool.h
+++ b/include/net/page_pool.h
@@ -117,6 +117,10 @@  struct page_pool_stats {
 	struct page_pool_recycle_stats recycle_stats;
 };
 
+int page_pool_ethtool_stats_get_count(void);
+u8 *page_pool_ethtool_stats_get_strings(u8 *data);
+u64 *page_pool_ethtool_stats_get(u64 *data, struct page_pool_stats *stats);
+
 /*
  * Drivers that wish to harvest page pool stats and report them to users
  * (perhaps via ethtool, debugfs, or another mechanism) can allocate a
diff --git a/net/core/page_pool.c b/net/core/page_pool.c
index 4af55d28ffa3..e82b94040ed1 100644
--- a/net/core/page_pool.c
+++ b/net/core/page_pool.c
@@ -18,6 +18,7 @@ 
 #include <linux/page-flags.h>
 #include <linux/mm.h> /* for __put_page() */
 #include <linux/poison.h>
+#include <linux/ethtool.h>
 
 #include <trace/events/page_pool.h>
 
@@ -42,6 +43,20 @@ 
 		this_cpu_add(s->__stat, val);						\
 	} while (0)
 
+static const char pp_stats[][ETH_GSTRING_LEN] = {
+	"rx_pp_alloc_fast",
+	"rx_pp_alloc_slow",
+	"rx_pp_alloc_slow_ho",
+	"rx_pp_alloc_empty",
+	"rx_pp_alloc_refill",
+	"rx_pp_alloc_waive",
+	"rx_pp_recycle_cached",
+	"rx_pp_recycle_cache_full",
+	"rx_pp_recycle_ring",
+	"rx_pp_recycle_ring_full",
+	"rx_pp_recycle_released_ref",
+};
+
 bool page_pool_get_stats(struct page_pool *pool,
 			 struct page_pool_stats *stats)
 {
@@ -50,7 +65,13 @@  bool page_pool_get_stats(struct page_pool *pool,
 	if (!stats)
 		return false;
 
-	memcpy(&stats->alloc_stats, &pool->alloc_stats, sizeof(pool->alloc_stats));
+	/* The caller is responsible to initialize stats. */
+	stats->alloc_stats.fast += pool->alloc_stats.fast;
+	stats->alloc_stats.slow += pool->alloc_stats.slow;
+	stats->alloc_stats.slow_high_order += pool->alloc_stats.slow_high_order;
+	stats->alloc_stats.empty += pool->alloc_stats.empty;
+	stats->alloc_stats.refill += pool->alloc_stats.refill;
+	stats->alloc_stats.waive += pool->alloc_stats.waive;
 
 	for_each_possible_cpu(cpu) {
 		const struct page_pool_recycle_stats *pcpu =
@@ -66,6 +87,47 @@  bool page_pool_get_stats(struct page_pool *pool,
 	return true;
 }
 EXPORT_SYMBOL(page_pool_get_stats);
+
+u8 *page_pool_ethtool_stats_get_strings(u8 *data)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
+		memcpy(data, pp_stats[i], ETH_GSTRING_LEN);
+		data += ETH_GSTRING_LEN;
+	}
+
+	return data;
+}
+EXPORT_SYMBOL(page_pool_ethtool_stats_get_strings);
+
+int page_pool_ethtool_stats_get_count(void)
+{
+	return ARRAY_SIZE(pp_stats);
+}
+EXPORT_SYMBOL(page_pool_ethtool_stats_get_count);
+
+u64 *page_pool_ethtool_stats_get(u64 *data, struct page_pool_stats *stats)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pp_stats); i++) {
+		*data++ = stats->alloc_stats.fast;
+		*data++ = stats->alloc_stats.slow;
+		*data++ = stats->alloc_stats.slow_high_order;
+		*data++ = stats->alloc_stats.empty;
+		*data++ = stats->alloc_stats.refill;
+		*data++ = stats->alloc_stats.waive;
+		*data++ = stats->recycle_stats.cached;
+		*data++ = stats->recycle_stats.cache_full;
+		*data++ = stats->recycle_stats.ring;
+		*data++ = stats->recycle_stats.ring_full;
+		*data++ = stats->recycle_stats.released_refcnt;
+	}
+
+	return data;
+}
+EXPORT_SYMBOL(page_pool_ethtool_stats_get);
 #else
 #define alloc_stat_inc(pool, __stat)
 #define recycle_stat_inc(pool, __stat)