Re: [Libguestfs] [PATCH nbdkit 2/2] reflection: Add mode for reflecting server time.

Richard W.M. Jones
Sat, 28 Sep 2019 11:21:37 -0000

On Sat, Sep 28, 2019 at 10:25:17AM +0100, Richard W.M. Jones
wrote:
> Either wallclock time, uptime or time since client connection can be
> reflected back to the client in a big endian binary structure.
>
> $ nbdkit reflection time --run 'nbdsh --connect $uri -c
"sys.stdout.buffer.write(h.pread(12,0))" | hexdump -C'
> 00000000  00 00 00 00 5d 8f 24 c7  00 04 24 01
>                         \ |  /  /
>     $ date --date="@$(( 0x5d8f24c7 ))"
>     Sat 28 Sep 10:15:51 BST 2019
>
> $ nbdkit reflection uptime --run 'nbdsh --connect $uri -c
"sys.stdout.buffer.write(h.pread(12,0))" | hexdump -C'
> 00000000  00 00 00 00 00 00 00 00  00 00 60 4b
>                                          |   |
>                                   0x604b is about 25ms
>
> $ nbdkit reflection conntime --run 'nbdsh --connect $uri -c
"sys.stdout.buffer.write(h.pread(12,0))" | hexdump -C'
> 00000000  00 00 00 00 00 00 00 00  00 00 00 e0
>                                             |
>                                   0xe0 is about 200μs

There's an argument that this doesn't belong in the reflection plugin
because it's not reflecting client information back (except maybe
conntime).  It was very convenient to add it here though.

We might consider also:

 - Create a new plugin ("info"?) and add these features here, or

 - Rename the reflection plugin to info plugin, or

 - Do nothing

Rich.

> Suggested by Eric Blake.
> ---
>  plugins/reflection/Makefile.am                |  1 +
>  .../reflection/nbdkit-reflection-plugin.pod   | 41 ++++++++-
>  plugins/reflection/reflection.c               | 90 ++++++++++++++++++-
>  3 files changed, 128 insertions(+), 4 deletions(-)
>
> diff --git a/plugins/reflection/Makefile.am
b/plugins/reflection/Makefile.am
> index 40aa786..9544d98 100644
> --- a/plugins/reflection/Makefile.am
> +++ b/plugins/reflection/Makefile.am
> @@ -42,6 +42,7 @@ nbdkit_reflection_plugin_la_SOURCES = \
>
>  nbdkit_reflection_plugin_la_CPPFLAGS = \
>  	-I$(top_srcdir)/include \
> +	-I$(top_srcdir)/common/include \
>  	$(NULL)
>  nbdkit_reflection_plugin_la_CFLAGS = $(WARNINGS_CFLAGS)
>  nbdkit_reflection_plugin_la_LDFLAGS = \
> diff --git a/plugins/reflection/nbdkit-reflection-plugin.pod
b/plugins/reflection/nbdkit-reflection-plugin.pod
> index f971cef..09deb26 100644
> --- a/plugins/reflection/nbdkit-reflection-plugin.pod
> +++ b/plugins/reflection/nbdkit-reflection-plugin.pod
> @@ -4,7 +4,8 @@ nbdkit-reflection-plugin - reflect client info back to the
client
>
>  =head1 SYNOPSIS
>
> - nbdkit reflection [mode=]exportname|base64exportname|address
> + nbdkit reflection [mode=]exportname|base64exportname|address|
> +                          time|uptime|conntime
>
>  =head1 DESCRIPTION
>
> @@ -22,6 +23,10 @@ than this.
>  C<mode=address> creates a disk which contains the client's IP
address
>  and port number as a string.
>
> +C<mode=time>, C<mode=uptime> and C<mode=conntime> report
server
> +wallclock time, nbdkit uptime, or time since the connection was opened
> +respectively and may be used to measure latency.
> +
>  The plugin only supports read-only access.  To make the disk writable,
>  add L<nbdkit-cow-filter(1)> on top.
>
> @@ -103,6 +108,40 @@ name cannot contain ASCII NUL characters.
>
>  This is the default mode.
>
> +=item [B<mode=>]B
> +
> +Reflect server wallclock time as seconds and microseconds since the
> +Epoch (see L<gettimeofday(2)>):
> +
> + ┌────────┬────────┬────────────┬──────────────────────┐
> + │ offset │ length │ format     │ field                │
> + ╞════════╪════════╪════════════╪══════════════════════╡
> + │   0    │    8   │ 64 bit int │ seconds              │
> + │        │        │ big endian │                      │
> + ├────────┼────────┼────────────┼──────────────────────┤
> + │   8    │    4   │ 32 bit int │ microseconds         │
> + │        │        │ big endian │                      │
> + └────────┴────────┴────────────┴──────────────────────┘
> +
> +To be able to read this atomically you must read the whole 12 bytes in
> +a single request.
> +
> +Note that exposing server time may be insecure.  It is safer to use
> +C<mode=uptime> or C<mode=conntime> instead.
> +
> +=item [B<mode=>]B<uptime>
> +
> +Reflect nbdkit uptime in seconds and microseconds (ie. both fields are
> +C<0> immediately after nbdkit starts, although a client would never
be
> +able to observe this).  The format is exactly the same as for
> +C<mode=time> above.
> +
> +=item [B<mode=>]B<conntime>
> +
> +Reflect time since the NBD client connection was opened in seconds and
> +milliseconds.  The format is exactly the same as for C<mode=time>
> +above.
> +
>  C<mode=> is a magic config key and may be omitted in most cases.
>  See L<nbdkit(1)/Magic parameters>.
>
> diff --git a/plugins/reflection/reflection.c
b/plugins/reflection/reflection.c
> index 6fd1962..1459503 100644
> --- a/plugins/reflection/reflection.c
> +++ b/plugins/reflection/reflection.c
> @@ -35,6 +35,7 @@
>  #include <stdio.h>
>  #include <stdlib.h>
>  #include <string.h>
> +#include <sys/time.h>
>  #include <sys/socket.h>
>  #include <netinet/in.h>
>  #include <arpa/inet.h>
> @@ -48,14 +49,29 @@
>
>  #include <nbdkit-plugin.h>
>
> +#include "byte-swapping.h"
> +#include "tvdiff.h"
> +
>  /* The mode. */
>  enum mode {
>    MODE_EXPORTNAME,
>    MODE_BASE64EXPORTNAME,
>    MODE_ADDRESS,
> +  MODE_TIME,
> +  MODE_UPTIME,
> +  MODE_CONNTIME,
>  };
>  static enum mode mode = MODE_EXPORTNAME;
>
> +/* Plugin load time. */
> +static struct timeval load_t;
> +
> +static void
> +reflection_load (void)
> +{
> +  gettimeofday (&load_t, NULL);
> +}
> +
>  static int
>  reflection_config (const char *key, const char *value)
>  {
> @@ -73,9 +89,14 @@ reflection_config (const char *key, const char *value)
>        return -1;
>  #endif
>      }
> -    else if (strcasecmp (value, "address") == 0) {
> +    else if (strcasecmp (value, "address") == 0)
>        mode = MODE_ADDRESS;
> -    }
> +    else if (strcasecmp (value, "time") == 0)
> +      mode = MODE_TIME;
> +    else if (strcasecmp (value, "uptime") == 0)
> +      mode = MODE_UPTIME;
> +    else if (strcasecmp (value, "conntime") == 0)
> +      mode = MODE_CONNTIME;
>      else {
>        nbdkit_error ("unknown mode: '%s'", value);
>        return -1;
> @@ -90,7 +111,8 @@ reflection_config (const char *key, const char *value)
>  }
>
>  #define reflection_config_help \
> -  "mode=exportname|base64exportname|address  Plugin mode (default
exportname)."
> +  "mode=exportname|base64exportname|address|time|uptime\n" \
> +  "                                      Plugin mode (default
exportname)."
>
>  /* Provide a way to detect if the base64 feature is supported. */
>  static void
> @@ -105,6 +127,7 @@ reflection_dump_plugin (void)
>  struct handle {
>    void *data;                   /* Block device data. */
>    size_t len;                   /* Length of data in bytes. */
> +  struct timeval conn_t;        /* Time since connection was opened. */
>  };
>
>  static int
> @@ -279,6 +302,19 @@ reflection_open (int readonly)
>      }
>      return h;
>
> +  case MODE_TIME:
> +  case MODE_UPTIME:
> +  case MODE_CONNTIME:
> +    gettimeofday (&h->conn_t, NULL);
> +    h->len = 12;
> +    h->data = malloc (h->len);
> +    if (h->data == NULL) {
> +      nbdkit_error ("malloc: %m");
> +      free (h);
> +      return NULL;
> +    }
> +    return h;
> +
>    default:
>      abort ();
>    }
> @@ -320,6 +356,13 @@ reflection_can_multi_conn (void *handle)
>       */
>    case MODE_ADDRESS:
>      return 0;
> +    /* All time modes will read different values at different times,
> +     * so all of them are unsafe for multi-conn.
> +     */
> +  case MODE_TIME:
> +  case MODE_UPTIME:
> +  case MODE_CONNTIME:
> +    return 0;
>
>      /* Keep GCC happy. */
>    default:
> @@ -337,6 +380,42 @@ reflection_can_cache (void *handle)
>    return NBDKIT_CACHE_NATIVE;
>  }
>
> +static void
> +update_time (struct handle *h)
> +{
> +  struct timeval tv;
> +  int64_t secs;
> +  int32_t usecs;
> +  char *p;
> +
> +  gettimeofday (&tv, NULL);
> +
> +  switch (mode) {
> +  case MODE_TIME:
> +    break;
> +
> +  case MODE_UPTIME:
> +    subtract_timeval (&load_t, &tv, &tv);
> +    break;
> +
> +  case MODE_CONNTIME:
> +    subtract_timeval (&h->conn_t, &tv, &tv);
> +    break;
> +
> +  default:
> +    abort ();
> +  }
> +
> +  /* Pack the result into the output buffer. */
> +  secs = tv.tv_sec;
> +  usecs = tv.tv_usec;
> +  secs = htobe64 (secs);
> +  usecs = htobe32 (usecs);
> +  p = h->data;
> +  memcpy (&p[0], &secs, 8);
> +  memcpy (&p[8], &usecs, 4);
> +}
> +
>  /* Read data. */
>  static int
>  reflection_pread (void *handle, void *buf, uint32_t count, uint64_t
offset,
> @@ -344,6 +423,10 @@ reflection_pread (void *handle, void *buf, uint32_t
count, uint64_t offset,
>  {
>    struct handle *h = handle;
>
> +  /* For the time modes we update the data on every read. */
> +  if (mode == MODE_TIME || mode == MODE_UPTIME || mode == MODE_CONNTIME)
> +    update_time (h);
> +
>    memcpy (buf, h->data + offset, count);
>    return 0;
>  }
> @@ -351,6 +434,7 @@ reflection_pread (void *handle, void *buf, uint32_t
count, uint64_t offset,
>  static struct nbdkit_plugin plugin = {
>    .name              = "reflection",
>    .version           = PACKAGE_VERSION,
> +  .load              = reflection_load,
>    .config            = reflection_config,
>    .config_help       = reflection_config_help,
>    .dump_plugin       = reflection_dump_plugin,
> --
> 2.23.0
>
> _______________________________________________
> Libguestfs mailing list
> Libguestfs@redhat.com
> https://www.redhat.com/mailman/listinfo/libguestfs

--
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
virt-top is 'top' for virtual machines.  Tiny program with many
powerful monitoring features, net stats, disk stats, logging, etc.