diff --git a/clock.c b/clock.c index 4a714eb..9b3283f 100644 --- a/clock.c +++ b/clock.c @@ -31,6 +31,7 @@ #include "phc.h" #include "port.h" #include "servo.h" +#include "stats.h" #include "print.h" #include "tlv.h" #include "uds.h" @@ -50,6 +51,13 @@ struct freq_estimator { unsigned int count; }; +struct clock_stats { + struct stats *offset; + struct stats *freq; + struct stats *delay; + int max_count; +}; + struct clock { clockid_t clkid; struct servo *servo; @@ -80,6 +88,8 @@ struct clock { tmv_t t1; tmv_t t2; struct clock_description desc; + struct clock_stats stats; + int stats_interval; }; struct clock the_clock; @@ -104,6 +114,9 @@ void clock_destroy(struct clock *c) } servo_destroy(c->servo); 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)); msg_cleanup(); } @@ -232,6 +245,40 @@ static int clock_master_lost(struct clock *c) 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) { double fui; @@ -278,9 +325,13 @@ static enum servo_state clock_no_adjust(struct clock *c) tmv_dbl(tmv_sub(c->t2, f->ingress1)); freq = (1.0 - ratio) * 1e9; - pr_info("master offset %10" PRId64 " s%d freq %+7.0f " - "path delay %9" PRId64, - c->master_offset, state, freq, c->path_delay); + if (c->stats.max_count > 1) { + clock_stats_update(&c->stats, c->master_offset, freq); + } 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; @@ -433,7 +484,7 @@ UInteger8 clock_class(struct clock *c) struct clock *clock_create(int phc_index, struct interface *iface, int count, 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; 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"); 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; @@ -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); 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) { c->path_delay = ppd; 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) @@ -903,9 +968,13 @@ enum servo_state clock_synchronize(struct clock *c, adj = servo_sample(c->servo, c->master_offset, ingress, &state); - pr_info("master offset %10" PRId64 " s%d freq %+7.0f " - "path delay %9" PRId64, - c->master_offset, state, adj, c->path_delay); + if (c->stats.max_count > 1) { + clock_stats_update(&c->stats, c->master_offset, adj); + } 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) { case SERVO_UNLOCKED: @@ -925,12 +994,17 @@ enum servo_state clock_synchronize(struct clock *c, 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) shift = 0; - 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) diff --git a/clock.h b/clock.h index 581fa14..6bc98cd 100644 --- a/clock.h +++ b/clock.h @@ -67,11 +67,12 @@ UInteger8 clock_class(struct clock *c); * @param timestamping The timestamping mode for this clock. * @param dds A pointer to a default data set for the clock. * @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. */ struct clock *clock_create(int phc_index, struct interface *iface, int count, 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. diff --git a/config.c b/config.c index e4b9c22..d0bbd63 100644 --- a/config.c +++ b/config.c @@ -378,6 +378,11 @@ static enum parser_result parse_global_setting(const char *option, for (i = 0; i < OUI_LEN; 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 return NOT_PARSED; diff --git a/config.h b/config.h index 497d683..3af193c 100644 --- a/config.h +++ b/config.h @@ -76,6 +76,7 @@ struct config { int print_level; int use_syslog; int verbose; + int summary_interval; }; int config_read(char *name, struct config *cfg); diff --git a/default.cfg b/default.cfg index 10401ac..7b3e2f7 100644 --- a/default.cfg +++ b/default.cfg @@ -32,6 +32,7 @@ follow_up_info 0 tx_timestamp_retries 100 use_syslog 1 verbose 0 +summary_interval 0 # # Servo Options # diff --git a/gPTP.cfg b/gPTP.cfg index 3c47e28..ecd5f71 100644 --- a/gPTP.cfg +++ b/gPTP.cfg @@ -31,6 +31,7 @@ follow_up_info 1 tx_timestamp_retries 100 use_syslog 1 verbose 0 +summary_interval 0 # # Servo options # diff --git a/makefile b/makefile index 9f2b587..671de48 100644 --- a/makefile +++ b/makefile @@ -30,8 +30,8 @@ CFLAGS = -Wall $(VER) $(INC) $(DEBUG) $(FEAT_CFLAGS) $(EXTRA_CFLAGS) LDLIBS = -lm -lrt $(EXTRA_LDFLAGS) 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 \ - print.o raw.o servo.o sk.o tlv.o tmtab.o transport.o udp.o udp6.o uds.o util.o \ - version.o + print.o raw.o servo.o sk.o stats.o tlv.o tmtab.o transport.o udp.o udp6.o \ + uds.o util.o version.o OBJECTS = $(OBJ) hwstamp_ctl.o phc2sys.o pmc.o pmc_common.o sysoff.o 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 \ 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 \ - tlv.o transport.o udp.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 stats.o \ + sysoff.o tlv.o transport.o udp.o udp6.o uds.o util.o version.o hwstamp_ctl: hwstamp_ctl.o version.o diff --git a/phc2sys.8 b/phc2sys.8 index 578d4ab..0542ba7 100644 --- a/phc2sys.8 +++ b/phc2sys.8 @@ -27,6 +27,8 @@ phc2sys \- synchronize two clocks ] [ .BI \-O " offset" ] [ +.BI \-u " summary-updates" +] [ .B \-w ] [ .BI \-l " print-level" @@ -112,6 +114,15 @@ Without .B \-w the default is 0. .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 Wait until ptp4l is in a synchronized state. .TP diff --git a/phc2sys.c b/phc2sys.c index 2b2f42a..479bb83 100644 --- a/phc2sys.c +++ b/phc2sys.c @@ -42,6 +42,7 @@ #include "print.h" #include "servo.h" #include "sk.h" +#include "stats.h" #include "sysoff.h" #include "tlv.h" #include "version.h" @@ -155,8 +156,47 @@ struct clock { clockid_t clkid; struct servo *servo; 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, int64_t offset, uint64_t ts, int64_t delay) { @@ -176,13 +216,17 @@ static void update_clock(struct clock *clock, break; } - if (delay >= 0) { - pr_info("%s offset %9" PRId64 " s%d freq %+7.0f " - "delay %6" PRId64, - clock->source_label, offset, state, ppb, delay); + if (clock->offset_stats) { + update_clock_stats(clock, offset, ppb, delay); } else { - pr_info("%s offset %9" PRId64 " s%d freq %+7.0f", - clock->source_label, offset, state, ppb); + if (delay >= 0) { + 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" " -N [num] number of master clock readings per update (5)\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" " -h prints this message 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. */ progname = strrchr(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) { case 'c': dst_clock.clkid = clock_open(optarg); @@ -484,6 +530,9 @@ int main(int argc, char *argv[]) case 'i': ethdev = optarg; break; + case 'u': + dst_clock.stats_max_count = atoi(optarg); + break; case 'w': wait_sync = 1; break; @@ -529,6 +578,18 @@ int main(int argc, char *argv[]) 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_verbose(verbose); print_set_syslog(use_syslog); diff --git a/ptp4l.8 b/ptp4l.8 index 370b7cf..3ee222d 100644 --- a/ptp4l.8 +++ b/ptp4l.8 @@ -307,6 +307,16 @@ The default is 0 (disabled). Print messages to the system log if enabled. The default is 1 (enabled). .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 The time stamping method. The allowed values are hardware, software and legacy. The default is hardware. diff --git a/ptp4l.c b/ptp4l.c index 3da3388..2a8eeb0 100644 --- a/ptp4l.c +++ b/ptp4l.c @@ -99,6 +99,7 @@ static struct config cfg_settings = { .print_level = LOG_INFO, .use_syslog = 1, .verbose = 0, + .summary_interval = 0, .cfg_ignore = 0, }; @@ -351,7 +352,8 @@ int main(int argc, char *argv[]) clock = clock_create(phc_index, iface, cfg_settings.nports, *timestamping, &cfg_settings.dds, - cfg_settings.clock_servo); + cfg_settings.clock_servo, + cfg_settings.summary_interval); if (!clock) { fprintf(stderr, "failed to create a clock\n"); return -1; diff --git a/stats.c b/stats.c new file mode 100644 index 0000000..41136f0 --- /dev/null +++ b/stats.c @@ -0,0 +1,85 @@ +/** + * @file stats.c + * @note Copyright (C) 2013 Miroslav Lichvar + * + * 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 +#include +#include + +#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); +} diff --git a/stats.h b/stats.h new file mode 100644 index 0000000..541a376 --- /dev/null +++ b/stats.h @@ -0,0 +1,75 @@ +/** + * @file stats.h + * @brief Implements various statistics. + * @note Copyright (C) 2013 Miroslav Lichvar + * + * 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