diff options
Diffstat (limited to '')
-rw-r--r-- | manual.c | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/manual.c b/manual.c new file mode 100644 index 0000000..98a3aa2 --- /dev/null +++ b/manual.c @@ -0,0 +1,332 @@ +/* + 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) +{ + LCL_RemoveParameterChangeHandler(slew_samples, NULL); +} + +/* ================================================== */ + +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; + +} + +/* ================================================== */ + + |