When initialized (recorder_initialization), the Agent Interface launch a GThread (handle_communications) that opens a TCP server socket and waits for Smart Local Agent connections. When a Local Agent connects to the sockets, the communication is initialized (agent_initialize_communication), the communication socket is stored and the list of Recorders is sent. In return, the local agent indicates which recorders to enable. On the SPICE side, the Agent Interface handles the record() calls (recorder_append*). When a record is received from SPICE, and if the recorder is enabled, the record entry is sent through the TCP connection. Otherwise, the record is dropped. Signed-off-by: Kevin Pouget <kpouget@xxxxxxxxxx> --- v1->v2: fix tests/test-dummy-recorder.c --- common/agent_interface.c | 467 +++++++++++++++++++++++++++++++ common/agent_interface.h | 542 ++++++++++++++++++++++++++++++++++++ tests/test-dummy-recorder.c | 3 +- 3 files changed, 1011 insertions(+), 1 deletion(-) create mode 100644 common/agent_interface.c create mode 100644 common/agent_interface.h diff --git a/common/agent_interface.c b/common/agent_interface.c new file mode 100644 index 0000000..768b6a9 --- /dev/null +++ b/common/agent_interface.c @@ -0,0 +1,467 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2019 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include <config.h> +#include <stdlib.h> +#include <sys/time.h> +#include <string.h> +#include <glib.h> +#include <stdio.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <sys/eventfd.h> +#include <errno.h> +#include <poll.h> + +#include <common/agent_interface.h> + +typedef struct sockaddr SA; + +static GThread *recorder_comm_thr; +static bool agent_terminated = false; +static int terminate_efd = -1; +static FILE *communication_f = NULL; + +#define NB_MAX_RECORDERS 16 +static recorder_info *recorders[NB_MAX_RECORDERS]; +static uint32_t nb_recorders = 0; + +static uintptr_t recorder_tick(void); + +#ifndef RECORDER_HZ +#define RECORDER_HZ 1000000 +#endif // RECORDER_HZ + +static GMutex mutex_socket; + +static int agent_initialize_communication(int socket) +{ + uint32_t i; + int ret = -1; + FILE *socket_f; + + g_mutex_lock(&mutex_socket); + + if (communication_f != NULL) { + g_warning("A client is already connected, rejecting the connection."); + + goto unlock; + } + + socket_f = fdopen(socket, "w+b"); + + fprintf(socket_f, "Recorders: "); + for (i = 0; i < nb_recorders; i++) { + g_debug("Sending %s", recorders[i]->name); + fprintf(socket_f, "%s;", recorders[i]->name); + } + fprintf(socket_f, "\n"); + fflush(socket_f); + + for (i = 0; i < nb_recorders; i++) { + char enable; + + if (read(socket, &enable, sizeof(enable)) != sizeof(enable)) { + g_warning("Invalid read on the client socket"); + + goto unlock; + } + if (enable != '0' && enable != '1') { + g_critical("Invalid enable-value received for recorder '%s': %u", + recorders[i]->name, enable); + + goto unlock; + } + + if (enable == '0') { + continue; + } + + recorders[i]->trace = 1; + g_info("Enable recorder '%s'", recorders[i]->name); + } + + communication_f = socket_f; + ret = 0; + +unlock: + g_mutex_unlock(&mutex_socket); + + return ret; +} + +static void agent_finalize_communication(int socket) +{ + uint32_t i; + g_info("Communication socket closed."); + + g_mutex_lock(&mutex_socket); + g_assert(socket == fileno(communication_f)); + + fclose(communication_f); + communication_f = NULL; + + for (i = 0; i < nb_recorders; i++) { + recorders[i]->trace = 0; + } + g_mutex_unlock(&mutex_socket); +} + +static int agent_process_communication(int socket) +{ + static char msg_in[128]; + + static long unsigned int len = 0; + + g_assert(socket == fileno(communication_f)); + + int nbytes = read(socket, msg_in + len, 1); + + if (nbytes < 0 && errno == EINTR) { + return 0; + } + + if (nbytes <= 0) { + agent_finalize_communication(socket); + return -1; // socket closed + } + + if (msg_in[len] == '\0') { + // TODO: process quality indicator + len = 0; + return 0; + } + + len += nbytes; + + if (len >= sizeof(msg_in) - 1) { + msg_in[sizeof(msg_in) - 1] = '\0'; + g_warning("Invalid message received (too long?): %s", msg_in); + len = 0; + } + + return 0; +} + +static int make_socket(guint port) +{ + struct sockaddr_in servaddr; + int listen_socket = socket(AF_INET, SOCK_STREAM, 0); + + if (listen_socket == -1) { + g_critical("socket creation failed"); + return -1; + } + + int enable = 1; + if (setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { + g_critical("setsockopt(SO_REUSEADDR) failed"); + close(listen_socket); + return -1; + } + + memset(&servaddr, 0, sizeof(servaddr)); + + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htonl(INADDR_ANY); + servaddr.sin_port = htons(port); + + if (bind(listen_socket, (SA *) &servaddr, sizeof(servaddr)) != 0) { + g_critical("socket bind failed"); + close(listen_socket); + return -1; + } + + return listen_socket; +} + +static gpointer handle_communications(gpointer user_data) +{ + struct pollfd fds[3]; + int nb_fd = 0; + int listen_socket; + int i; + guint port = GPOINTER_TO_UINT(user_data); + + listen_socket = make_socket(port); + if (listen_socket < 0) { + return NULL; + } + + g_debug("Listening!"); + + if ((listen(listen_socket, 1)) != 0) { + g_critical("listen failed: %m"); + return NULL; + } + + fds[0].fd = terminate_efd; + fds[0].events = POLLIN; + fds[1].fd = listen_socket; + fds[1].events = POLLIN; + nb_fd = 2; + + while (!agent_terminated) { + + /* Block until input arrives on one or more active sockets. */ + int ret = poll(fds, nb_fd, -1); + + if (ret < 0) { + g_critical("poll failed: %m"); + break; + } + + /* Service all the sockets with input pending. */ + for (i = 0; i < nb_fd; i++) { + int fd = fds[i].fd; + if (fd == terminate_efd) { + if (fds[i].revents & POLLIN) { + g_assert(agent_terminated); + break; + } + } else if (fd == listen_socket) { + if (fds[i].revents & ~POLLIN) { + g_critical("server socket closed"); + break; + } + if (!(fds[i].revents & POLLIN)) { + continue; + } + + /* Connection request on original socket. */ + int new_fd = accept(listen_socket, NULL, NULL); + + if (new_fd < 0) { + g_critical("accept failed: %m"); + break; + } + + if (nb_fd == 3) { + close(new_fd); + g_warning("Too many clients accepted ..."); + continue; + } + + g_debug("Agent Interface: client connected!"); + + if (agent_initialize_communication(new_fd)) { + close(new_fd); + g_warning("Initialization failed ..."); + continue; + } + + fds[nb_fd].fd = new_fd; + fds[nb_fd].events = POLLIN; + nb_fd++; + + /* fds array modified, restart the poll. */ + break; + } else { + if (!(fds[i].revents & POLLIN)) { + continue; + } + + /* Data arriving on an already-connected socket. */ + if (agent_process_communication(fd) < 0) { + nb_fd--; + } + } + } + } + + close(terminate_efd); + close(listen_socket); + + g_info("Agent interface thread: bye!"); + return NULL; +} + +static void recorder_deregister(void); + +static void recorder_initialization(unsigned int port) +{ + GError *error = NULL; + + terminate_efd = eventfd(0, 0); + if (terminate_efd == -1) { + g_critical("eventfd failed: %m"); + return; + } + + recorder_comm_thr = g_thread_try_new("smart_agent_interface", + handle_communications, + GUINT_TO_POINTER((guint) port), &error); + if (error) { + g_assert(!recorder_comm_thr); + g_critical("Error: Could not start the agent interface thread: %s", error->message); + g_error_free(error); + return; + } + + atexit(recorder_deregister); +} + +static void recorder_interrupt_communications(void) +{ + agent_terminated = true; + + uint64_t msg = 1; + ssize_t s = write(terminate_efd, &msg, sizeof(uint64_t)); + + if (s != sizeof(uint64_t)) { + g_warning("failed to send recorder thread termination event: %m"); + } +} + + +static void recorder_deregister(void) +{ + if (recorder_comm_thr) { + recorder_interrupt_communications(); + g_thread_join(recorder_comm_thr); + recorder_comm_thr = NULL; + } +} + +void recorder_activate(recorder_info *recorder) +{ + if (nb_recorders >= NB_MAX_RECORDERS) { + g_critical("Too many recorders configured (nb max: %d)", NB_MAX_RECORDERS); + return; + } + + recorders[nb_recorders] = recorder; + nb_recorders++; +} + +static void do_send_entry(FILE *dest, recorder_info *info, recorder_entry *entry, va_list args) +{ + fprintf(dest, "Name: %s\nFunction: %s\nTime: %lu\n", + info->name, entry->where, entry->timestamp); + + vfprintf(dest, entry->format, args); + fprintf(dest, "\n\n"); + + fflush(dest); +} + + +static void recorder_trace_entry(recorder_info *info, recorder_entry *entry, ...) +// ---------------------------------------------------------------------------- +// Show a recorder entry when a trace is enabled +// ---------------------------------------------------------------------------- +{ + va_list args; + + if (strchr(entry->format, '\n') != NULL) { + g_critical("Agent records cannot contain '\n' char ... (%s)", entry->where); + return; + } + + // send info/entry to the socket + g_mutex_lock(&mutex_socket); + + if (communication_f == NULL) { + g_mutex_unlock(&mutex_socket); + return; + } + + va_start(args, entry); + do_send_entry(communication_f, info, entry, args); + va_end(args); + + if (g_strcmp0(g_getenv("SPICE_AGENT_LOG_RECORDS"), "1") == 0) { + va_start(args, entry); + do_send_entry(stderr, info, entry, args); + va_end(args); + } + + g_mutex_unlock(&mutex_socket); +} + +void recorder_append(recorder_info *rec, + const char *where, + const char *format, + uintptr_t a0, + uintptr_t a1, + uintptr_t a2, + uintptr_t a3) +// ---------------------------------------------------------------------------- +// Enter a record entry in ring buffer with given set of args +// ---------------------------------------------------------------------------- +{ + recorder_entry entry; + + if (!rec->trace) { + return; + } + + entry.format = format; + entry.timestamp = recorder_tick(); + entry.where = where; + + recorder_trace_entry(rec, &entry, a0, a1, a2, a3); +} + +void recorder_append2(recorder_info *rec, + const char *where, + const char *format, + uintptr_t a0, + uintptr_t a1, + uintptr_t a2, + uintptr_t a3, + uintptr_t a4, + uintptr_t a5, + uintptr_t a6, + uintptr_t a7) +// ---------------------------------------------------------------------------- +// Enter a double record (up to 8 args) +// ---------------------------------------------------------------------------- +{ + recorder_entry entry; + + if (!rec->trace) { + return; + } + + entry.format = format; + entry.timestamp = recorder_tick(); + entry.where = where; + + recorder_trace_entry(rec, &entry, a0, a1, a2, a3, a4, a5, a6, a7); +} + +// ============================================================================ +// +// Support functions +// +// ============================================================================ + +static uintptr_t recorder_tick(void) +// ---------------------------------------------------------------------------- +// Return the "ticks" as stored in the recorder +// ---------------------------------------------------------------------------- +{ + struct timeval t; + + gettimeofday(&t, NULL); + + return t.tv_sec * RECORDER_HZ + t.tv_usec / (1000000 / RECORDER_HZ); +} diff --git a/common/agent_interface.h b/common/agent_interface.h new file mode 100644 index 0000000..042120e --- /dev/null +++ b/common/agent_interface.h @@ -0,0 +1,542 @@ +#pragma once + +// ***************************************************************************** +// This software is licensed under the GNU Lesser General Public License v2+ +// (C) 2017-2019, Christophe de Dinechin <christophe@xxxxxxxxxxxx> +// ***************************************************************************** +// This file was part of Recorder +// +// Recorder is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// Recorder is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Recorder, in a file named COPYING. +// If not, see <https://www.gnu.org/licenses/>. +// ***************************************************************************** +/* This file is based on Recorder's recorder.h file, that describes a general- + * purpose instrumentation interface. agent_interface.h is a trimmed-down + * version of it. */ + +#include <stdarg.h> +#include <stdint.h> +#include <stdbool.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +static inline void +recorder_dump_on_common_signals(unsigned add, unsigned remove) +{ +} + +// ============================================================================ +// +// Recorder data structures +// +// ============================================================================ + +typedef struct recorder_entry +/// --------------------------------------------------------------------------- +/// Entry in the flight recorder. +///---------------------------------------------------------------------------- +/// Notice that the arguments are stored as "intptr_t" because that type +/// is guaranteed to be the same size as a pointer. This allows us to +/// properly align recorder entries to powers of 2 for efficiency. +/// Also read explanations of \ref _recorder_double and \ref _recorder_float +/// below regarding how to use floating-point with the recorder. +{ + const char *format; ///< Printf-style format for record + file/line + uintptr_t timestamp; ///< Time at which record took place + const char *where; ///< Source code function + uintptr_t args[4]; ///< Four arguments, for a total of 8 fields +} recorder_entry; + + +/// A global counter indicating the order of entries across recorders. +/// this is incremented atomically for each record() call. +/// It must be exposed because all XYZ_record() implementations need to +/// touch the same shared variable in order to provide a global order. +extern uintptr_t recorder_order; + +typedef struct recorder_info +///---------------------------------------------------------------------------- +/// A linked list of the activated recorders +///---------------------------------------------------------------------------- +{ + intptr_t trace; ///< Trace this recorder + const char * name; ///< Name of this parameter / recorder + const char * description;///< Description of what is recorded + recorder_entry data[0]; ///< Data for this recorder +} recorder_info; + +// ============================================================================ +// +// Adding data to a recorder +// +// ============================================================================ + +extern void recorder_append(recorder_info *rec, + const char *where, + const char *format, + uintptr_t a0, + uintptr_t a1, + uintptr_t a2, + uintptr_t a3); +extern void recorder_append2(recorder_info *rec, + const char *where, + const char *format, + uintptr_t a0, + uintptr_t a1, + uintptr_t a2, + uintptr_t a3, + uintptr_t a4, + uintptr_t a5, + uintptr_t a6, + uintptr_t a7); +extern void recorder_append3(recorder_info *rec, + const char *where, + const char *format, + uintptr_t a0, + uintptr_t a1, + uintptr_t a2, + uintptr_t a3, + uintptr_t a4, + uintptr_t a5, + uintptr_t a6, + uintptr_t a7, + uintptr_t a8, + uintptr_t a9, + uintptr_t a10, + uintptr_t a11); + +/// Activate a recorder (during construction time) +extern void recorder_activate(recorder_info *recorder); + +// ============================================================================ +// +// Declaration of recorders and tweaks +// +// ============================================================================ + +#define RECORDER_DECLARE(Name) \ +/* ----------------------------------------------------------------*/ \ +/* Declare a recorder with the given name (for use in headers) */ \ +/* ----------------------------------------------------------------*/ \ + extern recorder_info * const recorder_info_ptr_for_##Name; \ + extern struct recorder_info_for_##Name recorder_info_for_##Name + + +// ============================================================================ +// +// Definition of recorders and tweaks +// +// ============================================================================ + +#define RECORDER(Name, Size, Info) RECORDER_DEFINE(Name,Size,Info) + +#define RECORDER_DEFINE(Name, Size, Info) \ +/*!----------------------------------------------------------------*/ \ +/*! Define a recorder type with Size elements */ \ +/*!----------------------------------------------------------------*/ \ +/*! \param Name is the C name fo the recorder. \ + *! \param Size is the number of entries in the circular buffer. \ + *! \param Info is a description of the recorder for help. */ \ + \ +/* The entry in linked list for this type */ \ +struct recorder_info_for_##Name \ +{ \ + recorder_info info; \ + recorder_entry data[Size]; \ +} \ +recorder_info_for_##Name = \ +{ \ + { \ + 0, #Name, Info, {} \ + }, \ + {} \ +}; \ +recorder_info * const recorder_info_ptr_for_##Name = \ + &recorder_info_for_##Name.info; \ + \ +RECORDER_CONSTRUCTOR \ +static void recorder_activate_##Name(void) \ +/* ----------------------------------------------------------------*/ \ +/* Activate recorder before entering main() */ \ +/* ----------------------------------------------------------------*/ \ +{ \ + recorder_activate(RECORDER_INFO(Name)); \ +} \ + \ +/* Purposefully generate compile error if macro not followed by ; */ \ +extern void recorder_activate(recorder_info *recorder) + +typedef struct SpiceDummyTweak { + intptr_t tweak_value; +} SpiceDummyTweak; + +typedef struct SpiceEmptyStruct { + char dummy[0]; +} SpiceEmptyStruct; + +#define RECORDER_TWEAK_DECLARE(rec) \ + extern const SpiceDummyTweak spice_recorder_tweak_ ## rec + +#define RECORDER_TWEAK_DEFINE(rec, value, comment) \ + const SpiceDummyTweak spice_recorder_tweak_ ## rec = { (value) } + +#define RECORDER_TWEAK(rec) \ + ((spice_recorder_tweak_ ## rec).tweak_value) + +#define RECORDER_TRACE(rec) \ + (sizeof(struct recorder_info_for_ ## rec) != sizeof(SpiceEmptyStruct)) + + +// ============================================================================ +// +// Access to recorder and tweak info +// +// ============================================================================ + +#define RECORDER_INFO(Name) (recorder_info_ptr_for_##Name) + +// ============================================================================ +// +// Recording stuff +// +// ============================================================================ + +#define record(Name, ...) RECORD_MACRO(Name, __VA_ARGS__) +#define RECORD(Name,...) RECORD_MACRO(Name, __VA_ARGS__) +#define RECORD_MACRO(Name, Format,...) \ + RECORD_(RECORD,RECORD_COUNT_(__VA_ARGS__),Name,Format,##__VA_ARGS__) +#define RECORD_(RECORD,RCOUNT,Name,Format,...) \ + RECORD__(RECORD,RCOUNT,Name,Format,## __VA_ARGS__) +#define RECORD__(RECORD,RCOUNT,Name,Format,...) \ + RECORD##RCOUNT(Name,Format,##__VA_ARGS__) +#define RECORD_COUNT_(...) RECORD_COUNT__(Dummy,##__VA_ARGS__,_X,_X,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,_1,_0) +#define RECORD_COUNT__(Dummy,_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_N,...) _N + +#define RECORD_0(Name, Format) \ + recorder_append(RECORDER_INFO(Name), \ + RECORDER_SOURCE_FUNCTION, \ + RECORDER_SOURCE_LOCATION \ + Format, 0, 0, 0, 0) +#define RECORD_1(Name, Format, a) \ + recorder_append(RECORDER_INFO(Name), \ + RECORDER_SOURCE_FUNCTION, \ + RECORDER_SOURCE_LOCATION \ + Format, \ + RECORDER_ARG(a), 0, 0, 0) +#define RECORD_2(Name, Format, a,b) \ + recorder_append(RECORDER_INFO(Name), \ + RECORDER_SOURCE_FUNCTION, \ + RECORDER_SOURCE_LOCATION \ + Format, \ + RECORDER_ARG(a), \ + RECORDER_ARG(b), 0, 0) +#define RECORD_3(Name, Format, a,b,c) \ + recorder_append(RECORDER_INFO(Name), \ + RECORDER_SOURCE_FUNCTION, \ + RECORDER_SOURCE_LOCATION \ + Format, \ + RECORDER_ARG(a), \ + RECORDER_ARG(b), \ + RECORDER_ARG(c), 0) +#define RECORD_4(Name, Format, a,b,c,d) \ + recorder_append(RECORDER_INFO(Name), \ + RECORDER_SOURCE_FUNCTION, \ + RECORDER_SOURCE_LOCATION \ + Format, \ + RECORDER_ARG(a), \ + RECORDER_ARG(b), \ + RECORDER_ARG(c), \ + RECORDER_ARG(d)) +#define RECORD_5(Name, Format, a,b,c,d,e) \ + recorder_append2(RECORDER_INFO(Name), \ + RECORDER_SOURCE_FUNCTION, \ + RECORDER_SOURCE_LOCATION \ + Format, \ + RECORDER_ARG(a), \ + RECORDER_ARG(b), \ + RECORDER_ARG(c), \ + RECORDER_ARG(d), \ + RECORDER_ARG(e), 0, 0, 0) +#define RECORD_6(Name, Format, a,b,c,d,e,f) \ + recorder_append2(RECORDER_INFO(Name), \ + RECORDER_SOURCE_FUNCTION, \ + RECORDER_SOURCE_LOCATION \ + Format, \ + RECORDER_ARG(a), \ + RECORDER_ARG(b), \ + RECORDER_ARG(c), \ + RECORDER_ARG(d), \ + RECORDER_ARG(e), \ + RECORDER_ARG(f), 0, 0) +#define RECORD_7(Name, Format, a,b,c,d,e,f,g) \ + recorder_append2(RECORDER_INFO(Name), \ + RECORDER_SOURCE_FUNCTION, \ + RECORDER_SOURCE_LOCATION \ + Format, \ + RECORDER_ARG(a), \ + RECORDER_ARG(b), \ + RECORDER_ARG(c), \ + RECORDER_ARG(d), \ + RECORDER_ARG(e), \ + RECORDER_ARG(f), \ + RECORDER_ARG(g), 0) +#define RECORD_8(Name, Format, a,b,c,d,e,f,g,h) \ + recorder_append2(RECORDER_INFO(Name), \ + RECORDER_SOURCE_FUNCTION, \ + RECORDER_SOURCE_LOCATION \ + Format, \ + RECORDER_ARG(a), \ + RECORDER_ARG(b), \ + RECORDER_ARG(c), \ + RECORDER_ARG(d), \ + RECORDER_ARG(e), \ + RECORDER_ARG(f), \ + RECORDER_ARG(g), \ + RECORDER_ARG(h)) +#define RECORD_9(Name, Format, a,b,c,d,e,f,g,h,i) \ + recorder_append3(RECORDER_INFO(Name), \ + RECORDER_SOURCE_FUNCTION, \ + RECORDER_SOURCE_LOCATION \ + Format, \ + RECORDER_ARG(a), \ + RECORDER_ARG(b), \ + RECORDER_ARG(c), \ + RECORDER_ARG(d), \ + RECORDER_ARG(e), \ + RECORDER_ARG(f), \ + RECORDER_ARG(g), \ + RECORDER_ARG(h), \ + RECORDER_ARG(i), 0,0,0) +#define RECORD_10(Name, Format, a,b,c,d,e,f,g,h,i,j) \ + recorder_append3(RECORDER_INFO(Name), \ + RECORDER_SOURCE_FUNCTION, \ + RECORDER_SOURCE_LOCATION \ + Format, \ + RECORDER_ARG(a), \ + RECORDER_ARG(b), \ + RECORDER_ARG(c), \ + RECORDER_ARG(d), \ + RECORDER_ARG(e), \ + RECORDER_ARG(f), \ + RECORDER_ARG(g), \ + RECORDER_ARG(h), \ + RECORDER_ARG(i), \ + RECORDER_ARG(j), 0,0) +#define RECORD_11(Name, Format, a,b,c,d,e,f,g,h,i,j,k) \ + recorder_append3(RECORDER_INFO(Name), \ + RECORDER_SOURCE_FUNCTION, \ + RECORDER_SOURCE_LOCATION \ + Format, \ + RECORDER_ARG(a), \ + RECORDER_ARG(b), \ + RECORDER_ARG(c), \ + RECORDER_ARG(d), \ + RECORDER_ARG(e), \ + RECORDER_ARG(f), \ + RECORDER_ARG(g), \ + RECORDER_ARG(h), \ + RECORDER_ARG(i), \ + RECORDER_ARG(j), \ + RECORDER_ARG(k),0) +#define RECORD_12(Name,Format,a,b,c,d,e,f,g,h,i,j,k,l) \ + recorder_append3(RECORDER_INFO(Name), \ + RECORDER_SOURCE_FUNCTION, \ + RECORDER_SOURCE_LOCATION \ + Format, \ + RECORDER_ARG(a), \ + RECORDER_ARG(b), \ + RECORDER_ARG(c), \ + RECORDER_ARG(d), \ + RECORDER_ARG(e), \ + RECORDER_ARG(f), \ + RECORDER_ARG(g), \ + RECORDER_ARG(h), \ + RECORDER_ARG(i), \ + RECORDER_ARG(j), \ + RECORDER_ARG(k), \ + RECORDER_ARG(l)) +#define RECORD_X(Name, Format, ...) RECORD_TOO_MANY_ARGS(printf(Format, __VA_ARGS__)) + + +// Some ugly macro drudgery to make things easy to use. Adjust type. +#ifdef __cplusplus +#define RECORDER_ARG(arg) _recorder_arg(arg) +#else // !__cplusplus + +#if defined(__GNUC__) && !defined(__clang__) +# if __GNUC__ <= 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 9) +# define RECORDER_WITHOUT_GENERIC +# endif +#endif // __GNUC__ + +#ifdef RECORDER_WITHOUT_GENERIC +#define RECORDER_ARG(arg) ((uintptr_t) (arg)) +#else // !RECORDER_WITHOUT_GENERIC +#define RECORDER_ARG(arg) \ + _Generic(arg, \ + unsigned char: _recorder_unsigned, \ + unsigned short: _recorder_unsigned, \ + unsigned: _recorder_unsigned, \ + unsigned long: _recorder_unsigned, \ + unsigned long long:_recorder_unsigned, \ + char: _recorder_char, \ + signed char: _recorder_signed, \ + signed short: _recorder_signed, \ + signed: _recorder_signed, \ + signed long: _recorder_signed, \ + signed long long: _recorder_signed, \ + float: _recorder_float, \ + double: _recorder_double, \ + default: _recorder_pointer)(arg) +#endif // RECORDER_WITHOUT_GENERIC +#endif // __cplusplus + +// ============================================================================ +// +// Timing information +// +// ============================================================================ + +#define RECORD_TIMING_BEGIN(rec) \ + do { RECORD(rec, "begin"); +#define RECORD_TIMING_END(rec, op, name, value) \ + RECORD(rec, "end" op name); \ + } while (0) + + +// ============================================================================ +// +// Support macros +// +// ============================================================================ + +#define RECORDER_SOURCE_FUNCTION __func__ /* Works in C99 and C++11 */ +#define RECORDER_SOURCE_LOCATION __FILE__ ":" RECORDER_STRING(__LINE__) ":" +#define RECORDER_STRING(LINE) RECORDER_STRING_(LINE) +#define RECORDER_STRING_(LINE) #LINE + +#ifdef __GNUC__ +#define RECORDER_CONSTRUCTOR __attribute__((constructor)) +#else +#define RECORDER_CONSTRUCTOR +#endif + +#ifdef __cplusplus +} +#endif // __cplusplus + +// ============================================================================ +// +// Utility: Convert floating point values for vararg format +// +// ============================================================================ +// +// The recorder stores only uintptr_t in recorder entries. Integer types +// are promoted, pointer types are converted. Floating point values +// are converted a floating point type of the same size as uintptr_t, +// i.e. float are converted to double on 64-bit platforms, and conversely. + +#ifdef __cplusplus +#include <string> + +// In C++, we don't use _Generic but actual overloading +template <class inttype> +static inline uintptr_t _recorder_arg(inttype i) +{ + return (uintptr_t) i; +} + + +static inline uintptr_t _recorder_arg(const std::string &arg) +{ + return (uintptr_t) arg.c_str(); +} +#define _recorder_float _recorder_arg +#define _recorder_double _recorder_arg + +#else // !__cplusplus + +static inline uintptr_t _recorder_char(char c) +// ---------------------------------------------------------------------------- +// Necessary because of the way generic selections work +// ---------------------------------------------------------------------------- +{ + return c; +} + + +static inline uintptr_t _recorder_unsigned(uintptr_t i) +// ---------------------------------------------------------------------------- +// Necessary because of the way generic selections work +// ---------------------------------------------------------------------------- +{ + return i; +} + + +static inline uintptr_t _recorder_signed(intptr_t i) +// ---------------------------------------------------------------------------- +// Necessary because of the way generic selections work +// ---------------------------------------------------------------------------- +{ + return (uintptr_t) i; +} + + +static inline uintptr_t _recorder_pointer(const void *i) +// ---------------------------------------------------------------------------- +// Necessary because of the way generic selections work +// ---------------------------------------------------------------------------- +{ + return (uintptr_t) i; +} + +#endif // __cplusplus + + +static inline uintptr_t _recorder_float(float f) +// ---------------------------------------------------------------------------- +// Convert floating point number to intptr_t representation for recorder +// ---------------------------------------------------------------------------- +{ + if (sizeof(float) == sizeof(intptr_t)) { + union { float f; uintptr_t i; } u; + u.f = f; + return u.i; + } else { + union { double d; uintptr_t i; } u; + u.d = (double) f; + return u.i; + } +} + + +static inline uintptr_t _recorder_double(double d) +// ---------------------------------------------------------------------------- +// Convert double-precision floating point number to intptr_t representation +// ---------------------------------------------------------------------------- +{ + if (sizeof(double) == sizeof(intptr_t)) { + union { double d; uintptr_t i; } u; + u.d = d; + return u.i; + } else { + // Better to lose precision than not store any data + union { float f; uintptr_t i; } u; + u.f = d; + return u.i; + } +} diff --git a/tests/test-dummy-recorder.c b/tests/test-dummy-recorder.c index 4e674a9..5be4cbd 100644 --- a/tests/test-dummy-recorder.c +++ b/tests/test-dummy-recorder.c @@ -19,7 +19,8 @@ #include <config.h> #include <assert.h> -#undef ENABLE_RECORDER +#undef ENABLE_C3D_RECORDER +#undef ENABLE_AGENT_INTERFACE #include <common/recorder.h> -- 2.21.0 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel