mactime: add a "top" command to watch device stats

Include a binary API change NOT suitable for cherry-picking into 19.08

Type: feature

Signed-off-by: Dave Barach <dave@barachs.net>
Change-Id: Id369514a3085f5e4bcee34819c55c4636df9b518
diff --git a/src/plugins/mactime/CMakeLists.txt b/src/plugins/mactime/CMakeLists.txt
index ac5077d..5477674 100644
--- a/src/plugins/mactime/CMakeLists.txt
+++ b/src/plugins/mactime/CMakeLists.txt
@@ -22,3 +22,21 @@
   API_TEST_SOURCES
   mactime_test.c
 )
+
+option(VPP_BUILD_MACTIME_TOP "Build mactime plugin 'top' tool" OFF)
+if(VPP_BUILD_MACTIME_TOP)
+  add_vpp_executable(mactime_top ENABLE_EXPORTS
+    SOURCES
+    mactime_top.c
+
+    DEPENDS api_headers
+
+    LINK_LIBRARIES
+    vlibmemoryclient
+    vppapiclient
+    svm
+    vppinfra
+    Threads::Threads
+    rt m dl crypto
+    )
+endif()
diff --git a/src/plugins/mactime/mactime.api b/src/plugins/mactime/mactime.api
index edad2c4..224f071 100644
--- a/src/plugins/mactime/mactime.api
+++ b/src/plugins/mactime/mactime.api
@@ -13,12 +13,12 @@
  * limitations under the License.
  */
 
-/** \file
+/** @file
     This file defines vpp mactime control-plane API messages
 */
-option version = "1.1.1";
+option version = "1.2.1";
 
-/** \brief api to enable or disable the time-based src mac filter on
+/** @brief api to enable or disable the time-based src mac filter on
     an interface
 */
 
@@ -31,7 +31,7 @@
   option vat_help = "<intfc> [disable]";
 };
 
-/** \brief a time range structure
+/** @brief a time range structure
  * times are in double-precision fp seconds since 1/1/1970,
  * which was a Thursday.
  */
@@ -41,7 +41,7 @@
   f64 end;                      /**< end of the time range */
 };
 
-/** \brief configure per src-mac time ranges
+/** @brief configure per src-mac time ranges
  *
  * Usage:
  *  to create a static allow entry:
@@ -56,6 +56,10 @@
  *   set each range start/end in seconds since Sunday began
  *   As in: start/end >= 0.0 && start/end < 7.0 *86400.0
  *
+ *  to create a (time-range-based) dynamic allow entry with quota:
+ *   Outside of stated time ranges, such entries revert to allow with no quota.
+ *   previous setup, s/allow=1/allow_quota=1/
+ *
  *  to create a (time-range-based) dynamic drop entry:
  *   Same procedure to create a dynamic allow entry,
  *   set drop=1 instead of allow=1
@@ -86,6 +90,58 @@
   option vat_help = "name <devname> mac <mac-addr> allow drop allow-range Mon - Fri 9:00 - 17:00";
 };
 
+/** @brief a time range, in fp seconds since Sunday midnight
+ */
+
+typedef mactime_time_range
+{
+  f64 start;
+  f64 end;
+};
+
+/** @brief dump mactime table
+ *
+ * Request a mactime client pool dump
+ * Sequence:
+ * client send vl_api_mactime_dump to vpp
+ * vpp replies with zero or more vl_api_mactime_entry_t's
+ * vpp replies with a vl_api_mactime_dump_reply_t
+ * @param my_table_epoch dump table only if update needed, 0 => full dump
+ */
+
+define mactime_dump
+{
+  u32 client_index;             /**< client index, from api_main */
+  u32 context;                  /**< application context */
+  u32 my_table_epoch;           /**< to suppress dump if no changes */
+};
+
+/** @brief mactime table entry details
+ */
+
+define mactime_details
+{
+  u32 context;
+  u32 pool_index;
+  u8 mac_address[6];
+  u64 data_quota;
+  u64 data_used_in_range;
+  u32 flags;
+  u8 device_name[64];
+  u32 nranges;
+  vl_api_mactime_time_range_t ranges[nranges];
+};
+
+/** @brief dump mactime table reply
+ * Includes the vpp table epoch, needed to optimize API traffic
+ */
+define mactime_dump_reply
+{
+  u32 context;
+  i32 retval;
+  u32 table_epoch;
+};
+
 /*
  * Local Variables:
  * eval: (c-set-style "gnu")
diff --git a/src/plugins/mactime/mactime.c b/src/plugins/mactime/mactime.c
index e346171..502ac98 100644
--- a/src/plugins/mactime/mactime.c
+++ b/src/plugins/mactime/mactime.c
@@ -158,6 +158,77 @@
   REPLY_MACRO (VL_API_MACTIME_ENABLE_DISABLE_REPLY);
 }
 
+static void
+vl_api_mactime_dump_t_handler (vl_api_mactime_dump_t * mp)
+{
+  vl_api_mactime_details_t *ep;
+  vl_api_mactime_dump_reply_t *rmp;
+  mactime_device_t *dev;
+  mactime_main_t *mm = &mactime_main;
+  vl_api_registration_t *rp;
+  int rv = 0, i;
+  u32 his_table_epoch = clib_net_to_host_u32 (mp->my_table_epoch);
+  u32 message_size;
+  u32 name_len;
+  u32 nranges;
+
+  rp = vl_api_client_index_to_registration (mp->client_index);
+  if (rp == 0)
+    return;
+
+  if (his_table_epoch == mm->device_table_epoch)
+    {
+      rv = VNET_API_ERROR_NO_CHANGE;
+      goto send_reply;
+    }
+
+  /* *INDENT-OFF* */
+  pool_foreach (dev, mm->devices,
+  ({
+    message_size = sizeof(*ep) + vec_len(dev->device_name) +
+      vec_len(dev->ranges) * sizeof(ep->ranges[0]);
+
+    ep = vl_msg_api_alloc (message_size);
+    memset (ep, 0, message_size);
+    ep->_vl_msg_id = clib_host_to_net_u16 (VL_API_MACTIME_DETAILS
+                                           + mm->msg_id_base);
+    /* Index is the key for the stats segment combined counters */
+    ep->pool_index = clib_host_to_net_u32 (dev - mm->devices);
+
+    clib_memcpy_fast (ep->mac_address, dev->mac_address,
+                      sizeof (ep->mac_address));
+    ep->data_quota = clib_host_to_net_u64 (dev->data_quota);
+    ep->data_used_in_range = clib_host_to_net_u64 (dev->data_used_in_range);
+    ep->flags = clib_host_to_net_u32 (dev->flags);
+    nranges = vec_len (dev->ranges);
+    ep->nranges = clib_host_to_net_u32 (nranges);
+
+    for (i = 0; i < vec_len (dev->ranges); i++)
+      {
+        ep->ranges[i].start = dev->ranges[i].start;
+        ep->ranges[i].end = dev->ranges[i].end;
+      }
+
+    name_len = vec_len (dev->device_name);
+    name_len = (name_len < ARRAY_LEN(ep->device_name)) ?
+      name_len : ARRAY_LEN(ep->device_name) - 1;
+
+    clib_memcpy_fast (ep->device_name, dev->device_name,
+                      name_len);
+    ep->device_name [ARRAY_LEN(ep->device_name) -1] = 0;
+    vl_api_send_msg (rp, (u8 *)ep);
+  }));
+  /* *INDENT-OFF* */
+
+ send_reply:
+  /* *INDENT-OFF* */
+  REPLY_MACRO2 (VL_API_MACTIME_DUMP_REPLY,
+  ({
+    rmp->table_epoch = clib_host_to_net_u32 (mm->device_table_epoch);
+  }));
+  /* *INDENT-ON* */
+}
+
 /** Create a lookup table entry for the indicated mac address
  */
 void
@@ -201,6 +272,14 @@
 
   feature_init (mm);
 
+  /*
+   * Change the table epoch. Skip 0 so clients can code my_table_epoch = 0
+   * to receive a full dump.
+   */
+  mm->device_table_epoch++;
+  if (PREDICT_FALSE (mm->device_table_epoch == 0))
+    mm->device_table_epoch++;
+
   data_quota = clib_net_to_host_u64 (mp->data_quota);
 
   clib_memset (&kv, 0, sizeof (kv));
diff --git a/src/plugins/mactime/mactime.h b/src/plugins/mactime/mactime.h
index 11e33c1..2ce1cf9 100644
--- a/src/plugins/mactime/mactime.h
+++ b/src/plugins/mactime/mactime.h
@@ -29,26 +29,7 @@
 #include <vppinfra/time_range.h>
 #include <vppinfra/bihash_8_8.h>
 
-#define MACTIME_RANGE_TYPE_DROP 0
-#define MACTIME_RANGE_TYPE_ALLOW 1
-
-typedef struct
-{
-  u8 *device_name;
-  u8 mac_address[6];
-  u64 data_quota;
-  u64 data_used_in_range;
-  u32 flags;
-  clib_timebase_range_t *ranges;
-} mactime_device_t;
-
-/** Always drop packets from this device */
-#define MACTIME_DEVICE_FLAG_STATIC_DROP		(1<<0)
-#define MACTIME_DEVICE_FLAG_STATIC_ALLOW	(1<<1)
-#define MACTIME_DEVICE_FLAG_DYNAMIC_DROP	(1<<2)
-#define MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW	(1<<3)
-#define MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW_QUOTA	(1<<4)
-#define MACTIME_DEVICE_FLAG_DROP_UDP_10001      (1<<5)
+#include <mactime/mactime_device.h>
 
 typedef struct
 {
@@ -75,6 +56,7 @@
 
   /* Device table */
   mactime_device_t *devices;
+  u32 device_table_epoch;
 
   /* Counters */
   vlib_combined_counter_main_t allow_counters;
diff --git a/src/plugins/mactime/mactime_device.h b/src/plugins/mactime/mactime_device.h
new file mode 100644
index 0000000..d747f09
--- /dev/null
+++ b/src/plugins/mactime/mactime_device.h
@@ -0,0 +1,53 @@
+/*
+ * mactime_device.h - device table entry
+ *
+ * Copyright (c) 2018 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef included_mactime_device_h
+#define included_mactime_device_h
+#include <vppinfra/time_range.h>
+
+#define MACTIME_RANGE_TYPE_DROP 0
+#define MACTIME_RANGE_TYPE_ALLOW 1
+
+typedef struct
+{
+  u8 *device_name;
+  u8 mac_address[6];
+  u64 data_quota;
+  u64 data_used_in_range;
+  u32 flags;
+  u32 pool_index;
+  f64 last_seen;
+  clib_timebase_range_t *ranges;
+} mactime_device_t;
+
+/** Always drop packets from this device */
+#define MACTIME_DEVICE_FLAG_STATIC_DROP		(1<<0)
+#define MACTIME_DEVICE_FLAG_STATIC_ALLOW	(1<<1)
+#define MACTIME_DEVICE_FLAG_DYNAMIC_DROP	(1<<2)
+#define MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW	(1<<3)
+#define MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW_QUOTA	(1<<4)
+#define MACTIME_DEVICE_FLAG_DROP_UDP_10001      (1<<5)
+
+#endif /* included_mactime_device_h */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/mactime/mactime_test.c b/src/plugins/mactime/mactime_test.c
index c13ae8c..f7f4600 100644
--- a/src/plugins/mactime/mactime_test.c
+++ b/src/plugins/mactime/mactime_test.c
@@ -20,6 +20,8 @@
 #include <vppinfra/error.h>
 #include <vppinfra/time_range.h>
 #include <vnet/ethernet/ethernet.h>
+#include <mactime/mactime_device.h>
+#include <vpp-api/client/stat_client.h>
 
 /* Declare message IDs */
 #include <mactime/mactime.api_enum.h>
@@ -27,6 +29,16 @@
 
 typedef struct
 {
+  /* device table */
+  mactime_device_t *devices;
+  uword *device_by_device_name;
+  u32 vpp_table_epoch;
+
+  /* time range setup */
+  f64 sunday_midnight;
+  clib_timebase_t timebase;
+  f64 timezone_offset;
+
   /* API message ID base */
   u16 msg_id_base;
   vat_main_t *vat_main;
@@ -78,6 +90,206 @@
   return ret;
 }
 
+#if VPP_API_TEST_BUILTIN
+extern u8 *format_bytes_with_width (u8 * s, va_list * va);
+#else
+u8 *
+format_bytes_with_width (u8 * s, va_list * va)
+{
+  uword nbytes = va_arg (*va, u64);
+  int width = va_arg (*va, int);
+  f64 nbytes_f64;
+  u8 *fmt;
+  char *suffix = "";
+
+  if (width > 0)
+    fmt = format (0, "%%%d.3f%%s%c", width, 0);
+  else
+    fmt = format (0, "%%.3f%%s%c", 0);
+
+  if (nbytes > (1024ULL * 1024ULL * 1024ULL))
+    {
+      nbytes_f64 = ((f64) nbytes) / (1024.0 * 1024.0 * 1024.0);
+      suffix = "G";
+    }
+  else if (nbytes > (1024ULL * 1024ULL))
+    {
+      nbytes_f64 = ((f64) nbytes) / (1024.0 * 1024.0);
+      suffix = "M";
+    }
+  else if (nbytes > 1024ULL)
+    {
+      nbytes_f64 = ((f64) nbytes) / (1024.0);
+      suffix = "K";
+    }
+  else
+    {
+      nbytes_f64 = (f64) nbytes;
+      suffix = "B";
+    }
+
+  s = format (s, (char *) fmt, nbytes_f64, suffix);
+  vec_free (fmt);
+  return s;
+}
+#endif
+
+static u8 *
+format_device (u8 * s, va_list * args)
+{
+  mactime_device_t *dp = va_arg (*args, mactime_device_t *);
+  mactime_test_main_t *mm = &mactime_test_main;
+  int verbose = va_arg (*args, int);
+  int current_status = 99;
+  char *status_string;
+  u8 *macstring = 0;
+  f64 now;
+  int j;
+
+  if (dp == 0)
+    {
+      s = format (s, "%-15s %5s %18s %14s %10s %11s %13s",
+		  "Device Name", "Index", "Addresses", "Status",
+		  "AllowPkt", "AllowByte", "DropPkt");
+      vec_add1 (s, '\n');
+      return s;
+    }
+
+  now = clib_timebase_now (&mm->timebase);
+
+  /* Check dynamic ranges */
+  for (j = 0; j < vec_len (dp->ranges); j++)
+    {
+      clib_timebase_range_t *r = dp->ranges + j;
+      f64 start0, end0;
+
+      start0 = r->start + mm->sunday_midnight;
+      end0 = r->end + mm->sunday_midnight;
+      if (verbose)
+	s = format (s, "  Range %d: %U - %U\n", j,
+		    format_clib_timebase_time, start0,
+		    format_clib_timebase_time, end0);
+
+      if (now >= start0 && now <= end0)
+	{
+	  if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW)
+	    current_status = 3;
+	  else if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW_QUOTA)
+	    current_status = 5;
+	  else
+	    current_status = 2;
+	  if (verbose)
+	    {
+	      s = format (s, "  Time in range %d:", j);
+	      s = format (s, "     %U - %U\n",
+			  format_clib_timebase_time, start0,
+			  format_clib_timebase_time, end0);
+	    }
+	  goto print;
+	}
+    }
+  if (verbose && j)
+    s = format (s, "  No range match.\n");
+  if (dp->flags & MACTIME_DEVICE_FLAG_STATIC_DROP)
+    current_status = 0;
+  if (dp->flags & MACTIME_DEVICE_FLAG_STATIC_ALLOW)
+    current_status = 1;
+  if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW)
+    current_status = 2;
+  if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_DROP)
+    current_status = 3;
+  if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW_QUOTA)
+    current_status = 4;
+
+print:
+  macstring = format (0, "%U", format_mac_address, dp->mac_address);
+  switch (current_status)
+    {
+    case 0:
+      status_string = "static drop";
+      break;
+    case 1:
+      status_string = "static allow";
+      break;
+    case 2:
+      status_string = "dynamic drop";
+      break;
+    case 3:
+      status_string = "dynamic allow";
+      break;
+    case 4:
+      status_string = "d-quota inact";
+      break;
+    case 5:
+      status_string = "d-quota activ";
+      break;
+    default:
+      status_string = "code bug!";
+      break;
+    }
+
+  s = format (s, "%-15s %5d %18s %14s\n",
+	      dp->device_name, dp->pool_index, macstring, status_string);
+  vec_free (macstring);
+
+  if (dp->data_quota > 0)
+    {
+      s = format (s, "%-59s %s%U %s%U", " ", "Quota ",
+		  format_bytes_with_width, dp->data_quota, 10,
+		  "Use ", format_bytes_with_width, dp->data_used_in_range, 8);
+      vec_add1 (s, '\n');
+    }
+  return s;
+}
+
+static int
+api_mactime_dump (vat_main_t * vam)
+{
+  mactime_test_main_t *tm = &mactime_test_main;
+  unformat_input_t *i = vam->input;
+  vl_api_mactime_dump_t *mp;
+  int verbose = 0;
+  int ret;
+  f64 now;
+  mactime_device_t *dev;
+
+  now = clib_timebase_now (&tm->timebase);
+
+  if (PREDICT_FALSE ((now - tm->sunday_midnight) > 86400.0 * 7.0))
+    tm->sunday_midnight = clib_timebase_find_sunday_midnight (now);
+
+  /* Parse args required to build the message */
+  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (i, "force"))
+	tm->vpp_table_epoch = 0;
+      else if (unformat (i, "verbose"))
+	verbose = 1;
+      else
+	break;
+    }
+
+  /* Construct the API message */
+  M (MACTIME_DUMP, mp);
+  mp->my_table_epoch = clib_host_to_net_u32 (tm->vpp_table_epoch);
+
+  /* send it... */
+  S (mp);
+
+  /* Wait for a reply... */
+  W (ret);
+
+  fformat (vam->ofp, "%U", format_device, 0 /* header */ , 0 /* verbose */ );
+  /* *INDENT-OFF* */
+  pool_foreach (dev, tm->devices,
+  ({
+    fformat (vam->ofp, "%U", format_device, dev, verbose);
+  }));
+  /* *INDENT-ON* */
+
+  return ret;
+}
+
 /* These two ought to be in a library somewhere but they aren't */
 static uword
 my_unformat_mac_address (unformat_input_t * input, va_list * args)
@@ -212,6 +424,14 @@
   return ret;
 }
 
+/* We shouldn't get these... */
+static void
+vl_api_mactime_details_t_handler (vl_api_mactime_details_t * mp)
+{
+  clib_warning ("WARNING: stub called...");
+}
+
+
 #include <mactime/mactime.api_test.c>
 
 /*
diff --git a/src/plugins/mactime/mactime_top.c b/src/plugins/mactime/mactime_top.c
new file mode 100644
index 0000000..df7c755
--- /dev/null
+++ b/src/plugins/mactime/mactime_top.c
@@ -0,0 +1,505 @@
+#include <vppinfra/time.h>
+#include <vppinfra/hash.h>
+#include <vppinfra/pool.h>
+#include <vpp/stats/stat_segment.h>
+#include <vpp-api/client/stat_client.h>
+#include <vppinfra/vec.h>
+#include <mactime/mactime_device.h>
+#include <vlibapi/api_common.h>
+#include <vlibmemory/memory_client.h>
+#include <vlibmemory/api.h>
+#include <vnet/api_errno.h>
+#include <svm/queue.h>
+
+/* define message IDs */
+#include <mactime/mactime.api_enum.h>
+#include <mactime/mactime.api_types.h>
+
+typedef struct
+{
+  /* device database */
+  uword *device_by_device_name;
+  mactime_device_t *devices;
+  u32 my_table_epoch;
+
+  /* Stat segment variables */
+  stat_client_main_t *stat_client_main;
+  u8 **pattern1, **pattern2;
+  u32 *ls_result1, *ls_result2;
+  vlib_counter_t *allow_counters;
+  vlib_counter_t *drop_counters;
+
+  /* Timebase */
+  clib_time_t clib_time;
+  clib_timebase_t timebase;
+  f64 timezone_offset;
+  f64 sunday_midnight;
+
+  /* API message-handling */
+  svm_queue_t *vl_input_queue;
+  u32 my_client_index;
+  u16 msg_id_base;
+  volatile u32 result_ready;
+  volatile i32 retval;
+} mt_main_t;
+
+mt_main_t mt_main;
+
+/* Indispensable for debugging in gdb... */
+
+u32
+vl (void *x)
+{
+  return vec_len (x);
+}
+
+#define foreach_mactime_api_msg                 \
+_(MACTIME_DUMP_REPLY, mactime_dump_reply)       \
+_(MACTIME_DETAILS, mactime_details)
+
+static void vl_api_mactime_dump_reply_t_handler
+  (vl_api_mactime_dump_reply_t * mp)
+{
+  mt_main_t *mm = &mt_main;
+  i32 retval = clib_net_to_host_u32 (mp->retval);
+
+  mm->retval = retval;
+  mm->result_ready = 1;
+}
+
+static void
+vl_api_mactime_details_t_handler (vl_api_mactime_details_t * mp)
+{
+  mt_main_t *mm = &mt_main;
+  mactime_device_t *dev;
+  int i;
+  clib_timebase_range_t *rp;
+  uword *p;
+
+  if (PREDICT_FALSE (mm->device_by_device_name == 0))
+    mm->device_by_device_name = hash_create_string (0, sizeof (uword));
+
+  p = hash_get_mem (mm->device_by_device_name, mp->device_name);
+  if (p)
+    dev = pool_elt_at_index (mm->devices, p[0]);
+  else
+    {
+      u8 *hash_name_copy = format (0, "%s%c", mp->device_name, 0);
+      pool_get (mm->devices, dev);
+      memset (dev, 0, sizeof (*dev));
+      dev->device_name = vec_dup (hash_name_copy);
+      hash_set_mem (mm->device_by_device_name, hash_name_copy,
+		    dev - mm->devices);
+    }
+
+  clib_memcpy_fast (dev->mac_address, mp->mac_address,
+		    sizeof (dev->mac_address));
+  dev->data_quota = clib_net_to_host_u64 (mp->data_quota);
+  dev->data_used_in_range = clib_net_to_host_u64 (mp->data_used_in_range);
+  dev->flags = clib_net_to_host_u32 (mp->flags);
+  dev->pool_index = clib_net_to_host_u32 (mp->pool_index);
+  vec_reset_length (dev->ranges);
+  for (i = 0; i < clib_net_to_host_u32 (mp->nranges); i++)
+    {
+      vec_add2 (dev->ranges, rp, 1);
+      rp->start = mp->ranges[i].start;
+      rp->end = mp->ranges[i].end;
+    }
+}
+
+#define vl_print(handle, ...) fformat(handle, __VA_ARGS__)
+
+#define vl_endianfun
+#define vl_printfun
+#define vl_api_version(n,v) static u32 api_version = v;
+#include <mactime/mactime.api.h>
+#undef vl_api_version
+#undef vl_printfun
+#undef vl_endianfun
+
+static int
+connect_to_vpp (char *name)
+{
+  api_main_t *am = &api_main;
+  mt_main_t *mm = &mt_main;
+  u8 *msg_base_lookup_name;
+
+  if (vl_client_connect_to_vlib ("/vpe-api", name, 32) < 0)
+    return -1;
+
+  mm->vl_input_queue = am->shmem_hdr->vl_input_queue;
+  mm->my_client_index = am->my_client_index;
+
+  msg_base_lookup_name = format (0, "mactime_%08x%c", api_version, 0);
+
+  mm->msg_id_base = vl_client_get_first_plugin_msg_id
+    ((char *) msg_base_lookup_name);
+
+  vec_free (msg_base_lookup_name);
+
+  if (mm->msg_id_base == ~0)
+    return -1;
+
+#define _(N,n)                                                  \
+    vl_msg_api_set_handlers((VL_API_##N + mm->msg_id_base),     \
+                           #n,					\
+                           vl_api_##n##_t_handler,              \
+                           vl_noop_handler,                     \
+                           vl_api_##n##_t_endian,               \
+                           vl_api_##n##_t_print,                \
+                           sizeof(vl_api_##n##_t), 1);
+  foreach_mactime_api_msg;
+#undef _
+
+  return 0;
+}
+
+static void
+dump_mactime_table (mt_main_t * mm)
+{
+  vl_api_mactime_dump_t *mp;
+  u32 deadman_counter = 1000;
+
+  /* Send the dump request */
+  mp = vl_msg_api_alloc (sizeof (*mp));
+  memset (mp, 0, sizeof (*mp));
+  mp->_vl_msg_id =
+    clib_host_to_net_u16 (VL_API_MACTIME_DUMP + mm->msg_id_base);
+  mp->client_index = mm->my_client_index;
+  mp->my_table_epoch = mm->my_table_epoch;
+  vl_msg_api_send_shmem (mm->vl_input_queue, (u8 *) & mp);
+
+  /* Wait up to 1 second for vpp to reply */
+  while (deadman_counter-- && mm->result_ready == 0)
+    unix_sleep (1e-3);
+
+  if (mm->retval && (mm->retval != VNET_API_ERROR_NO_CHANGE))
+    clib_warning ("dump reply %d", mm->retval);
+
+}
+
+static void
+scrape_stats_segment (mt_main_t * mm)
+{
+  vlib_counter_t **counters_by_thread;
+  vlib_counter_t *counters;
+  u64 *offset_vector;
+  mactime_device_t *dev;
+  stat_segment_access_t sa;
+  stat_client_main_t *sm = mm->stat_client_main;
+  stat_segment_directory_entry_t *ep;
+  int need_update2 = 0;
+  static u32 *pool_indices;
+  int i, j;
+
+  vec_reset_length (pool_indices);
+  /* *INDENT-OFF* */
+  pool_foreach (dev, mm->devices,
+  ({
+    vec_add1 (pool_indices, dev->pool_index);
+  }));
+  /* *INDENT-ON* */
+
+  /* Nothing to do... */
+  if (vec_len (pool_indices) == 0)
+    return;
+
+again1:
+
+  /* Has directory been updated? */
+  if (mm->ls_result1 == 0 || (sm->shared_header->epoch != sm->current_epoch))
+    {
+      need_update2 = 1;
+      vec_free (mm->ls_result1);
+      mm->ls_result1 = stat_segment_ls (mm->pattern1);
+    }
+
+  stat_segment_access_start (&sa, sm);
+
+  ep = vec_elt_at_index (sm->directory_vector, mm->ls_result1[0]);
+  counters_by_thread = stat_segment_pointer (sm->shared_header, ep->offset);
+  offset_vector = stat_segment_pointer (sm->shared_header, ep->offset_vector);
+
+  for (i = 0; i < vec_len (pool_indices); i++)
+    {
+      u32 index = pool_indices[i];
+
+      vec_validate (mm->allow_counters, index);
+      mm->allow_counters[index].packets = 0;
+      mm->allow_counters[index].bytes = 0;
+
+      for (j = 0; j < vec_len (counters_by_thread); j++)
+	{
+	  counters = stat_segment_pointer (sm->shared_header,
+					   offset_vector[j]);
+	  mm->allow_counters[index].packets += counters[index].packets;
+	  mm->allow_counters[index].bytes += counters[index].bytes;
+	}
+    }
+
+  /* Ugh, segment changed during access. Try again */
+  if (stat_segment_access_end (&sa, sm))
+    goto again1;
+
+  /* Has directory been updated? */
+  if (mm->ls_result2 == 0 || need_update2)
+    {
+      vec_free (mm->ls_result2);
+      mm->ls_result2 = stat_segment_ls (mm->pattern2);
+    }
+
+again2:
+  stat_segment_access_start (&sa, sm);
+
+  ep = vec_elt_at_index (sm->directory_vector, mm->ls_result2[0]);
+  counters_by_thread = stat_segment_pointer (sm->shared_header, ep->offset);
+  offset_vector = stat_segment_pointer (sm->shared_header, ep->offset_vector);
+
+  for (i = 0; i < vec_len (pool_indices); i++)
+    {
+      u32 index = pool_indices[i];
+
+      vec_validate (mm->drop_counters, index);
+      mm->drop_counters[index].packets = 0;
+      mm->drop_counters[index].bytes = 0;
+
+      for (j = 0; j < vec_len (counters_by_thread); j++)
+	{
+	  counters = stat_segment_pointer (sm->shared_header,
+					   offset_vector[j]);
+	  mm->drop_counters[index].packets += counters[index].packets;
+	  mm->drop_counters[index].bytes += counters[index].bytes;
+	}
+    }
+  /* Ugh, segment changed during access. Try again */
+  if (stat_segment_access_end (&sa, sm))
+    goto again2;
+}
+
+static u8 *
+format_mac_address (u8 * s, va_list * args)
+{
+  u8 *a = va_arg (*args, u8 *);
+
+  return format (s, "%02x:%02x:%02x:%02x:%02x:%02x",
+		 a[0], a[1], a[2], a[3], a[4], a[5]);
+}
+
+static u8 *
+format_bytes_with_width (u8 * s, va_list * va)
+{
+  uword nbytes = va_arg (*va, u64);
+  int width = va_arg (*va, int);
+  f64 nbytes_f64;
+  u8 *fmt;
+  char *suffix = "";
+
+  if (width > 0)
+    fmt = format (0, "%%%d.3f%%s%c", width, 0);
+  else
+    fmt = format (0, "%%.3f%%s%c", 0);
+
+  if (nbytes > (1024ULL * 1024ULL * 1024ULL))
+    {
+      nbytes_f64 = ((f64) nbytes) / (1024.0 * 1024.0 * 1024.0);
+      suffix = "G";
+    }
+  else if (nbytes > (1024ULL * 1024ULL))
+    {
+      nbytes_f64 = ((f64) nbytes) / (1024.0 * 1024.0);
+      suffix = "M";
+    }
+  else if (nbytes > 1024ULL)
+    {
+      nbytes_f64 = ((f64) nbytes) / (1024.0);
+      suffix = "K";
+    }
+  else
+    {
+      nbytes_f64 = (f64) nbytes;
+      suffix = "B";
+    }
+
+  s = format (s, (char *) fmt, nbytes_f64, suffix);
+  vec_free (fmt);
+  return s;
+}
+
+static u8 *
+format_device (u8 * s, va_list * args)
+{
+  mactime_device_t *dp = va_arg (*args, mactime_device_t *);
+  mt_main_t *mm = &mt_main;
+  int verbose = va_arg (*args, int);
+  int current_status = 99;
+  char *status_string;
+  u8 *macstring = 0;
+  f64 now;
+  int j;
+
+  if (dp == 0)
+    {
+      s = format (s, "%-15s %5s %18s %14s %10s %11s %13s",
+		  "Device Name", "Index", "Addresses", "Status",
+		  "AllowPkt", "AllowByte", "DropPkt");
+      vec_add1 (s, '\n');
+      return s;
+    }
+
+  now = clib_timebase_now (&mm->timebase);
+
+  if (PREDICT_FALSE ((now - mm->sunday_midnight) > 86400.0 * 7.0))
+    mm->sunday_midnight = clib_timebase_find_sunday_midnight (now);
+
+  /* Check dynamic ranges */
+  for (j = 0; j < vec_len (dp->ranges); j++)
+    {
+      clib_timebase_range_t *r = dp->ranges + j;
+      f64 start0, end0;
+
+      start0 = r->start + mm->sunday_midnight;
+      end0 = r->end + mm->sunday_midnight;
+      if (verbose)
+	s = format (s, "  Range %d: %U - %U\n", j,
+		    format_clib_timebase_time, start0,
+		    format_clib_timebase_time, end0);
+
+      if (now >= start0 && now <= end0)
+	{
+	  if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW)
+	    current_status = 3;
+	  else if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW_QUOTA)
+	    current_status = 5;
+	  else
+	    current_status = 2;
+	  if (verbose)
+	    {
+	      s = format (s, "  Time in range %d:", j);
+	      s = format (s, "     %U - %U\n",
+			  format_clib_timebase_time, start0,
+			  format_clib_timebase_time, end0);
+	    }
+	  goto print;
+	}
+    }
+  if (verbose && j)
+    s = format (s, "  No range match.\n");
+  if (dp->flags & MACTIME_DEVICE_FLAG_STATIC_DROP)
+    current_status = 0;
+  if (dp->flags & MACTIME_DEVICE_FLAG_STATIC_ALLOW)
+    current_status = 1;
+  if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW)
+    current_status = 2;
+  if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_DROP)
+    current_status = 3;
+  if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW_QUOTA)
+    current_status = 4;
+
+print:
+  macstring = format (0, "%U", format_mac_address, dp->mac_address);
+  switch (current_status)
+    {
+    case 0:
+      status_string = "static drop";
+      break;
+    case 1:
+      status_string = "static allow";
+      break;
+    case 2:
+      status_string = "dynamic drop";
+      break;
+    case 3:
+      status_string = "dynamic allow";
+      break;
+    case 4:
+      status_string = "d-quota inact";
+      break;
+    case 5:
+      status_string = "d-quota activ";
+      break;
+    default:
+      status_string = "code bug!";
+      break;
+    }
+
+  s = format (s, "%-15s %5d %18s %14s %10lld %U %13lld\n",
+	      dp->device_name, dp->pool_index, macstring, status_string,
+	      mm->allow_counters[dp->pool_index].packets,
+	      format_bytes_with_width,
+	      mm->allow_counters[dp->pool_index].bytes, 10,
+	      mm->drop_counters[dp->pool_index].packets);
+  vec_free (macstring);
+
+  if (dp->data_quota > 0)
+    {
+      s = format (s, "%-59s %s%U %s%U", " ", "Quota ",
+		  format_bytes_with_width, dp->data_quota, 10,
+		  "Use ", format_bytes_with_width, dp->data_used_in_range, 8);
+      vec_add1 (s, '\n');
+    }
+  return s;
+}
+
+static void
+print_device_table (mt_main_t * mm)
+{
+  mactime_device_t *dev;
+
+  fformat (stdout, "%U", format_device, 0 /* header */ , 0 /* verbose */ );
+  /* *INDENT-OFF* */
+  pool_foreach (dev, mm->devices,
+  ({
+    fformat (stdout, "%U", format_device, dev, 0 /* verbose */);
+  }));
+  /* *INDENT-ON* */
+}
+
+int
+main (int argc, char **argv)
+{
+  mt_main_t *mm = &mt_main;
+  extern stat_client_main_t stat_client_main;
+
+  clib_mem_init (0, 64 << 20);
+
+  if (connect_to_vpp ("mactime_top") < 0)
+    {
+      fformat (stderr, "vpp api client connect error\n");
+      exit (1);
+    }
+
+  if (stat_segment_connect (argv[1]) < 0)
+    {
+      fformat (stderr, "stat segment connect error");
+      exit (1);
+    }
+
+  mm->stat_client_main = (stat_client_main_t *) & stat_client_main;
+
+  /* US EDT - $$$ FIXME */
+  clib_time_init (&mm->clib_time);
+  mm->timezone_offset = -5.0;
+  clib_timebase_init (&mm->timebase, mm->timezone_offset,
+		      CLIB_TIMEBASE_DAYLIGHT_USA);
+
+  vec_add1 (mm->pattern1, (u8 *) "^/mactime/allow");
+  vec_add1 (mm->pattern2, (u8 *) "^/mactime/drop");
+
+  while (1)
+    {
+      dump_mactime_table (mm);
+      scrape_stats_segment (mm);
+      print_device_table (mm);
+      unix_sleep (5.0);
+    }
+  return 0;
+}
+
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/api_errno.h b/src/vnet/api_errno.h
index 0702fd7..a345142 100644
--- a/src/vnet/api_errno.h
+++ b/src/vnet/api_errno.h
@@ -150,7 +150,9 @@
 _(RSRC_IN_USE, -155, "Resource In Use")                                 \
 _(KEY_LENGTH, -156, "invalid Key Length")                               \
 _(FIB_PATH_UNSUPPORTED_NH_PROTO, -157, "Unsupported FIB Path protocol") \
-_(API_ENDIAN_FAILED, -159, "Endian mismatch detected")
+_(API_ENDIAN_FAILED, -159, "Endian mismatch detected")			\
+_(NO_CHANGE, -160, "No change in table")
+
 typedef enum
 {
 #define _(a,b,c) VNET_API_ERROR_##a = (b),