Add summary statistics.

Add new options to ptp4l and phc2sys to print summary statistics of the
clock instead of the individual samples.

[ RC - Fix () function prototype with (void).
     - Use unsigned for sample counter.
     - Fix over-zealous line breaks. ]

Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
Signed-off-by: Richard Cochran <richardcochran@gmail.com>
master
Miroslav Lichvar 2013-02-07 19:56:53 +01:00 committed by Richard Cochran
parent c7098364f2
commit 3136e3e46c
13 changed files with 349 additions and 22 deletions

92
clock.c
View File

@ -31,6 +31,7 @@
#include "phc.h" #include "phc.h"
#include "port.h" #include "port.h"
#include "servo.h" #include "servo.h"
#include "stats.h"
#include "print.h" #include "print.h"
#include "tlv.h" #include "tlv.h"
#include "uds.h" #include "uds.h"
@ -50,6 +51,13 @@ struct freq_estimator {
unsigned int count; unsigned int count;
}; };
struct clock_stats {
struct stats *offset;
struct stats *freq;
struct stats *delay;
int max_count;
};
struct clock { struct clock {
clockid_t clkid; clockid_t clkid;
struct servo *servo; struct servo *servo;
@ -80,6 +88,8 @@ struct clock {
tmv_t t1; tmv_t t1;
tmv_t t2; tmv_t t2;
struct clock_description desc; struct clock_description desc;
struct clock_stats stats;
int stats_interval;
}; };
struct clock the_clock; struct clock the_clock;
@ -104,6 +114,9 @@ void clock_destroy(struct clock *c)
} }
servo_destroy(c->servo); servo_destroy(c->servo);
mave_destroy(c->avg_delay); mave_destroy(c->avg_delay);
stats_destroy(c->stats.offset);
stats_destroy(c->stats.freq);
stats_destroy(c->stats.delay);
memset(c, 0, sizeof(*c)); memset(c, 0, sizeof(*c));
msg_cleanup(); msg_cleanup();
} }
@ -232,6 +245,40 @@ static int clock_master_lost(struct clock *c)
return 1; return 1;
} }
static void clock_stats_update(struct clock_stats *s,
int64_t offset, double freq)
{
struct stats_result offset_stats, freq_stats, delay_stats;
stats_add_value(s->offset, offset);
stats_add_value(s->freq, freq);
if (stats_get_num_values(s->offset) < s->max_count)
return;
stats_get_result(s->offset, &offset_stats);
stats_get_result(s->freq, &freq_stats);
/* Path delay stats are updated separately, they may be empty. */
if (!stats_get_result(s->delay, &delay_stats)) {
pr_info("rms %4.0f max %4.0f "
"freq %+6.0f +/- %3.0f "
"delay %5.0f +/- %3.0f",
offset_stats.rms, offset_stats.max_abs,
freq_stats.mean, freq_stats.stddev,
delay_stats.mean, delay_stats.stddev);
} else {
pr_info("rms %4.0f max %4.0f "
"freq %+6.0f +/- %3.0f",
offset_stats.rms, offset_stats.max_abs,
freq_stats.mean, freq_stats.stddev);
}
stats_reset(s->offset);
stats_reset(s->freq);
stats_reset(s->delay);
}
static enum servo_state clock_no_adjust(struct clock *c) static enum servo_state clock_no_adjust(struct clock *c)
{ {
double fui; double fui;
@ -278,9 +325,13 @@ static enum servo_state clock_no_adjust(struct clock *c)
tmv_dbl(tmv_sub(c->t2, f->ingress1)); tmv_dbl(tmv_sub(c->t2, f->ingress1));
freq = (1.0 - ratio) * 1e9; freq = (1.0 - ratio) * 1e9;
pr_info("master offset %10" PRId64 " s%d freq %+7.0f " if (c->stats.max_count > 1) {
"path delay %9" PRId64, clock_stats_update(&c->stats, c->master_offset, freq);
c->master_offset, state, freq, c->path_delay); } else {
pr_info("master offset %10" PRId64 " s%d freq %+7.0f "
"path delay %9" PRId64,
c->master_offset, state, freq, c->path_delay);
}
fui = 1.0 + (c->status.cumulativeScaledRateOffset + 0.0) / POW2_41; fui = 1.0 + (c->status.cumulativeScaledRateOffset + 0.0) / POW2_41;
@ -433,7 +484,7 @@ UInteger8 clock_class(struct clock *c)
struct clock *clock_create(int phc_index, struct interface *iface, int count, struct clock *clock_create(int phc_index, struct interface *iface, int count,
enum timestamp_type timestamping, struct default_ds *dds, enum timestamp_type timestamping, struct default_ds *dds,
enum servo_type servo) enum servo_type servo, int stats_interval)
{ {
int i, fadj = 0, max_adj = 0.0, sw_ts = timestamping == TS_SOFTWARE ? 1 : 0; int i, fadj = 0, max_adj = 0.0, sw_ts = timestamping == TS_SOFTWARE ? 1 : 0;
struct clock *c = &the_clock; struct clock *c = &the_clock;
@ -486,6 +537,14 @@ struct clock *clock_create(int phc_index, struct interface *iface, int count,
pr_err("Failed to create moving average"); pr_err("Failed to create moving average");
return NULL; return NULL;
} }
c->stats_interval = stats_interval;
c->stats.offset = stats_create();
c->stats.freq = stats_create();
c->stats.delay = stats_create();
if (!c->stats.offset || !c->stats.freq || !c->stats.delay) {
pr_err("failed to create stats");
return NULL;
}
c->dds = dds->dds; c->dds = dds->dds;
@ -834,12 +893,18 @@ void clock_path_delay(struct clock *c, struct timespec req, struct timestamp rx,
c->cur.meanPathDelay = tmv_to_TimeInterval(c->path_delay); c->cur.meanPathDelay = tmv_to_TimeInterval(c->path_delay);
pr_debug("path delay %10lld %10lld", c->path_delay, pd); pr_debug("path delay %10lld %10lld", c->path_delay, pd);
if (c->stats.delay)
stats_add_value(c->stats.delay, pd);
} }
void clock_peer_delay(struct clock *c, tmv_t ppd, double nrr) void clock_peer_delay(struct clock *c, tmv_t ppd, double nrr)
{ {
c->path_delay = ppd; c->path_delay = ppd;
c->nrr = nrr; c->nrr = nrr;
if (c->stats.delay)
stats_add_value(c->stats.delay, ppd);
} }
void clock_remove_fda(struct clock *c, struct port *p, struct fdarray fda) void clock_remove_fda(struct clock *c, struct port *p, struct fdarray fda)
@ -903,9 +968,13 @@ enum servo_state clock_synchronize(struct clock *c,
adj = servo_sample(c->servo, c->master_offset, ingress, &state); adj = servo_sample(c->servo, c->master_offset, ingress, &state);
pr_info("master offset %10" PRId64 " s%d freq %+7.0f " if (c->stats.max_count > 1) {
"path delay %9" PRId64, clock_stats_update(&c->stats, c->master_offset, adj);
c->master_offset, state, adj, c->path_delay); } else {
pr_info("master offset %10" PRId64 " s%d freq %+7.0f "
"path delay %9" PRId64,
c->master_offset, state, adj, c->path_delay);
}
switch (state) { switch (state) {
case SERVO_UNLOCKED: case SERVO_UNLOCKED:
@ -925,12 +994,17 @@ enum servo_state clock_synchronize(struct clock *c,
void clock_sync_interval(struct clock *c, int n) void clock_sync_interval(struct clock *c, int n)
{ {
int shift = c->freq_est_interval - n; int shift;
shift = c->freq_est_interval - n;
if (shift < 0) if (shift < 0)
shift = 0; shift = 0;
c->fest.max_count = (1 << shift); c->fest.max_count = (1 << shift);
shift = c->stats_interval - n;
if (shift < 0)
shift = 0;
c->stats.max_count = (1 << shift);
} }
struct timePropertiesDS *clock_time_properties(struct clock *c) struct timePropertiesDS *clock_time_properties(struct clock *c)

View File

@ -67,11 +67,12 @@ UInteger8 clock_class(struct clock *c);
* @param timestamping The timestamping mode for this clock. * @param timestamping The timestamping mode for this clock.
* @param dds A pointer to a default data set for the clock. * @param dds A pointer to a default data set for the clock.
* @param servo The servo that this clock will use. * @param servo The servo that this clock will use.
* @param stats_interval Interval in which are printed clock statistics.
* @return A pointer to the single global clock instance. * @return A pointer to the single global clock instance.
*/ */
struct clock *clock_create(int phc_index, struct interface *iface, int count, struct clock *clock_create(int phc_index, struct interface *iface, int count,
enum timestamp_type timestamping, struct default_ds *dds, enum timestamp_type timestamping, struct default_ds *dds,
enum servo_type servo); enum servo_type servo, int stats_interval);
/** /**
* Obtains a clock's default data set. * Obtains a clock's default data set.

View File

@ -378,6 +378,11 @@ static enum parser_result parse_global_setting(const char *option,
for (i = 0; i < OUI_LEN; i++) for (i = 0; i < OUI_LEN; i++)
cfg->dds.clock_desc.manufacturerIdentity[i] = oui[i]; cfg->dds.clock_desc.manufacturerIdentity[i] = oui[i];
} else if (!strcmp(option, "summary_interval")) {
if (1 != sscanf(value, "%d", &val))
return BAD_VALUE;
cfg->summary_interval = val;
} else } else
return NOT_PARSED; return NOT_PARSED;

View File

@ -76,6 +76,7 @@ struct config {
int print_level; int print_level;
int use_syslog; int use_syslog;
int verbose; int verbose;
int summary_interval;
}; };
int config_read(char *name, struct config *cfg); int config_read(char *name, struct config *cfg);

View File

@ -32,6 +32,7 @@ follow_up_info 0
tx_timestamp_retries 100 tx_timestamp_retries 100
use_syslog 1 use_syslog 1
verbose 0 verbose 0
summary_interval 0
# #
# Servo Options # Servo Options
# #

View File

@ -31,6 +31,7 @@ follow_up_info 1
tx_timestamp_retries 100 tx_timestamp_retries 100
use_syslog 1 use_syslog 1
verbose 0 verbose 0
summary_interval 0
# #
# Servo options # Servo options
# #

View File

@ -30,8 +30,8 @@ CFLAGS = -Wall $(VER) $(INC) $(DEBUG) $(FEAT_CFLAGS) $(EXTRA_CFLAGS)
LDLIBS = -lm -lrt $(EXTRA_LDFLAGS) LDLIBS = -lm -lrt $(EXTRA_LDFLAGS)
PRG = ptp4l pmc phc2sys hwstamp_ctl PRG = ptp4l pmc phc2sys hwstamp_ctl
OBJ = bmc.o clock.o config.o fsm.o ptp4l.o mave.o msg.o phc.o pi.o port.o \ OBJ = bmc.o clock.o config.o fsm.o ptp4l.o mave.o msg.o phc.o pi.o port.o \
print.o raw.o servo.o sk.o tlv.o tmtab.o transport.o udp.o udp6.o uds.o util.o \ print.o raw.o servo.o sk.o stats.o tlv.o tmtab.o transport.o udp.o udp6.o \
version.o uds.o util.o version.o
OBJECTS = $(OBJ) hwstamp_ctl.o phc2sys.o pmc.o pmc_common.o sysoff.o OBJECTS = $(OBJ) hwstamp_ctl.o phc2sys.o pmc.o pmc_common.o sysoff.o
SRC = $(OBJECTS:.o=.c) SRC = $(OBJECTS:.o=.c)
@ -52,8 +52,8 @@ ptp4l: $(OBJ)
pmc: msg.o pmc.o pmc_common.o print.o raw.o sk.o tlv.o transport.o udp.o \ pmc: msg.o pmc.o pmc_common.o print.o raw.o sk.o tlv.o transport.o udp.o \
udp6.o uds.o util.o version.o udp6.o uds.o util.o version.o
phc2sys: msg.o phc2sys.o pmc_common.o print.o pi.o servo.o raw.o sk.o sysoff.o \ phc2sys: msg.o phc2sys.o pmc_common.o print.o pi.o servo.o raw.o sk.o stats.o \
tlv.o transport.o udp.o udp6.o uds.o util.o version.o sysoff.o tlv.o transport.o udp.o udp6.o uds.o util.o version.o
hwstamp_ctl: hwstamp_ctl.o version.o hwstamp_ctl: hwstamp_ctl.o version.o

View File

@ -27,6 +27,8 @@ phc2sys \- synchronize two clocks
] [ ] [
.BI \-O " offset" .BI \-O " offset"
] [ ] [
.BI \-u " summary-updates"
] [
.B \-w .B \-w
] [ ] [
.BI \-l " print-level" .BI \-l " print-level"
@ -112,6 +114,15 @@ Without
.B \-w .B \-w
the default is 0. the default is 0.
.TP .TP
.BI \-u " summary-updates"
Specify the number of clock updates included in summary statistics. The
statistics include offset root mean square (RMS), maximum absolute offset,
frequency offset mean and standard deviation, and mean of the delay in clock
readings and standard deviation. The units are nanoseconds and parts per
billion (ppb). If zero, the individual samples are printed instead of the
statistics. The messages are printed at the LOG_INFO level.
The default is 0 (disabled).
.TP
.B \-w .B \-w
Wait until ptp4l is in a synchronized state. Wait until ptp4l is in a synchronized state.
.TP .TP

View File

@ -42,6 +42,7 @@
#include "print.h" #include "print.h"
#include "servo.h" #include "servo.h"
#include "sk.h" #include "sk.h"
#include "stats.h"
#include "sysoff.h" #include "sysoff.h"
#include "tlv.h" #include "tlv.h"
#include "version.h" #include "version.h"
@ -155,8 +156,47 @@ struct clock {
clockid_t clkid; clockid_t clkid;
struct servo *servo; struct servo *servo;
const char *source_label; const char *source_label;
struct stats *offset_stats;
struct stats *freq_stats;
struct stats *delay_stats;
int stats_max_count;
}; };
static void update_clock_stats(struct clock *clock,
int64_t offset, double freq, int64_t delay)
{
struct stats_result offset_stats, freq_stats, delay_stats;
stats_add_value(clock->offset_stats, offset);
stats_add_value(clock->freq_stats, freq);
if (delay >= 0)
stats_add_value(clock->delay_stats, delay);
if (stats_get_num_values(clock->offset_stats) < clock->stats_max_count)
return;
stats_get_result(clock->offset_stats, &offset_stats);
stats_get_result(clock->freq_stats, &freq_stats);
if (!stats_get_result(clock->delay_stats, &delay_stats)) {
pr_info("rms %4.0f max %4.0f "
"freq %+6.0f +/- %3.0f "
"delay %5.0f +/- %3.0f",
offset_stats.rms, offset_stats.max_abs,
freq_stats.mean, freq_stats.stddev,
delay_stats.mean, delay_stats.stddev);
} else {
pr_info("rms %4.0f max %4.0f "
"freq %+6.0f +/- %3.0f",
offset_stats.rms, offset_stats.max_abs,
freq_stats.mean, freq_stats.stddev);
}
stats_reset(clock->offset_stats);
stats_reset(clock->freq_stats);
stats_reset(clock->delay_stats);
}
static void update_clock(struct clock *clock, static void update_clock(struct clock *clock,
int64_t offset, uint64_t ts, int64_t delay) int64_t offset, uint64_t ts, int64_t delay)
{ {
@ -176,13 +216,17 @@ static void update_clock(struct clock *clock,
break; break;
} }
if (delay >= 0) { if (clock->offset_stats) {
pr_info("%s offset %9" PRId64 " s%d freq %+7.0f " update_clock_stats(clock, offset, ppb, delay);
"delay %6" PRId64,
clock->source_label, offset, state, ppb, delay);
} else { } else {
pr_info("%s offset %9" PRId64 " s%d freq %+7.0f", if (delay >= 0) {
clock->source_label, offset, state, ppb); pr_info("%s offset %9" PRId64 " s%d freq %+7.0f "
"delay %6" PRId64,
clock->source_label, offset, state, ppb, delay);
} else {
pr_info("%s offset %9" PRId64 " s%d freq %+7.0f",
clock->source_label, offset, state, ppb);
}
} }
} }
@ -423,6 +467,7 @@ static void usage(char *progname)
" -R [rate] slave clock update rate in HZ (1)\n" " -R [rate] slave clock update rate in HZ (1)\n"
" -N [num] number of master clock readings per update (5)\n" " -N [num] number of master clock readings per update (5)\n"
" -O [offset] slave-master time offset (0)\n" " -O [offset] slave-master time offset (0)\n"
" -u [num] number of clock updates in summary stats (0)\n"
" -w wait for ptp4l\n" " -w wait for ptp4l\n"
" -h prints this message and exits\n" " -h prints this message and exits\n"
" -v prints the software version and exits\n" " -v prints the software version and exits\n"
@ -446,7 +491,8 @@ int main(int argc, char *argv[])
/* Process the command line arguments. */ /* Process the command line arguments. */
progname = strrchr(argv[0], '/'); progname = strrchr(argv[0], '/');
progname = progname ? 1+progname : argv[0]; progname = progname ? 1+progname : argv[0];
while (EOF != (c = getopt(argc, argv, "c:d:hs:P:I:S:R:N:O:i:wl:mqv"))) { while (EOF != (c = getopt(argc, argv,
"c:d:hs:P:I:S:R:N:O:i:u:wl:mqv"))) {
switch (c) { switch (c) {
case 'c': case 'c':
dst_clock.clkid = clock_open(optarg); dst_clock.clkid = clock_open(optarg);
@ -484,6 +530,9 @@ int main(int argc, char *argv[])
case 'i': case 'i':
ethdev = optarg; ethdev = optarg;
break; break;
case 'u':
dst_clock.stats_max_count = atoi(optarg);
break;
case 'w': case 'w':
wait_sync = 1; wait_sync = 1;
break; break;
@ -529,6 +578,18 @@ int main(int argc, char *argv[])
return -1; return -1;
} }
if (dst_clock.stats_max_count > 0) {
dst_clock.offset_stats = stats_create();
dst_clock.freq_stats = stats_create();
dst_clock.delay_stats = stats_create();
if (!dst_clock.offset_stats ||
!dst_clock.freq_stats ||
!dst_clock.delay_stats) {
fprintf(stderr, "failed to create stats");
return -1;
}
}
print_set_progname(progname); print_set_progname(progname);
print_set_verbose(verbose); print_set_verbose(verbose);
print_set_syslog(use_syslog); print_set_syslog(use_syslog);

10
ptp4l.8
View File

@ -307,6 +307,16 @@ The default is 0 (disabled).
Print messages to the system log if enabled. Print messages to the system log if enabled.
The default is 1 (enabled). The default is 1 (enabled).
.TP .TP
.B summary_interval
The time interval in which are printed summary statistics of the clock. It is
specified as a power of two in seconds. The statistics include offset root mean
square (RMS), maximum absolute offset, frequency offset mean and standard
deviation, and path delay mean and standard deviation. The units are
nanoseconds and parts per billion (ppb). If there is only one clock update in
the interval, the sample will be printed instead of the statistics. The
messages are printed at the LOG_INFO level.
The default is 0 (1 second).
.TP
.B time_stamping .B time_stamping
The time stamping method. The allowed values are hardware, software and legacy. The time stamping method. The allowed values are hardware, software and legacy.
The default is hardware. The default is hardware.

View File

@ -99,6 +99,7 @@ static struct config cfg_settings = {
.print_level = LOG_INFO, .print_level = LOG_INFO,
.use_syslog = 1, .use_syslog = 1,
.verbose = 0, .verbose = 0,
.summary_interval = 0,
.cfg_ignore = 0, .cfg_ignore = 0,
}; };
@ -351,7 +352,8 @@ int main(int argc, char *argv[])
clock = clock_create(phc_index, iface, cfg_settings.nports, clock = clock_create(phc_index, iface, cfg_settings.nports,
*timestamping, &cfg_settings.dds, *timestamping, &cfg_settings.dds,
cfg_settings.clock_servo); cfg_settings.clock_servo,
cfg_settings.summary_interval);
if (!clock) { if (!clock) {
fprintf(stderr, "failed to create a clock\n"); fprintf(stderr, "failed to create a clock\n");
return -1; return -1;

85
stats.c 100644
View File

@ -0,0 +1,85 @@
/**
* @file stats.c
* @note Copyright (C) 2013 Miroslav Lichvar <mlichvar@redhat.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "stats.h"
struct stats {
unsigned int num;
double min;
double max;
double mean;
double sum_sqr;
double sum_diff_sqr;
};
struct stats *stats_create(void)
{
struct stats *stats;
stats = calloc(1, sizeof *stats);
return stats;
}
void stats_destroy(struct stats *stats)
{
free(stats);
}
void stats_add_value(struct stats *stats, double value)
{
double old_mean = stats->mean;
if (!stats->num || stats->max < value)
stats->max = value;
if (!stats->num || stats->min > value)
stats->min = value;
stats->num++;
stats->mean = old_mean + (value - old_mean) / stats->num;
stats->sum_sqr += value * value;
stats->sum_diff_sqr += (value - old_mean) * (value - stats->mean);
}
unsigned int stats_get_num_values(struct stats *stats)
{
return stats->num;
}
int stats_get_result(struct stats *stats, struct stats_result *result)
{
if (!stats->num)
return -1;
result->min = stats->min;
result->max = stats->max;
result->max_abs = stats->max > -stats->min ? stats->max : -stats->min;
result->mean = stats->mean;
result->rms = sqrt(stats->sum_sqr / stats->num);
result->stddev = sqrt(stats->sum_diff_sqr / stats->num);
return 0;
}
void stats_reset(struct stats *stats)
{
memset(stats, 0, sizeof *stats);
}

75
stats.h 100644
View File

@ -0,0 +1,75 @@
/**
* @file stats.h
* @brief Implements various statistics.
* @note Copyright (C) 2013 Miroslav Lichvar <mlichvar@redhat.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef HAVE_STATS_H
#define HAVE_STATS_H
/** Opaque type */
struct stats;
/**
* Create a new instance of statistics.
* @return A pointer to a new stats on success, NULL otherwise.
*/
struct stats *stats_create(void);
/**
* Destroy an instance of stats.
* @param servo Pointer to stats obtained via @ref stats_create().
*/
void stats_destroy(struct stats *stats);
/**
* Add a new value to the stats.
* @param stats Pointer to stats obtained via @ref stats_create().
* @param value The measured value.
*/
void stats_add_value(struct stats *stats, double value);
/**
* Get the number of values collected in the stats so far.
* @param stats Pointer to stats obtained via @ref stats_create().
* @return The number of values.
*/
unsigned int stats_get_num_values(struct stats *stats);
struct stats_result {
double min;
double max;
double max_abs;
double mean;
double rms;
double stddev;
};
/**
* Obtain the results of the calculated statistics.
* @param stats Pointer to stats obtained via @ref stats_create().
* @param stats_result Pointer to stats_result to store the results.
* @return Zero on success, non-zero if no values were added.
*/
int stats_get_result(struct stats *stats, struct stats_result *result);
/**
* Reset all statistics.
* @param stats Pointer to stats obtained via @ref stats_create().
*/
void stats_reset(struct stats *stats);
#endif