250 lines
7.2 KiB
C
250 lines
7.2 KiB
C
/*
|
|
* 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_one (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_one (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
|