selog: selog client lib 59/43859/7
authorMohammed Hawari <[email protected]>
Tue, 7 Oct 2025 15:55:48 +0000 (17:55 +0200)
committerDamjan Marion <[email protected]>
Wed, 8 Oct 2025 19:15:42 +0000 (19:15 +0000)
Change-Id: I4a81981dc52ed5c1acab9afd858990baebad1cc9
Type: feature
Signed-off-by: Mohammed Hawari <[email protected]>
src/plugins/selog/CMakeLists.txt
src/plugins/selog/selog_client/CMakeLists.txt [new file with mode: 0644]
src/plugins/selog/selog_client/selog_client.c [new file with mode: 0644]
src/plugins/selog/selog_client/selog_client.h [new file with mode: 0644]
src/plugins/selog/selog_client/selog_client_example.c [new file with mode: 0644]
src/plugins/selog/selog_client/selog_client_internal.h [new file with mode: 0644]

index eeb8da5..f0f0b6f 100644 (file)
@@ -14,4 +14,6 @@ add_vpp_plugin(selog
   INSTALL_HEADERS
   selog.h
   selog_shared.h
-)
\ No newline at end of file
+)
+
+add_subdirectory(selog_client)
\ No newline at end of file
diff --git a/src/plugins/selog/selog_client/CMakeLists.txt b/src/plugins/selog/selog_client/CMakeLists.txt
new file mode 100644 (file)
index 0000000..df28240
--- /dev/null
@@ -0,0 +1,20 @@
+add_vpp_library(selog_client
+  SOURCES
+  selog_client.c
+
+  LINK_LIBRARIES
+  vppinfra svm vlibmemoryclient rt pthread
+
+  DEPENDS
+  selog_plugin_api_headers
+
+  INSTALL_HEADERS
+  selog_client.h
+)
+
+add_vpp_executable(selog_client_example
+  SOURCES
+  selog_client_example.c
+  LINK_LIBRARIES
+  selog_client
+)
\ No newline at end of file
diff --git a/src/plugins/selog/selog_client/selog_client.c b/src/plugins/selog/selog_client/selog_client.c
new file mode 100644 (file)
index 0000000..f35fe9a
--- /dev/null
@@ -0,0 +1,942 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2025 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 <vlibmemory/api.h>
+#include <vlibmemory/memclnt.api_enum.h>
+#include <vlibmemory/memclnt.api_types.h>
+#include <selog/selog_client/selog_client_internal.h>
+#include <sys/mman.h>
+#include <selog/selog.api_enum.h>
+#include <selog/selog.api_types.h>
+
+#ifndef SELOG_CLIENT_HEAPSIZE
+#define SELOG_CLIENT_HEAPSIZE (16 * 1024 * 1024)
+#endif
+
+#define SELOG_REPLY_MSG_ID_BASE selog_msg_id_base
+
+static u16 selog_msg_id_base;
+
+static void
+selog_client_init_mem (void)
+{
+  void *mem = 0;
+  void *heap = 0;
+  mem = mmap (0, SELOG_CLIENT_HEAPSIZE, PROT_READ | PROT_WRITE,
+             MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+  if (mem == MAP_FAILED)
+    {
+      SELOG_LOG_ERROR ("mmap failed");
+      abort ();
+    }
+
+  heap = clib_mem_init_thread_safe (mem, SELOG_CLIENT_HEAPSIZE);
+  if (!heap)
+    {
+      SELOG_LOG_ERROR ("clib_mem_init_thread_safe failed");
+      abort ();
+    }
+}
+
+selog_client_ctx_t *
+selog_client_ctx_alloc ()
+{
+  selog_client_main_t *scm = &selog_client_main;
+  selog_client_internal_ctx_t *ictx;
+  pool_get_zero (scm->internal_ctx, ictx);
+  clib_spinlock_init (&ictx->lock);
+  ictx->state = SELOG_CLIENT_INTERNAL_STATE_DISCONNECTED;
+  elog_init (&ictx->private_em, 0);
+  return &ictx->client_ctx;
+}
+
+void
+selog_client_ctx_free (selog_client_ctx_t *ctx)
+{
+  selog_client_main_t *scm = &selog_client_main;
+  selog_client_internal_ctx_t *ictx = SELOG_INTERNAL_CTX (ctx);
+  if (ictx->bapi_sock_ctx.socket_fd != 0)
+    {
+      SELOG_LOG_ERROR ("Freeing a connected context, undefined behavior!");
+      abort ();
+    }
+  clib_spinlock_free (&ictx->lock);
+  pool_put (scm->internal_ctx, ictx);
+}
+
+#define foreach_selog_msg                                                     \
+  _ (SELOG_GET_SHM_REPLY, selog_get_shm_reply)                                \
+  _ (SELOG_GET_STRING_TABLE_REPLY, selog_get_string_table_reply)              \
+  _ (SELOG_TRACK_DETAILS, selog_track_details)                                \
+  _ (SELOG_EVENT_TYPE_DETAILS, selog_event_type_details)                      \
+  _ (SELOG_EVENT_TYPE_STRING_DETAILS, selog_event_type_string_details)
+
+#define foreach_memclnt_msg _ (CONTROL_PING_REPLY, control_ping_reply)
+
+#define vl_endianfun /* define message structures */
+#include <selog/selog.api.h>
+#include <vlibmemory/memclnt.api.h>
+#undef vl_endianfun
+
+#define vl_calcsizefun
+#include <selog/selog.api.h>
+#include <vlibmemory/memclnt.api.h>
+#undef vl_calcsizefun
+
+/* instantiate all the print functions we know about */
+#define vl_printfun
+#include <selog/selog.api.h>
+#include <vlibmemory/memclnt.api.h>
+#undef vl_printfun
+
+#define vl_api_version(n, v) static u32 selog_api_version = v;
+#include <selog/selog.api.h>
+#undef vl_api_version
+
+/* all API message handlers */
+static void
+vl_api_selog_get_shm_reply_t_handler (vl_api_selog_get_shm_reply_t *mp)
+{
+  selog_client_internal_ctx_t *ictx;
+  clib_error_t *err = 0;
+  int rv = 0;
+  ssvm_private_t *ssvm;
+  int fd;
+
+  vl_api_selog_get_shm_reply_t_endian (mp, 0 /* to host byte order */);
+  ictx = pool_elt_at_index (selog_client_main.internal_ctx, mp->context);
+  SELOG_LOG_DEBUG ("%s: vl_api_selog_get_shm_reply_t_handler",
+                  ictx->client_ctx.client_name);
+  if (mp->retval < 0)
+    {
+      SELOG_LOG_ERROR ("vl_api_selog_get_shm_reply_t_handler: error %d",
+                      mp->retval);
+      goto failed;
+    }
+  /* Receive fds */
+  if ((err =
+        vl_socket_client_recv_fd_msg2 (&ictx->bapi_sock_ctx, &fd, 1, 1)) != 0)
+    {
+      SELOG_LOG_ERROR ("%s: vl_socket_client_recv_fd_msg2 failed",
+                      ictx->client_ctx.client_name);
+      clib_error_report (err);
+      goto failed;
+    }
+
+  /* Connect the SSVM */
+  ssvm = &ictx->ssvm;
+  ssvm->my_pid = getpid ();
+  ssvm->name =
+    format (0, "selog_client_%s%c", ictx->client_ctx.client_name, 0);
+  ssvm->requested_va = 0;
+  ssvm->fd = fd;
+
+  if ((rv = ssvm_client_init_memfd (ssvm)) != 0)
+    {
+      SELOG_LOG_ERROR ("%s: ssvm_client_init_memfd failed", ssvm->name);
+      goto failed;
+    }
+  ictx->sh =
+    (selog_shared_header_t *) ((u8 *) ssvm->sh + (uword) ssvm->sh->opaque[0]);
+
+  /* Replace the private elog ring with the shared one */
+  vec_free (ictx->private_em.event_ring);
+  ictx->private_em.event_ring = ictx->sh->em.event_ring;
+  ictx->private_em.event_ring_size = ictx->sh->em.event_ring_size;
+
+  clib_atomic_store_rel_n (&ictx->state,
+                          SELOG_CLIENT_INTERNAL_STATE_SHM_RECEIVED);
+  return;
+failed:
+  clib_atomic_store_rel_n (&ictx->state, SELOG_CLIENT_INTERNAL_STATE_ERROR);
+  return;
+}
+
+static void
+vl_api_selog_get_string_table_reply_t_handler (
+  vl_api_selog_get_string_table_reply_t *mp)
+{
+  selog_client_internal_ctx_t *ictx;
+  u8 *string_tmp = 0;
+  uword offset;
+  vl_api_selog_get_string_table_reply_t_endian (mp,
+                                               0 /* to host byte order */);
+  ictx = pool_elt_at_index (selog_client_main.internal_ctx, mp->context);
+  SELOG_LOG_DEBUG ("%s: vl_api_selog_get_string_table_reply_t_handler",
+                  ictx->client_ctx.client_name);
+
+  if (mp->retval < 0)
+    {
+      SELOG_LOG_ERROR (
+       "%s: vl_api_selog_get_string_table_reply_t_handler: error %d",
+       ictx->client_ctx.client_name, mp->retval);
+      goto failed;
+    }
+
+  /* Check that the beginning of the string table matches the existing one */
+  if (vl_api_string_len (&mp->s) && ictx->private_em.string_table &&
+      clib_memcmp (mp->s.buf, ictx->private_em.string_table,
+                  clib_min (vl_api_string_len (&mp->s),
+                            vec_len (ictx->private_em.string_table))) != 0)
+    {
+      SELOG_LOG_ERROR ("%s: string table mismatch",
+                      ictx->client_ctx.client_name);
+      goto failed;
+    }
+
+  offset = vec_len (ictx->private_em.string_table);
+
+  while (offset < vl_api_string_len (&mp->s))
+    {
+      u8 *s;
+      s = (u8 *) mp->s.buf + offset;
+      string_tmp = format (0, "%s%c", s, 0);
+      vec_append (ictx->private_em.string_table, string_tmp);
+      hash_set_mem (ictx->private_em.string_table_hash, string_tmp, offset);
+      offset += clib_strnlen ((char *) s, vl_api_string_len (&mp->s)) + 1;
+      string_tmp = 0;
+    }
+  clib_atomic_store_rel_n (&ictx->state,
+                          SELOG_CLIENT_INTERNAL_STATE_STRING_TABLE_LOADED);
+  return;
+failed:
+  clib_atomic_store_rel_n (&ictx->state, SELOG_CLIENT_INTERNAL_STATE_ERROR);
+  return;
+}
+
+static void
+vl_api_selog_track_details_t_handler (vl_api_selog_track_details_t *mp)
+{
+  selog_client_internal_ctx_t *ictx;
+  vl_api_selog_track_details_t_endian (mp, 0 /* to host byte order */);
+  ictx = pool_elt_at_index (selog_client_main.internal_ctx, mp->context);
+  SELOG_LOG_DEBUG ("%s index %d: vl_api_selog_track_details_t_handler",
+                  ictx->client_ctx.client_name, mp->index);
+  if (mp->index >= vec_len (ictx->private_em.tracks))
+    {
+      elog_track_t track = { 0 };
+      char *name;
+      name = vl_api_from_api_to_new_c_string (&mp->name);
+      track.name = name;
+      elog_track_register (&ictx->private_em, &track);
+      vec_free (name);
+      ASSERT (mp->index == vec_len (ictx->private_em.tracks) - 1);
+      SELOG_LOG_INFO ("%s: registered track index %d (%s)",
+                     ictx->client_ctx.client_name, mp->index, track.name);
+    }
+  else
+    SELOG_LOG_WARNING ("%s: track index %d already exists",
+                      ictx->client_ctx.client_name, mp->index);
+}
+
+static char *
+selog_client_parse_digits (char *s, char *end, u8 *value_parsed)
+{
+  u8 v = 0;
+  *value_parsed = 0;
+  while (s < end && *s >= '0' && *s <= '9')
+    {
+      v = v * 10 + (*s - '0');
+      s++;
+    }
+  *value_parsed = v;
+  return s;
+}
+
+static void
+selog_client_parse_elog_type_for_strings (selog_client_internal_ctx_t *ictx,
+                                         uword type_index)
+{
+  elog_event_type_t *et =
+    vec_elt_at_index (ictx->private_em.event_types, type_index);
+  vec_validate (ictx->event_type_private, type_index);
+  selog_type_private_t *st =
+    vec_elt_at_index (ictx->event_type_private, type_index);
+  char *s = (char *) et->format_args;
+  u8 current_offset = 0;
+  u8 var_size = 0;
+  u8 digits_parsed;
+  while (*s && s < vec_end (et->format_args))
+    {
+      switch (s[0])
+       {
+       case 'i':
+       case 't':
+       case 'f':
+       case 's':
+         s += 1;
+         s = selog_client_parse_digits (s, vec_end (et->format_args),
+                                        &digits_parsed);
+         current_offset += digits_parsed;
+         if (digits_parsed == 0)
+           {
+             SELOG_LOG_DEBUG (
+               "Variable size string detected in event type %s", et->format);
+             var_size = 1;
+           }
+         break;
+       case 'T':
+         if (var_size)
+           {
+             SELOG_LOG_ERROR ("String after variable size string is not "
+                              "supported by client %s",
+                              et->format);
+             abort ();
+           }
+         s += 1;
+         s = selog_client_parse_digits (s, vec_end (et->format_args),
+                                        &digits_parsed);
+         vec_add1 (st->string_offset, current_offset);
+         current_offset += digits_parsed;
+         vec_add1 (st->string_size, digits_parsed);
+         break;
+       default:
+         SELOG_LOG_ERROR ("Unknown format args '%s' in event type %s",
+                          et->format_args, et->format);
+         abort ();
+       }
+    }
+}
+
+static void
+vl_api_selog_event_type_details_t_handler (
+  vl_api_selog_event_type_details_t *mp)
+{
+  selog_client_internal_ctx_t *ictx;
+  vl_api_selog_event_type_details_t_endian (mp, 0 /* to host byte order */);
+  ictx = pool_elt_at_index (selog_client_main.internal_ctx, mp->context);
+  SELOG_LOG_DEBUG ("%s index %d: vl_api_selog_event_type_details_t_handler",
+                  ictx->client_ctx.client_name, mp->index);
+  if (mp->index >= vec_len (ictx->private_em.event_types))
+    {
+      elog_event_type_t event_type = { 0 };
+      char *s = 0;
+      event_type.format = vl_api_from_api_to_new_c_string (&mp->fmt);
+      vec_validate (s, sizeof (mp->fmt_args) - 1);
+      event_type.format_args = s;
+      clib_strncpy (event_type.format_args, (char *) mp->fmt_args,
+                   vec_len (event_type.format_args) - 1);
+      uword l;
+      l = elog_event_type_register (&ictx->private_em, &event_type);
+      selog_client_parse_elog_type_for_strings (ictx, l);
+      ASSERT (mp->index == l);
+      SELOG_LOG_INFO (
+       "%s: registered event type index %d for format %s and args %s",
+       ictx->client_ctx.client_name, mp->index, event_type.format, s);
+      vec_free (s);
+    }
+  else
+    SELOG_LOG_WARNING ("%s: event type index %d already exists",
+                      ictx->client_ctx.client_name, mp->index);
+}
+
+static void
+vl_api_selog_event_type_string_details_t_handler (
+  vl_api_selog_event_type_string_details_t *mp)
+{
+  selog_client_internal_ctx_t *ictx;
+  elog_event_type_t *event_type;
+  char *s;
+  vl_api_selog_event_type_string_details_t_endian (mp,
+                                                  0 /* to host byte order */);
+  ictx = pool_elt_at_index (selog_client_main.internal_ctx, mp->context);
+  event_type = vec_elt_at_index (ictx->private_em.event_types,
+                                ictx->current_event_type_index);
+  SELOG_LOG_DEBUG ("%s index %lu enum_string_index %d: "
+                  "vl_api_selog_event_type_string_details_t_handler",
+                  ictx->client_ctx.client_name,
+                  ictx->current_event_type_index, mp->index);
+
+  if (mp->index >= vec_len (event_type->enum_strings_vector))
+    {
+      s = vl_api_from_api_to_new_c_string (&mp->s);
+      vec_validate (event_type->enum_strings_vector, mp->index);
+      event_type->enum_strings_vector[mp->index] = s;
+      ASSERT (mp->index == vec_len (event_type->enum_strings_vector) - 1);
+      SELOG_LOG_INFO ("%s: registered event type index %lu enum string index "
+                     "%d (%s)",
+                     ictx->client_ctx.client_name,
+                     ictx->current_event_type_index, mp->index, s);
+    }
+  else
+    SELOG_LOG_WARNING (
+      "%s: event type index %lu enum string index %d already exists",
+      ictx->client_ctx.client_name, ictx->current_event_type_index, mp->index);
+}
+
+static void
+vl_api_control_ping_reply_t_handler (vl_api_control_ping_reply_t *mp)
+{
+  selog_client_internal_ctx_t *ictx;
+  vl_api_control_ping_reply_t_endian (mp, 0 /* to host byte order */);
+  ictx = pool_elt_at_index (selog_client_main.internal_ctx, mp->context);
+  SELOG_LOG_DEBUG ("%s: vl_api_control_ping_reply_t_handler",
+                  ictx->client_ctx.client_name);
+
+  clib_atomic_store_rel_n (&ictx->multipart_done, 1);
+  return;
+}
+static void
+selog_client_bapi_hookup (void)
+{
+  u8 *msg_base_lookup_name = format (0, "selog_%08x%c", selog_api_version, 0);
+
+  SELOG_REPLY_MSG_ID_BASE =
+    vl_client_get_first_plugin_msg_id ((char *) msg_base_lookup_name);
+
+  vec_free (msg_base_lookup_name);
+
+  if (SELOG_REPLY_MSG_ID_BASE == (u16) ~0)
+    {
+      SELOG_LOG_ERROR ("vl_client_get_first_plugin_msg_id failed for selog");
+      abort ();
+    }
+
+#define _(N, n)                                                               \
+  vl_msg_api_config (&(vl_msg_api_msg_config_t){                              \
+    .id = SELOG_REPLY_MSG_ID_BASE + VL_API_##N,                               \
+    .name = #n,                                                               \
+    .handler = vl_api_##n##_t_handler,                                        \
+    .endian = vl_api_##n##_t_endian,                                          \
+    .format_fn = vl_api_##n##_t_format,                                       \
+    .size = sizeof (vl_api_##n##_t),                                          \
+    .traced = (u32) 1,                                                        \
+    .tojson = vl_api_##n##_t_tojson,                                          \
+    .fromjson = vl_api_##n##_t_fromjson,                                      \
+    .calc_size = vl_api_##n##_t_calc_size,                                    \
+  });
+  foreach_selog_msg;
+#undef _
+
+#define _(N, n)                                                               \
+  vl_msg_api_config (&(vl_msg_api_msg_config_t){                              \
+    .id = VL_API_##N + 1,                                                     \
+    .name = #n,                                                               \
+    .handler = vl_api_##n##_t_handler,                                        \
+    .endian = vl_api_##n##_t_endian,                                          \
+    .format_fn = vl_api_##n##_t_format,                                       \
+    .size = sizeof (vl_api_##n##_t),                                          \
+    .traced = (u32) 1,                                                        \
+    .tojson = vl_api_##n##_t_tojson,                                          \
+    .fromjson = vl_api_##n##_t_fromjson,                                      \
+    .calc_size = vl_api_##n##_t_calc_size,                                    \
+  });
+  foreach_memclnt_msg;
+#undef _
+}
+
+static int
+selog_client_ictx_wait_for_state_change (selog_client_internal_ctx_t *ictx,
+                                        u8 expected_state)
+{
+  f64 timeout = clib_time_now (&selog_client_main.time) + 5.0;
+  int async_error = 0;
+
+  while (clib_time_now (&selog_client_main.time) < timeout)
+    {
+      if (clib_atomic_load_acq_n (&ictx->state) == expected_state)
+       return SELOG_CLIENT_ERROR_NONE;
+
+      if (clib_atomic_load_acq_n (&ictx->state) ==
+         SELOG_CLIENT_INTERNAL_STATE_ERROR)
+       {
+         SELOG_LOG_ERROR ("%s: in error state", ictx->client_ctx.client_name);
+         async_error = ictx->async_error;
+         ictx->async_error = 0;
+         return -async_error;
+       }
+    }
+  SELOG_LOG_DEBUG ("%s: timeout waiting for state %s, current state %s",
+                  ictx->client_ctx.client_name,
+                  selog_client_internal_state_str (expected_state),
+                  selog_client_internal_state_str (ictx->state));
+
+  return -SELOG_CLIENT_ERROR_TIMEOUT;
+}
+
+static int
+selog_client_ictx_wait_for_multipart_done (selog_client_internal_ctx_t *ictx)
+{
+  f64 timeout = clib_time_now (&selog_client_main.time) + 5.0;
+
+  while (clib_time_now (&selog_client_main.time) < timeout)
+    {
+      if (clib_atomic_load_acq_n (&ictx->multipart_done) == 1)
+       {
+         ictx->multipart_done = 0;
+         if (ictx->state ==
+             SELOG_CLIENT_INTERNAL_STATE_WAITING_FOR_ALL_TRACKS)
+           clib_atomic_store_rel_n (
+             &ictx->state, SELOG_CLIENT_INTERNAL_STATE_ALL_TRACKS_LOADED);
+         else if (ictx->state ==
+                  SELOG_CLIENT_INTERNAL_STATE_WAITING_FOR_ALL_EVENT_TYPES)
+           clib_atomic_store_rel_n (
+             &ictx->state,
+             SELOG_CLIENT_INTERNAL_STATE_ALL_EVENT_TYPES_LOADED);
+         else if (ictx->state ==
+                  SELOG_CLIENT_INTERNAL_STATE_WAITING_FOR_ENUM_STRINGS)
+           clib_atomic_store_rel_n (
+             &ictx->state, SELOG_CLIENT_INTERNAL_STATE_ENUM_STRINGS_LOADED);
+         else
+           {
+             SELOG_LOG_ERROR ("%s: unexpected state %s on multipart done",
+                              ictx->client_ctx.client_name,
+                              selog_client_internal_state_str (ictx->state));
+             abort ();
+           }
+         return SELOG_CLIENT_ERROR_NONE;
+       }
+    }
+  SELOG_LOG_DEBUG ("%s: timeout waiting for multipart done",
+                  ictx->client_ctx.client_name);
+  ictx->multipart_done = 0;
+  return -SELOG_CLIENT_ERROR_TIMEOUT;
+}
+
+static void
+selog_client_ictx_want_multipart_done (selog_client_internal_ctx_t *ictx)
+{
+  vl_api_control_ping_t *mp;
+
+  mp = vl_msg_api_alloc (sizeof (*mp));
+  if (!mp)
+    {
+      SELOG_LOG_ERROR ("vl_msg_api_alloc failed");
+      return;
+    }
+  clib_memset (mp, 0, sizeof (*mp));
+  mp->_vl_msg_id = VL_API_CONTROL_PING + 1;
+  mp->client_index = ictx->api_client_handle;
+  mp->context = ictx - selog_client_main.internal_ctx;
+  vl_api_control_ping_t_endian (mp, 1 /* to net byte order */);
+
+  ASSERT (ictx->multipart_done == 0);
+  vl_msg_api_send_shmem (ictx->vl_input_queue, (u8 *) &mp);
+}
+
+static int32_t
+selog_client_ictx_retrieve_shm (selog_client_internal_ctx_t *ictx)
+{
+  vl_api_selog_get_shm_t *mp;
+  int rv = SELOG_CLIENT_ERROR_NONE;
+
+  mp = vl_msg_api_alloc (sizeof (*mp));
+  if (!mp)
+    {
+      SELOG_LOG_ERROR ("vl_msg_api_alloc failed");
+      return -SELOG_CLIENT_ERROR_INVALID_ARG;
+    }
+
+  clib_memset (mp, 0, sizeof (*mp));
+  mp->_vl_msg_id = SELOG_REPLY_MSG_ID_BASE + VL_API_SELOG_GET_SHM;
+  mp->client_index = ictx->api_client_handle;
+  mp->context = ictx - selog_client_main.internal_ctx;
+  vl_api_selog_get_shm_t_endian (mp, 1 /* to net byte order */);
+  ictx->state = SELOG_CLIENT_INTERNAL_STATE_WAITING_FOR_SHM;
+  vl_msg_api_send_shmem (ictx->vl_input_queue, (u8 *) &mp);
+  rv = selog_client_ictx_wait_for_state_change (
+    ictx, SELOG_CLIENT_INTERNAL_STATE_SHM_RECEIVED);
+
+  return rv;
+}
+
+static int32_t
+selog_client_ictx_retrieve_string_table (selog_client_internal_ctx_t *ictx)
+{
+  vl_api_selog_get_string_table_t *mp;
+  int rv = SELOG_CLIENT_ERROR_NONE;
+
+  mp = vl_msg_api_alloc (sizeof (*mp));
+  if (!mp)
+    {
+      SELOG_LOG_ERROR ("vl_msg_api_alloc failed");
+      return -SELOG_CLIENT_ERROR_INVALID_ARG;
+    }
+
+  clib_memset (mp, 0, sizeof (*mp));
+  mp->_vl_msg_id = SELOG_REPLY_MSG_ID_BASE + VL_API_SELOG_GET_STRING_TABLE;
+  mp->client_index = ictx->api_client_handle;
+  mp->context = ictx - selog_client_main.internal_ctx;
+  vl_api_selog_get_string_table_t_endian (mp, 1 /* to net byte order */);
+  ictx->state = SELOG_CLIENT_INTERNAL_STATE_WAITING_FOR_STRING_TABLE;
+  vl_msg_api_send_shmem (ictx->vl_input_queue, (u8 *) &mp);
+  rv = selog_client_ictx_wait_for_state_change (
+    ictx, SELOG_CLIENT_INTERNAL_STATE_STRING_TABLE_LOADED);
+
+  return rv;
+}
+
+static int32_t
+selog_client_ictx_retrieve_all_tracks (selog_client_internal_ctx_t *ictx)
+{
+  vl_api_selog_track_dump_t *mp;
+  int rv = SELOG_CLIENT_ERROR_NONE;
+
+  mp = vl_msg_api_alloc (sizeof (*mp));
+  if (!mp)
+    {
+      SELOG_LOG_ERROR ("vl_msg_api_alloc failed");
+      return -SELOG_CLIENT_ERROR_INVALID_ARG;
+    }
+  clib_memset (mp, 0, sizeof (*mp));
+  mp->_vl_msg_id = SELOG_REPLY_MSG_ID_BASE + VL_API_SELOG_TRACK_DUMP;
+  mp->client_index = ictx->api_client_handle;
+  mp->context = ictx - selog_client_main.internal_ctx;
+  vl_api_selog_track_dump_t_endian (mp, 1 /* to net byte order */);
+  ictx->state = SELOG_CLIENT_INTERNAL_STATE_WAITING_FOR_ALL_TRACKS;
+  vl_msg_api_send_shmem (ictx->vl_input_queue, (u8 *) &mp);
+  selog_client_ictx_want_multipart_done (ictx);
+  rv = selog_client_ictx_wait_for_multipart_done (ictx);
+
+  return rv;
+}
+
+static int32_t
+selog_client_ictx_retrieve_all_event_types (selog_client_internal_ctx_t *ictx)
+{
+  vl_api_selog_event_type_dump_t *mp;
+  int rv = SELOG_CLIENT_ERROR_NONE;
+
+  mp = vl_msg_api_alloc (sizeof (*mp));
+  if (!mp)
+    {
+      SELOG_LOG_ERROR ("vl_msg_api_alloc failed");
+      return -SELOG_CLIENT_ERROR_INVALID_ARG;
+    }
+
+  clib_memset (mp, 0, sizeof (*mp));
+  mp->_vl_msg_id = SELOG_REPLY_MSG_ID_BASE + VL_API_SELOG_EVENT_TYPE_DUMP;
+  mp->client_index = ictx->api_client_handle;
+  mp->context = ictx - selog_client_main.internal_ctx;
+  vl_api_selog_event_type_dump_t_endian (mp, 1 /* to net byte order */);
+  ictx->state = SELOG_CLIENT_INTERNAL_STATE_WAITING_FOR_ALL_EVENT_TYPES;
+  vl_msg_api_send_shmem (ictx->vl_input_queue, (u8 *) &mp);
+  selog_client_ictx_want_multipart_done (ictx);
+  rv = selog_client_ictx_wait_for_multipart_done (ictx);
+
+  return rv;
+}
+
+static int32_t
+selog_client_ictx_retrieve_enum_strings_for_event_type (
+  selog_client_internal_ctx_t *ictx, u32 event_type_index)
+{
+  vl_api_selog_event_type_string_dump_t *mp;
+  int rv = SELOG_CLIENT_ERROR_NONE;
+
+  mp = vl_msg_api_alloc (sizeof (*mp));
+  if (!mp)
+    {
+      SELOG_LOG_ERROR ("vl_msg_api_alloc failed");
+      return -SELOG_CLIENT_ERROR_INVALID_ARG;
+    }
+
+  clib_memset (mp, 0, sizeof (*mp));
+  mp->_vl_msg_id =
+    SELOG_REPLY_MSG_ID_BASE + VL_API_SELOG_EVENT_TYPE_STRING_DUMP;
+  mp->client_index = ictx->api_client_handle;
+  mp->context = ictx - selog_client_main.internal_ctx;
+  mp->event_type_index = event_type_index;
+  vl_api_selog_event_type_string_dump_t_endian (mp, 1 /* to net byte order */);
+  ictx->current_event_type_index = event_type_index;
+  ictx->state = SELOG_CLIENT_INTERNAL_STATE_WAITING_FOR_ENUM_STRINGS;
+  vl_msg_api_send_shmem (ictx->vl_input_queue, (u8 *) &mp);
+  selog_client_ictx_want_multipart_done (ictx);
+  rv = selog_client_ictx_wait_for_multipart_done (ictx);
+
+  return rv;
+}
+
+int32_t
+selog_client_connect_to_vpp (selog_client_ctx_t *ctx)
+{
+  selog_client_internal_ctx_t *ictx = SELOG_INTERNAL_CTX (ctx);
+  api_main_t *am;
+  int rv = SELOG_CLIENT_ERROR_NONE;
+  int i;
+
+  clib_spinlock_lock_if_init (&ictx->lock);
+
+  vlibapi_set_main (&ictx->bapi_api_ctx);
+  vlibapi_set_memory_client_main (&ictx->bapi_mem_ctx);
+
+  if (ctx->sock_name == 0)
+    {
+      rv = -SELOG_CLIENT_ERROR_INVALID_ARG;
+      SELOG_LOG_ERROR ("No socket name specified");
+      goto done;
+    }
+
+  if (ctx->client_name == 0)
+    {
+      rv = -SELOG_CLIENT_ERROR_INVALID_ARG;
+      SELOG_LOG_ERROR ("No client name specified");
+      goto done;
+    }
+
+  if (vl_socket_client_connect2 (&ictx->bapi_sock_ctx, (char *) ctx->sock_name,
+                                (char *) ctx->client_name,
+                                0 /* default rx/tx buffer */))
+    {
+      rv = -SELOG_CLIENT_ERROR_CONNECT_FAIL;
+      SELOG_LOG_ERROR ("vl_socket_client_connect2 failed");
+      goto done;
+    }
+
+  if (vl_socket_client_init_shm2 (&ictx->bapi_sock_ctx, 0,
+                                 1 /* want_pthread */))
+    {
+      SELOG_LOG_ERROR ("%s: vl_socket_client_init_shm2 failed",
+                      ctx->client_name);
+      rv = -SELOG_CLIENT_ERROR_CONNECT_FAIL;
+      goto done;
+    }
+
+  selog_client_bapi_hookup ();
+
+  am = vlibapi_get_main ();
+  ictx->vl_input_queue = am->shmem_hdr->vl_input_queue;
+  ictx->api_client_handle = (u32) am->my_client_index;
+
+  SELOG_LOG_DEBUG ("%s: connected to vpp", ctx->client_name);
+
+  /* Retrieve the SHM from VPP */
+  rv = selog_client_ictx_retrieve_shm (ictx);
+  if (rv < 0)
+    {
+      SELOG_LOG_ERROR ("%s: selog_client_ctx_retrieve_shm failed",
+                      ctx->client_name);
+      goto done;
+    }
+
+  /* Retrieve string table */
+  rv = selog_client_ictx_retrieve_string_table (ictx);
+  if (rv < 0)
+    {
+      SELOG_LOG_ERROR ("%s: selog_client_ictx_retrieve_string_table failed",
+                      ctx->client_name);
+      goto done;
+    }
+
+  /* Retrieve all tracks */
+  rv = selog_client_ictx_retrieve_all_tracks (ictx);
+  if (rv < 0)
+    {
+      SELOG_LOG_ERROR ("%s: selog_client_ictx_retrieve_all_tracks failed",
+                      ctx->client_name);
+      goto done;
+    }
+
+  /* Retrieve all event types */
+  rv = selog_client_ictx_retrieve_all_event_types (ictx);
+  if (rv < 0)
+    {
+      SELOG_LOG_ERROR ("%s: selog_client_ictx_retrieve_all_event_types failed",
+                      ctx->client_name);
+      goto done;
+    }
+
+  /* Retrieve enum strings for each event type */
+  vec_foreach_index (i, ictx->private_em.event_types)
+    {
+      rv = selog_client_ictx_retrieve_enum_strings_for_event_type (ictx, i);
+      if (rv < 0)
+       {
+         SELOG_LOG_ERROR (
+           "%s: selog_client_ictx_retrieve_enum_strings_for_event_type "
+           "failed for event type index %u",
+           ctx->client_name, i);
+         goto done;
+       }
+    }
+
+  ictx->state = SELOG_CLIENT_INTERNAL_STATE_CONNECTED;
+
+done:
+  clib_spinlock_unlock_if_init (&ictx->lock);
+  return rv;
+}
+
+int32_t
+selog_client_disconnect_from_vpp (selog_client_ctx_t *ctx)
+{
+  selog_client_internal_ctx_t *ictx = SELOG_INTERNAL_CTX (ctx);
+  int rv = SELOG_CLIENT_ERROR_NONE;
+
+  clib_spinlock_lock_if_init (&ictx->lock);
+  if (ictx->bapi_sock_ctx.socket_fd == 0)
+    {
+      SELOG_LOG_ERROR ("Not connected");
+      rv = -SELOG_CLIENT_ERROR_INVALID_ARG;
+      goto done;
+    }
+
+  vl_socket_client_disconnect2 (&ictx->bapi_sock_ctx);
+
+  ictx->vl_input_queue = 0;
+  ictx->api_client_handle = ~0;
+
+  SELOG_LOG_DEBUG ("%s: disconnected from vpp", ctx->client_name);
+
+done:
+  clib_spinlock_unlock_if_init (&ictx->lock);
+  return rv;
+}
+
+int32_t
+selog_client_poll_event (selog_client_ctx_t *ctx, selog_event_t *event,
+                        uint32_t max_events)
+{
+  selog_client_internal_ctx_t *ictx = SELOG_INTERNAL_CTX (ctx);
+  clib_spinlock_lock_if_init (&ictx->lock);
+  uword n_total_events = ictx->sh->em.n_total_events;
+  uword next_event = ictx->next_event;
+  word n_events = n_total_events - next_event;
+
+  /* If more events than the ring size, it means some were missed */
+  if (n_events > ictx->sh->em.event_ring_size)
+    {
+      SELOG_LOG_WARNING ("%s: missed %ld events", ctx->client_name,
+                        n_events - ictx->sh->em.event_ring_size);
+      next_event = n_total_events - ictx->sh->em.event_ring_size;
+      n_events = ictx->sh->em.event_ring_size;
+    }
+
+  n_events = clib_min (n_events, max_events);
+  for (word i = 0; i < n_events; i++)
+    {
+      uword e = (next_event + i) & (ictx->sh->em.event_ring_size - 1);
+      elog_event_t *src_event = &ictx->sh->em.event_ring[e];
+      elog_event_t edata = src_event[0];
+      edata.time = (edata.time_cycles - ictx->sh->em.init_time.cpu) *
+                  ictx->sh->em.cpu_timer.seconds_per_clock;
+      clib_memcpy_fast (&event[i], &edata, sizeof (edata));
+    }
+  ictx->next_event = next_event + n_events;
+  clib_spinlock_unlock_if_init (&ictx->lock);
+  return n_events;
+}
+
+static void
+selog_client_scan_and_update_events (selog_client_internal_ctx_t *ictx,
+                                    selog_event_t *events, uint32_t n_events)
+{
+  u32 n_event_type_index = vec_len (ictx->private_em.event_types);
+  u32 n_track_index = vec_len (ictx->private_em.tracks);
+
+  for (uint32_t i = 0; i < n_events; i++)
+    {
+      selog_event_t *e = &events[i];
+      if (e->event_type >= n_event_type_index)
+       {
+         SELOG_LOG_DEBUG ("%s: event %d has invalid event type index %d",
+                          ictx->client_ctx.client_name, i, e->event_type);
+         SELOG_LOG_DEBUG ("%s: fetching all event types",
+                          ictx->client_ctx.client_name);
+         selog_client_ictx_retrieve_all_event_types (ictx);
+       }
+      if (e->track >= n_track_index)
+       {
+         SELOG_LOG_DEBUG ("%s: event %d has invalid track index %d",
+                          ictx->client_ctx.client_name, i, e->track);
+         SELOG_LOG_DEBUG ("%s: fetching all tracks",
+                          ictx->client_ctx.client_name);
+         selog_client_ictx_retrieve_all_tracks (ictx);
+       }
+    }
+  /* For each event, look for unknown string, if anyone is found, refetch
+   * string table */
+  for (uint32_t i = 0; i < n_events; i++)
+    {
+      selog_event_t *e = &events[i];
+      selog_type_private_t *set =
+       vec_elt_at_index (ictx->event_type_private, e->event_type);
+      uword j;
+      vec_foreach_index (j, set->string_offset)
+       {
+         uword string_index;
+         if (set->string_size[j] == 1)
+           string_index = (e->data + set->string_offset[j])[0];
+         else if (set->string_size[j] == 2)
+           string_index =
+             clib_mem_unaligned (e->data + set->string_offset[j], u16);
+         else if (set->string_size[j] == 4)
+           string_index =
+             clib_mem_unaligned (e->data + set->string_offset[j], u32);
+         else if (set->string_size[j] == 8)
+           string_index =
+             clib_mem_unaligned (e->data + set->string_offset[j], u64);
+         else
+           {
+             SELOG_LOG_ERROR ("Unsupported string size %d in event type %d",
+                              set->string_size[j], e->event_type);
+             abort ();
+           }
+         if (string_index >= vec_len (ictx->private_em.string_table))
+           {
+             SELOG_LOG_DEBUG ("%s: event %d has unknown string index %lu",
+                              ictx->client_ctx.client_name, i, string_index);
+             SELOG_LOG_DEBUG ("%s: fetching all string table",
+                              ictx->client_ctx.client_name);
+             selog_client_ictx_retrieve_string_table (ictx);
+           }
+       }
+    }
+}
+void
+selog_client_format_events (selog_client_ctx_t *ctx, selog_event_t *events,
+                           uint32_t n_events, char **result)
+{
+  selog_client_internal_ctx_t *ictx = SELOG_INTERNAL_CTX (ctx);
+  clib_spinlock_lock_if_init (&ictx->lock);
+  u8 *s = 0;
+  elog_main_t *em = &ictx->private_em;
+  selog_client_scan_and_update_events (ictx, events, n_events);
+  for (uint32_t i = 0; i < n_events; i++)
+    {
+      s = format (0, "%U%c", format_elog_event, em, &events[i], 0);
+      result[i] = (char *) s;
+    }
+  clib_spinlock_unlock_if_init (&ictx->lock);
+}
+
+void
+selog_client_free_formatted_events (char **result, uint32_t n_events)
+{
+  for (uint32_t i = 0; i < n_events; i++)
+    vec_free (result[i]);
+}
+
+__clib_constructor static void
+selog_client_init (void)
+{
+  char *envvar;
+  /* First init memory */
+  selog_client_init_mem ();
+
+  clib_time_init (&selog_client_main.time);
+
+  /* Default log level */
+  if ((envvar = getenv ("SELOG_CLIENT_LOG_LEVEL")) != 0)
+    {
+      selog_client_main.log_lvl = atoi (envvar);
+    }
+  else
+    selog_client_main.log_lvl = SELOG_LOG_LEVEL_ERROR;
+}
+
+selog_client_main_t selog_client_main;
\ No newline at end of file
diff --git a/src/plugins/selog/selog_client/selog_client.h b/src/plugins/selog/selog_client/selog_client.h
new file mode 100644 (file)
index 0000000..d9a15f5
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2025 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_selog_client_h__
+#define __included_selog_client_h__
+
+#include <stdint.h>
+#include <stddef.h>
+
+#define foreach_selog_client_errors                                           \
+  _ (NONE, "no error")                                                        \
+  _ (INVALID_ARG, "invalid argument")                                         \
+  _ (CONNECT_FAIL, "connect to vpp failed")                                   \
+  _ (TIMEOUT, "timeout")
+
+enum
+{
+#define _(sym, str) SELOG_CLIENT_ERROR_##sym,
+  foreach_selog_client_errors
+#undef _
+    SELOG_CLIENT_N_ERROR,
+};
+
+const char *selog_client_error_strings[] = {
+#define _(sym, str) str,
+  foreach_selog_client_errors
+#undef _
+};
+
+typedef struct
+{
+  const char *sock_name;
+  const char *client_name;
+} selog_client_ctx_t;
+
+typedef struct
+{
+  union
+  {
+    uint64_t time_cycles;
+    double time;
+  };
+  uint16_t event_type;
+  uint16_t track;
+  uint8_t data[20];
+} selog_event_t;
+
+selog_client_ctx_t *selog_client_ctx_alloc ();
+void selog_client_ctx_free (selog_client_ctx_t *ctx);
+int32_t selog_client_connect_to_vpp (selog_client_ctx_t *ctx);
+int32_t selog_client_disconnect_from_vpp (selog_client_ctx_t *ctx);
+
+int32_t selog_client_poll_event (selog_client_ctx_t *ctx, selog_event_t *event,
+                                uint32_t max_events);
+
+void selog_client_format_events (selog_client_ctx_t *ctx,
+                                selog_event_t *events, uint32_t n_events,
+                                char **result);
+void selog_client_free_formatted_events (char **result, uint32_t n_events);
+#endif /* __included_selog_client_h__ */
diff --git a/src/plugins/selog/selog_client/selog_client_example.c b/src/plugins/selog/selog_client/selog_client_example.c
new file mode 100644 (file)
index 0000000..5ed8d4e
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2025 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 <selog/selog_client/selog_client.h>
+#include <stdio.h>
+#include <time.h>
+
+int
+main (int argc, char *argv[])
+{
+  selog_client_ctx_t *ctx = selog_client_ctx_alloc ();
+  int32_t rv;
+  ctx->sock_name = "/run/vpp/api.sock";
+  ctx->client_name = "selog_client_example";
+  rv = selog_client_connect_to_vpp (ctx);
+  if (rv)
+    {
+      printf ("selog_client_connect_to_vpp failed: %d %s\n", rv,
+             selog_client_error_strings[-rv]);
+      return rv;
+    }
+  while (1)
+    {
+      selog_event_t event;
+      int rv = selog_client_poll_event (ctx, &event, 1);
+      if (rv == 1)
+       {
+         char *s;
+         selog_client_format_events (ctx, &event, 1, &s);
+         printf ("%s\n", s);
+         selog_client_free_formatted_events (&s, 1);
+       }
+      else
+       {
+         /* Sleep 100ms */
+         struct timespec req = { 0 }, rem = { 0 };
+         req.tv_sec = 0;
+         req.tv_nsec = 100 * 1000000L;
+         nanosleep (&req, &rem);
+       }
+    }
+  selog_client_disconnect_from_vpp (ctx);
+  return 0;
+}
\ No newline at end of file
diff --git a/src/plugins/selog/selog_client/selog_client_internal.h b/src/plugins/selog/selog_client/selog_client_internal.h
new file mode 100644 (file)
index 0000000..29b39f9
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2025 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_selog_client_internal_h__
+#define __included_selog_client_internal_h__
+
+#include <svm/ssvm.h>
+#include <vlibmemory/socket_client.h>
+#include <vlibmemory/memory_client.h>
+#include <selog/selog_shared.h>
+#include "selog_client.h"
+
+STATIC_ASSERT_SIZEOF (selog_event_t, 32);
+STATIC_ASSERT_SIZEOF (elog_event_t, 32);
+STATIC_ASSERT_OFFSET_OF (selog_event_t, time_cycles,
+                        offsetof (elog_event_t, time_cycles));
+STATIC_ASSERT_OFFSET_OF (selog_event_t, time, offsetof (elog_event_t, time));
+STATIC_ASSERT_OFFSET_OF (selog_event_t, event_type,
+                        offsetof (elog_event_t, event_type));
+STATIC_ASSERT_OFFSET_OF (selog_event_t, track, offsetof (elog_event_t, track));
+STATIC_ASSERT_OFFSET_OF (selog_event_t, data, offsetof (elog_event_t, data));
+
+#define foreach_selog_client_internal_state                                   \
+  _ (DISCONNECTED, "disconnected")                                            \
+  _ (ERROR, "error")                                                          \
+  _ (CONNECTED, "connected")                                                  \
+  _ (WAITING_FOR_SHM, "waiting for shm")                                      \
+  _ (SHM_RECEIVED, "shm received")                                            \
+  _ (WAITING_FOR_STRING_TABLE, "waiting for string table")                    \
+  _ (STRING_TABLE_LOADED, "string table loaded")                              \
+  _ (WAITING_FOR_ALL_TRACKS, "waiting for all tracks")                        \
+  _ (ALL_TRACKS_LOADED, "all tracks loaded")                                  \
+  _ (WAITING_FOR_ALL_EVENT_TYPES, "waiting for all event types")              \
+  _ (ALL_EVENT_TYPES_LOADED, "all event types loaded")                        \
+  _ (WAITING_FOR_ENUM_STRINGS, "waiting for enum strings")                    \
+  _ (ENUM_STRINGS_LOADED, "enum strings loaded")
+
+enum
+{
+#define _(sym, str) SELOG_CLIENT_INTERNAL_STATE_##sym,
+  foreach_selog_client_internal_state
+#undef _
+    SELOG_CLIENT_INTERNAL_N_STATE,
+};
+
+const char *selog_client_internal_state_strings[] = {
+#define _(sym, str) str,
+  foreach_selog_client_internal_state
+#undef _
+};
+
+static inline const char *
+selog_client_internal_state_str (u8 state)
+{
+  if (state >= SELOG_CLIENT_INTERNAL_N_STATE)
+    return "unknown";
+  return selog_client_internal_state_strings[state];
+}
+typedef struct
+{
+  u8 *string_offset;
+  u8 *string_size;
+} selog_type_private_t;
+
+typedef struct
+{
+  selog_client_ctx_t client_ctx;
+  clib_socket_t app_api_sock;
+  socket_client_main_t bapi_sock_ctx;
+  api_main_t bapi_api_ctx;
+  memory_client_main_t bapi_mem_ctx;
+  ssvm_private_t ssvm;
+  clib_spinlock_t lock;
+  elog_main_t private_em;
+  svm_queue_t *vl_input_queue;
+  selog_shared_header_t *sh;
+  selog_type_private_t *event_type_private;
+  uword current_event_type_index;
+  uword next_event;
+  u32 api_client_handle;
+  int async_error;
+  volatile u8 state;
+  volatile u8 multipart_done;
+} selog_client_internal_ctx_t;
+
+typedef struct
+{
+  selog_client_internal_ctx_t *internal_ctx;
+  u8 log_lvl;
+  clib_time_t time;
+} selog_client_main_t;
+
+extern selog_client_main_t selog_client_main;
+
+enum
+{
+  SELOG_LOG_LEVEL_ERROR,
+  SELOG_LOG_LEVEL_WARNING,
+  SELOG_LOG_LEVEL_INFO,
+  SELOG_LOG_LEVEL_DEBUG,
+  SELOG_LOG_LEVEL_TRACE
+};
+#define SELOG_INTERNAL_CTX(x) ((selog_client_internal_ctx_t *) x)
+#define SELOG_LOG(lvl_, fmt_, args_...)                                       \
+  do                                                                          \
+    {                                                                         \
+      if (selog_client_main.log_lvl > lvl_)                                   \
+       fprintf (stderr, fmt_ "\n", ##args_);                                 \
+    }                                                                         \
+  while (0)
+
+#define SELOG_LOG_ERROR(fmt_, args_...)                                       \
+  SELOG_LOG (SELOG_LOG_LEVEL_ERROR, fmt_, ##args_)
+
+#define SELOG_LOG_WARNING(fmt_, args_...)                                     \
+  SELOG_LOG (SELOG_LOG_LEVEL_WARNING, fmt_, ##args_)
+
+#define SELOG_LOG_INFO(fmt_, args_...)                                        \
+  SELOG_LOG (SELOG_LOG_LEVEL_INFO, fmt_, ##args_)
+
+#define SELOG_LOG_DEBUG(fmt_, args_...)                                       \
+  SELOG_LOG (SELOG_LOG_LEVEL_DEBUG, fmt_, ##args_)
+
+#define SELOG_LOG_TRACE(fmt_, args_...)                                       \
+  SELOG_LOG (SELOG_LOG_LEVEL_TRACE, fmt_, ##args_)
+
+#endif /* __included_selog_client_internal_h__ */