diff mbox

example: l2fwd print packets per second

Message ID 1425649536-6580-1-git-send-email-maxim.uvarov@linaro.org
State New
Headers show

Commit Message

Maxim Uvarov March 6, 2015, 1:45 p.m. UTC
Current print in l2fwd is not useful with slow links and
also it's hard to say how fast does it work. Print pps
and packets drops.

Signed-off-by: Maxim Uvarov <maxim.uvarov@linaro.org>
---
 example/l2fwd/odp_l2fwd.c | 67 ++++++++++++++++++++++++++++++-----------------
 1 file changed, 43 insertions(+), 24 deletions(-)

Comments

Mike Holmes March 6, 2015, 1:55 p.m. UTC | #1
Like the idea.
However I think we need to move forward making the examples simpler so that
they show one concept very clearly and not be mini apps, so I am not in
favor of adding code to them.
The worst offender we have for an example that is too complex is the ipsec
example, that is a full blown app and not amenable to being referenced from
the Doxygen at all.

I feel this change should be made and that we also migrate l2fwd into
test/performance where users can still see it, but we then leave
odp/examples open to have examples per API rather  than per mini app like
l2fwd.  In addition then the ODP API doc, the validation tests, the bug
tracker AND the examples all follow the pattern of per API module content
making it easier to navigate.

On 6 March 2015 at 08:45, Maxim Uvarov <maxim.uvarov@linaro.org> wrote:

> Current print in l2fwd is not useful with slow links and
> also it's hard to say how fast does it work. Print pps
> and packets drops.
>
> Signed-off-by: Maxim Uvarov <maxim.uvarov@linaro.org>
> ---
>  example/l2fwd/odp_l2fwd.c | 67
> ++++++++++++++++++++++++++++++-----------------
>  1 file changed, 43 insertions(+), 24 deletions(-)
>
> diff --git a/example/l2fwd/odp_l2fwd.c b/example/l2fwd/odp_l2fwd.c
> index d062a72..71c2fc5 100644
> --- a/example/l2fwd/odp_l2fwd.c
> +++ b/example/l2fwd/odp_l2fwd.c
> @@ -104,6 +104,14 @@ static void parse_args(int argc, char *argv[],
> appl_args_t *appl_args);
>  static void print_info(char *progname, appl_args_t *appl_args);
>  static void usage(char *progname);
>
> +/* speed and stats */
> +typedef struct {
> +       uint64_t packets[MAX_WORKERS];
> +       uint64_t drops[MAX_WORKERS];
> +} stats_t;
> +
> +static stats_t stats;
> +
>  /**
>   * Packet IO worker thread using ODP queues
>   *
> @@ -115,8 +123,6 @@ static void *pktio_queue_thread(void *arg)
>         odp_queue_t outq_def;
>         odp_packet_t pkt;
>         odp_event_t ev;
> -       unsigned long pkt_cnt = 0;
> -       unsigned long err_cnt = 0;
>
>         (void)arg;
>
> @@ -132,7 +138,7 @@ static void *pktio_queue_thread(void *arg)
>
>                 /* Drop packets with errors */
>                 if (odp_unlikely(drop_err_pkts(&pkt, 1) == 0)) {
> -                       EXAMPLE_ERR("Drop frame - err_cnt:%lu\n",
> ++err_cnt);
> +                       stats.drops[thr] += 1;
>                         continue;
>                 }
>
> @@ -141,11 +147,7 @@ static void *pktio_queue_thread(void *arg)
>                 /* Enqueue the packet for output */
>                 odp_queue_enq(outq_def, ev);
>
> -               /* Print packet counts every once in a while */
> -               if (odp_unlikely(pkt_cnt++ % 100000 == 0)) {
> -                       printf("  [%02i] pkt_cnt:%lu\n", thr, pkt_cnt);
> -                       fflush(NULL);
> -               }
> +               stats.packets[thr] += 1;
>         }
>
>  /* unreachable */
> @@ -186,9 +188,6 @@ static void *pktio_ifburst_thread(void *arg)
>         thread_args_t *thr_args;
>         int pkts, pkts_ok;
>         odp_packet_t pkt_tbl[MAX_PKT_BURST];
> -       unsigned long pkt_cnt = 0;
> -       unsigned long err_cnt = 0;
> -       unsigned long tmp = 0;
>         int src_idx, dst_idx;
>         odp_pktio_t pktio_src, pktio_dst;
>
> @@ -218,23 +217,13 @@ static void *pktio_ifburst_thread(void *arg)
>                 if (pkts_ok > 0)
>                         odp_pktio_send(pktio_dst, pkt_tbl, pkts_ok);
>
> -               if (odp_unlikely(pkts_ok != pkts)) {
> -                       err_cnt += pkts-pkts_ok;
> -                       EXAMPLE_ERR("Dropped frames:%u - err_cnt:%lu\n",
> -                                   pkts-pkts_ok, err_cnt);
> -               }
> +               if (odp_unlikely(pkts_ok != pkts))
> +                       stats.drops[thr] += pkts - pkts_ok;
>
>                 if (pkts_ok == 0)
>                         continue;
>
> -               /* Print packet counts every once in a while */
> -               tmp += pkts_ok;
> -               if (odp_unlikely(tmp >= 100000 || pkt_cnt == 0)) {
> -                       pkt_cnt += tmp;
> -                       tmp = 0;
> -                       printf("  [%02i] pkt_cnt:%lu\n", thr, pkt_cnt);
> -                       fflush(NULL);
> -               }
> +               stats.packets[thr] += pkts_ok;
>         }
>
>  /* unreachable */
> @@ -295,6 +284,34 @@ static odp_pktio_t create_pktio(const char *dev,
> odp_pool_t pool,
>         return pktio;
>  }
>
> +static void print_speed_stats(int num_workers)
> +{
> +       stats_t old_stats;
> +       stats_t new_stats;
> +       uint64_t old;
> +       uint64_t new;
> +       int i;
> +       int timeout = 10;
> +
> +       while (1) {
> +               memcpy(&old_stats, &stats, sizeof(stats_t));
> +               sleep(timeout);
> +               memcpy(&new_stats, &stats, sizeof(stats_t));
> +
> +               for (i = old = new = 0; i < num_workers; i++) {
> +                       old += old_stats.packets[i];
> +                       new += new_stats.packets[i];
> +               }
> +               printf(" %" PRIu64 " pps, ", (new - old) / timeout);
> +
> +               for (i = old = new = 0; i < num_workers; i++) {
> +                       old += old_stats.drops[i];
> +                       new += new_stats.drops[i];
> +               }
> +               printf(" %" PRIu64 " drops\n", (new - old) / timeout);
> +       }
> +}
> +
>  /**
>   * ODP L2 forwarding main function
>   */
> @@ -412,6 +429,8 @@ int main(int argc, char *argv[])
>                 cpu = odp_cpumask_next(&cpumask, cpu);
>         }
>
> +       print_speed_stats(num_workers);
> +
>         /* Master thread waits for other threads to exit */
>         odph_linux_pthread_join(thread_tbl, num_workers);
>
> --
> 1.9.1
>
>
> _______________________________________________
> lng-odp mailing list
> lng-odp@lists.linaro.org
> http://lists.linaro.org/mailman/listinfo/lng-odp
>
Maxim Uvarov March 6, 2015, 2:07 p.m. UTC | #2
On 03/06/15 16:55, Mike Holmes wrote:
> Like the idea.
> However I think we need to move forward making the examples simpler so 
> that they show one concept very clearly and not be mini apps, so I am 
> not in favor of adding code to them.
> The worst offender we have for an example that is too complex is the 
> ipsec example, that is a full blown app and not amenable to being 
> referenced from the Doxygen at all.
>
> I feel this change should be made and that we also migrate l2fwdinto 
> test/performance where users can still see it, but we then leave 
> odp/examples open to have examples per API rather  than per mini app 
> like l2fwd.  In addition then the ODP API doc, the validation tests, 
> the bug tracker AND the examples all follow the pattern of per API 
> module content making it easier to navigate.

yes, I agree l2fwd is more likely ./test/performance then ./example. For 
packet i/o good example is ./example/packet. But I also don't like print 
there and I have to hack it each time.

Maxim.

>
> On 6 March 2015 at 08:45, Maxim Uvarov <maxim.uvarov@linaro.org 
> <mailto:maxim.uvarov@linaro.org>> wrote:
>
>     Current print in l2fwd is not useful with slow links and
>     also it's hard to say how fast does it work. Print pps
>     and packets drops.
>
>     Signed-off-by: Maxim Uvarov <maxim.uvarov@linaro.org
>     <mailto:maxim.uvarov@linaro.org>>
>     ---
>      example/l2fwd/odp_l2fwd.c | 67
>     ++++++++++++++++++++++++++++++-----------------
>      1 file changed, 43 insertions(+), 24 deletions(-)
>
>     diff --git a/example/l2fwd/odp_l2fwd.c b/example/l2fwd/odp_l2fwd.c
>     index d062a72..71c2fc5 100644
>     --- a/example/l2fwd/odp_l2fwd.c
>     +++ b/example/l2fwd/odp_l2fwd.c
>     @@ -104,6 +104,14 @@ static void parse_args(int argc, char
>     *argv[], appl_args_t *appl_args);
>      static void print_info(char *progname, appl_args_t *appl_args);
>      static void usage(char *progname);
>
>     +/* speed and stats */
>     +typedef struct {
>     +       uint64_t packets[MAX_WORKERS];
>     +       uint64_t drops[MAX_WORKERS];
>     +} stats_t;
>     +
>     +static stats_t stats;
>     +
>      /**
>       * Packet IO worker thread using ODP queues
>       *
>     @@ -115,8 +123,6 @@ static void *pktio_queue_thread(void *arg)
>             odp_queue_t outq_def;
>             odp_packet_t pkt;
>             odp_event_t ev;
>     -       unsigned long pkt_cnt = 0;
>     -       unsigned long err_cnt = 0;
>
>             (void)arg;
>
>     @@ -132,7 +138,7 @@ static void *pktio_queue_thread(void *arg)
>
>                     /* Drop packets with errors */
>                     if (odp_unlikely(drop_err_pkts(&pkt, 1) == 0)) {
>     -                       EXAMPLE_ERR("Drop frame - err_cnt:%lu\n",
>     ++err_cnt);
>     +                       stats.drops[thr] += 1;
>                             continue;
>                     }
>
>     @@ -141,11 +147,7 @@ static void *pktio_queue_thread(void *arg)
>                     /* Enqueue the packet for output */
>                     odp_queue_enq(outq_def, ev);
>
>     -               /* Print packet counts every once in a while */
>     -               if (odp_unlikely(pkt_cnt++ % 100000 == 0)) {
>     -                       printf("  [%02i] pkt_cnt:%lu\n", thr,
>     pkt_cnt);
>     -                       fflush(NULL);
>     -               }
>     +               stats.packets[thr] += 1;
>             }
>
>      /* unreachable */
>     @@ -186,9 +188,6 @@ static void *pktio_ifburst_thread(void *arg)
>             thread_args_t *thr_args;
>             int pkts, pkts_ok;
>             odp_packet_t pkt_tbl[MAX_PKT_BURST];
>     -       unsigned long pkt_cnt = 0;
>     -       unsigned long err_cnt = 0;
>     -       unsigned long tmp = 0;
>             int src_idx, dst_idx;
>             odp_pktio_t pktio_src, pktio_dst;
>
>     @@ -218,23 +217,13 @@ static void *pktio_ifburst_thread(void *arg)
>                     if (pkts_ok > 0)
>                             odp_pktio_send(pktio_dst, pkt_tbl, pkts_ok);
>
>     -               if (odp_unlikely(pkts_ok != pkts)) {
>     -                       err_cnt += pkts-pkts_ok;
>     -                       EXAMPLE_ERR("Dropped frames:%u -
>     err_cnt:%lu\n",
>     -                                   pkts-pkts_ok, err_cnt);
>     -               }
>     +               if (odp_unlikely(pkts_ok != pkts))
>     +                       stats.drops[thr] += pkts - pkts_ok;
>
>                     if (pkts_ok == 0)
>                             continue;
>
>     -               /* Print packet counts every once in a while */
>     -               tmp += pkts_ok;
>     -               if (odp_unlikely(tmp >= 100000 || pkt_cnt == 0)) {
>     -                       pkt_cnt += tmp;
>     -                       tmp = 0;
>     -                       printf("  [%02i] pkt_cnt:%lu\n", thr,
>     pkt_cnt);
>     -                       fflush(NULL);
>     -               }
>     +               stats.packets[thr] += pkts_ok;
>             }
>
>      /* unreachable */
>     @@ -295,6 +284,34 @@ static odp_pktio_t create_pktio(const char
>     *dev, odp_pool_t pool,
>             return pktio;
>      }
>
>     +static void print_speed_stats(int num_workers)
>     +{
>     +       stats_t old_stats;
>     +       stats_t new_stats;
>     +       uint64_t old;
>     +       uint64_t new;
>     +       int i;
>     +       int timeout = 10;
>     +
>     +       while (1) {
>     +               memcpy(&old_stats, &stats, sizeof(stats_t));
>     +               sleep(timeout);
>     +               memcpy(&new_stats, &stats, sizeof(stats_t));
>     +
>     +               for (i = old = new = 0; i < num_workers; i++) {
>     +                       old += old_stats.packets[i];
>     +                       new += new_stats.packets[i];
>     +               }
>     +               printf(" %" PRIu64 " pps, ", (new - old) / timeout);
>     +
>     +               for (i = old = new = 0; i < num_workers; i++) {
>     +                       old += old_stats.drops[i];
>     +                       new += new_stats.drops[i];
>     +               }
>     +               printf(" %" PRIu64 " drops\n", (new - old) / timeout);
>     +       }
>     +}
>     +
>      /**
>       * ODP L2 forwarding main function
>       */
>     @@ -412,6 +429,8 @@ int main(int argc, char *argv[])
>                     cpu = odp_cpumask_next(&cpumask, cpu);
>             }
>
>     +       print_speed_stats(num_workers);
>     +
>             /* Master thread waits for other threads to exit */
>             odph_linux_pthread_join(thread_tbl, num_workers);
>
>     --
>     1.9.1
>
>
>     _______________________________________________
>     lng-odp mailing list
>     lng-odp@lists.linaro.org <mailto:lng-odp@lists.linaro.org>
>     http://lists.linaro.org/mailman/listinfo/lng-odp
>
>
>
>
> -- 
> Mike Holmes
> Technical Manager - Linaro Networking Group
> Linaro.org <http://www.linaro.org/>***│ *Open source software for ARM SoCs
>
Ciprian Barbu March 13, 2015, 10:57 a.m. UTC | #3
On Fri, Mar 6, 2015 at 4:07 PM, Maxim Uvarov <maxim.uvarov@linaro.org> wrote:
> On 03/06/15 16:55, Mike Holmes wrote:
>>
>> Like the idea.
>> However I think we need to move forward making the examples simpler so
>> that they show one concept very clearly and not be mini apps, so I am not in
>> favor of adding code to them.
>> The worst offender we have for an example that is too complex is the ipsec
>> example, that is a full blown app and not amenable to being referenced from
>> the Doxygen at all.

I like this too. See some comments below.

>>
>> I feel this change should be made and that we also migrate l2fwdinto
>> test/performance where users can still see it, but we then leave
>> odp/examples open to have examples per API rather  than per mini app like
>> l2fwd.  In addition then the ODP API doc, the validation tests, the bug
>> tracker AND the examples all follow the pattern of per API module content
>> making it easier to navigate.

+1

>
>
> yes, I agree l2fwd is more likely ./test/performance then ./example. For
> packet i/o good example is ./example/packet. But I also don't like print
> there and I have to hack it each time.
>
> Maxim.
>
>>
>> On 6 March 2015 at 08:45, Maxim Uvarov <maxim.uvarov@linaro.org
>> <mailto:maxim.uvarov@linaro.org>> wrote:
>>
>>     Current print in l2fwd is not useful with slow links and
>>     also it's hard to say how fast does it work. Print pps
>>     and packets drops.
>>
>>     Signed-off-by: Maxim Uvarov <maxim.uvarov@linaro.org
>>     <mailto:maxim.uvarov@linaro.org>>
>>
>>     ---
>>      example/l2fwd/odp_l2fwd.c | 67
>>     ++++++++++++++++++++++++++++++-----------------
>>      1 file changed, 43 insertions(+), 24 deletions(-)
>>
>>     diff --git a/example/l2fwd/odp_l2fwd.c b/example/l2fwd/odp_l2fwd.c
>>     index d062a72..71c2fc5 100644
>>     --- a/example/l2fwd/odp_l2fwd.c
>>     +++ b/example/l2fwd/odp_l2fwd.c
>>     @@ -104,6 +104,14 @@ static void parse_args(int argc, char
>>     *argv[], appl_args_t *appl_args);
>>      static void print_info(char *progname, appl_args_t *appl_args);
>>      static void usage(char *progname);
>>
>>     +/* speed and stats */
>>     +typedef struct {
>>     +       uint64_t packets[MAX_WORKERS];
>>     +       uint64_t drops[MAX_WORKERS];
>>     +} stats_t;
>>     +
>>     +static stats_t stats;
>>     +
>>      /**
>>       * Packet IO worker thread using ODP queues
>>       *
>>     @@ -115,8 +123,6 @@ static void *pktio_queue_thread(void *arg)
>>             odp_queue_t outq_def;
>>             odp_packet_t pkt;
>>             odp_event_t ev;
>>     -       unsigned long pkt_cnt = 0;
>>     -       unsigned long err_cnt = 0;
>>
>>             (void)arg;
>>
>>     @@ -132,7 +138,7 @@ static void *pktio_queue_thread(void *arg)
>>
>>                     /* Drop packets with errors */
>>                     if (odp_unlikely(drop_err_pkts(&pkt, 1) == 0)) {
>>     -                       EXAMPLE_ERR("Drop frame - err_cnt:%lu\n",
>>     ++err_cnt);
>>     +                       stats.drops[thr] += 1;
>>                             continue;
>>                     }
>>
>>     @@ -141,11 +147,7 @@ static void *pktio_queue_thread(void *arg)
>>                     /* Enqueue the packet for output */
>>                     odp_queue_enq(outq_def, ev);
>>
>>     -               /* Print packet counts every once in a while */
>>     -               if (odp_unlikely(pkt_cnt++ % 100000 == 0)) {
>>     -                       printf("  [%02i] pkt_cnt:%lu\n", thr,
>>     pkt_cnt);
>>     -                       fflush(NULL);
>>     -               }
>>     +               stats.packets[thr] += 1;
>>             }
>>
>>      /* unreachable */
>>     @@ -186,9 +188,6 @@ static void *pktio_ifburst_thread(void *arg)
>>             thread_args_t *thr_args;
>>             int pkts, pkts_ok;
>>             odp_packet_t pkt_tbl[MAX_PKT_BURST];
>>     -       unsigned long pkt_cnt = 0;
>>     -       unsigned long err_cnt = 0;
>>     -       unsigned long tmp = 0;
>>             int src_idx, dst_idx;
>>             odp_pktio_t pktio_src, pktio_dst;
>>
>>     @@ -218,23 +217,13 @@ static void *pktio_ifburst_thread(void *arg)
>>                     if (pkts_ok > 0)
>>                             odp_pktio_send(pktio_dst, pkt_tbl, pkts_ok);
>>
>>     -               if (odp_unlikely(pkts_ok != pkts)) {
>>     -                       err_cnt += pkts-pkts_ok;
>>     -                       EXAMPLE_ERR("Dropped frames:%u -
>>     err_cnt:%lu\n",
>>     -                                   pkts-pkts_ok, err_cnt);
>>     -               }
>>     +               if (odp_unlikely(pkts_ok != pkts))
>>     +                       stats.drops[thr] += pkts - pkts_ok;
>>
>>                     if (pkts_ok == 0)
>>                             continue;
>>
>>     -               /* Print packet counts every once in a while */
>>     -               tmp += pkts_ok;
>>     -               if (odp_unlikely(tmp >= 100000 || pkt_cnt == 0)) {
>>     -                       pkt_cnt += tmp;
>>     -                       tmp = 0;
>>     -                       printf("  [%02i] pkt_cnt:%lu\n", thr,
>>     pkt_cnt);
>>     -                       fflush(NULL);
>>     -               }
>>     +               stats.packets[thr] += pkts_ok;
>>             }
>>
>>      /* unreachable */
>>     @@ -295,6 +284,34 @@ static odp_pktio_t create_pktio(const char
>>     *dev, odp_pool_t pool,
>>             return pktio;
>>      }
>>
>>     +static void print_speed_stats(int num_workers)
>>     +{
>>     +       stats_t old_stats;
>>     +       stats_t new_stats;
>>     +       uint64_t old;
>>     +       uint64_t new;
>>     +       int i;
>>     +       int timeout = 10;
>>     +
>>     +       while (1) {
>>     +               memcpy(&old_stats, &stats, sizeof(stats_t));
>>     +               sleep(timeout);

Could we use ODP timers instead of sleep? And possibly print stats
every second, like netmap pkt-gen does, maybe make the interval
configurable?

>>     +               memcpy(&new_stats, &stats, sizeof(stats_t));
>>     +
>>     +               for (i = old = new = 0; i < num_workers; i++) {
>>     +                       old += old_stats.packets[i];
>>     +                       new += new_stats.packets[i];
>>     +               }
>>     +               printf(" %" PRIu64 " pps, ", (new - old) / timeout);
>>     +
>>     +               for (i = old = new = 0; i < num_workers; i++) {
>>     +                       old += old_stats.drops[i];
>>     +                       new += new_stats.drops[i];
>>     +               }
>>     +               printf(" %" PRIu64 " drops\n", (new - old) / timeout);
>>     +       }
>>     +}
>>     +
>>      /**
>>       * ODP L2 forwarding main function
>>       */
>>     @@ -412,6 +429,8 @@ int main(int argc, char *argv[])
>>                     cpu = odp_cpumask_next(&cpumask, cpu);
>>             }
>>
>>     +       print_speed_stats(num_workers);
>>     +
>>             /* Master thread waits for other threads to exit */
>>             odph_linux_pthread_join(thread_tbl, num_workers);
>>
>>     --
>>     1.9.1
>>
>>
>>     _______________________________________________
>>     lng-odp mailing list
>>     lng-odp@lists.linaro.org <mailto:lng-odp@lists.linaro.org>
>>     http://lists.linaro.org/mailman/listinfo/lng-odp
>>
>>
>>
>>
>> --
>> Mike Holmes
>> Technical Manager - Linaro Networking Group
>> Linaro.org <http://www.linaro.org/>***│ *Open source software for ARM SoCs
>>
>
>
> _______________________________________________
> lng-odp mailing list
> lng-odp@lists.linaro.org
> http://lists.linaro.org/mailman/listinfo/lng-odp
Maxim Uvarov March 13, 2015, 12:44 p.m. UTC | #4
Hi Ciprian, I have update of that patch, added there time to run 
odp_fwd, maximum pps and added it to odp_pktio_run script. So that make 
check can provide some numbers and we can find performance regressions. 
Even if linux-generic is not performance target regressions might be 
useful information.

On 03/13/15 13:57, Ciprian Barbu wrote:
>>>      +               sleep(timeout);
> Could we use ODP timers instead of sleep? And possibly print stats
> every second, like netmap pkt-gen does, maybe make the interval
> configurable?
>
Uhh, need to do something with that timers. Most likely to add some 
helper function with more
posix style to init and use odp posix style timers instead of timer 
pool, timer queue, timer buffer.
That is not per packet sleep. So might be sleep() is ok here. I not sure 
which where we should use
odp timers and where system timers. Or should we always use odp timers.

Maybe it's better to add parameter for time accuracy. 1 second for 
linux-generic and my x86 is too
small for output approximation. Might be other applications involved or 
laptop goes to turbo mode,
but fact is 10 seconds is good for me. So I will add parameter to 
configure that time.

Thanks,
Maxim.
diff mbox

Patch

diff --git a/example/l2fwd/odp_l2fwd.c b/example/l2fwd/odp_l2fwd.c
index d062a72..71c2fc5 100644
--- a/example/l2fwd/odp_l2fwd.c
+++ b/example/l2fwd/odp_l2fwd.c
@@ -104,6 +104,14 @@  static void parse_args(int argc, char *argv[], appl_args_t *appl_args);
 static void print_info(char *progname, appl_args_t *appl_args);
 static void usage(char *progname);
 
+/* speed and stats */
+typedef struct {
+	uint64_t packets[MAX_WORKERS];
+	uint64_t drops[MAX_WORKERS];
+} stats_t;
+
+static stats_t stats;
+
 /**
  * Packet IO worker thread using ODP queues
  *
@@ -115,8 +123,6 @@  static void *pktio_queue_thread(void *arg)
 	odp_queue_t outq_def;
 	odp_packet_t pkt;
 	odp_event_t ev;
-	unsigned long pkt_cnt = 0;
-	unsigned long err_cnt = 0;
 
 	(void)arg;
 
@@ -132,7 +138,7 @@  static void *pktio_queue_thread(void *arg)
 
 		/* Drop packets with errors */
 		if (odp_unlikely(drop_err_pkts(&pkt, 1) == 0)) {
-			EXAMPLE_ERR("Drop frame - err_cnt:%lu\n", ++err_cnt);
+			stats.drops[thr] += 1;
 			continue;
 		}
 
@@ -141,11 +147,7 @@  static void *pktio_queue_thread(void *arg)
 		/* Enqueue the packet for output */
 		odp_queue_enq(outq_def, ev);
 
-		/* Print packet counts every once in a while */
-		if (odp_unlikely(pkt_cnt++ % 100000 == 0)) {
-			printf("  [%02i] pkt_cnt:%lu\n", thr, pkt_cnt);
-			fflush(NULL);
-		}
+		stats.packets[thr] += 1;
 	}
 
 /* unreachable */
@@ -186,9 +188,6 @@  static void *pktio_ifburst_thread(void *arg)
 	thread_args_t *thr_args;
 	int pkts, pkts_ok;
 	odp_packet_t pkt_tbl[MAX_PKT_BURST];
-	unsigned long pkt_cnt = 0;
-	unsigned long err_cnt = 0;
-	unsigned long tmp = 0;
 	int src_idx, dst_idx;
 	odp_pktio_t pktio_src, pktio_dst;
 
@@ -218,23 +217,13 @@  static void *pktio_ifburst_thread(void *arg)
 		if (pkts_ok > 0)
 			odp_pktio_send(pktio_dst, pkt_tbl, pkts_ok);
 
-		if (odp_unlikely(pkts_ok != pkts)) {
-			err_cnt += pkts-pkts_ok;
-			EXAMPLE_ERR("Dropped frames:%u - err_cnt:%lu\n",
-				    pkts-pkts_ok, err_cnt);
-		}
+		if (odp_unlikely(pkts_ok != pkts))
+			stats.drops[thr] += pkts - pkts_ok;
 
 		if (pkts_ok == 0)
 			continue;
 
-		/* Print packet counts every once in a while */
-		tmp += pkts_ok;
-		if (odp_unlikely(tmp >= 100000 || pkt_cnt == 0)) {
-			pkt_cnt += tmp;
-			tmp = 0;
-			printf("  [%02i] pkt_cnt:%lu\n", thr, pkt_cnt);
-			fflush(NULL);
-		}
+		stats.packets[thr] += pkts_ok;
 	}
 
 /* unreachable */
@@ -295,6 +284,34 @@  static odp_pktio_t create_pktio(const char *dev, odp_pool_t pool,
 	return pktio;
 }
 
+static void print_speed_stats(int num_workers)
+{
+	stats_t old_stats;
+	stats_t new_stats;
+	uint64_t old;
+	uint64_t new;
+	int i;
+	int timeout = 10;
+
+	while (1) {
+		memcpy(&old_stats, &stats, sizeof(stats_t));
+		sleep(timeout);
+		memcpy(&new_stats, &stats, sizeof(stats_t));
+
+		for (i = old = new = 0; i < num_workers; i++) {
+			old += old_stats.packets[i];
+			new += new_stats.packets[i];
+		}
+		printf(" %" PRIu64 " pps, ", (new - old) / timeout);
+
+		for (i = old = new = 0; i < num_workers; i++) {
+			old += old_stats.drops[i];
+			new += new_stats.drops[i];
+		}
+		printf(" %" PRIu64 " drops\n", (new - old) / timeout);
+	}
+}
+
 /**
  * ODP L2 forwarding main function
  */
@@ -412,6 +429,8 @@  int main(int argc, char *argv[])
 		cpu = odp_cpumask_next(&cpumask, cpu);
 	}
 
+	print_speed_stats(num_workers);
+
 	/* Master thread waits for other threads to exit */
 	odph_linux_pthread_join(thread_tbl, num_workers);