[Spice-commits] 3 commits - common/agent_interface.c common/agent_interface.h common/Makefile.am common/meson.build common/recorder.h configure.ac m4/spice-deps.m4 meson.build meson_options.txt tests/test-dummy-recorder.c

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Fri Oct 18 16:06:36 UTC 2019


 common/Makefile.am          |    7 
 common/agent_interface.c    |  510 +++++++++++++++++++++++++++++++++++++++
 common/agent_interface.h    |  564 ++++++++++++++++++++++++++++++++++++++++++++
 common/meson.build          |    8 
 common/recorder.h           |   37 ++
 configure.ac                |    2 
 m4/spice-deps.m4            |   23 +
 meson.build                 |    5 
 meson_options.txt           |   10 
 tests/test-dummy-recorder.c |    1 
 10 files changed, 1146 insertions(+), 21 deletions(-)

New commits:
commit f191053a8cd2d35cf8f73dc4cd2931a8031dbc57
Author: Kevin Pouget <kpouget at redhat.com>
Date:   Fri Oct 18 17:51:19 2019 +0200

    build: Introduce the agent-interface as an alternative instrumentation library
    
    The agent-interface is an experimental instrumentation library for
    capturing and sharing Spice performance indicators with an external
    agent.
    
        --enable-instrumentation=[recorder/agent/no]
                 Enable instrumentation [default=no]
    
    The former configuration option '--enable-recorder' is transformed
    into '--enable-instrumentation=recorder'.
    
    Signed-off-by: Kevin Pouget <kpouget at redhat.com>
    Acked-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/common/Makefile.am b/common/Makefile.am
index 9638635..b6c29e1 100644
--- a/common/Makefile.am
+++ b/common/Makefile.am
@@ -65,6 +65,13 @@ libspice_common_la_SOURCES += \
 	$(NULL)
 endif
 
+if ENABLE_AGENT_INTERFACE
+libspice_common_la_SOURCES += \
+	agent_interface.c		\
+	agent_interface.h		\
+	$(NULL)
+endif
+
 # These 2 files are not build as part of spice-common
 # build system, but modules using spice-common will build
 # them with the appropriate options. We need to let automake
diff --git a/common/meson.build b/common/meson.build
index 9a2725f..7356cc0 100644
--- a/common/meson.build
+++ b/common/meson.build
@@ -41,7 +41,7 @@ spice_common_sources = [
   'recorder.h'
 ]
 
-if get_option('recorder')
+if get_option('instrumentation') == 'recorder'
   spice_common_sources += [
     'recorder/recorder.c',
     'recorder/recorder.h',
@@ -49,6 +49,12 @@ if get_option('recorder')
     'recorder/recorder_ring.h'
   ]
 endif
+if get_option('instrumentation') == 'agent'
+  spice_common_sources += [
+    'agent_interface.c',
+    'agent_interface.h'
+  ]
+endif
 
 spice_common_lib = static_library('spice-common', spice_common_sources,
                                   install : false,
diff --git a/common/recorder.h b/common/recorder.h
index 7194ab5..61aa759 100644
--- a/common/recorder.h
+++ b/common/recorder.h
@@ -16,7 +16,14 @@
 */
 /* This file include recorder library headers or if disabled provide
  * replacement declarations */
-#ifndef ENABLE_RECORDER
+
+#ifdef ENABLE_RECORDER
+#include <common/recorder/recorder.h>
+
+#elif defined(ENABLE_AGENT_INTERFACE)
+#include <common/agent_interface.h>
+
+#else
 
 #include <stdio.h>
 #include <stdint.h>
@@ -69,9 +76,6 @@ static inline void
 recorder_dump_on_common_signals(unsigned add, unsigned remove)
 {
 }
-
-#else
-#include <common/recorder/recorder.h>
 #endif
 
 #if !defined(ENABLE_AGENT_INTERFACE)
diff --git a/configure.ac b/configure.ac
index da0a687..9d10287 100644
--- a/configure.ac
+++ b/configure.ac
@@ -41,7 +41,7 @@ AC_ARG_ENABLE([alignment-checks],
 AS_IF([test "x$enable_alignment_checks" = "xyes"],
       [AC_DEFINE([SPICE_DEBUG_ALIGNMENT], 1, [Enable runtime checks for cast alignment])])
 
-SPICE_CHECK_RECORDER
+SPICE_CHECK_INSTRUMENTATION
 
 # Checks for libraries
 PKG_CHECK_MODULES([PROTOCOL], [spice-protocol >= 0.12.12])
diff --git a/m4/spice-deps.m4 b/m4/spice-deps.m4
index 1214341..c4a16e7 100644
--- a/m4/spice-deps.m4
+++ b/m4/spice-deps.m4
@@ -341,17 +341,20 @@ AC_DEFUN([SPICE_CHECK_OPENSSL], [
     PKG_CHECK_MODULES(OPENSSL, openssl)
 ])
 
-# SPICE_CHECK_RECORDER
+# SPICE_CHECK_INSTRUMENTATION
 # -----------------
-# Check for the availability of recorder library.
+# Check for the availability of an instrumentation library.
 #------------------
-AC_DEFUN([SPICE_CHECK_RECORDER], [
-    AC_ARG_ENABLE([recorder],
-      AS_HELP_STRING([--enable-recorder],
-                     [Enable recorder instrumentation @<:@default=no@:>@]),
+AC_DEFUN([SPICE_CHECK_INSTRUMENTATION], [
+    AC_ARG_ENABLE([instrumentation],
+      AS_HELP_STRING([--enable-instrumentation=@<:@recorder/agent/no@:>@],
+                     [Enable instrumentation @<:@default=no@:>@]),
       [],
-      enable_recorder="no")
-    AS_IF([test "$enable_recorder" = "yes"],
-           AC_DEFINE([ENABLE_RECORDER], [1], [Define if recorder instrumentation is enabled]))
-    AM_CONDITIONAL([ENABLE_RECORDER],[test "$enable_recorder" = "yes"])
+      enable_instrumentation="no")
+    AS_IF([test "$enable_instrumentation" = "recorder"],
+           AC_DEFINE([ENABLE_RECORDER], [1], [Define if the recorder instrumentation is enabled]))
+    AS_IF([test "$enable_instrumentation" = "agent"],
+           AC_DEFINE([ENABLE_AGENT_INTERFACE], [1], [Define if the agent-interface instrumentation is enabled]))
+    AM_CONDITIONAL([ENABLE_RECORDER],[test "$enable_instrumentation" = "recorder"])
+    AM_CONDITIONAL([ENABLE_AGENT_INTERFACE],[test "$enable_instrumentation" = "agent"])
 ])
diff --git a/meson.build b/meson.build
index 694119d..2e75e89 100644
--- a/meson.build
+++ b/meson.build
@@ -36,9 +36,12 @@ if host_machine.endian() == 'big'
   spice_common_config_data.set('WORDS_BIGENDIAN', '1')
 endif
 
-if get_option('recorder')
+if get_option('instrumentation') == 'recorder'
   spice_common_config_data.set('ENABLE_RECORDER', '1')
 endif
+if get_option('instrumentation') == 'agent'
+  spice_common_config_data.set('ENABLE_AGENT_INTERFACE', '1')
+endif
 
 spice_common_generate_code = get_option('generate-code')
 spice_common_generate_client_code = spice_common_generate_code == 'all' or spice_common_generate_code == 'client'
diff --git a/meson_options.txt b/meson_options.txt
index c982736..84445fc 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -21,11 +21,11 @@ option('opus',
     yield : true,
     description: 'Enable Opus audio codec')
 
-option('recorder',
-    type : 'boolean',
-    value : false,
-    yield : true,
-    description: 'Enable recorder instrumentation')
+option('instrumentation',
+    type : 'combo',
+    value : 'no',
+    choices : ['recorder', 'agent', 'no'],
+    description: 'Enable instrumentation')
 
 option('smartcard',
     type : 'feature',
commit 3331ad1a042e3152bac73d841b7f5da9bdc06eb1
Author: Kevin Pouget <kpouget at redhat.com>
Date:   Fri Oct 18 17:51:18 2019 +0200

    agent-interface: add configuration functions
    
    agent_interface_start: this function allows launching the agent
    interface (ie, its listening socket) on a given port.
    
    agent_interface_set_on_connect_cb: this function allows passing a
    callback function that will be triggered when a client (a Local Agent)
    connects to the Agent Interface socket.
    
    agent_interface_set_forward_quality_cb: this function allows SPICE to
    provide a function that will forward Quality messages received by the
    Agent Interface towards SPICE server, for a centralized processing of
    the messages.
    
    Signed-off-by: Kevin Pouget <kpouget at redhat.com>
    Acked-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/common/agent_interface.c b/common/agent_interface.c
index 768b6a9..eac071c 100644
--- a/common/agent_interface.c
+++ b/common/agent_interface.c
@@ -43,6 +43,12 @@ static FILE *communication_f = NULL;
 static recorder_info *recorders[NB_MAX_RECORDERS];
 static uint32_t nb_recorders = 0;
 
+static forward_quality_cb_t forward_quality_cb;
+static void *forward_quality_cb_data;
+
+static on_connect_cb_t on_connect_cb;
+static void *on_connect_cb_data;
+
 static uintptr_t recorder_tick(void);
 
 #ifndef RECORDER_HZ
@@ -98,6 +104,10 @@ static int agent_initialize_communication(int socket)
         g_info("Enable recorder '%s'", recorders[i]->name);
     }
 
+    if (on_connect_cb && on_connect_cb(on_connect_cb_data)) {
+        goto unlock;
+    }
+
     communication_f = socket_f;
     ret = 0;
 
@@ -124,6 +134,18 @@ static void agent_finalize_communication(int socket)
     g_mutex_unlock(&mutex_socket);
 }
 
+static void forward_quality(const char *quality)
+{
+    if (!forward_quality_cb) {
+        g_warning("Quality: No callback set, dropping the message (%s).", quality);
+        return;
+    }
+
+    g_info("Quality: Forwarding '%s'", quality);
+
+    forward_quality_cb(forward_quality_cb_data, quality);
+}
+
 static int agent_process_communication(int socket)
 {
     static char msg_in[128];
@@ -144,7 +166,8 @@ static int agent_process_communication(int socket)
     }
 
     if (msg_in[len] == '\0') {
-        // TODO: process quality indicator
+        // process quality indicator
+        forward_quality(msg_in);
         len = 0;
         return 0;
     }
@@ -396,6 +419,26 @@ static void recorder_trace_entry(recorder_info *info, recorder_entry *entry, ...
     g_mutex_unlock(&mutex_socket);
 }
 
+void agent_interface_start(unsigned int port)
+{
+    g_info("Launch on port %u", port);
+    recorder_initialization(port);
+}
+
+void agent_interface_set_forward_quality_cb(forward_quality_cb_t cb, void *data)
+{
+    g_debug("Received forward_quality callback");
+    forward_quality_cb = cb;
+    forward_quality_cb_data = data;
+}
+
+void agent_interface_set_on_connect_cb(on_connect_cb_t cb, void *data)
+{
+    g_debug("Received on_connect callback");
+    on_connect_cb = cb;
+    on_connect_cb_data = data;
+}
+
 void recorder_append(recorder_info *rec,
                      const char *where,
                      const char *format,
diff --git a/common/agent_interface.h b/common/agent_interface.h
index 042120e..ce5b8d9 100644
--- a/common/agent_interface.h
+++ b/common/agent_interface.h
@@ -540,3 +540,25 @@ static inline uintptr_t _recorder_double(double d)
         return u.i;
     }
 }
+
+// ============================================================================
+//   Agent-Interface specific definitions
+// ============================================================================
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+// launch the Agent-Interface server socket
+extern void agent_interface_start(unsigned int port);
+
+//
+typedef void (*forward_quality_cb_t)(void *, const char *);
+extern void agent_interface_set_forward_quality_cb(forward_quality_cb_t cb, void *data);
+
+// set a callback function triggered when a new client connects to the socket
+typedef int (*on_connect_cb_t)(void *);
+extern void agent_interface_set_on_connect_cb(on_connect_cb_t cb, void *data);
+#ifdef __cplusplus
+}
+#endif // __cplusplus
diff --git a/common/recorder.h b/common/recorder.h
index 8448e02..7194ab5 100644
--- a/common/recorder.h
+++ b/common/recorder.h
@@ -73,3 +73,30 @@ recorder_dump_on_common_signals(unsigned add, unsigned remove)
 #else
 #include <common/recorder/recorder.h>
 #endif
+
+#if !defined(ENABLE_AGENT_INTERFACE)
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/* Stubs for Agent-Interface specific definitions */
+static inline void
+agent_interface_start(unsigned int port)
+{
+}
+
+typedef void (*forward_quality_cb_t)(void *, const char *);
+static inline void
+agent_interface_set_forward_quality_cb(forward_quality_cb_t cb, void *data)
+{
+}
+
+typedef int (*on_connect_cb_t)(void *);
+static inline void
+agent_interface_set_on_connect_cb(on_connect_cb_t cb, void *data)
+{
+}
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+#endif
commit a16c7027c55f0dd98f5937bfbe793740a22e882d
Author: Kevin Pouget <kpouget at redhat.com>
Date:   Fri Oct 18 17:51:17 2019 +0200

    agent-interface: introduce the core of the Agent Interface
    
    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 at redhat.com>
    Acked-by: Frediano Ziglio <fziglio at redhat.com>

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 at dinechin.org>
+// *****************************************************************************
+// 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..e54f4ef 100644
--- a/tests/test-dummy-recorder.c
+++ b/tests/test-dummy-recorder.c
@@ -20,6 +20,7 @@
 #include <assert.h>
 
 #undef ENABLE_RECORDER
+#undef ENABLE_AGENT_INTERFACE
 
 #include <common/recorder.h>
 


More information about the Spice-commits mailing list