summaryrefslogtreecommitdiffstats
path: root/manual.c
diff options
context:
space:
mode:
Diffstat (limited to 'manual.c')
-rw-r--r--manual.c331
1 files changed, 331 insertions, 0 deletions
diff --git a/manual.c b/manual.c
new file mode 100644
index 0000000..bf52d14
--- /dev/null
+++ b/manual.c
@@ -0,0 +1,331 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Routines for implementing manual input of real time.
+
+ The daemon accepts manual time input over the control connection,
+ and adjusts the system time to match. Besides this, though, it can
+ determine the average rate of time loss or gain of the local system
+ and adjust the frequency accordingly.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "manual.h"
+#include "logging.h"
+#include "local.h"
+#include "conf.h"
+#include "util.h"
+#include "ntp.h"
+#include "reference.h"
+#include "regress.h"
+
+static int enabled = 0;
+
+/* More recent samples at highest indices */
+typedef struct {
+ struct timespec when; /* This is our 'cooked' time */
+ double orig_offset; /*+ Not modified by slew samples */
+ double offset; /*+ if we are fast of the supplied reference */
+ double residual; /*+ regression residual (sign convention given by
+ (measured-predicted)) */
+} Sample;
+
+#define MIN_SAMPLE_SEPARATION 1.0
+
+#define MAX_SAMPLES 16
+
+static Sample samples[16];
+static int n_samples;
+
+/* ================================================== */
+
+static void
+slew_samples(struct timespec *raw,
+ struct timespec *cooked,
+ double dfreq,
+ double doffset,
+ LCL_ChangeType change_type,
+ void *not_used);
+
+/* ================================================== */
+
+void
+MNL_Initialise(void)
+{
+ if (CNF_GetManualEnabled()) {
+ enabled = 1;
+ } else {
+ enabled = 0;
+ }
+
+ n_samples = 0;
+
+ LCL_AddParameterChangeHandler(slew_samples, NULL);
+}
+
+/* ================================================== */
+
+void
+MNL_Finalise(void)
+{
+}
+
+/* ================================================== */
+
+static void
+estimate_and_set_system(struct timespec *now, int offset_provided, double offset,
+ double *reg_offset, double *dfreq_ppm, double *new_afreq_ppm)
+{
+ double agos[MAX_SAMPLES], offsets[MAX_SAMPLES];
+ double b0, b1;
+ int n_runs, best_start; /* Unused results from regression analyser */
+ int i;
+ double freq = 0.0;
+ double skew = 0.099999999; /* All 9's when printed to log file */
+ int found_freq;
+ double slew_by;
+
+ b0 = offset_provided ? offset : 0.0;
+ b1 = freq = 0.0;
+ found_freq = 0;
+
+ if (n_samples > 1) {
+ for (i=0; i<n_samples; i++) {
+ agos[i] = UTI_DiffTimespecsToDouble(&samples[n_samples - 1].when, &samples[i].when);
+ offsets[i] = samples[i].offset;
+ }
+
+ if (RGR_FindBestRobustRegression(agos, offsets, n_samples, 1.0e-8,
+ &b0, &b1, &n_runs, &best_start)) {
+ /* Ignore b0 from regression; treat offset as being the most
+ recently entered value. (If the administrator knows he's put
+ an outlier in, he will rerun the settime operation.) However,
+ the frequency estimate comes from the regression. */
+ freq = -b1;
+ found_freq = 1;
+ }
+ } else {
+ agos[0] = 0.0;
+ offsets[0] = b0;
+ }
+
+ if (offset_provided) {
+ slew_by = offset;
+ } else {
+ slew_by = b0;
+ }
+
+ if (found_freq) {
+ LOG(LOGS_INFO, "Making a frequency change of %.3f ppm and a slew of %.6f",
+ 1.0e6 * freq, slew_by);
+
+ REF_SetManualReference(now,
+ slew_by,
+ freq, skew);
+ } else {
+ LOG(LOGS_INFO, "Making a slew of %.6f", slew_by);
+ REF_SetManualReference(now,
+ slew_by,
+ 0.0, skew);
+ }
+
+ if (reg_offset) *reg_offset = b0;
+ if (dfreq_ppm) *dfreq_ppm = 1.0e6 * freq;
+ if (new_afreq_ppm) *new_afreq_ppm = LCL_ReadAbsoluteFrequency();
+
+ /* Calculate residuals to store them */
+ for (i=0; i<n_samples; i++) {
+ samples[i].residual = offsets[i] - (b0 + agos[i] * b1);
+ }
+
+}
+
+/* ================================================== */
+
+int
+MNL_AcceptTimestamp(struct timespec *ts, double *reg_offset, double *dfreq_ppm, double *new_afreq_ppm)
+{
+ struct timespec now;
+ double offset, diff;
+ int i;
+
+ if (enabled) {
+ LCL_ReadCookedTime(&now, NULL);
+
+ /* Make sure the provided timestamp is sane and the sample
+ is not too close to the last one */
+
+ if (!UTI_IsTimeOffsetSane(ts, 0.0))
+ return 0;
+
+ if (n_samples) {
+ diff = UTI_DiffTimespecsToDouble(&now, &samples[n_samples - 1].when);
+ if (diff < MIN_SAMPLE_SEPARATION)
+ return 0;
+ }
+
+ offset = UTI_DiffTimespecsToDouble(&now, ts);
+
+ /* Check if buffer full up */
+ if (n_samples == MAX_SAMPLES) {
+ /* Shift samples down */
+ for (i=1; i<n_samples; i++) {
+ samples[i-1] = samples[i];
+ }
+ --n_samples;
+ }
+
+ samples[n_samples].when = now;
+ samples[n_samples].offset = offset;
+ samples[n_samples].orig_offset = offset;
+ ++n_samples;
+
+ estimate_and_set_system(&now, 1, offset, reg_offset, dfreq_ppm, new_afreq_ppm);
+
+ return 1;
+
+ } else {
+
+ return 0;
+
+ }
+}
+
+/* ================================================== */
+
+static void
+slew_samples(struct timespec *raw,
+ struct timespec *cooked,
+ double dfreq,
+ double doffset,
+ LCL_ChangeType change_type,
+ void *not_used)
+{
+ double delta_time;
+ int i;
+
+ if (change_type == LCL_ChangeUnknownStep) {
+ MNL_Reset();
+ }
+
+ for (i=0; i<n_samples; i++) {
+ UTI_AdjustTimespec(&samples[i].when, cooked, &samples[i].when, &delta_time,
+ dfreq, doffset);
+ samples[i].offset += delta_time;
+ }
+}
+
+/* ================================================== */
+
+void
+MNL_Enable(void)
+{
+ enabled = 1;
+}
+
+
+/* ================================================== */
+
+void
+MNL_Disable(void)
+{
+ enabled = 0;
+}
+
+/* ================================================== */
+
+void
+MNL_Reset(void)
+{
+ n_samples = 0;
+}
+
+/* ================================================== */
+
+int
+MNL_IsEnabled(void)
+{
+ return enabled;
+}
+
+/* ================================================== */
+/* Generate report data for the REQ_MANUAL_LIST command/monitoring
+ protocol */
+
+void
+MNL_ReportSamples(RPT_ManualSamplesReport *report, int max, int *n)
+{
+ int i;
+
+ if (n_samples > max) {
+ *n = max;
+ } else {
+ *n = n_samples;
+ }
+
+ for (i=0; i<n_samples && i<max; i++) {
+ report[i].when = samples[i].when;
+ report[i].slewed_offset = samples[i].offset;
+ report[i].orig_offset = samples[i].orig_offset;
+ report[i].residual = samples[i].residual;
+ }
+}
+
+/* ================================================== */
+/* Delete a sample if it's within range, re-estimate the error and
+ drift and apply it to the system clock. */
+
+int
+MNL_DeleteSample(int index)
+{
+ int i;
+ struct timespec now;
+
+ if ((index < 0) || (index >= n_samples)) {
+ return 0;
+ }
+
+ /* Crunch the samples down onto the one being deleted */
+
+ for (i=index; i<(n_samples-1); i++) {
+ samples[i] = samples[i+1];
+ }
+
+ n_samples -= 1;
+
+ /* Now re-estimate. NULLs because we don't want the parameters back
+ in this case. */
+ LCL_ReadCookedTime(&now, NULL);
+ estimate_and_set_system(&now, 0, 0.0, NULL, NULL, NULL);
+
+ return 1;
+
+}
+
+/* ================================================== */
+
+