STATS: stat_client updates. 39/14439/16
authorOle Troan <ot@cisco.com>
Thu, 23 Aug 2018 11:00:53 +0000 (13:00 +0200)
committerDamjan Marion <dmarion@me.com>
Wed, 29 Aug 2018 09:39:38 +0000 (09:39 +0000)
New stat segment client library: vpp-api/client/stat_client.h
New stat segment query app: vpp_get_stats [ls | dump | poll ] <pattern ...>
Prometheus integration through: vpp_prometheus_export <pattern>

Change-Id: I6f370cf599e9fcf066f22965a62d3a8acd529994
Signed-off-by: Ole Troan <ot@cisco.com>
15 files changed:
src/vlib/counter.c
src/vlib/error.c
src/vpp-api.am
src/vpp-api/CMakeLists.txt
src/vpp-api/client/libvppapiclient.map
src/vpp-api/client/stat_client.c [new file with mode: 0644]
src/vpp-api/client/stat_client.h [new file with mode: 0644]
src/vpp.am
src/vpp/CMakeLists.txt
src/vpp/app/stat_client.c [deleted file]
src/vpp/app/stat_client.h [deleted file]
src/vpp/app/vpp_get_stats.c [new file with mode: 0644]
src/vpp/app/vpp_prometheus_export.c [new file with mode: 0644]
src/vpp/stats/stat_segment.c
src/vpp/stats/stats.h

index 29cd004..6afa73e 100644 (file)
@@ -81,13 +81,12 @@ vlib_stats_push_heap (void)
   return 0;
 };
 
-void vlib_stats_pop_heap (void *, void *) __attribute__ ((weak));
+void vlib_stats_pop_heap (void *, void *, int) __attribute__ ((weak));
 void
-vlib_stats_pop_heap (void *notused, void *notused2)
+vlib_stats_pop_heap (void *notused, void *notused2, int type)
 {
 };
 
-
 void
 vlib_validate_simple_counter (vlib_simple_counter_main_t * cm, u32 index)
 {
@@ -99,7 +98,8 @@ vlib_validate_simple_counter (vlib_simple_counter_main_t * cm, u32 index)
   for (i = 0; i < tm->n_vlib_mains; i++)
     vec_validate_aligned (cm->counters[i], index, CLIB_CACHE_LINE_BYTES);
 
-  vlib_stats_pop_heap (cm, oldheap);
+  vlib_stats_pop_heap (cm, oldheap,
+                      3 /* STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE */ );
 }
 
 void
@@ -113,7 +113,8 @@ vlib_validate_combined_counter (vlib_combined_counter_main_t * cm, u32 index)
   for (i = 0; i < tm->n_vlib_mains; i++)
     vec_validate_aligned (cm->counters[i], index, CLIB_CACHE_LINE_BYTES);
 
-  vlib_stats_pop_heap (cm, oldheap);
+  vlib_stats_pop_heap (cm, oldheap,
+                      4 /*STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED */ );
 }
 
 u32
index 3ea62e5..aa53324 100644 (file)
@@ -208,7 +208,7 @@ vlib_register_errors (vlib_main_t * vm,
 
     for (i = 0; i < n_errors; i++)
       {
-       error_name = format (0, "/err/%s/%s%c", n->name, error_strings[i], 0);
+       error_name = format (0, "/err/%v/%s%c", n->name, error_strings[i], 0);
        /* Note: error_name consumed by the following call */
        vlib_stats_register_error_index (error_name, n->error_heap_index + i);
       }
index 553eafa..8742556 100644 (file)
@@ -15,8 +15,9 @@
 # VPP API C wrapper extension
 #
 lib_LTLIBRARIES += libvppapiclient.la
-libvppapiclient_la_SOURCES = \
-  vpp-api/client/client.c \
+libvppapiclient_la_SOURCES =                   \
+  vpp-api/client/client.c                      \
+  vpp-api/client/stat_client.c                 \
   vpp-api/client/libvppapiclient.map
 
 libvppapiclient_la_LIBADD = \
index 6132df0..872e9cc 100644 (file)
@@ -18,6 +18,7 @@
 add_vpp_library (vppapiclient
   SOURCES
   client/client.c
+  client/stat_client.c
   client/libvppapiclient.map
 
   LINK_LIBRARIES vppinfra vlibmemoryclient svm pthread m rt
@@ -26,6 +27,7 @@ add_dependencies(vppapiclient vpp_version_h api_headers)
 
 add_vpp_headers(vpp-api
   client/vppapiclient.h
+  client/stat_client.h
 )
 
 add_subdirectory(vapi)
index a9d8f7d..81391ea 100644 (file)
@@ -1,5 +1,5 @@
 
-VPPAPICLIENT_17.07 {
+VPPAPICLIENT_18.10 {
        global:
        vac_read;
        vac_write;
@@ -12,8 +12,14 @@ VPPAPICLIENT_17.07 {
        vac_rx_resume;
        vac_free;
        vac_msg_table_size;
-
        api_main;
-
+       stat_segment_connect;
+       stat_segment_disconnect;
+       stat_segment_register;
+       stat_segment_collect;
+       stat_segment_ls;
+       stat_segment_dump;
+       stat_segment_data_free;
+       stat_segment_heartbeat;
        local: *;
 };
diff --git a/src/vpp-api/client/stat_client.c b/src/vpp-api/client/stat_client.c
new file mode 100644 (file)
index 0000000..d7d2045
--- /dev/null
@@ -0,0 +1,372 @@
+/*
+ *------------------------------------------------------------------
+ * stat_client.c - Library for access to VPP statistics segment
+ *
+ * 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.
+ *------------------------------------------------------------------
+ */
+
+#include <vlib/vlib.h>
+#include <vppinfra/socket.h>
+#include <svm/ssvm.h>
+#include <vpp/stats/stats.h>
+#include <regex.h>
+#include "stat_client.h"
+
+typedef struct
+{
+  u64 current_epoch;
+  volatile int segment_ready;
+  ssvm_private_t stat_segment; /* mapped stats segment object */
+  ssvm_shared_header_t *shared_header;
+  clib_spinlock_t *stat_segment_lockp; /* Spinlock for the stats segment */
+  uword *counter_vector_by_name;
+  u64 *error_base;
+} stat_client_main_t;
+
+stat_client_main_t stat_client_main;
+
+int
+stat_segment_connect (char *socket_name)
+{
+  stat_client_main_t *sm = &stat_client_main;
+  ssvm_private_t *ssvmp = &sm->stat_segment;
+  clib_socket_t s = { 0 };
+  clib_error_t *err;
+  int fd = -1, retval;
+
+  memset (sm, 0, sizeof (*sm));
+  s.config = socket_name;
+  s.flags = CLIB_SOCKET_F_IS_CLIENT | CLIB_SOCKET_F_SEQPACKET;
+  err = clib_socket_init (&s);
+  if (err)
+    {
+      clib_error_report (err);
+      return -1;
+    }
+  err = clib_socket_recvmsg (&s, 0, 0, &fd, 1);
+  if (err)
+    {
+      clib_error_report (err);
+      return -1;
+    }
+  clib_socket_close (&s);
+
+  memset (ssvmp, 0, sizeof (*ssvmp));
+  ssvmp->fd = fd;
+
+  /* Note: this closes memfd.fd */
+  retval = ssvm_slave_init_memfd (ssvmp);
+  if (retval)
+    {
+      fprintf (stderr, "WARNING: segment map returned %d\n", retval);
+      return -1;
+    }
+
+  ASSERT (ssvmp && ssvmp->sh);
+
+  /* Pick up the segment lock from the shared memory header */
+  sm->shared_header = ssvmp->sh;
+  sm->stat_segment_lockp = (clib_spinlock_t *) (sm->shared_header->opaque[0]);
+  sm->segment_ready = 1;
+
+  sm->counter_vector_by_name =
+    (uword *) sm->shared_header->opaque[STAT_SEGMENT_OPAQUE_DIR];
+
+  return 0;
+}
+
+void
+stat_segment_disconnect (void)
+{
+  stat_client_main_t *sm = &stat_client_main;
+  ssvm_delete_memfd (&sm->stat_segment);
+  return;
+}
+
+/*
+ * The application needs to register which counters it is interested
+ * in.
+ */
+stat_segment_cached_pointer_t *
+stat_segment_register (u8 * stats[])
+{
+  int i;
+  uword *p;
+  stat_client_main_t *sm = &stat_client_main;
+  stat_segment_cached_pointer_t *cp, *cached_pointer_vec = 0;
+
+  for (i = 0; i < vec_len (stats); i++)
+    {
+      p = hash_get_mem (sm->counter_vector_by_name, stats[i]);
+      if (p == 0)
+       {
+         fprintf (stderr, "WARN: %s not in directory!", stats[i]);
+         continue;
+       }
+      vec_add2 (cached_pointer_vec, cp, 1);
+      cp->name = strdup ((char *) stats[i]);   // Point to p->key instead?
+    }
+  return cached_pointer_vec;
+}
+
+static u64 *
+get_error_base (u32 thread_index)
+{
+  u64 *error_base = 0;
+  uword *p;
+  stat_client_main_t *sm = &stat_client_main;
+  stat_segment_directory_entry_t *ep;
+
+  /* Special case /err/0/counter_vector */
+  p = hash_get_mem (sm->counter_vector_by_name,
+                   format (0, "/err/%d/counter_vector", thread_index));
+  if (p)
+    {
+      ep = (stat_segment_directory_entry_t *) (p[0]);
+      error_base = ep->value;
+    }
+  return error_base;
+}
+
+f64
+stat_segment_heartbeat (void)
+{
+  f64 *heartbeat = 0;
+  uword *p;
+  stat_client_main_t *sm = &stat_client_main;
+  stat_segment_directory_entry_t *ep;
+
+  /* Special case /err/0/counter_vector */
+  p = hash_get_mem (sm->counter_vector_by_name,
+                   format (0, "/sys/heartbeat%c", 0));
+  if (p)
+    {
+      ep = (stat_segment_directory_entry_t *) (p[0]);
+      heartbeat = ep->value;
+    }
+  return *heartbeat;
+}
+
+static void
+maybe_update_cached_pointers (stat_segment_cached_pointer_t * cached_pointers)
+{
+  stat_client_main_t *sm = &stat_client_main;
+  stat_segment_cached_pointer_t *cp;
+  uword *p;
+  int i;
+
+  /* Cached pointers OK? */
+  if (sm->current_epoch ==
+      (u64) sm->shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH])
+    return;
+
+  /* Special case /err/0/counter_vector */
+  sm->error_base = get_error_base (0);
+
+  /* Nope, fix them... */
+  for (i = 0; i < vec_len (cached_pointers); i++)
+    {
+      cp = &cached_pointers[i];
+
+      p = hash_get_mem (sm->counter_vector_by_name, cp->name);
+      if (p == 0)
+       {
+         fprintf (stderr, "WARN: %s not in directory!", cp->name);
+         continue;
+       }
+      cp->ep = (stat_segment_directory_entry_t *) (p[0]);
+    }
+
+  /* And remember that we did... */
+  sm->current_epoch =
+    (u64) sm->shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH];
+}
+
+stat_segment_data_t
+copy_data (stat_segment_directory_entry_t * ep, u64 * error_base, char *name)
+{
+  stat_segment_data_t result = { 0 };
+  u32 error_index;
+  int i;
+  vlib_counter_t **combined_c; /* Combined counter */
+  counter_t **simple_c;                /* Simple counter */
+  result.type = ep->type;
+  result.name = name;
+  switch (ep->type)
+    {
+    case STAT_DIR_TYPE_SCALAR_POINTER:
+      result.scalar_value = *(f64 *) ep->value;
+      break;
+
+    case STAT_DIR_TYPE_VECTOR_POINTER:
+      result.vector_pointer = ep->value;
+      break;
+
+    case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
+      simple_c = ep->value;
+      result.simple_counter_vec = vec_dup (simple_c);
+      for (i = 0; i < vec_len (simple_c); i++)
+       result.simple_counter_vec[i] = vec_dup (simple_c[i]);
+      break;
+
+    case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
+      combined_c = ep->value;
+      result.combined_counter_vec = vec_dup (combined_c);
+      for (i = 0; i < vec_len (combined_c); i++)
+       result.combined_counter_vec[i] = vec_dup (combined_c[i]);
+      break;
+
+    case STAT_DIR_TYPE_ERROR_INDEX:
+      error_index = (uintptr_t) ep->value;
+      result.error_value = error_base[error_index];
+      break;
+
+    default:
+      fprintf (stderr, "Unknown type: %d", ep->type);
+    }
+  return result;
+}
+
+stat_segment_data_t *
+stat_segment_collect (stat_segment_cached_pointer_t * cached_pointers)
+{
+  stat_client_main_t *sm = &stat_client_main;
+  stat_segment_data_t *res = 0;
+  int i;
+
+  /* Grab the stats segment lock */
+  clib_spinlock_lock (sm->stat_segment_lockp);
+
+  /* see if we need to update cached pointers */
+  maybe_update_cached_pointers (cached_pointers);
+
+  for (i = 0; i < vec_len (cached_pointers); i++)
+    {
+      vec_add1 (res,
+               copy_data (cached_pointers[i].ep, sm->error_base,
+                          cached_pointers[i].name));
+    }
+
+  /* Drop the lock */
+  clib_spinlock_unlock (sm->stat_segment_lockp);
+
+  return res;
+}
+
+void
+stat_segment_data_free (stat_segment_data_t * res)
+{
+  int i, j;
+  for (i = 0; i < vec_len (res); i++)
+    {
+      switch (res[i].type)
+       {
+       case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
+         for (j = 0; j < vec_len (res[i].simple_counter_vec); j++)
+           vec_free (res[i].simple_counter_vec[j]);
+         vec_free (res[i].simple_counter_vec);
+         break;
+       case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
+         for (j = 0; j < vec_len (res[i].combined_counter_vec); j++)
+           vec_free (res[i].combined_counter_vec[j]);
+         vec_free (res[i].combined_counter_vec);
+         break;
+       default:
+         ;
+       }
+    }
+  vec_free (res);
+}
+
+u8 **
+stat_segment_ls (u8 ** patterns)
+{
+  stat_client_main_t *sm = &stat_client_main;
+  hash_pair_t *p;
+  u8 **dir = 0;
+  regex_t regex[vec_len (patterns)];
+
+  int i;
+  for (i = 0; i < vec_len (patterns); i++)
+    {
+      int rv = regcomp (&regex[i], (char *) patterns[i], 0);
+      if (rv)
+       {
+         fprintf (stderr, "Could not compile regex %s\n", patterns[i]);
+         return dir;
+       }
+    }
+
+  clib_spinlock_lock (sm->stat_segment_lockp);
+
+  /* *INDENT-OFF* */
+  hash_foreach_pair (p, sm->counter_vector_by_name,
+  ({
+    for (i = 0; i < vec_len(patterns); i++) {
+      int rv = regexec(&regex[i], (char *)p->key, 0, NULL, 0);
+      if (rv == 0) {
+       vec_add1 (dir, (u8 *)p->key);
+       break;
+      }
+    }
+    if (vec_len(patterns) == 0)
+      vec_add1 (dir, (u8 *)p->key);
+  }));
+  /* *INDENT-ON* */
+
+  clib_spinlock_unlock (sm->stat_segment_lockp);
+
+  for (i = 0; i < vec_len (patterns); i++)
+    regfree (&regex[i]);
+
+  return dir;
+}
+
+stat_segment_data_t *
+stat_segment_dump (u8 * stats[])
+{
+  int i;
+  uword *p;
+  stat_client_main_t *sm = &stat_client_main;
+  stat_segment_directory_entry_t *ep;
+  stat_segment_data_t *res = 0;
+
+  clib_spinlock_lock (sm->stat_segment_lockp);
+
+  sm->error_base = get_error_base (0);
+  for (i = 0; i < vec_len (stats); i++)
+    {
+      p = hash_get_mem (sm->counter_vector_by_name, stats[i]);
+      if (p == 0)
+       {
+         fprintf (stderr, "WARN: %s not in directory!", stats[i]);
+         continue;
+       }
+      /* Collect counter */
+      ep = (stat_segment_directory_entry_t *) (p[0]);
+      vec_add1 (res, copy_data (ep, sm->error_base, (char *) stats[i]));
+    }
+  clib_spinlock_unlock (sm->stat_segment_lockp);
+
+  return res;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vpp-api/client/stat_client.h b/src/vpp-api/client/stat_client.h
new file mode 100644 (file)
index 0000000..5f07668
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * stat_client.h - Library for access to VPP statistics segment
+ *
+ * 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_stat_client_h
+#define included_stat_client_h
+
+#include <vlib/vlib.h>
+#include <vpp/stats/stats.h>
+
+typedef struct
+{
+  char *name;
+  stat_directory_type_t type;
+  union
+  {
+    f64 scalar_value;
+    u64 error_value;
+    u64 *vector_pointer;
+    counter_t **simple_counter_vec;
+    vlib_counter_t **combined_counter_vec;
+  };
+} stat_segment_data_t;
+
+typedef struct
+{
+  char *name;
+  stat_segment_directory_entry_t *ep;
+} stat_segment_cached_pointer_t;
+
+int stat_segment_connect (char *socket_name);
+void stat_segment_disconnect (void);
+
+u8 **stat_segment_ls (u8 ** pattern);
+stat_segment_data_t *stat_segment_dump (u8 ** counter_vec);
+
+stat_segment_cached_pointer_t *stat_segment_register (u8 ** counter_vec);
+stat_segment_data_t *stat_segment_collect (stat_segment_cached_pointer_t *);   /* Collects registered counters */
+
+void stat_segment_data_free (stat_segment_data_t * res);
+
+f64 stat_segment_heartbeat (void);
+
+#endif /* included_stat_client_h */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
index e8c0155..60be623 100644 (file)
@@ -131,18 +131,29 @@ bin_summary_stats_client_LDADD = \
    libvppinfra.la \
    -lpthread -lm -lrt
 
-noinst_PROGRAMS += bin/stat_client
+bin_PROGRAMS += bin/vpp_get_stats
 
-bin_stat_client_SOURCES = \
-  vpp/app/stat_client.c \
-  vpp/app/stat_client.h
+bin_vpp_get_stats_SOURCES = \
+  vpp/app/vpp_get_stats.c
 
-bin_stat_client_LDADD = \
-  libvlibmemoryclient.la \
-  libsvm.la \
-  libvppinfra.la \
+bin_vpp_get_stats_LDADD =                              \
+  libvppapiclient.la                           \
+  libvlibmemoryclient.la                       \
+  libsvm.la                                    \
+  libvppinfra.la                               \
   -lpthread -lm -lrt
 
+bin_PROGRAMS += bin/vpp_prometheus_export
+
+bin_vpp_prometheus_export_SOURCES = \
+  vpp/app/vpp_prometheus_export.c
+
+bin_vpp_prometheus_export_LDADD =                              \
+  libvppapiclient.la                           \
+  libvlibmemoryclient.la                       \
+  libsvm.la                                    \
+  libvppinfra.la                               \
+  -lpthread -lm -lrt
 
 
 bin_PROGRAMS += bin/vpp_get_metrics
index 7e59090..78ca867 100644 (file)
@@ -110,10 +110,15 @@ add_vpp_executable(summary_stats_client
   NO_INSTALL
 )
 
-add_vpp_executable(stat_client
-  SOURCES app/stat_client.c
-  LINK_LIBRARIES vppinfra svm vlibmemoryclient
+add_vpp_executable(vpp_get_stats
+  SOURCES app/vpp_get_stats.c
+  LINK_LIBRARIES vppapiclient vppinfra svm vlibmemoryclient
   DEPENDS api_headers
   NO_INSTALL
 )
 
+add_vpp_executable(vpp_prometheus_export
+  SOURCES app/vpp_prometheus_export.c
+  LINK_LIBRARIES vppapiclient vppinfra svm vlibmemoryclient
+  DEPENDS api_headers
+)
diff --git a/src/vpp/app/stat_client.c b/src/vpp/app/stat_client.c
deleted file mode 100644 (file)
index 2bedf6f..0000000
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- *------------------------------------------------------------------
- * stat_client.c
- *
- * 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.
- *------------------------------------------------------------------
- */
-
-#include <vpp/app/stat_client.h>
-
-stat_client_main_t stat_client_main;
-
-static int
-stat_segment_connect (stat_client_main_t * sm)
-{
-  ssvm_private_t *ssvmp = &sm->stat_segment;
-  ssvm_shared_header_t *shared_header;
-  clib_socket_t s = { 0 };
-  clib_error_t *err;
-  int fd = -1, retval;
-
-  s.config = (char *) sm->socket_name;
-  s.flags = CLIB_SOCKET_F_IS_CLIENT | CLIB_SOCKET_F_SEQPACKET;
-  err = clib_socket_init (&s);
-  if (err)
-    {
-      clib_error_report (err);
-      exit (1);
-    }
-  err = clib_socket_recvmsg (&s, 0, 0, &fd, 1);
-  if (err)
-    {
-      clib_error_report (err);
-      return -1;
-    }
-  clib_socket_close (&s);
-
-  memset (ssvmp, 0, sizeof (*ssvmp));
-  ssvmp->fd = fd;
-
-  /* Note: this closes memfd.fd */
-  retval = ssvm_slave_init_memfd (ssvmp);
-  if (retval)
-    {
-      clib_warning ("WARNING: segment map returned %d", retval);
-      return -1;
-    }
-
-  fformat (stdout, "Stat segment mapped OK...\n");
-
-  ASSERT (ssvmp && ssvmp->sh);
-
-  /* Pick up the segment lock from the shared memory header */
-  shared_header = ssvmp->sh;
-  sm->stat_segment_lockp = (clib_spinlock_t *) (shared_header->opaque[0]);
-  sm->segment_ready = 1;
-
-  return 0;
-}
-
-#define foreach_cached_pointer                                          \
-_(/sys/vector_rate, SCALAR_POINTER, &stat_client_main.vector_rate_ptr) \
-_(/sys/input_rate, SCALAR_POINTER, &stat_client_main.input_rate_ptr)   \
-_(/sys/last_update, SCALAR_POINTER, &stat_client_main.last_runtime_ptr)        \
-_(/sys/last_stats_clear, SCALAR_POINTER,                               \
-  &stat_client_main.last_runtime_stats_clear_ptr)                       \
-_(/if/rx, COUNTER_VECTOR, &stat_client_main.intfc_rx_counters)         \
-_(/if/tx, COUNTER_VECTOR, &stat_client_main.intfc_tx_counters)         \
-_(/err/0/counter_vector, VECTOR_POINTER,                                \
-  &stat_client_main.thread_0_error_counts)                              \
-_(serialized_nodes, SERIALIZED_NODES,                                   \
-  &stat_client_main.serialized_nodes)
-
-typedef struct
-{
-  char *name;
-  stat_directory_type_t type;
-  void *valuep;
-} cached_pointer_t;
-
-cached_pointer_t cached_pointers[] = {
-#define _(n,t,p) {#n, STAT_DIR_TYPE_##t, (void *)p},
-  foreach_cached_pointer
-#undef _
-};
-
-static void
-maybe_update_cached_pointers (stat_client_main_t * sm,
-                             ssvm_shared_header_t * shared_header)
-{
-  uword *p, *counter_vector_by_name;
-  int i;
-  stat_segment_directory_entry_t *ep;
-  cached_pointer_t *cp;
-  u64 *valuep;
-
-  /* Cached pointers OK? */
-  if (sm->current_epoch ==
-      (u64) shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH])
-    return;
-
-  fformat (stdout, "Updating cached pointers...\n");
-
-  /* Nope, fix them... */
-  counter_vector_by_name = (uword *)
-    shared_header->opaque[STAT_SEGMENT_OPAQUE_DIR];
-
-  for (i = 0; i < ARRAY_LEN (cached_pointers); i++)
-    {
-      cp = &cached_pointers[i];
-
-      p = hash_get_mem (counter_vector_by_name, cp->name);
-
-      if (p == 0)
-       {
-         clib_warning ("WARN: %s not in directory!", cp->name);
-         continue;
-       }
-      ep = (stat_segment_directory_entry_t *) (p[0]);
-      ASSERT (ep->type == cp->type);
-      valuep = (u64 *) cp->valuep;
-      *valuep = (u64) ep->value;
-    }
-
-  /* And remember that we did... */
-  sm->current_epoch = (u64) shared_header->opaque[STAT_SEGMENT_OPAQUE_EPOCH];
-}
-
-static void
-stat_poll_loop (stat_client_main_t * sm)
-{
-  struct timespec ts, tsrem;
-  ssvm_private_t *ssvmp = &sm->stat_segment;
-  ssvm_shared_header_t *shared_header;
-  vlib_counter_t *thread0_rx_counters = 0, *thread0_tx_counters = 0;
-  vlib_node_t ***nodes_by_thread;
-  vlib_node_t **nodes;
-  vlib_node_t *n;
-  f64 vector_rate, input_rate;
-  u32 len;
-  int i, j;
-  u32 source_address_match_errors;
-
-  /* Wait until the stats segment is mapped */
-  while (!sm->segment_ready)
-    {
-      ts.tv_sec = 0;
-      ts.tv_nsec = 100000000;
-      while (nanosleep (&ts, &tsrem) < 0)
-       ts = tsrem;
-    }
-
-  shared_header = ssvmp->sh;
-  ASSERT (ssvmp->sh);
-
-  while (1)
-    {
-      /* Scrape stats every 5 seconds */
-      ts.tv_sec = 5;
-      ts.tv_nsec = 0;
-      while (nanosleep (&ts, &tsrem) < 0)
-       ts = tsrem;
-
-      vec_reset_length (thread0_rx_counters);
-      vec_reset_length (thread0_tx_counters);
-
-      /* Grab the stats segment lock */
-      clib_spinlock_lock (sm->stat_segment_lockp);
-
-      /* see if we need to update cached pointers */
-      maybe_update_cached_pointers (sm, shared_header);
-
-      ASSERT (sm->vector_rate_ptr);
-      ASSERT (sm->intfc_rx_counters);
-      ASSERT (sm->intfc_tx_counters);
-
-      /* Read data from the segment */
-      vector_rate = *sm->vector_rate_ptr;
-      input_rate = *sm->input_rate_ptr;
-
-      len = vec_len (sm->intfc_rx_counters[0]);
-
-      ASSERT (len);
-
-      vec_validate (thread0_rx_counters, len - 1);
-      vec_validate (thread0_tx_counters, len - 1);
-
-      clib_memcpy (thread0_rx_counters, sm->intfc_rx_counters[0],
-                  len * sizeof (vlib_counter_t));
-      clib_memcpy (thread0_tx_counters, sm->intfc_tx_counters[0],
-                  len * sizeof (vlib_counter_t));
-
-      source_address_match_errors =
-       sm->thread_0_error_counts[sm->source_address_match_error_index];
-
-      /* Drop the lock */
-      clib_spinlock_unlock (sm->stat_segment_lockp);
-
-      /* And print results... */
-
-      fformat (stdout, "vector_rate %.2f input_rate %.2f\n",
-              vector_rate, input_rate);
-
-      for (i = 0; i < vec_len (thread0_rx_counters); i++)
-       {
-         fformat (stdout, "[%d]: %lld rx packets, %lld rx bytes\n",
-                  i, thread0_rx_counters[i].packets,
-                  thread0_rx_counters[i].bytes);
-         fformat (stdout, "[%d]: %lld tx packets, %lld tx bytes\n",
-                  i, thread0_tx_counters[i].packets,
-                  thread0_tx_counters[i].bytes);
-       }
-
-      fformat (stdout, "%lld source address match errors\n",
-              source_address_match_errors);
-
-      if (sm->serialized_nodes)
-       {
-         nodes_by_thread = vlib_node_unserialize (sm->serialized_nodes);
-
-         /* Across all threads... */
-         for (i = 0; i < vec_len (nodes_by_thread); i++)
-           {
-             u64 n_input, n_output, n_drop, n_punt;
-             u64 n_internal_vectors, n_internal_calls;
-             u64 n_clocks, l, v, c;
-             f64 dt;
-
-             nodes = nodes_by_thread[i];
-
-             fformat (stdout, "Thread %d -------------------------\n", i);
-
-             n_input = n_output = n_drop = n_punt = n_clocks = 0;
-             n_internal_vectors = n_internal_calls = 0;
-
-             /* Across all nodes */
-             for (j = 0; j < vec_len (nodes); j++)
-               {
-                 n = nodes[j];
-
-                 /* Exactly stolen from node_cli.c... */
-                 l = n->stats_total.clocks - n->stats_last_clear.clocks;
-                 n_clocks += l;
-
-                 v = n->stats_total.vectors - n->stats_last_clear.vectors;
-                 c = n->stats_total.calls - n->stats_last_clear.calls;
-
-                 switch (n->type)
-                   {
-                   default:
-                     vec_free (n->name);
-                     vec_free (n->next_nodes);
-                     vec_free (n);
-                     continue;
-
-                   case VLIB_NODE_TYPE_INTERNAL:
-                     n_output +=
-                       (n->flags & VLIB_NODE_FLAG_IS_OUTPUT) ? v : 0;
-                     n_drop += (n->flags & VLIB_NODE_FLAG_IS_DROP) ? v : 0;
-                     n_punt += (n->flags & VLIB_NODE_FLAG_IS_PUNT) ? v : 0;
-                     if (!(n->flags & VLIB_NODE_FLAG_IS_OUTPUT))
-                       {
-                         n_internal_vectors += v;
-                         n_internal_calls += c;
-                       }
-                     if (n->flags & VLIB_NODE_FLAG_IS_HANDOFF)
-                       n_input += v;
-                     break;
-
-                   case VLIB_NODE_TYPE_INPUT:
-                     n_input += v;
-                     break;
-                   }
-
-                 if (n->stats_total.calls)
-                   {
-                     fformat (stdout,
-                              "%s (%s): clocks %lld calls %lld vectors %lld ",
-                              n->name,
-                              n->state_string,
-                              n->stats_total.clocks,
-                              n->stats_total.calls, n->stats_total.vectors);
-                     if (n->stats_total.vectors)
-                       fformat (stdout, "clocks/pkt %.2f\n",
-                                (f64) n->stats_total.clocks /
-                                (f64) n->stats_total.vectors);
-                     else
-                       fformat (stdout, "\n");
-                   }
-                 vec_free (n->name);
-                 vec_free (n->next_nodes);
-                 vec_free (n);
-               }
-
-             fformat (stdout, "average vectors/node %.2f\n",
-                      (n_internal_calls > 0
-                       ? (f64) n_internal_vectors / (f64) n_internal_calls
-                       : 0));
-
-
-             dt = *sm->last_runtime_ptr - *sm->last_runtime_stats_clear_ptr;
-
-             fformat (stdout,
-                      " vectors rates in %.4e, out %.4e, drop %.4e, "
-                      "punt %.4e\n",
-                      (f64) n_input / dt,
-                      (f64) n_output / dt, (f64) n_drop / dt,
-                      (f64) n_punt / dt);
-
-             vec_free (nodes);
-           }
-         vec_free (nodes_by_thread);
-       }
-      else
-       {
-         fformat (stdout, "serialized nodes NULL?\n");
-       }
-
-    }
-}
-
-int
-main (int argc, char **argv)
-{
-  unformat_input_t _argv, *a = &_argv;
-  stat_client_main_t *sm = &stat_client_main;
-  u8 *stat_segment_name;
-  int rv;
-
-  clib_mem_init (0, 128 << 20);
-
-  unformat_init_command_line (a, argv);
-
-  stat_segment_name = (u8 *) STAT_SEGMENT_SOCKET_FILE;
-
-  while (unformat_check_input (a) != UNFORMAT_END_OF_INPUT)
-    {
-      if (unformat (a, "socket-name %s", &stat_segment_name))
-       ;
-      else
-       {
-         fformat (stderr, "%s: usage [socket-name <name>]\n", argv[0]);
-         exit (1);
-       }
-    }
-
-  sm->socket_name = stat_segment_name;
-
-  rv = stat_segment_connect (sm);
-  if (rv)
-    {
-      fformat (stderr, "Couldn't connect to vpp, does %s exist?\n",
-              stat_segment_name);
-      exit (1);
-    }
-
-  stat_poll_loop (sm);
-  exit (0);
-}
-
-/*
- * fd.io coding-style-patch-verification: ON
- *
- * Local Variables:
- * eval: (c-set-style "gnu")
- * End:
- */
diff --git a/src/vpp/app/stat_client.h b/src/vpp/app/stat_client.h
deleted file mode 100644 (file)
index 97bd3f9..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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_stat_client_h__
-#define __included_stat_client_h__
-
-#include <vlib/vlib.h>
-#include <vppinfra/socket.h>
-#include <svm/ssvm.h>
-#include <vpp/stats/stats.h>
-
-typedef struct
-{
-  u64 current_epoch;
-
-  /* Cached pointers to scalar quantities, these wont change */
-  f64 *vector_rate_ptr;
-  f64 *input_rate_ptr;
-  f64 *last_runtime_ptr;
-  f64 *last_runtime_stats_clear_ptr;
-
-  volatile int segment_ready;
-
-  /*
-   * Cached pointers to vector quantities,
-   * MUST invalidate when the epoch changes
-   */
-  vlib_counter_t **intfc_rx_counters;
-  vlib_counter_t **intfc_tx_counters;
-  u8 *serialized_nodes;
-
-  u64 *thread_0_error_counts;
-  u64 source_address_match_error_index;
-
-  /* mapped stats segment object */
-  ssvm_private_t stat_segment;
-
-  /* Spinlock for the stats segment */
-  clib_spinlock_t *stat_segment_lockp;
-
-  u8 *socket_name;
-} stat_client_main_t;
-
-extern stat_client_main_t stat_client_main;
-
-#endif /* __included_stat_client_h__ */
-
-
-/*
- * fd.io coding-style-patch-verification: ON
- *
- * Local Variables:
- * eval: (c-set-style "gnu")
- * End:
- */
diff --git a/src/vpp/app/vpp_get_stats.c b/src/vpp/app/vpp_get_stats.c
new file mode 100644 (file)
index 0000000..86e511e
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ *------------------------------------------------------------------
+ * vpp_get_stats.c
+ *
+ * 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.
+ *------------------------------------------------------------------
+ */
+
+#include <vpp-api/client/stat_client.h>
+#include <vlib/vlib.h>
+#include <vpp/stats/stats.h>
+
+static int
+stat_poll_loop (stat_segment_cached_pointer_t * cp)
+{
+  struct timespec ts, tsrem;
+  stat_segment_data_t *res;
+  int i, j, k, lost_connection = 0;
+  f64 heartbeat, prev_heartbeat = 0;
+
+  printf ("\033[2J");          /*  clear the screen  */
+  while (1)
+    {
+      heartbeat = stat_segment_heartbeat ();
+      if (heartbeat > prev_heartbeat)
+       {
+         prev_heartbeat = heartbeat;
+         lost_connection = 0;
+       }
+      else
+       {
+         lost_connection++;
+       }
+      if (lost_connection > 10)
+       {
+         fformat (stderr, "Lost connection to VPP...\n");
+         return -1;
+       }
+
+      printf ("\033[H");       /* Cursor top left corner */
+      res = stat_segment_collect (cp);
+      for (i = 0; i < vec_len (res); i++)
+       {
+         switch (res[i].type)
+           {
+           case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
+             for (k = 0; k < vec_len (res[i].simple_counter_vec); k++)
+               for (j = 0; j < vec_len (res[i].simple_counter_vec[k]); j++)
+                 fformat (stdout, "[%d]: %lld packets %s\n",
+                          j, res[i].simple_counter_vec[k][j], res[i].name);
+             break;
+
+           case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
+             for (k = 0; k < vec_len (res[i].simple_counter_vec); k++)
+               for (j = 0; j < vec_len (res[i].combined_counter_vec[k]); j++)
+                 fformat (stdout, "[%d]: %lld packets, %lld bytes %s\n",
+                          j, res[i].combined_counter_vec[k][j].packets,
+                          res[i].combined_counter_vec[k][j].bytes,
+                          res[i].name);
+             break;
+
+           case STAT_DIR_TYPE_ERROR_INDEX:
+             fformat (stdout, "%lld %s\n", res[i].error_value, res[i].name);
+             break;
+
+           case STAT_DIR_TYPE_SCALAR_POINTER:
+             fformat (stdout, "%.2f %s\n", res[i].scalar_value, res[i].name);
+             break;
+
+           default:
+             printf ("Unknown value\n");
+             ;
+           }
+       }
+      stat_segment_data_free (res);
+      /* Scrape stats every 5 seconds */
+      ts.tv_sec = 1;
+      ts.tv_nsec = 0;
+      while (nanosleep (&ts, &tsrem) < 0)
+       ts = tsrem;
+
+    }
+}
+
+enum stat_client_cmd_e
+{
+  STAT_CLIENT_CMD_UNKNOWN,
+  STAT_CLIENT_CMD_LS,
+  STAT_CLIENT_CMD_POLL,
+  STAT_CLIENT_CMD_DUMP,
+};
+
+int
+main (int argc, char **argv)
+{
+  unformat_input_t _argv, *a = &_argv;
+  u8 *stat_segment_name, *pattern = 0, **patterns = 0;
+  int rv;
+  enum stat_client_cmd_e cmd = STAT_CLIENT_CMD_UNKNOWN;
+
+  clib_mem_init (0, 128 << 20);
+
+  unformat_init_command_line (a, argv);
+
+  stat_segment_name = (u8 *) STAT_SEGMENT_SOCKET_FILE;
+
+  while (unformat_check_input (a) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (a, "socket-name %s", &stat_segment_name))
+       ;
+      else if (unformat (a, "ls"))
+       {
+         cmd = STAT_CLIENT_CMD_LS;
+       }
+      else if (unformat (a, "dump"))
+       {
+         cmd = STAT_CLIENT_CMD_DUMP;
+       }
+      else if (unformat (a, "poll"))
+       {
+         cmd = STAT_CLIENT_CMD_POLL;
+       }
+      else if (unformat (a, "%s", &pattern))
+       {
+         vec_add1 (patterns, pattern);
+       }
+      else
+       {
+         fformat (stderr,
+                  "%s: usage [socket-name <name>] [ls|dump|poll] <patterns> ...\n",
+                  argv[0]);
+         exit (1);
+       }
+    }
+reconnect:
+  rv = stat_segment_connect ((char *) stat_segment_name);
+  if (rv)
+    {
+      fformat (stderr, "Couldn't connect to vpp, does %s exist?\n",
+              stat_segment_name);
+      exit (1);
+    }
+
+  u8 **dir;
+  int i, j, k;
+  stat_segment_data_t *res;
+  stat_segment_cached_pointer_t *cp;
+
+  dir = stat_segment_ls (patterns);
+
+  switch (cmd)
+    {
+    case STAT_CLIENT_CMD_LS:
+      /* List all counters */
+      for (i = 0; i < vec_len (dir); i++)
+       {
+         printf ("%s\n", (char *) dir[i]);
+       }
+      break;
+
+    case STAT_CLIENT_CMD_DUMP:
+      res = stat_segment_dump (dir);
+      for (i = 0; i < vec_len (res); i++)
+       {
+         switch (res[i].type)
+           {
+           case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
+             for (k = 0; k < vec_len (res[i].simple_counter_vec) - 1; k++)
+               for (j = 0; j < vec_len (res[i].simple_counter_vec[k]); j++)
+                 fformat (stdout, "[%d @ %d]: %lld packets %s\n",
+                          j, k, res[i].simple_counter_vec[k][j],
+                          res[i].name);
+             break;
+
+           case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
+             for (k = 0; k < vec_len (res[i].simple_counter_vec); k++)
+               for (j = 0; j < vec_len (res[i].combined_counter_vec[k]); j++)
+                 fformat (stdout, "[%d @ %d]: %lld packets, %lld bytes %s\n",
+                          j, k, res[i].combined_counter_vec[k][j].packets,
+                          res[i].combined_counter_vec[k][j].bytes,
+                          res[i].name);
+             break;
+
+           case STAT_DIR_TYPE_ERROR_INDEX:
+             fformat (stdout, "%lld %s\n", res[i].error_value, dir[i]);
+             break;
+
+           case STAT_DIR_TYPE_SCALAR_POINTER:
+             fformat (stdout, "%.2f %s\n", dir[i], res[i].scalar_value,
+                      res[i].name);
+             break;
+
+           default:
+             ;
+           }
+       }
+      stat_segment_data_free (res);
+      break;
+
+    case STAT_CLIENT_CMD_POLL:
+      cp = stat_segment_register (dir);
+      if (!cp)
+       {
+         fformat (stderr,
+                  "Couldn't register required counters with stat segment\n");
+         exit (1);
+       }
+      stat_poll_loop (cp);
+      /* We can only exist the pool loop if we lost connection to VPP */
+      stat_segment_disconnect ();
+      goto reconnect;
+      break;
+
+    default:
+      fformat (stderr,
+              "%s: usage [socket-name <name>] [ls|dump|poll] <patterns> ...\n",
+              argv[0]);
+    }
+
+  stat_segment_disconnect ();
+
+  exit (0);
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vpp/app/vpp_prometheus_export.c b/src/vpp/app/vpp_prometheus_export.c
new file mode 100644 (file)
index 0000000..57c6517
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+ *------------------------------------------------------------------
+ * vpp_get_stats.c
+ *
+ * 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.
+ *------------------------------------------------------------------
+ */
+
+#include <arpa/inet.h>
+#include <sys/epoll.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <vpp-api/client/stat_client.h>
+#include <vlib/vlib.h>
+#include <vpp/stats/stats.h>
+#include <ctype.h>
+
+/* https://github.com/prometheus/prometheus/wiki/Default-port-allocations */
+#define SERVER_PORT 9477
+
+static char *
+prom_string (char *s)
+{
+  char *p = s;
+  while (*p)
+    {
+      if (!isalnum (*p))
+       *p = '_';
+      p++;
+    }
+  return s;
+}
+
+static void
+dump_metrics (FILE * stream, stat_segment_cached_pointer_t * cp)
+{
+  stat_segment_data_t *res;
+  int i, j, k;
+
+  res = stat_segment_collect (cp);
+  for (i = 0; i < vec_len (res); i++)
+    {
+      switch (res[i].type)
+       {
+       case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
+         fformat (stream, "# TYPE %s counter\n", prom_string (res[i].name));
+         for (k = 0; k < vec_len (res[i].simple_counter_vec); k++)
+           for (j = 0; j < vec_len (res[i].simple_counter_vec[k]); j++)
+             fformat (stream, "%s{thread=\"%d\",interface=\"%d\"} %lld\n",
+                      prom_string (res[i].name), k, j,
+                      res[i].simple_counter_vec[k][j]);
+         break;
+
+       case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
+         fformat (stream, "# TYPE %s_packets counter\n",
+                  prom_string (res[i].name));
+         fformat (stream, "# TYPE %s_bytes counter\n",
+                  prom_string (res[i].name));
+         for (k = 0; k < vec_len (res[i].simple_counter_vec); k++)
+           for (j = 0; j < vec_len (res[i].combined_counter_vec[k]); j++)
+             {
+               fformat (stream,
+                        "%s_packets{thread=\"%d\",interface=\"%d\"} %lld\n",
+                        prom_string (res[i].name), k, j,
+                        res[i].combined_counter_vec[k][j].packets);
+               fformat (stream,
+                        "%s_bytes{thread=\"%d\",interface=\"%d\"} %lld\n",
+                        prom_string (res[i].name), k, j,
+                        res[i].combined_counter_vec[k][j].bytes);
+             }
+         break;
+       case STAT_DIR_TYPE_ERROR_INDEX:
+         fformat (stream, "# TYPE %s counter\n", prom_string (res[i].name));
+         fformat (stream, "%s{thread=\"0\"} %lld\n",
+                  prom_string (res[i].name), res[i].error_value);
+         break;
+
+       case STAT_DIR_TYPE_SCALAR_POINTER:
+         fformat (stream, "# TYPE %s counter\n", prom_string (res[i].name));
+         fformat (stream, "%s %.2f\n", prom_string (res[i].name),
+                  res[i].scalar_value);
+         break;
+
+       default:
+         fformat (stderr, "Unknown value %d\n", res[i].type);
+         ;
+       }
+    }
+  stat_segment_data_free (res);
+
+}
+
+
+#define ROOTPAGE  "<html><head><title>Metrics exporter</title></head><body><ul><li><a href=\"/metrics\">metrics</a></li></ul></body></html>"
+#define NOT_FOUND_ERROR "<html><head><title>Document not found</title></head><body><h1>404 - Document not found</h1></body></html>"
+
+static void
+http_handler (FILE * stream, stat_segment_cached_pointer_t * cp)
+{
+  char status[80] = { 0 };
+  if (fgets (status, sizeof (status) - 1, stream) == 0)
+    {
+      fprintf (stderr, "fgets error: %s %s\n", status, strerror (errno));
+      return;
+    }
+  char *saveptr;
+  char *method = strtok_r (status, " \t\r\n", &saveptr);
+  if (method == 0 || strncmp (method, "GET", 4) != 0)
+    {
+      fputs ("HTTP/1.0 405 Method Not Allowed\r\n", stream);
+      return;
+    }
+  char *request_uri = strtok_r (NULL, " \t", &saveptr);
+  char *protocol = strtok_r (NULL, " \t\r\n", &saveptr);
+  if (protocol == 0 || strncmp (protocol, "HTTP/1.", 7) != 0)
+    {
+      fputs ("HTTP/1.0 400 Bad Request\r\n", stream);
+      return;
+    }
+  /* Read the other headers */
+  for (;;)
+    {
+      char header[1024];
+      if (fgets (header, sizeof (header) - 1, stream) == 0)
+       {
+         fprintf (stderr, "fgets error: %s\n", strerror (errno));
+         return;
+       }
+      if (header[0] == '\n' || header[1] == '\n')
+       {
+         break;
+       }
+    }
+  if (strcmp (request_uri, "/") == 0)
+    {
+      fprintf (stream, "HTTP/1.0 200 OK\r\nContent-Length: %lu\r\n\r\n",
+              (unsigned long) strlen (ROOTPAGE));
+      fputs (ROOTPAGE, stream);
+      return;
+    }
+  if (strcmp (request_uri, "/metrics") != 0)
+    {
+      fprintf (stream,
+              "HTTP/1.0 404 Not Found\r\nContent-Length: %lu\r\n\r\n",
+              (unsigned long) strlen (NOT_FOUND_ERROR));
+      fputs (NOT_FOUND_ERROR, stream);
+      return;
+    }
+  fputs ("HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n", stream);
+  dump_metrics (stream, cp);
+}
+
+static int
+start_listen (u16 port)
+{
+  struct sockaddr_in6 serveraddr;
+  int addrlen = sizeof (serveraddr);
+  int enable = 1;
+
+  int listenfd = socket (AF_INET6, SOCK_STREAM, 0);
+  if (listenfd == -1)
+    {
+      perror ("Failed opening socket");
+    }
+
+  int rv =
+    setsockopt (listenfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof (int));
+  if (rv < 0)
+    {
+      perror ("Failed setsockopt");
+    }
+
+  memset (&serveraddr, 0, sizeof (serveraddr));
+  serveraddr.sin6_family = AF_INET6;
+  serveraddr.sin6_port = htons (port);
+  serveraddr.sin6_addr = in6addr_any;
+
+  if (bind (listenfd, (struct sockaddr *) &serveraddr, addrlen) < 0)
+    {
+      fprintf (stderr, "bind() error %s\n", strerror (errno));
+      return -1;
+    }
+  if (listen (listenfd, 1000000) != 0)
+    {
+      fprintf (stderr, "listen() error for %s\n", strerror (errno));
+      return -1;
+    }
+  return listenfd;
+}
+
+/* Socket epoll, linux-specific */
+union my_sockaddr
+{
+  struct sockaddr_storage storage;
+  struct sockaddr addr;
+  struct sockaddr_in sin_addr;
+  struct sockaddr_in6 sin6_addr;
+};
+
+
+
+int
+main (int argc, char **argv)
+{
+  unformat_input_t _argv, *a = &_argv;
+  u8 *stat_segment_name, *pattern = 0, **patterns = 0;
+  int rv;
+
+  clib_mem_init (0, 128 << 20);
+
+  unformat_init_command_line (a, argv);
+
+  stat_segment_name = (u8 *) STAT_SEGMENT_SOCKET_FILE;
+
+  while (unformat_check_input (a) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (a, "socket-name %s", &stat_segment_name))
+       ;
+      else if (unformat (a, "%s", &pattern))
+       {
+         vec_add1 (patterns, pattern);
+       }
+      else
+       {
+         fformat (stderr,
+                  "%s: usage [socket-name <name>] <patterns> ...\n",
+                  argv[0]);
+         exit (1);
+       }
+    }
+
+  rv = stat_segment_connect ((char *) stat_segment_name);
+  if (rv)
+    {
+      fformat (stderr, "Couldn't connect to vpp, does %s exist?\n",
+              stat_segment_name);
+      exit (1);
+    }
+
+  u8 **dir;
+  stat_segment_cached_pointer_t *cp;
+
+  dir = stat_segment_ls (patterns);
+  cp = stat_segment_register (dir);
+  if (!cp)
+    {
+      fformat (stderr,
+              "Couldn't register required counters with stat segment\n");
+      exit (1);
+    }
+
+  int fd = start_listen (SERVER_PORT);
+  if (fd < 0)
+    {
+      exit (1);
+    }
+  for (;;)
+    {
+      int conn_sock = accept (fd, NULL, NULL);
+      if (conn_sock < 0)
+       {
+         fprintf (stderr, "Accept failed: %s", strerror (errno));
+         continue;
+       }
+      else
+       {
+         struct sockaddr_in6 clientaddr;
+         char address[INET6_ADDRSTRLEN];
+         socklen_t addrlen;
+         getpeername (conn_sock, (struct sockaddr *) &clientaddr, &addrlen);
+         if (inet_ntop
+             (AF_INET6, &clientaddr.sin6_addr, address, sizeof (address)))
+           {
+             printf ("Client address is [%s]:%d\n", address,
+                     ntohs (clientaddr.sin6_port));
+           }
+       }
+
+      FILE *stream = fdopen (conn_sock, "r+");
+      if (stream == NULL)
+       {
+         fprintf (stderr, "fdopen error: %s\n", strerror (errno));
+         close (conn_sock);
+         continue;
+       }
+      /* Single reader at the moment */
+      http_handler (stream, cp);
+      fclose (stream);
+    }
+
+  stat_segment_disconnect ();
+
+  exit (0);
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
index 29b3bf3..8459138 100644 (file)
@@ -43,7 +43,7 @@ vlib_stats_push_heap (void)
 }
 
 void
-vlib_stats_pop_heap (void *cm_arg, void *oldheap)
+vlib_stats_pop_heap (void *cm_arg, void *oldheap, stat_directory_type_t type)
 {
   vlib_simple_counter_main_t *cm = (vlib_simple_counter_main_t *) cm_arg;
   stats_main_t *sm = &stats_main;
@@ -80,7 +80,7 @@ vlib_stats_pop_heap (void *cm_arg, void *oldheap)
        }
       name_copy = format (0, "%s%c", stat_segment_name, 0);
       ep = clib_mem_alloc (sizeof (*ep));
-      ep->type = STAT_DIR_TYPE_COUNTER_VECTOR;
+      ep->type = type;
       ep->value = cm->counters;
       hash_set_mem (sm->counter_vector_by_name, name_copy, ep);
 
@@ -236,6 +236,7 @@ vlib_map_stat_segment_init (void)
   sm->input_rate_ptr = (scalar_data + 1);
   sm->last_runtime_ptr = (scalar_data + 2);
   sm->last_runtime_stats_clear_ptr = (scalar_data + 3);
+  sm->heartbeat_ptr = (scalar_data + 4);
 
   name = format (0, "/sys/vector_rate%c", 0);
   ep = clib_mem_alloc (sizeof (*ep));
@@ -265,6 +266,13 @@ vlib_map_stat_segment_init (void)
 
   hash_set_mem (sm->counter_vector_by_name, name, ep);
 
+  name = format (0, "/sys/heartbeat%c", 0);
+  ep = clib_mem_alloc (sizeof (*ep));
+  ep->type = STAT_DIR_TYPE_SCALAR_POINTER;
+  ep->value = sm->heartbeat_ptr;
+
+  hash_set_mem (sm->counter_vector_by_name, name, ep);
+
 
   /* Publish the hash table */
   shared_header->opaque[STAT_SEGMENT_OPAQUE_DIR] = sm->counter_vector_by_name;
@@ -309,7 +317,8 @@ format_stat_dir_entry (u8 * s, va_list * args)
       type_name = "VectorPtr";
       break;
 
-    case STAT_DIR_TYPE_COUNTER_VECTOR:
+    case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
+    case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
       type_name = "CMainPtr";
       break;
 
@@ -512,6 +521,9 @@ do_stat_segment_updates (stats_main_t * sm)
 
   if (sm->serialize_nodes)
     update_serialized_nodes (sm);
+
+  /* Heartbeat, so clients detect we're still here */
+  (*sm->heartbeat_ptr)++;
 }
 
 static clib_error_t *
index aea2da7..911706c 100644 (file)
@@ -175,6 +175,7 @@ typedef struct
   f64 *last_runtime_ptr;
   f64 *last_runtime_stats_clear_ptr;
   f64 *vector_rate_ptr;
+  f64 *heartbeat_ptr;
   u64 last_input_packets;
 
   /* Pointers to vector stats maintained by the stat thread */
@@ -209,7 +210,8 @@ typedef enum
   STAT_DIR_TYPE_ILLEGAL = 0,
   STAT_DIR_TYPE_SCALAR_POINTER,
   STAT_DIR_TYPE_VECTOR_POINTER,
-  STAT_DIR_TYPE_COUNTER_VECTOR,
+  STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE,
+  STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED,
   STAT_DIR_TYPE_ERROR_INDEX,
   STAT_DIR_TYPE_SERIALIZED_NODES,
 } stat_directory_type_t;