diff mbox

[PATCHv2,1/2] example: l2fwd print packets per second

Message ID 1426259027-14083-2-git-send-email-maxim.uvarov@linaro.org
State New
Headers show

Commit Message

Maxim Uvarov March 13, 2015, 3:03 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 | 98 ++++++++++++++++++++++++++++++++++-------------
 1 file changed, 71 insertions(+), 27 deletions(-)

Comments

Ciprian Barbu March 16, 2015, 10:22 a.m. UTC | #1
So are we still going with this change, before moving l2fwd to
performance tests?

On Mon, Mar 16, 2015 at 10:26 AM, Savolainen, Petri (Nokia - FI/Espoo)
<petri.savolainen@nokia.com> wrote:
>
>
>> -----Original Message-----
>> From: lng-odp-bounces@lists.linaro.org [mailto:lng-odp-
>> bounces@lists.linaro.org] On Behalf Of ext Maxim Uvarov
>> Sent: Friday, March 13, 2015 5:04 PM
>> To: lng-odp@lists.linaro.org
>> Subject: [lng-odp] [PATCHv2 1/2] example: l2fwd print packets per second
>>
>> 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 | 98 ++++++++++++++++++++++++++++++++++--------
>> -----
>>  1 file changed, 71 insertions(+), 27 deletions(-)
>>
>> diff --git a/example/l2fwd/odp_l2fwd.c b/example/l2fwd/odp_l2fwd.c
>> index d062a72..0299278 100644
>> --- a/example/l2fwd/odp_l2fwd.c
>> +++ b/example/l2fwd/odp_l2fwd.c
>> @@ -73,6 +73,8 @@ typedef struct {
>>       int if_count;           /**< Number of interfaces to be used */
>>       char **if_names;        /**< Array of pointers to interface names */
>>       int mode;               /**< Packet IO mode */
>> +     int time;               /**< Time in seconds to run. */
>> +     int accuracy;           /**< Number of seconds to get and print
>> statistics */
>>  } appl_args_t;
>>
>>  /**
>> @@ -104,6 +106,15 @@ 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;
>> +static int exit_threads;
>> +
>>  /**
>>   * Packet IO worker thread using ODP queues
>>   *
>> @@ -115,8 +126,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;
>>
>> @@ -125,14 +134,14 @@ static void *pktio_queue_thread(void *arg)
>>       printf("[%02i] QUEUE mode\n", thr);
>>
>>       /* Loop packets */
>> -     for (;;) {
>> +     while (!exit_threads) {
>>               /* Use schedule to get buf from any input queue */
>>               ev  = odp_schedule(NULL, ODP_SCHED_WAIT);
>>               pkt = odp_packet_from_event(ev);
>>
>>               /* 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 +150,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;
>
> Per thread statistics should be forced to separate cache lines. Now this line is likely to cause a cache miss per packet due to false sharing.
>
>>       }
>>
>>  /* unreachable */
>> @@ -186,9 +191,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;
>>
>> @@ -208,7 +210,7 @@ static void *pktio_ifburst_thread(void *arg)
>>              odp_pktio_to_u64(pktio_src), odp_pktio_to_u64(pktio_dst));
>>
>>       /* Loop packets */
>> -     for (;;) {
>> +     while (!exit_threads) {
>>               pkts = odp_pktio_recv(pktio_src, pkt_tbl, MAX_PKT_BURST);
>>               if (pkts <= 0)
>>                       continue;
>> @@ -218,23 +220,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 +287,41 @@ static odp_pktio_t create_pktio(const char *dev,
>> odp_pool_t pool,
>>       return pktio;
>>  }
>>
>> +static void print_speed_stats(int num_workers, int duration, int timeout)
>> +{
>> +     stats_t old_stats, new_stats;
>> +     uint64_t pps, maximum_pps = 0;
>> +     uint64_t errors;
>> +     int i;
>> +     int elapsed = 0;
>> +
>> +     do {
>> +             memcpy(&old_stats, &stats, sizeof(stats_t));
>
> No need to copy two times. One copy, destination pointer ping-pong and time measurement over the entire loop would do the trick.
>
> -Petri
>
>
>> +             sleep(timeout);
>> +             memcpy(&new_stats, &stats, sizeof(stats_t));
>> +
>> +             for (i = 0, pps = 0; i < num_workers; i++)
>> +                     pps += new_stats.packets[i] - old_stats.packets[i];
>> +             pps = pps / timeout;
>> +             if (pps > maximum_pps)
>> +                     maximum_pps = pps;
>> +
>> +             printf("%" PRIu64 " pps, %" PRIu64 " max pps, ",  pps,
>> +                    maximum_pps);
>> +
>> +             for (i = 0, errors = 0; i < num_workers; i++)
>> +                     errors += new_stats.drops[i] - old_stats.drops[i];
>> +             errors = errors / timeout;
>> +
>> +             printf(" %" PRIu64 " drops\n", errors);
>> +             elapsed += timeout;
>> +     } while (elapsed < duration);
>> +
>> +     printf("TEST RESULT: %" PRIu64 " maximum packets per second.\n",
>> +            maximum_pps);
>> +     return;
>> +}
>> +
>>  /**
>>   * ODP L2 forwarding main function
>>   */
>> @@ -412,6 +439,11 @@ int main(int argc, char *argv[])
>>               cpu = odp_cpumask_next(&cpumask, cpu);
>>       }
>>
>> +     print_speed_stats(num_workers, gbl_args->appl.time,
>> +                       gbl_args->appl.accuracy);
>> +
>> +     exit_threads = 1;
>> +
>>       /* Master thread waits for other threads to exit */
>>       odph_linux_pthread_join(thread_tbl, num_workers);
>>
>> @@ -467,16 +499,20 @@ static void parse_args(int argc, char *argv[],
>> appl_args_t *appl_args)
>>       int i;
>>       static struct option longopts[] = {
>>               {"count", required_argument, NULL, 'c'},
>> +             {"time", required_argument, NULL, 't'},
>> +             {"accuracy", required_argument, NULL, 'a'},
>>               {"interface", required_argument, NULL, 'i'},    /* return 'i'
>> */
>>               {"mode", required_argument, NULL, 'm'},         /* return 'm'
>> */
>>               {"help", no_argument, NULL, 'h'},               /* return 'h' */
>>               {NULL, 0, NULL, 0}
>>       };
>>
>> +     appl_args->time =  0;
>> +     appl_args->accuracy = 10; /* get and print pps stats each X seconds
>> */
>>       appl_args->mode = -1; /* Invalid, must be changed by parsing */
>>
>>       while (1) {
>> -             opt = getopt_long(argc, argv, "+c:i:m:h",
>> +             opt = getopt_long(argc, argv, "+c:+t:+a:i:m:h",
>>                                 longopts, &long_index);
>>
>>               if (opt == -1)
>> @@ -486,6 +522,12 @@ static void parse_args(int argc, char *argv[],
>> appl_args_t *appl_args)
>>               case 'c':
>>                       appl_args->cpu_count = atoi(optarg);
>>                       break;
>> +             case 't':
>> +                     appl_args->time = atoi(optarg);
>> +                     break;
>> +             case 'a':
>> +                     appl_args->accuracy = atoi(optarg);
>> +                     break;
>>                       /* parse packet-io interface names */
>>               case 'i':
>>                       len = strlen(optarg);
>> @@ -612,6 +654,8 @@ static void usage(char *progname)
>>              "\n"
>>              "Optional OPTIONS\n"
>>              "  -c, --count <number> CPU count.\n"
>> +            "  -t, --time  <number> Time in seconds to run.\n"
>> +            "  -a, --accuracy <number> Time in seconds get print
>> statistics.\n"
>>              "  -h, --help           Display help and exit.\n\n"
>>              " environment variables: ODP_PKTIO_DISABLE_SOCKET_MMAP\n"
>>              "                        ODP_PKTIO_DISABLE_SOCKET_MMSG\n"
>> --
>> 1.9.1
>>
>>
>> _______________________________________________
>> lng-odp mailing list
>> lng-odp@lists.linaro.org
>> http://lists.linaro.org/mailman/listinfo/lng-odp
>
> _______________________________________________
> lng-odp mailing list
> lng-odp@lists.linaro.org
> http://lists.linaro.org/mailman/listinfo/lng-odp
Maxim Uvarov March 16, 2015, 2:53 p.m. UTC | #2
On 03/16/15 13:22, Ciprian Barbu wrote:
> So are we still going with this change, before moving l2fwd to
> performance tests?
I think yes, why not? Even if after then it will be not hard to ally 
that patch.


> On Mon, Mar 16, 2015 at 10:26 AM, Savolainen, Petri (Nokia - FI/Espoo)
> <petri.savolainen@nokia.com> wrote:
>>
>>> -----Original Message-----
>>> From: lng-odp-bounces@lists.linaro.org [mailto:lng-odp-
>>> bounces@lists.linaro.org] On Behalf Of ext Maxim Uvarov
>>> Sent: Friday, March 13, 2015 5:04 PM
>>> To: lng-odp@lists.linaro.org
>>> Subject: [lng-odp] [PATCHv2 1/2] example: l2fwd print packets per second
>>>
>>> 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 | 98 ++++++++++++++++++++++++++++++++++--------
>>> -----
>>>   1 file changed, 71 insertions(+), 27 deletions(-)
>>>
>>> diff --git a/example/l2fwd/odp_l2fwd.c b/example/l2fwd/odp_l2fwd.c
>>> index d062a72..0299278 100644
>>> --- a/example/l2fwd/odp_l2fwd.c
>>> +++ b/example/l2fwd/odp_l2fwd.c
>>> @@ -73,6 +73,8 @@ typedef struct {
>>>        int if_count;           /**< Number of interfaces to be used */
>>>        char **if_names;        /**< Array of pointers to interface names */
>>>        int mode;               /**< Packet IO mode */
>>> +     int time;               /**< Time in seconds to run. */
>>> +     int accuracy;           /**< Number of seconds to get and print
>>> statistics */
>>>   } appl_args_t;
>>>
>>>   /**
>>> @@ -104,6 +106,15 @@ 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;
>>> +static int exit_threads;
>>> +
>>>   /**
>>>    * Packet IO worker thread using ODP queues
>>>    *
>>> @@ -115,8 +126,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;
>>>
>>> @@ -125,14 +134,14 @@ static void *pktio_queue_thread(void *arg)
>>>        printf("[%02i] QUEUE mode\n", thr);
>>>
>>>        /* Loop packets */
>>> -     for (;;) {
>>> +     while (!exit_threads) {
>>>                /* Use schedule to get buf from any input queue */
>>>                ev  = odp_schedule(NULL, ODP_SCHED_WAIT);
>>>                pkt = odp_packet_from_event(ev);
>>>
>>>                /* 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 +150,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;
>> Per thread statistics should be forced to separate cache lines. Now this line is likely to cause a cache miss per packet due to false sharing.

Ok, will change it. If each thread will have variable for that counter 
and pointer to that variable will be provided to main with thread args 
that has to work, right?

>>>        }
>>>
>>>   /* unreachable */
>>> @@ -186,9 +191,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;
>>>
>>> @@ -208,7 +210,7 @@ static void *pktio_ifburst_thread(void *arg)
>>>               odp_pktio_to_u64(pktio_src), odp_pktio_to_u64(pktio_dst));
>>>
>>>        /* Loop packets */
>>> -     for (;;) {
>>> +     while (!exit_threads) {
>>>                pkts = odp_pktio_recv(pktio_src, pkt_tbl, MAX_PKT_BURST);
>>>                if (pkts <= 0)
>>>                        continue;
>>> @@ -218,23 +220,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 +287,41 @@ static odp_pktio_t create_pktio(const char *dev,
>>> odp_pool_t pool,
>>>        return pktio;
>>>   }
>>>
>>> +static void print_speed_stats(int num_workers, int duration, int timeout)
>>> +{
>>> +     stats_t old_stats, new_stats;
>>> +     uint64_t pps, maximum_pps = 0;
>>> +     uint64_t errors;
>>> +     int i;
>>> +     int elapsed = 0;
>>> +
>>> +     do {
>>> +             memcpy(&old_stats, &stats, sizeof(stats_t));
>> No need to copy two times. One copy, destination pointer ping-pong and time measurement over the entire loop would do the trick.
>>
>> -Petri
>>
>>
>>> +             sleep(timeout);
>>> +             memcpy(&new_stats, &stats, sizeof(stats_t));
>>> +
>>> +             for (i = 0, pps = 0; i < num_workers; i++)
>>> +                     pps += new_stats.packets[i] - old_stats.packets[i];
>>> +             pps = pps / timeout;
>>> +             if (pps > maximum_pps)
>>> +                     maximum_pps = pps;
>>> +
>>> +             printf("%" PRIu64 " pps, %" PRIu64 " max pps, ",  pps,
>>> +                    maximum_pps);
>>> +
>>> +             for (i = 0, errors = 0; i < num_workers; i++)
>>> +                     errors += new_stats.drops[i] - old_stats.drops[i];
>>> +             errors = errors / timeout;
>>> +
>>> +             printf(" %" PRIu64 " drops\n", errors);
>>> +             elapsed += timeout;
>>> +     } while (elapsed < duration);
>>> +
>>> +     printf("TEST RESULT: %" PRIu64 " maximum packets per second.\n",
>>> +            maximum_pps);
>>> +     return;
>>> +}
>>> +
>>>   /**
>>>    * ODP L2 forwarding main function
>>>    */
>>> @@ -412,6 +439,11 @@ int main(int argc, char *argv[])
>>>                cpu = odp_cpumask_next(&cpumask, cpu);
>>>        }
>>>
>>> +     print_speed_stats(num_workers, gbl_args->appl.time,
>>> +                       gbl_args->appl.accuracy);
>>> +
>>> +     exit_threads = 1;
>>> +
>>>        /* Master thread waits for other threads to exit */
>>>        odph_linux_pthread_join(thread_tbl, num_workers);
>>>
>>> @@ -467,16 +499,20 @@ static void parse_args(int argc, char *argv[],
>>> appl_args_t *appl_args)
>>>        int i;
>>>        static struct option longopts[] = {
>>>                {"count", required_argument, NULL, 'c'},
>>> +             {"time", required_argument, NULL, 't'},
>>> +             {"accuracy", required_argument, NULL, 'a'},
>>>                {"interface", required_argument, NULL, 'i'},    /* return 'i'
>>> */
>>>                {"mode", required_argument, NULL, 'm'},         /* return 'm'
>>> */
>>>                {"help", no_argument, NULL, 'h'},               /* return 'h' */
>>>                {NULL, 0, NULL, 0}
>>>        };
>>>
>>> +     appl_args->time =  0;
>>> +     appl_args->accuracy = 10; /* get and print pps stats each X seconds
>>> */
>>>        appl_args->mode = -1; /* Invalid, must be changed by parsing */
>>>
>>>        while (1) {
>>> -             opt = getopt_long(argc, argv, "+c:i:m:h",
>>> +             opt = getopt_long(argc, argv, "+c:+t:+a:i:m:h",
>>>                                  longopts, &long_index);
>>>
>>>                if (opt == -1)
>>> @@ -486,6 +522,12 @@ static void parse_args(int argc, char *argv[],
>>> appl_args_t *appl_args)
>>>                case 'c':
>>>                        appl_args->cpu_count = atoi(optarg);
>>>                        break;
>>> +             case 't':
>>> +                     appl_args->time = atoi(optarg);
>>> +                     break;
>>> +             case 'a':
>>> +                     appl_args->accuracy = atoi(optarg);
>>> +                     break;
>>>                        /* parse packet-io interface names */
>>>                case 'i':
>>>                        len = strlen(optarg);
>>> @@ -612,6 +654,8 @@ static void usage(char *progname)
>>>               "\n"
>>>               "Optional OPTIONS\n"
>>>               "  -c, --count <number> CPU count.\n"
>>> +            "  -t, --time  <number> Time in seconds to run.\n"
>>> +            "  -a, --accuracy <number> Time in seconds get print
>>> statistics.\n"
>>>               "  -h, --help           Display help and exit.\n\n"
>>>               " environment variables: ODP_PKTIO_DISABLE_SOCKET_MMAP\n"
>>>               "                        ODP_PKTIO_DISABLE_SOCKET_MMSG\n"
>>> --
>>> 1.9.1
>>>
>>>
>>> _______________________________________________
>>> lng-odp mailing list
>>> lng-odp@lists.linaro.org
>>> http://lists.linaro.org/mailman/listinfo/lng-odp
>> _______________________________________________
>> lng-odp mailing list
>> lng-odp@lists.linaro.org
>> http://lists.linaro.org/mailman/listinfo/lng-odp
diff mbox

Patch

diff --git a/example/l2fwd/odp_l2fwd.c b/example/l2fwd/odp_l2fwd.c
index d062a72..0299278 100644
--- a/example/l2fwd/odp_l2fwd.c
+++ b/example/l2fwd/odp_l2fwd.c
@@ -73,6 +73,8 @@  typedef struct {
 	int if_count;		/**< Number of interfaces to be used */
 	char **if_names;	/**< Array of pointers to interface names */
 	int mode;		/**< Packet IO mode */
+	int time;		/**< Time in seconds to run. */
+	int accuracy;		/**< Number of seconds to get and print statistics */
 } appl_args_t;
 
 /**
@@ -104,6 +106,15 @@  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;
+static int exit_threads;
+
 /**
  * Packet IO worker thread using ODP queues
  *
@@ -115,8 +126,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;
 
@@ -125,14 +134,14 @@  static void *pktio_queue_thread(void *arg)
 	printf("[%02i] QUEUE mode\n", thr);
 
 	/* Loop packets */
-	for (;;) {
+	while (!exit_threads) {
 		/* Use schedule to get buf from any input queue */
 		ev  = odp_schedule(NULL, ODP_SCHED_WAIT);
 		pkt = odp_packet_from_event(ev);
 
 		/* 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 +150,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 +191,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;
 
@@ -208,7 +210,7 @@  static void *pktio_ifburst_thread(void *arg)
 	       odp_pktio_to_u64(pktio_src), odp_pktio_to_u64(pktio_dst));
 
 	/* Loop packets */
-	for (;;) {
+	while (!exit_threads) {
 		pkts = odp_pktio_recv(pktio_src, pkt_tbl, MAX_PKT_BURST);
 		if (pkts <= 0)
 			continue;
@@ -218,23 +220,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 +287,41 @@  static odp_pktio_t create_pktio(const char *dev, odp_pool_t pool,
 	return pktio;
 }
 
+static void print_speed_stats(int num_workers, int duration, int timeout)
+{
+	stats_t old_stats, new_stats;
+	uint64_t pps, maximum_pps = 0;
+	uint64_t errors;
+	int i;
+	int elapsed = 0;
+
+	do {
+		memcpy(&old_stats, &stats, sizeof(stats_t));
+		sleep(timeout);
+		memcpy(&new_stats, &stats, sizeof(stats_t));
+
+		for (i = 0, pps = 0; i < num_workers; i++)
+			pps += new_stats.packets[i] - old_stats.packets[i];
+		pps = pps / timeout;
+		if (pps > maximum_pps)
+			maximum_pps = pps;
+
+		printf("%" PRIu64 " pps, %" PRIu64 " max pps, ",  pps,
+		       maximum_pps);
+
+		for (i = 0, errors = 0; i < num_workers; i++)
+			errors += new_stats.drops[i] - old_stats.drops[i];
+		errors = errors / timeout;
+
+		printf(" %" PRIu64 " drops\n", errors);
+		elapsed += timeout;
+	} while (elapsed < duration);
+
+	printf("TEST RESULT: %" PRIu64 " maximum packets per second.\n",
+	       maximum_pps);
+	return;
+}
+
 /**
  * ODP L2 forwarding main function
  */
@@ -412,6 +439,11 @@  int main(int argc, char *argv[])
 		cpu = odp_cpumask_next(&cpumask, cpu);
 	}
 
+	print_speed_stats(num_workers, gbl_args->appl.time,
+			  gbl_args->appl.accuracy);
+
+	exit_threads = 1;
+
 	/* Master thread waits for other threads to exit */
 	odph_linux_pthread_join(thread_tbl, num_workers);
 
@@ -467,16 +499,20 @@  static void parse_args(int argc, char *argv[], appl_args_t *appl_args)
 	int i;
 	static struct option longopts[] = {
 		{"count", required_argument, NULL, 'c'},
+		{"time", required_argument, NULL, 't'},
+		{"accuracy", required_argument, NULL, 'a'},
 		{"interface", required_argument, NULL, 'i'},	/* return 'i' */
 		{"mode", required_argument, NULL, 'm'},		/* return 'm' */
 		{"help", no_argument, NULL, 'h'},		/* return 'h' */
 		{NULL, 0, NULL, 0}
 	};
 
+	appl_args->time =  0;
+	appl_args->accuracy = 10; /* get and print pps stats each X seconds */
 	appl_args->mode = -1; /* Invalid, must be changed by parsing */
 
 	while (1) {
-		opt = getopt_long(argc, argv, "+c:i:m:h",
+		opt = getopt_long(argc, argv, "+c:+t:+a:i:m:h",
 				  longopts, &long_index);
 
 		if (opt == -1)
@@ -486,6 +522,12 @@  static void parse_args(int argc, char *argv[], appl_args_t *appl_args)
 		case 'c':
 			appl_args->cpu_count = atoi(optarg);
 			break;
+		case 't':
+			appl_args->time = atoi(optarg);
+			break;
+		case 'a':
+			appl_args->accuracy = atoi(optarg);
+			break;
 			/* parse packet-io interface names */
 		case 'i':
 			len = strlen(optarg);
@@ -612,6 +654,8 @@  static void usage(char *progname)
 	       "\n"
 	       "Optional OPTIONS\n"
 	       "  -c, --count <number> CPU count.\n"
+	       "  -t, --time  <number> Time in seconds to run.\n"
+	       "  -a, --accuracy <number> Time in seconds get print statistics.\n"
 	       "  -h, --help           Display help and exit.\n\n"
 	       " environment variables: ODP_PKTIO_DISABLE_SOCKET_MMAP\n"
 	       "                        ODP_PKTIO_DISABLE_SOCKET_MMSG\n"