ddsperf enhancements
* per-thread CPU usage (only those threads where the load is over 0.5%, if the sum of threads below that threshold exceeds 0.5%, it prints an aggregate for those threads); * also report RSS; * network load (only on request, as percentage of specified network bandwidth and actual bytes in/out, with the output suppressed if it is 0%); * publish CPU usage so a ddsperf instance can display CPU loads for its peers; * handle SIGXFSZ (file size exceeded) by displaying one last line of statistics before killing itself; this simply a debugging tool to make it easier to get a trace covering a high sample-rate start-up issue; * default topic changed to "KS" because that allows all the options to be used, this has a negative impact on performance (both latency and small-sample throughput) but it should be less surprising to users; * specifying a size is now done by appending "size N" (where N is the size in bytes) after a "ping" or "pub" command, rather than it having to set it via a command-line option; Note that some of this is platform-dependent -- SIGXFSZ is currently only on Linux and macOS, and CPU and network load reporting is currently only on Linux, macOS and Windows. Signed-off-by: Erik Boasson <eb@ilities.com>
This commit is contained in:
		
							parent
							
								
									ecb77d481c
								
							
						
					
					
						commit
						952029dba0
					
				
					 7 changed files with 627 additions and 42 deletions
				
			
		| 
						 | 
				
			
			@ -11,7 +11,7 @@
 | 
			
		|||
#
 | 
			
		||||
 | 
			
		||||
idlc_generate(ddsperf_types ddsperf_types.idl)
 | 
			
		||||
add_executable(ddsperf ddsperf.c)
 | 
			
		||||
add_executable(ddsperf ddsperf.c cputime.c cputime.h netload.c netload.h)
 | 
			
		||||
target_link_libraries(ddsperf ddsperf_types ddsc)
 | 
			
		||||
 | 
			
		||||
if(WIN32)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										250
									
								
								src/tools/ddsperf/cputime.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								src/tools/ddsperf/cputime.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,250 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright(c) 2019 ADLINK Technology Limited and others
 | 
			
		||||
 *
 | 
			
		||||
 * This program and the accompanying materials are made available under the
 | 
			
		||||
 * terms of the Eclipse Public License v. 2.0 which is available at
 | 
			
		||||
 * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
 | 
			
		||||
 * v. 1.0 which is available at
 | 
			
		||||
 * http://www.eclipse.org/org/documents/edl-v10.php.
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 | 
			
		||||
 */
 | 
			
		||||
#define _ISOC99_SOURCE
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
 | 
			
		||||
#include "dds/dds.h"
 | 
			
		||||
 | 
			
		||||
#include "dds/ddsrt/heap.h"
 | 
			
		||||
#include "dds/ddsrt/process.h"
 | 
			
		||||
#include "dds/ddsrt/sockets.h"
 | 
			
		||||
#include "dds/ddsrt/threads.h"
 | 
			
		||||
#include "dds/ddsrt/string.h"
 | 
			
		||||
#include "dds/ddsrt/rusage.h"
 | 
			
		||||
 | 
			
		||||
#include "cputime.h"
 | 
			
		||||
#include "ddsperf_types.h"
 | 
			
		||||
 | 
			
		||||
static void print (char *line, size_t sz, size_t *pos, const char *name, double du, double ds)
 | 
			
		||||
{
 | 
			
		||||
  if (*pos < sz)
 | 
			
		||||
    *pos += (size_t) snprintf (line + *pos, sz - *pos, " %s:%.0f%%+%.0f%%", name, 100.0 * du, 100.0 * ds);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool print_cputime (const struct CPUStats *s, const char *prefix, bool print_host, bool is_fresh)
 | 
			
		||||
{
 | 
			
		||||
  if (!s->some_above)
 | 
			
		||||
    return false;
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
    char line[512];
 | 
			
		||||
    size_t pos = 0;
 | 
			
		||||
    assert (is_fresh || !print_host);
 | 
			
		||||
    pos += (size_t) snprintf (line + pos, sizeof (line) - pos, "%s", prefix);
 | 
			
		||||
    if (!is_fresh)
 | 
			
		||||
      pos += (size_t) snprintf (line + pos, sizeof (line) - pos, " (stale)");
 | 
			
		||||
    if (print_host)
 | 
			
		||||
    {
 | 
			
		||||
      int n = (int) strlen (s->hostname);
 | 
			
		||||
      if (n > 100) n = 100;
 | 
			
		||||
      pos += (size_t) snprintf (line + pos, sizeof (line) - pos, " @%*.*s:%"PRId32, n, n, s->hostname, s->pid);
 | 
			
		||||
    }
 | 
			
		||||
    if (s->maxrss > 1048576)
 | 
			
		||||
      pos += (size_t) snprintf (line + pos, sizeof (line) - pos, " rss:%.1fMB", s->maxrss / 1048576.0);
 | 
			
		||||
    else if (s->maxrss > 1024)
 | 
			
		||||
      pos += (size_t) snprintf (line + pos, sizeof (line) - pos, " rss:%.0fkB", s->maxrss / 1024.0);
 | 
			
		||||
    else {
 | 
			
		||||
      /* non-sensical value -- presumably maxrss is not available */
 | 
			
		||||
    }
 | 
			
		||||
    const size_t init_pos = pos;
 | 
			
		||||
    for (uint32_t i = 0; i < s->cpu._length; i++)
 | 
			
		||||
    {
 | 
			
		||||
      struct CPUStatThread * const thr = &s->cpu._buffer[i];
 | 
			
		||||
      print (line, sizeof (line), &pos, thr->name, thr->u_pct / 100.0, thr->s_pct / 100.0);
 | 
			
		||||
    }
 | 
			
		||||
    if (pos > init_pos)
 | 
			
		||||
      puts (line);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if DDSRT_HAVE_RUSAGE && DDSRT_HAVE_THREAD_LIST
 | 
			
		||||
 | 
			
		||||
struct record_cputime_state_thr {
 | 
			
		||||
  ddsrt_thread_list_id_t tid;
 | 
			
		||||
  char name[32];
 | 
			
		||||
  double ut, st;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct record_cputime_state {
 | 
			
		||||
  bool supported;
 | 
			
		||||
  dds_time_t tprev;
 | 
			
		||||
  size_t nthreads;
 | 
			
		||||
  struct record_cputime_state_thr *threads;
 | 
			
		||||
  dds_entity_t wr;
 | 
			
		||||
  struct CPUStats s;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void update (double *ut_old, double *st_old, double dt, double ut_new, double st_new, double *du, double *ds)
 | 
			
		||||
{
 | 
			
		||||
  *du = (ut_new - *ut_old) / dt;
 | 
			
		||||
  *ds = (st_new - *st_old) / dt;
 | 
			
		||||
  *ut_old = ut_new;
 | 
			
		||||
  *st_old = st_new;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool above_threshold (double *max, double *du_skip, double *ds_skip, double du, double ds)
 | 
			
		||||
{
 | 
			
		||||
  if (*max < du) *max = du;
 | 
			
		||||
  if (*max < ds) *max = ds;
 | 
			
		||||
  if (du >= 0.005 || ds >= 0.005)
 | 
			
		||||
    return true;
 | 
			
		||||
  else if (du_skip == NULL || ds_skip == NULL)
 | 
			
		||||
    return false;
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
    *du_skip += du;
 | 
			
		||||
    *ds_skip += ds;
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool record_cputime (struct record_cputime_state *state, const char *prefix, dds_time_t tnow)
 | 
			
		||||
{
 | 
			
		||||
  if (state == NULL)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  ddsrt_rusage_t usage;
 | 
			
		||||
  if (ddsrt_getrusage (DDSRT_RUSAGE_SELF, &usage) < 0)
 | 
			
		||||
    usage.maxrss = 0;
 | 
			
		||||
  double max = 0;
 | 
			
		||||
  double du_skip = 0.0, ds_skip = 0.0;
 | 
			
		||||
  const double dt = (double) (tnow - state->tprev) / 1e9;
 | 
			
		||||
  bool some_above = false;
 | 
			
		||||
 | 
			
		||||
  state->s.maxrss = (double) usage.maxrss;
 | 
			
		||||
  state->s.cpu._length = 0;
 | 
			
		||||
  for (size_t i = 0; i < state->nthreads; i++)
 | 
			
		||||
  {
 | 
			
		||||
    struct record_cputime_state_thr * const thr = &state->threads[i];
 | 
			
		||||
    if (ddsrt_getrusage_anythread (thr->tid, &usage) < 0)
 | 
			
		||||
      continue;
 | 
			
		||||
 | 
			
		||||
    const double ut = (double) usage.utime / 1e9;
 | 
			
		||||
    const double st = (double) usage.stime / 1e9;
 | 
			
		||||
    double du, ds;
 | 
			
		||||
    update (&thr->ut, &thr->st, dt, ut, st, &du, &ds);
 | 
			
		||||
    if (above_threshold (&max, &du_skip, &ds_skip, du, ds))
 | 
			
		||||
    {
 | 
			
		||||
      some_above = true;
 | 
			
		||||
      /* Thread names are often set by thread itself immediately after creation,
 | 
			
		||||
         and so it depends on the scheduling whether there is still a default
 | 
			
		||||
         name or the name we are interested in.  Lazily retrieving the name the
 | 
			
		||||
         first time the thread pops up in the CPU usage works around the timing
 | 
			
		||||
         problem. */
 | 
			
		||||
      if (thr->name[0] == 0)
 | 
			
		||||
      {
 | 
			
		||||
        if (ddsrt_thread_getname_anythread (thr->tid, thr->name, sizeof (thr->name)) < 0)
 | 
			
		||||
        {
 | 
			
		||||
          du_skip += du;
 | 
			
		||||
          ds_skip += ds;
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      struct CPUStatThread * const x = &state->s.cpu._buffer[state->s.cpu._length++];
 | 
			
		||||
      x->name = thr->name;
 | 
			
		||||
      x->u_pct = (int) (100.0 * du + 0.5);
 | 
			
		||||
      x->s_pct = (int) (100.0 * ds + 0.5);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (above_threshold (&max, NULL, NULL, du_skip, ds_skip))
 | 
			
		||||
  {
 | 
			
		||||
    struct CPUStatThread * const x = &state->s.cpu._buffer[state->s.cpu._length++];
 | 
			
		||||
    some_above = true;
 | 
			
		||||
    x->name = "others";
 | 
			
		||||
    x->u_pct = (int) (100.0 * du_skip + 0.5);
 | 
			
		||||
    x->s_pct = (int) (100.0 * ds_skip + 0.5);
 | 
			
		||||
  }
 | 
			
		||||
  state->tprev = tnow;
 | 
			
		||||
  state->s.some_above = some_above;
 | 
			
		||||
  dds_write (state->wr, &state->s);
 | 
			
		||||
  return print_cputime (&state->s, prefix, false, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct record_cputime_state *record_cputime_new (dds_entity_t wr)
 | 
			
		||||
{
 | 
			
		||||
  ddsrt_thread_list_id_t tids[100];
 | 
			
		||||
  dds_return_t n;
 | 
			
		||||
  if ((n = ddsrt_thread_list (tids, sizeof (tids) / sizeof (tids[0]))) <= 0)
 | 
			
		||||
    return NULL;
 | 
			
		||||
  else if (n > (dds_return_t) (sizeof (tids) / sizeof (tids[0])))
 | 
			
		||||
  {
 | 
			
		||||
    fprintf (stderr, "way more threads than expected\n");
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  struct record_cputime_state *state = malloc (sizeof (*state));
 | 
			
		||||
  state->tprev = dds_time ();
 | 
			
		||||
  state->wr = wr;
 | 
			
		||||
  state->threads = malloc ((size_t) n * sizeof (*state->threads));
 | 
			
		||||
  state->nthreads = 0;
 | 
			
		||||
  for (int32_t i = 0; i < n; i++)
 | 
			
		||||
  {
 | 
			
		||||
    struct record_cputime_state_thr * const thr = &state->threads[state->nthreads];
 | 
			
		||||
    ddsrt_rusage_t usage;
 | 
			
		||||
    if (ddsrt_getrusage_anythread (tids[i], &usage) < 0)
 | 
			
		||||
      continue;
 | 
			
		||||
    thr->tid = tids[i];
 | 
			
		||||
    thr->name[0] = 0;
 | 
			
		||||
    thr->ut = (double) usage.utime / 1e9;
 | 
			
		||||
    thr->st = (double) usage.stime / 1e9;
 | 
			
		||||
    state->nthreads++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  char hostname[128];
 | 
			
		||||
  if (ddsrt_gethostname (hostname, sizeof (hostname)) != DDS_RETCODE_OK)
 | 
			
		||||
    strcpy (hostname, "?");
 | 
			
		||||
  state->s.hostname = ddsrt_strdup (hostname);
 | 
			
		||||
  state->s.pid = (uint32_t) ddsrt_getpid ();
 | 
			
		||||
  state->s.cpu._length = 0;
 | 
			
		||||
  state->s.cpu._maximum = (uint32_t) state->nthreads;
 | 
			
		||||
  state->s.cpu._buffer = malloc (state->s.cpu._maximum * sizeof (*state->s.cpu._buffer));
 | 
			
		||||
  state->s.cpu._release = false;
 | 
			
		||||
  return state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void record_cputime_free (struct record_cputime_state *state)
 | 
			
		||||
{
 | 
			
		||||
  if (state)
 | 
			
		||||
  {
 | 
			
		||||
    free (state->threads);
 | 
			
		||||
    ddsrt_free (state->s.hostname);
 | 
			
		||||
    /* we alias thread names in state->s->cpu._buffer, so no need to free */
 | 
			
		||||
    free (state->s.cpu._buffer);
 | 
			
		||||
    free (state);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
bool record_cputime (struct record_cputime_state *state, const char *prefix, dds_time_t tnow)
 | 
			
		||||
{
 | 
			
		||||
  (void) state;
 | 
			
		||||
  (void) prefix;
 | 
			
		||||
  (void) tnow;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct record_cputime_state *record_cputime_new (dds_entity_t wr)
 | 
			
		||||
{
 | 
			
		||||
  (void) wr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void record_cputime_free (struct record_cputime_state *state)
 | 
			
		||||
{
 | 
			
		||||
  (void) state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										24
									
								
								src/tools/ddsperf/cputime.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/tools/ddsperf/cputime.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright(c) 2019 ADLINK Technology Limited and others
 | 
			
		||||
 *
 | 
			
		||||
 * This program and the accompanying materials are made available under the
 | 
			
		||||
 * terms of the Eclipse Public License v. 2.0 which is available at
 | 
			
		||||
 * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
 | 
			
		||||
 * v. 1.0 which is available at
 | 
			
		||||
 * http://www.eclipse.org/org/documents/edl-v10.php.
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 | 
			
		||||
 */
 | 
			
		||||
#ifndef CPUTIME_H
 | 
			
		||||
#define CPUTIME_H
 | 
			
		||||
 | 
			
		||||
#include "ddsperf_types.h"
 | 
			
		||||
 | 
			
		||||
struct record_cputime_state;
 | 
			
		||||
 | 
			
		||||
struct record_cputime_state *record_cputime_new (dds_entity_t wr);
 | 
			
		||||
void record_cputime_free (struct record_cputime_state *state);
 | 
			
		||||
bool record_cputime (struct record_cputime_state *state, const char *prefix, dds_time_t tnow);
 | 
			
		||||
bool print_cputime (const struct CPUStats *s, const char *prefix, bool print_host, bool is_fresh);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +37,9 @@
 | 
			
		|||
#include "dds/ddsrt/fibheap.h"
 | 
			
		||||
#include "dds/ddsrt/atomics.h"
 | 
			
		||||
 | 
			
		||||
#include "cputime.h"
 | 
			
		||||
#include "netload.h"
 | 
			
		||||
 | 
			
		||||
#if !defined(_WIN32) && !defined(LWIP_SOCKET)
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -74,22 +77,22 @@ static dds_entity_t rd_participants, rd_subscriptions, rd_publications;
 | 
			
		|||
 | 
			
		||||
/* Topics, readers, writers (except for pong writers: there are
 | 
			
		||||
   many of those) */
 | 
			
		||||
static dds_entity_t tp_data, tp_ping, tp_pong;
 | 
			
		||||
static dds_entity_t tp_data, tp_ping, tp_pong, tp_stat;
 | 
			
		||||
static char tpname_data[32], tpname_ping[32], tpname_pong[32];
 | 
			
		||||
static dds_entity_t sub, pub, wr_data, wr_ping, rd_data, rd_ping, rd_pong;
 | 
			
		||||
static dds_entity_t sub, pub, wr_data, wr_ping, wr_stat, rd_data, rd_ping, rd_pong, rd_stat;
 | 
			
		||||
 | 
			
		||||
/* Number of different key values to use (must be 1 for OU type) */
 | 
			
		||||
static unsigned nkeyvals = 1;
 | 
			
		||||
 | 
			
		||||
/* Topic type to use */
 | 
			
		||||
static enum topicsel topicsel = OU;
 | 
			
		||||
static enum topicsel topicsel = KS;
 | 
			
		||||
 | 
			
		||||
/* Data and ping/pong subscriber triggering modes */
 | 
			
		||||
static enum submode submode = SM_LISTENER;
 | 
			
		||||
static enum submode pingpongmode = SM_LISTENER;
 | 
			
		||||
 | 
			
		||||
/* Size of the sequence in KeyedSeq type in bytes */
 | 
			
		||||
static unsigned baggagesize = 0;
 | 
			
		||||
static uint32_t baggagesize = 0;
 | 
			
		||||
 | 
			
		||||
/* Whether or not to register instances prior to writing */
 | 
			
		||||
static bool register_instances = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -115,7 +118,7 @@ static uint32_t matchcount = 0;
 | 
			
		|||
static uint32_t matchtimeout = 0;
 | 
			
		||||
 | 
			
		||||
/* Data is published in bursts of this many samples */
 | 
			
		||||
static unsigned burstsize = 1;
 | 
			
		||||
static uint32_t burstsize = 1;
 | 
			
		||||
 | 
			
		||||
/* Whether to use reliable or best-effort readers/writers */
 | 
			
		||||
static bool reliable = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -490,7 +493,7 @@ static void hist_print (const char *prefix, struct hist *h, dds_time_t dt, int r
 | 
			
		|||
    hist_reset (h);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void *make_baggage (dds_sequence_t *b, unsigned cnt)
 | 
			
		||||
static void *make_baggage (dds_sequence_t *b, uint32_t cnt)
 | 
			
		||||
{
 | 
			
		||||
  b->_maximum = b->_length = cnt;
 | 
			
		||||
  if (cnt == 0)
 | 
			
		||||
| 
						 | 
				
			
			@ -558,7 +561,7 @@ static uint32_t pubthread (void *varg)
 | 
			
		|||
 | 
			
		||||
  tfirst0 = tfirst = dds_time();
 | 
			
		||||
 | 
			
		||||
  unsigned bi = 0;
 | 
			
		||||
  uint32_t bi = 0;
 | 
			
		||||
  while (!ddsrt_atomic_ld32 (&termflag))
 | 
			
		||||
  {
 | 
			
		||||
    /* lsb of timestamp is abused to signal whether the sample is a ping requiring a response or not */
 | 
			
		||||
| 
						 | 
				
			
			@ -783,6 +786,19 @@ static dds_entity_t get_pong_writer (dds_instance_handle_t pubhandle)
 | 
			
		|||
  return wr_pong;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint32_t topic_payload_size (enum topicsel tp, uint32_t bgsize)
 | 
			
		||||
{
 | 
			
		||||
  uint32_t size = 0;
 | 
			
		||||
  switch (tp)
 | 
			
		||||
  {
 | 
			
		||||
    case KS:   size = 12 + bgsize; break;
 | 
			
		||||
    case K32:  size = 32; break;
 | 
			
		||||
    case K256: size = 256; break;
 | 
			
		||||
    case OU:   size = 4; break;
 | 
			
		||||
  }
 | 
			
		||||
  return size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool process_data (dds_entity_t rd, struct subthread_arg *arg)
 | 
			
		||||
{
 | 
			
		||||
  uint32_t max_samples = arg->max_samples;
 | 
			
		||||
| 
						 | 
				
			
			@ -798,10 +814,13 @@ static bool process_data (dds_entity_t rd, struct subthread_arg *arg)
 | 
			
		|||
      uint32_t seq = 0, keyval = 0, size = 0;
 | 
			
		||||
      switch (topicsel)
 | 
			
		||||
      {
 | 
			
		||||
        case KS:   { KeyedSeq *d = (KeyedSeq *) mseq[i]; keyval = d->keyval; seq = d->seq; size = 12 + d->baggage._length; } break;
 | 
			
		||||
        case K32:  { Keyed32 *d  = (Keyed32 *)  mseq[i]; keyval = d->keyval; seq = d->seq; size = 32; } break;
 | 
			
		||||
        case K256: { Keyed256 *d = (Keyed256 *) mseq[i]; keyval = d->keyval; seq = d->seq; size = 256; } break;
 | 
			
		||||
        case OU:   { OneULong *d = (OneULong *) mseq[i]; keyval = 0;         seq = d->seq; size = 4; } break;
 | 
			
		||||
        case KS:   {
 | 
			
		||||
          KeyedSeq *d = (KeyedSeq *) mseq[i]; keyval = d->keyval; seq = d->seq; size = topic_payload_size (topicsel, d->baggage._length);
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        case K32:  { Keyed32 *d  = (Keyed32 *)  mseq[i]; keyval = d->keyval; seq = d->seq; size = topic_payload_size (topicsel, 0); } break;
 | 
			
		||||
        case K256: { Keyed256 *d = (Keyed256 *) mseq[i]; keyval = d->keyval; seq = d->seq; size = topic_payload_size (topicsel, 0); } break;
 | 
			
		||||
        case OU:   { OneULong *d = (OneULong *) mseq[i]; keyval = 0;         seq = d->seq; size = topic_payload_size (topicsel, 0); } break;
 | 
			
		||||
      }
 | 
			
		||||
      (void) check_eseq (&eseq_admin, seq, keyval, size, iseq[i].publication_handle);
 | 
			
		||||
      if (iseq[i].source_timestamp & 1)
 | 
			
		||||
| 
						 | 
				
			
			@ -1314,10 +1333,11 @@ static int cmp_uint64 (const void *va, const void *vb)
 | 
			
		|||
  return (*a == *b) ? 0 : (*a < *b) ? -1 : 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void print_stats (dds_time_t tstart, dds_time_t tnow, dds_time_t tprev)
 | 
			
		||||
static void print_stats (dds_time_t tref, dds_time_t tnow, dds_time_t tprev, struct record_cputime_state *cputime_state, struct record_netload_state *netload_state)
 | 
			
		||||
{
 | 
			
		||||
  char prefix[128];
 | 
			
		||||
  const double ts = (double) (tnow - tstart) / 1e9;
 | 
			
		||||
  const double ts = (double) (tnow - tref) / 1e9;
 | 
			
		||||
  bool output = false;
 | 
			
		||||
  snprintf (prefix, sizeof (prefix), "[%"PRIdPID"] %.3f ", ddsrt_getpid (), ts);
 | 
			
		||||
 | 
			
		||||
  if (pub_rate > 0)
 | 
			
		||||
| 
						 | 
				
			
			@ -1325,18 +1345,20 @@ static void print_stats (dds_time_t tstart, dds_time_t tnow, dds_time_t tprev)
 | 
			
		|||
    ddsrt_mutex_lock (&pubstat_lock);
 | 
			
		||||
    hist_print (prefix, pubstat_hist, tnow - tprev, 1);
 | 
			
		||||
    ddsrt_mutex_unlock (&pubstat_lock);
 | 
			
		||||
    output = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (submode != SM_NONE)
 | 
			
		||||
  {
 | 
			
		||||
    struct eseq_admin * const ea = &eseq_admin;
 | 
			
		||||
    uint64_t tot_nrecv = 0, nrecv = 0, nrecv_bytes = 0, nlost = 0;
 | 
			
		||||
    uint64_t tot_nrecv = 0, tot_nlost = 0, nrecv = 0, nrecv_bytes = 0, nlost = 0;
 | 
			
		||||
    uint32_t last_size = 0;
 | 
			
		||||
    ddsrt_mutex_lock (&ea->lock);
 | 
			
		||||
    for (uint32_t i = 0; i < ea->nph; i++)
 | 
			
		||||
    {
 | 
			
		||||
      struct eseq_stat * const x = &ea->stats[i];
 | 
			
		||||
      tot_nrecv += x->nrecv;
 | 
			
		||||
      tot_nlost += x->nlost;
 | 
			
		||||
      nrecv += x->nrecv - x->nrecv_ref;
 | 
			
		||||
      nlost += x->nlost - x->nlost_ref;
 | 
			
		||||
      nrecv_bytes += x->nrecv_bytes - x->nrecv_bytes_ref;
 | 
			
		||||
| 
						 | 
				
			
			@ -1349,8 +1371,11 @@ static void print_stats (dds_time_t tstart, dds_time_t tnow, dds_time_t tprev)
 | 
			
		|||
 | 
			
		||||
    if (nrecv > 0)
 | 
			
		||||
    {
 | 
			
		||||
      printf ("%s size %"PRIu32" ntot %"PRIu64" delta: %"PRIu64" lost %"PRIu64" rate %.2f Mb/s\n",
 | 
			
		||||
              prefix, last_size, tot_nrecv, nrecv, nlost, (double) nrecv_bytes * 8 * 1e3 / (double) (tnow - tprev));
 | 
			
		||||
      const double dt = (double) (tnow - tprev);
 | 
			
		||||
      printf ("%s size %"PRIu32" total %"PRIu64" lost %"PRIu64" delta %"PRIu64" lost %"PRIu64" rate %.2f kS/s %.2f Mb/s\n",
 | 
			
		||||
              prefix, last_size, tot_nrecv, tot_nlost, nrecv, nlost,
 | 
			
		||||
              (double) nrecv * 1e6 / dt, (double) nrecv_bytes * 8 * 1e3 / dt);
 | 
			
		||||
      output = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1380,8 +1405,8 @@ static void print_stats (dds_time_t tstart, dds_time_t tnow, dds_time_t tprev)
 | 
			
		|||
      ddsrt_mutex_unlock (&disc_lock);
 | 
			
		||||
 | 
			
		||||
      qsort (y.raw, rawcnt, sizeof (*y.raw), cmp_uint64);
 | 
			
		||||
      printf ("%s  %s mean %.3fus min %.3fus 50%% %.3fus 90%% %.3fus 99%% %.3fus max %.3fus cnt %"PRIu32"\n",
 | 
			
		||||
              prefix, ppinfo,
 | 
			
		||||
      printf ("%s %s size %"PRIu32" mean %.3fus min %.3fus 50%% %.3fus 90%% %.3fus 99%% %.3fus max %.3fus cnt %"PRIu32"\n",
 | 
			
		||||
              prefix, ppinfo, topic_payload_size (topicsel, baggagesize),
 | 
			
		||||
              (double) y.sum / (double) y.cnt / 1e3,
 | 
			
		||||
              (double) y.min / 1e3,
 | 
			
		||||
              (double) y.raw[rawcnt - (rawcnt + 1) / 2] / 1e3,
 | 
			
		||||
| 
						 | 
				
			
			@ -1389,6 +1414,7 @@ static void print_stats (dds_time_t tstart, dds_time_t tnow, dds_time_t tprev)
 | 
			
		|||
              (double) y.raw[rawcnt - (rawcnt + 99) / 100] / 1e3,
 | 
			
		||||
              (double) y.max / 1e3,
 | 
			
		||||
              y.cnt);
 | 
			
		||||
      output = true;
 | 
			
		||||
    }
 | 
			
		||||
    newraw = y.raw;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1396,6 +1422,42 @@ static void print_stats (dds_time_t tstart, dds_time_t tnow, dds_time_t tprev)
 | 
			
		|||
  }
 | 
			
		||||
  ddsrt_mutex_unlock (&pongstat_lock);
 | 
			
		||||
  free (newraw);
 | 
			
		||||
 | 
			
		||||
  if (record_cputime (cputime_state, prefix, tnow))
 | 
			
		||||
    output = true;
 | 
			
		||||
 | 
			
		||||
  if (rd_stat)
 | 
			
		||||
  {
 | 
			
		||||
#define MAXS 40 /* 40 participants is enough for everyone! */
 | 
			
		||||
    void *raw[MAXS];
 | 
			
		||||
    dds_sample_info_t si[MAXS];
 | 
			
		||||
    int32_t n;
 | 
			
		||||
    /* Read everything using a keep-last-1 reader: effectively latching the
 | 
			
		||||
       most recent value.  While not entirely correct, the nature of the process
 | 
			
		||||
       is such that things should be stable, and this allows printing the stats
 | 
			
		||||
       always in the same way despite the absence of synchronization. */
 | 
			
		||||
    raw[0] = NULL;
 | 
			
		||||
    if ((n = dds_take_mask (rd_stat, raw, si, MAXS, MAXS, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE)) > 0)
 | 
			
		||||
    {
 | 
			
		||||
      for (int32_t i = 0; i < n; i++)
 | 
			
		||||
        if (si[i].valid_data && si[i].sample_state == DDS_SST_NOT_READ)
 | 
			
		||||
          if (print_cputime (raw[i], prefix, true, true))
 | 
			
		||||
            output = true;
 | 
			
		||||
      dds_return_loan (rd_stat, raw, n);
 | 
			
		||||
    }
 | 
			
		||||
    if ((n = dds_read (rd_stat, raw, si, MAXS, MAXS)) > 0)
 | 
			
		||||
    {
 | 
			
		||||
      for (int32_t i = 0; i < n; i++)
 | 
			
		||||
        if (si[i].valid_data)
 | 
			
		||||
          if (print_cputime (raw[i], prefix, true, si[i].sample_state == DDS_SST_NOT_READ))
 | 
			
		||||
            output = true;
 | 
			
		||||
      dds_return_loan (rd_stat, raw, n);
 | 
			
		||||
    }
 | 
			
		||||
#undef MAXS
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (output)
 | 
			
		||||
    record_netload (netload_state, prefix, tnow);
 | 
			
		||||
  fflush (stdout);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1436,6 +1498,23 @@ static uint32_t sigthread (void *varg)
 | 
			
		|||
    error2 ("sigwait failed: %d\n", errno);
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if defined __APPLE__ || defined __linux
 | 
			
		||||
static void sigxfsz_handler (int sig __attribute__ ((unused)))
 | 
			
		||||
{
 | 
			
		||||
  static const char msg[] = "file size limit reached\n";
 | 
			
		||||
  static ddsrt_atomic_uint32_t seen = DDSRT_ATOMIC_UINT32_INIT (0);
 | 
			
		||||
  if (!ddsrt_atomic_or32_ov (&seen, 1))
 | 
			
		||||
  {
 | 
			
		||||
    dds_time_t tnow = dds_time ();
 | 
			
		||||
    if (write (2, msg, sizeof (msg) - 1) < 0) {
 | 
			
		||||
      /* may not ignore return value according to Linux/gcc */
 | 
			
		||||
    }
 | 
			
		||||
    print_stats (0, tnow, tnow - DDS_SECS (1), NULL, NULL);
 | 
			
		||||
    kill (getpid (), 9);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/********************
 | 
			
		||||
| 
						 | 
				
			
			@ -1449,7 +1528,7 @@ static void usage (void)
 | 
			
		|||
%s [OPTIONS] MODE...\n\
 | 
			
		||||
\n\
 | 
			
		||||
OPTIONS:\n\
 | 
			
		||||
  -T KS|K32|K256|OU   topic:\n\
 | 
			
		||||
  -T KS|K32|K256|OU   topic (KS is default):\n\
 | 
			
		||||
                        KS   seq num, key value, sequence-of-octets\n\
 | 
			
		||||
                        K32  seq num, key value, array of 24 octets\n\
 | 
			
		||||
                        K256 seq num, key value, array of 248 octets\n\
 | 
			
		||||
| 
						 | 
				
			
			@ -1465,7 +1544,7 @@ OPTIONS:\n\
 | 
			
		|||
  -M DUR              require those participants to match within DUR seconds\n\
 | 
			
		||||
\n\
 | 
			
		||||
MODE... is zero or more of:\n\
 | 
			
		||||
  ping [R[Hz]] [waitset|listener]\n\
 | 
			
		||||
  ping [R[Hz]] [size N] [waitset|listener]\n\
 | 
			
		||||
    Send a ping upon receiving all expected pongs, or send a ping at\n\
 | 
			
		||||
    rate R (optionally suffixed with Hz).  The triggering mode is either\n\
 | 
			
		||||
    a listener (default, unless -L has been specified) or a waitset.\n\
 | 
			
		||||
| 
						 | 
				
			
			@ -1476,7 +1555,7 @@ MODE... is zero or more of:\n\
 | 
			
		|||
  sub [waitset|listener|polling]\n\
 | 
			
		||||
    Subscribe to data, with calls to take occurring either in a listener\n\
 | 
			
		||||
    (default), when a waitset is triggered, or by polling at 1kHz.\n\
 | 
			
		||||
  pub [R[Hz]] [burst N] [[ping] X%%]\n\
 | 
			
		||||
  pub [R[Hz]] [size N] [burst N] [[ping] X%%]\n\
 | 
			
		||||
    Publish bursts of data at rate R, optionally suffixed with Hz.  If\n\
 | 
			
		||||
    no rate is given or R is \"inf\", data is published as fast as\n\
 | 
			
		||||
    possible.  Each burst is a single sample by default, but can be set\n\
 | 
			
		||||
| 
						 | 
				
			
			@ -1484,6 +1563,11 @@ MODE... is zero or more of:\n\
 | 
			
		|||
    If desired, a fraction of the samples can be treated as if it were a\n\
 | 
			
		||||
    ping, for this, specify a percentage either as \"ping X%%\" (the\n\
 | 
			
		||||
    \"ping\" keyword is optional, the %% sign is not).\n\
 | 
			
		||||
\n\
 | 
			
		||||
  Payload size (including fixed part of topic) may be set as part of a\n\
 | 
			
		||||
  \"ping\" or \"pub\" specification for topic KS (there is only size,\n\
 | 
			
		||||
  the last one given determines it for all) and should be either 0 (minimal,\n\
 | 
			
		||||
  equivalent to 12) or >= 12.\n\
 | 
			
		||||
\n\
 | 
			
		||||
If no MODE specified, it defaults to a 1Hz ping + responding to any pings.\n\
 | 
			
		||||
", argv0, argv0);
 | 
			
		||||
| 
						 | 
				
			
			@ -1516,7 +1600,7 @@ static int exact_string_int_map_lookup (const struct string_int_map_elem *elems,
 | 
			
		|||
    if (strcmp (elems[i].name, str) == 0)
 | 
			
		||||
      return elems[i].value;
 | 
			
		||||
  if (notfound_error)
 | 
			
		||||
    error3 ("%s: undefined %s", str, label);
 | 
			
		||||
    error3 ("%s: undefined %s\n", str, label);
 | 
			
		||||
  return -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1538,12 +1622,30 @@ static int string_int_map_lookup (const struct string_int_map_elem *elems, const
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (ambiguous)
 | 
			
		||||
    error3 ("%s: ambiguous %sspecification", str, label);
 | 
			
		||||
    error3 ("%s: ambiguous %sspecification\n", str, label);
 | 
			
		||||
  if (match == SIZE_MAX && notfound_error)
 | 
			
		||||
    error3 ("%s: undefined %s", str, label);
 | 
			
		||||
    error3 ("%s: undefined %s\n", str, label);
 | 
			
		||||
  return (match == SIZE_MAX) ? -1 : elems[match].value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool set_simple_uint32 (int *xoptind, int xargc, char * const xargv[], const char *token, uint32_t *val)
 | 
			
		||||
{
 | 
			
		||||
  if (strcmp (xargv[*xoptind], token) != 0)
 | 
			
		||||
    return false;
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
    unsigned x;
 | 
			
		||||
    int pos;
 | 
			
		||||
    if (++(*xoptind) == xargc)
 | 
			
		||||
      error3 ("argument missing in %s specification\n", token);
 | 
			
		||||
    if (sscanf (xargv[*xoptind], "%u%n", &x, &pos) == 1 && xargv[*xoptind][pos] == 0)
 | 
			
		||||
      *val = x;
 | 
			
		||||
    else
 | 
			
		||||
      error3 ("%s: invalid %s specification\n", xargv[*xoptind], token);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void set_mode_ping (int *xoptind, int xargc, char * const xargv[])
 | 
			
		||||
{
 | 
			
		||||
  ping_intv = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -1562,6 +1664,10 @@ static void set_mode_ping (int *xoptind, int xargc, char * const xargv[])
 | 
			
		|||
      else if (ping_rate > 0) ping_intv = (dds_duration_t) (1e9 / ping_rate + 0.5);
 | 
			
		||||
      else error3 ("%s: invalid ping rate\n", xargv[*xoptind]);
 | 
			
		||||
    }
 | 
			
		||||
    else if (set_simple_uint32 (xoptind, xargc, xargv, "size", &baggagesize))
 | 
			
		||||
    {
 | 
			
		||||
      /* no further work needed */
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      pingpongmode = (enum submode) string_int_map_lookup (pingpongmodes, "ping mode", xargv[*xoptind], true);
 | 
			
		||||
| 
						 | 
				
			
			@ -1614,15 +1720,13 @@ static void set_mode_pub (int *xoptind, int xargc, char * const xargv[])
 | 
			
		|||
      if (r < 0) error3 ("%s: invalid publish rate\n", xargv[*xoptind]);
 | 
			
		||||
      pub_rate = r;
 | 
			
		||||
    }
 | 
			
		||||
    else if (strcmp (xargv[*xoptind], "burst") == 0)
 | 
			
		||||
    else if (set_simple_uint32 (xoptind, xargc, xargv, "burst", &burstsize))
 | 
			
		||||
    {
 | 
			
		||||
      unsigned b;
 | 
			
		||||
      if (++(*xoptind) == xargc)
 | 
			
		||||
        error3 ("argument missing in burst size specification\n");
 | 
			
		||||
      if (sscanf (xargv[*xoptind], "%u%n", &b, &pos) == 1 && xargv[*xoptind][pos] == 0)
 | 
			
		||||
        burstsize = b;
 | 
			
		||||
      else
 | 
			
		||||
        error3 ("%s: invalid burst size specification\n", xargv[*xoptind]);
 | 
			
		||||
      /* no further work needed */
 | 
			
		||||
    }
 | 
			
		||||
    else if (set_simple_uint32 (xoptind, xargc, xargv, "size", &baggagesize))
 | 
			
		||||
    {
 | 
			
		||||
      /* no further work needed */
 | 
			
		||||
    }
 | 
			
		||||
    else if (sscanf (xargv[*xoptind], "%lf%n", &r, &pos) == 1 && strcmp (xargv[*xoptind] + pos, "%") == 0)
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -1675,28 +1779,43 @@ int main (int argc, char *argv[])
 | 
			
		|||
  dds_qos_t *qos;
 | 
			
		||||
  dds_listener_t *listener;
 | 
			
		||||
  int opt;
 | 
			
		||||
  bool collect_stats = false;
 | 
			
		||||
  dds_time_t tref = DDS_INFINITY;
 | 
			
		||||
  ddsrt_threadattr_t attr;
 | 
			
		||||
  ddsrt_thread_t pubtid, subtid, subpingtid, subpongtid;
 | 
			
		||||
#if !_WIN32 && !DDSRT_WITH_FREERTOS
 | 
			
		||||
  sigset_t sigset, osigset;
 | 
			
		||||
  ddsrt_thread_t sigtid;
 | 
			
		||||
#endif
 | 
			
		||||
  char netload_if[256];
 | 
			
		||||
  double netload_bw = 0;
 | 
			
		||||
  ddsrt_threadattr_init (&attr);
 | 
			
		||||
 | 
			
		||||
  argv0 = argv[0];
 | 
			
		||||
 | 
			
		||||
  if (argc == 2 && strcmp (argv[1], "help") == 0)
 | 
			
		||||
    usage ();
 | 
			
		||||
  while ((opt = getopt (argc, argv, "D:n:z:k:uLT:M:N:h")) != EOF)
 | 
			
		||||
  while ((opt = getopt (argc, argv, "cd:D:n:k:uLK:T:M:N:R:h")) != EOF)
 | 
			
		||||
  {
 | 
			
		||||
    switch (opt)
 | 
			
		||||
    {
 | 
			
		||||
      case 'c': collect_stats = true; break;
 | 
			
		||||
      case 'd': {
 | 
			
		||||
        char *col;
 | 
			
		||||
        int pos;
 | 
			
		||||
        ddsrt_strlcpy (netload_if, optarg, sizeof (netload_if));
 | 
			
		||||
        if ((col = strrchr (netload_if, ':')) == NULL || col == netload_if ||
 | 
			
		||||
            (sscanf (col+1, "%lf%n", &netload_bw, &pos) != 1 || (col+1)[pos] != 0))
 | 
			
		||||
          error3 ("-d%s: expected DEVICE:BANDWIDTH\n", optarg);
 | 
			
		||||
        *col = 0;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case 'D': dur = atof (optarg); if (dur <= 0) dur = HUGE_VAL; break;
 | 
			
		||||
      case 'n': nkeyvals = (unsigned) atoi (optarg); break;
 | 
			
		||||
      case 'u': reliable = false; break;
 | 
			
		||||
      case 'k': histdepth = atoi (optarg); if (histdepth < 0) histdepth = 0; break;
 | 
			
		||||
      case 'L': ignorelocal = DDS_IGNORELOCAL_NONE; break;
 | 
			
		||||
      case 'T':
 | 
			
		||||
      case 'T': case 'K': /* 'K' because of my muscle memory with pubsub ... */
 | 
			
		||||
        if (strcmp (optarg, "KS") == 0) topicsel = KS;
 | 
			
		||||
        else if (strcmp (optarg, "K32") == 0) topicsel = K32;
 | 
			
		||||
        else if (strcmp (optarg, "K256") == 0) topicsel = K256;
 | 
			
		||||
| 
						 | 
				
			
			@ -1705,7 +1824,7 @@ int main (int argc, char *argv[])
 | 
			
		|||
        break;
 | 
			
		||||
      case 'M': maxwait = atof (optarg); if (maxwait <= 0) maxwait = HUGE_VAL; break;
 | 
			
		||||
      case 'N': minmatch = (unsigned) atoi (optarg); break;
 | 
			
		||||
      case 'z': baggagesize = (unsigned) atoi (optarg); break;
 | 
			
		||||
      case 'R': tref = 0; sscanf (optarg, "%"SCNd64, &tref); break;
 | 
			
		||||
      case 'h': usage (); break;
 | 
			
		||||
      default: error3 ("-%c: unknown option\n", opt); break;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -1717,12 +1836,18 @@ int main (int argc, char *argv[])
 | 
			
		|||
  if (topicsel == OU && nkeyvals != 1)
 | 
			
		||||
    error3 ("-n%u invalid: topic OU has no key\n", nkeyvals);
 | 
			
		||||
  if (topicsel != KS && baggagesize != 0)
 | 
			
		||||
    error3 ("-z%u invalid: only topic KS has a sequence\n", baggagesize);
 | 
			
		||||
    error3 ("size %"PRIu32" invalid: only topic KS has a sequence\n", baggagesize);
 | 
			
		||||
  if (baggagesize != 0 && baggagesize < 12)
 | 
			
		||||
    error3 ("-z%u invalid: too small to allow for overhead\n", baggagesize);
 | 
			
		||||
    error3 ("size %"PRIu32" invalid: too small to allow for overhead\n", baggagesize);
 | 
			
		||||
  else if (baggagesize > 0)
 | 
			
		||||
    baggagesize -= 12;
 | 
			
		||||
 | 
			
		||||
  struct record_netload_state *netload_state;
 | 
			
		||||
  if (netload_bw <= 0)
 | 
			
		||||
    netload_state = NULL;
 | 
			
		||||
  else if ((netload_state = record_netload_new (netload_if, netload_bw)) == NULL)
 | 
			
		||||
    error3 ("can't get network utilization information for device %s\n", netload_if);
 | 
			
		||||
 | 
			
		||||
  ddsrt_avl_init (&ppants_td, &ppants);
 | 
			
		||||
  ddsrt_fibheap_init (&ppants_to_match_fhd, &ppants_to_match);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1735,7 +1860,7 @@ int main (int argc, char *argv[])
 | 
			
		|||
 | 
			
		||||
  qos = dds_create_qos ();
 | 
			
		||||
  /* set user data: magic cookie, whether we have a reader for the Data topic
 | 
			
		||||
     (all other endpoints always exist), and our hostname */
 | 
			
		||||
     (all other endpoints always exist), pid and hostname */
 | 
			
		||||
  {
 | 
			
		||||
    unsigned pos;
 | 
			
		||||
    char udata[256];
 | 
			
		||||
| 
						 | 
				
			
			@ -1760,6 +1885,12 @@ int main (int argc, char *argv[])
 | 
			
		|||
    error2 ("dds_create_publisher failed: %d\n", (int) dp);
 | 
			
		||||
  dds_delete_qos (qos);
 | 
			
		||||
 | 
			
		||||
  qos = dds_create_qos ();
 | 
			
		||||
  dds_qset_reliability (qos, DDS_RELIABILITY_RELIABLE, DDS_MSECS (100));
 | 
			
		||||
  if ((tp_stat = dds_create_topic (dp, &CPUStats_desc, "DDSPerfCPUStats", qos, NULL)) < 0)
 | 
			
		||||
    error2 ("dds_create_topic(%s) failed: %d\n", "DDSPerfCPUStats", (int) tp_stat);
 | 
			
		||||
  dds_delete_qos (qos);
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    const char *tp_suf = "";
 | 
			
		||||
    const dds_topic_descriptor_t *tp_desc = NULL;
 | 
			
		||||
| 
						 | 
				
			
			@ -1797,6 +1928,19 @@ int main (int argc, char *argv[])
 | 
			
		|||
  if ((rd_publications = dds_create_reader (dp, DDS_BUILTIN_TOPIC_DCPSPUBLICATION, NULL, NULL)) < 0)
 | 
			
		||||
    error2 ("dds_create_reader(publications) failed: %d\n", (int) rd_publications);
 | 
			
		||||
 | 
			
		||||
  /* stats writer always exists, reader only when we were requested to collect & print stats */
 | 
			
		||||
  qos = dds_create_qos ();
 | 
			
		||||
  dds_qset_history (qos, DDS_HISTORY_KEEP_LAST, 1);
 | 
			
		||||
  dds_qset_ignorelocal (qos, DDS_IGNORELOCAL_PARTICIPANT);
 | 
			
		||||
  if ((wr_stat = dds_create_writer (pub, tp_stat, qos, NULL)) < 0)
 | 
			
		||||
    error2 ("dds_create_writer(statistics) failed: %d\n", (int) wr_stat);
 | 
			
		||||
  if (collect_stats)
 | 
			
		||||
  {
 | 
			
		||||
    if ((rd_stat = dds_create_reader (sub, tp_stat, qos, NULL)) < 0)
 | 
			
		||||
      error2 ("dds_create_reader(statistics) failed: %d\n", (int) rd_stat);
 | 
			
		||||
  }
 | 
			
		||||
  dds_delete_qos (qos);
 | 
			
		||||
 | 
			
		||||
  /* ping reader/writer uses keep-last-1 history; not checking matching on these (yet) */
 | 
			
		||||
  qos = dds_create_qos ();
 | 
			
		||||
  dds_qset_history (qos, DDS_HISTORY_KEEP_LAST, 1);
 | 
			
		||||
| 
						 | 
				
			
			@ -1882,6 +2026,9 @@ int main (int argc, char *argv[])
 | 
			
		|||
  sigaddset (&sigset, SIGTERM);
 | 
			
		||||
  sigprocmask (SIG_BLOCK, &sigset, &osigset);
 | 
			
		||||
  ddsrt_thread_create (&sigtid, "sigthread", &attr, sigthread, &sigset);
 | 
			
		||||
#if defined __APPLE__ || defined __linux
 | 
			
		||||
  signal (SIGXFSZ, sigxfsz_handler);
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  /* Make publisher & subscriber thread arguments and start the threads we
 | 
			
		||||
| 
						 | 
				
			
			@ -1920,8 +2067,8 @@ int main (int argc, char *argv[])
 | 
			
		|||
  const bool pingpong_waitset = (ping_intv != DDS_NEVER && ignorelocal == DDS_IGNORELOCAL_NONE) || pingpongmode == SM_WAITSET;
 | 
			
		||||
  if (pingpong_waitset)
 | 
			
		||||
  {
 | 
			
		||||
    ddsrt_thread_create (&subpingtid, "sub", &attr, subpingthread_waitset, &subarg_pong);
 | 
			
		||||
    ddsrt_thread_create (&subpongtid, "sub", &attr, subpongthread_waitset, &subarg_pong);
 | 
			
		||||
    ddsrt_thread_create (&subpingtid, "ping", &attr, subpingthread_waitset, &subarg_pong);
 | 
			
		||||
    ddsrt_thread_create (&subpongtid, "pong", &attr, subpongthread_waitset, &subarg_pong);
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
| 
						 | 
				
			
			@ -1929,10 +2076,16 @@ int main (int argc, char *argv[])
 | 
			
		|||
    set_data_available_listener (rd_pong, "rd_pong", pong_available_listener, &subarg_pong);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* Have to do this after all threads have been created because it caches the list */
 | 
			
		||||
  struct record_cputime_state *cputime_state;
 | 
			
		||||
  cputime_state = record_cputime_new (wr_stat);
 | 
			
		||||
 | 
			
		||||
  /* Run until time limit reached or a signal received.  (The time calculations
 | 
			
		||||
     ignore the possibility of overflow around the year 2260.) */
 | 
			
		||||
  dds_time_t tnow = dds_time ();
 | 
			
		||||
  const dds_time_t tstart = tnow;
 | 
			
		||||
  if (tref == DDS_INFINITY)
 | 
			
		||||
    tref = tstart;
 | 
			
		||||
  dds_time_t tmatch = (maxwait == HUGE_VAL) ? DDS_NEVER : tstart + (int64_t) (maxwait * 1e9 + 0.5);
 | 
			
		||||
  const dds_time_t tstop = (dur == HUGE_VAL) ? DDS_NEVER : tstart + (int64_t) (dur * 1e9 + 0.5);
 | 
			
		||||
  dds_time_t tnext = tstart + DDS_SECS (1);
 | 
			
		||||
| 
						 | 
				
			
			@ -2003,7 +2156,7 @@ int main (int argc, char *argv[])
 | 
			
		|||
    tnow = dds_time ();
 | 
			
		||||
    if (tnext <= tnow)
 | 
			
		||||
    {
 | 
			
		||||
      print_stats (tstart, tnow, tlast);
 | 
			
		||||
      print_stats (tref, tnow, tlast, cputime_state, netload_state);
 | 
			
		||||
      tlast = tnow;
 | 
			
		||||
      if (tnow > tnext + DDS_MSECS (500))
 | 
			
		||||
        tnext = tnow + DDS_SECS (1);
 | 
			
		||||
| 
						 | 
				
			
			@ -2021,6 +2174,8 @@ int main (int argc, char *argv[])
 | 
			
		|||
      maybe_send_new_ping (tnow, &tnextping);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  record_netload_free (netload_state);
 | 
			
		||||
  record_cputime_free (cputime_state);
 | 
			
		||||
 | 
			
		||||
#if _WIN32
 | 
			
		||||
  signal_handler (SIGINT);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,3 +27,20 @@ struct KeyedSeq
 | 
			
		|||
  sequence<octet> baggage;
 | 
			
		||||
};
 | 
			
		||||
#pragma keylist KeyedSeq keyval
 | 
			
		||||
 | 
			
		||||
struct CPUStatThread
 | 
			
		||||
{
 | 
			
		||||
  string name;
 | 
			
		||||
  long u_pct;
 | 
			
		||||
  long s_pct;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct CPUStats
 | 
			
		||||
{
 | 
			
		||||
  string hostname;
 | 
			
		||||
  unsigned long pid;
 | 
			
		||||
  double maxrss;
 | 
			
		||||
  boolean some_above;
 | 
			
		||||
  sequence<CPUStatThread> cpu;
 | 
			
		||||
};
 | 
			
		||||
#pragma keylist CPUStats hostname pid
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										116
									
								
								src/tools/ddsperf/netload.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/tools/ddsperf/netload.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,116 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright(c) 2019 ADLINK Technology Limited and others
 | 
			
		||||
 *
 | 
			
		||||
 * This program and the accompanying materials are made available under the
 | 
			
		||||
 * terms of the Eclipse Public License v. 2.0 which is available at
 | 
			
		||||
 * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
 | 
			
		||||
 * v. 1.0 which is available at
 | 
			
		||||
 * http://www.eclipse.org/org/documents/edl-v10.php.
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 | 
			
		||||
 */
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include "dds/dds.h"
 | 
			
		||||
 | 
			
		||||
#include "dds/ddsrt/heap.h"
 | 
			
		||||
#include "dds/ddsrt/string.h"
 | 
			
		||||
#include "dds/ddsrt/netstat.h"
 | 
			
		||||
 | 
			
		||||
#include "netload.h"
 | 
			
		||||
 | 
			
		||||
#if DDSRT_HAVE_NETSTAT
 | 
			
		||||
 | 
			
		||||
struct record_netload_state {
 | 
			
		||||
  struct ddsrt_netstat_control *ctrl;
 | 
			
		||||
  char *name;
 | 
			
		||||
  double bw;
 | 
			
		||||
  bool errored;
 | 
			
		||||
  bool data_valid;
 | 
			
		||||
  dds_time_t tprev;
 | 
			
		||||
  uint64_t ibytes;
 | 
			
		||||
  uint64_t obytes;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void record_netload (struct record_netload_state *st, const char *prefix, dds_time_t tnow)
 | 
			
		||||
{
 | 
			
		||||
  if (st && !st->errored)
 | 
			
		||||
  {
 | 
			
		||||
    struct ddsrt_netstat x;
 | 
			
		||||
    dds_return_t ret = ddsrt_netstat_get (st->ctrl, &x);
 | 
			
		||||
    st->errored = (ret == DDS_RETCODE_ERROR);
 | 
			
		||||
    if (ret == DDS_RETCODE_OK)
 | 
			
		||||
    {
 | 
			
		||||
      if (st->data_valid)
 | 
			
		||||
      {
 | 
			
		||||
        /* interface speeds are in bits/s, so convert bytes to bits */
 | 
			
		||||
        const double dx = 8 * (double) (x.obytes - st->obytes);
 | 
			
		||||
        const double dr = 8 * (double) (x.ibytes - st->ibytes);
 | 
			
		||||
        const double dt = (double) (tnow - st->tprev) / 1e9;
 | 
			
		||||
        const double dxpct = 100.0 * dx / dt / st->bw;
 | 
			
		||||
        const double drpct = 100.0 * dr / dt / st->bw;
 | 
			
		||||
        if (dxpct >= 0.5 || drpct >= 0.5)
 | 
			
		||||
        {
 | 
			
		||||
          printf ("%s %s: xmit %.0f%% recv %.0f%% [%"PRIu64" %"PRIu64"]\n",
 | 
			
		||||
                  prefix, st->name, dxpct, drpct, x.obytes, x.ibytes);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      st->obytes = x.obytes;
 | 
			
		||||
      st->ibytes = x.ibytes;
 | 
			
		||||
      st->tprev = tnow;
 | 
			
		||||
      st->data_valid = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct record_netload_state *record_netload_new (const char *dev, double bw)
 | 
			
		||||
{
 | 
			
		||||
  struct record_netload_state *st = ddsrt_malloc (sizeof (*st));
 | 
			
		||||
  if (ddsrt_netstat_new (&st->ctrl, dev) != DDS_RETCODE_OK)
 | 
			
		||||
  {
 | 
			
		||||
    ddsrt_free (st);
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
  st->name = ddsrt_strdup (dev);
 | 
			
		||||
  st->bw = bw;
 | 
			
		||||
  st->data_valid = false;
 | 
			
		||||
  st->errored = false;
 | 
			
		||||
  record_netload (st, NULL, dds_time ());
 | 
			
		||||
  return st;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void record_netload_free (struct record_netload_state *st)
 | 
			
		||||
{
 | 
			
		||||
  if (st)
 | 
			
		||||
  {
 | 
			
		||||
    ddsrt_netstat_free (st->ctrl);
 | 
			
		||||
    ddsrt_free (st->name);
 | 
			
		||||
    ddsrt_free (st);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
void record_netload (struct record_netload_state *st, const char *prefix, dds_time_t tnow)
 | 
			
		||||
{
 | 
			
		||||
  (void) st;
 | 
			
		||||
  (void) prefix;
 | 
			
		||||
  (void ) tnow;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct record_netload_state *record_netload_new (const char *dev, double bw)
 | 
			
		||||
{
 | 
			
		||||
  (void) dev;
 | 
			
		||||
  (void) bw;
 | 
			
		||||
  return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void record_netload_free (struct record_netload_state *st)
 | 
			
		||||
{
 | 
			
		||||
  (void) st;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										23
									
								
								src/tools/ddsperf/netload.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/tools/ddsperf/netload.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright(c) 2019 ADLINK Technology Limited and others
 | 
			
		||||
 *
 | 
			
		||||
 * This program and the accompanying materials are made available under the
 | 
			
		||||
 * terms of the Eclipse Public License v. 2.0 which is available at
 | 
			
		||||
 * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
 | 
			
		||||
 * v. 1.0 which is available at
 | 
			
		||||
 * http://www.eclipse.org/org/documents/edl-v10.php.
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 | 
			
		||||
 */
 | 
			
		||||
#ifndef NETLOAD_H
 | 
			
		||||
#define NETLOAD_H
 | 
			
		||||
 | 
			
		||||
#include <dds/dds.h>
 | 
			
		||||
 | 
			
		||||
struct record_netload_state;
 | 
			
		||||
 | 
			
		||||
void record_netload (struct record_netload_state *st, const char *prefix, dds_time_t tnow);
 | 
			
		||||
struct record_netload_state *record_netload_new (const char *dev, double bw);
 | 
			
		||||
void record_netload_free (struct record_netload_state *st);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue