From ba4eee76f6a017404446263b01ae654539c23920 Mon Sep 17 00:00:00 2001 From: Mohammed Hawari Date: Tue, 7 Oct 2025 17:55:48 +0200 Subject: [PATCH] selog: selog client lib Change-Id: I4a81981dc52ed5c1acab9afd858990baebad1cc9 Type: feature Signed-off-by: Mohammed Hawari --- src/plugins/selog/CMakeLists.txt | 4 +- src/plugins/selog/selog_client/CMakeLists.txt | 20 + src/plugins/selog/selog_client/selog_client.c | 942 +++++++++++++++++++++ src/plugins/selog/selog_client/selog_client.h | 73 ++ .../selog/selog_client/selog_client_example.c | 55 ++ .../selog/selog_client/selog_client_internal.h | 138 +++ 6 files changed, 1231 insertions(+), 1 deletion(-) create mode 100644 src/plugins/selog/selog_client/CMakeLists.txt create mode 100644 src/plugins/selog/selog_client/selog_client.c create mode 100644 src/plugins/selog/selog_client/selog_client.h create mode 100644 src/plugins/selog/selog_client/selog_client_example.c create mode 100644 src/plugins/selog/selog_client/selog_client_internal.h diff --git a/src/plugins/selog/CMakeLists.txt b/src/plugins/selog/CMakeLists.txt index eeb8da5424b..f0f0b6fb092 100644 --- a/src/plugins/selog/CMakeLists.txt +++ b/src/plugins/selog/CMakeLists.txt @@ -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 index 00000000000..df282400a87 --- /dev/null +++ b/src/plugins/selog/selog_client/CMakeLists.txt @@ -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 index 00000000000..f35fe9ad5e2 --- /dev/null +++ b/src/plugins/selog/selog_client/selog_client.c @@ -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 +#include +#include +#include +#include +#include +#include + +#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 +#include +#undef vl_endianfun + +#define vl_calcsizefun +#include +#include +#undef vl_calcsizefun + +/* instantiate all the print functions we know about */ +#define vl_printfun +#include +#include +#undef vl_printfun + +#define vl_api_version(n, v) static u32 selog_api_version = v; +#include +#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 index 00000000000..d9a15f54a73 --- /dev/null +++ b/src/plugins/selog/selog_client/selog_client.h @@ -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 +#include + +#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 index 00000000000..5ed8d4eaaee --- /dev/null +++ b/src/plugins/selog/selog_client/selog_client_example.c @@ -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 +#include +#include + +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 index 00000000000..29b39f9aab7 --- /dev/null +++ b/src/plugins/selog/selog_client/selog_client_internal.h @@ -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 +#include +#include +#include +#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__ */ -- 2.16.6