#!/usr/bin/awk -f

# There are more comments below the following BEGIN block.

BEGIN {
	# This value is in seconds.
	# It must match the -i interval used with ntpclient if not
	# the default of 10 minutes.
	td = 10 * 60;

	# How many milliseconds may a sample be off by (system time) and
	# still be counted?
	# This check (with the above td) is here to tell you if you have
	# several ntpclient processes logging to the same file.
	tderr = 100;

	# How many ppm may one sample's computed frequency differ from the
	# previous one and still be counted?
	# This check filters out records resulting from an NTP server glitch.
	freq_diff = 5;

	# How many milliseconds may it take to get an answer from the
	# ntp server?
	# Long replies often produce glitchy results.
	ntp_delay = 100;
}

# This script is to determine what your /etc/ntp.adj should be
# It is invoked thusly:
#     awk -f rate2.awk < /var/lib/ntp.log
# Unlike the original rate.awk script supplied with ntpclient,
# this one can be run against the log of a running ntpclient which
# is adjusting the adjtimex frequency.  The alternative is a drag
# because it means you cannot be keeping time while you are determining
# a suitable adjtimex frequency.  You, of course, can do that with
# this program too; you just don't have to.  Given a file that works
# with the original rate.awk, this script should produce similar
# results.  You may have to increase the above thresholds to keep
# records from being ignored so you get the same answers.

# It is assumed that nothing besides ntpclient is adjusting that
# value for the duration of the creation of the log file.

# It is recommended that you collect lots of log entries.
# It is further recommended that you collect logs for roughly an
# integer number of days to even out daily cycles (temperature, etc.)


# No tweaking necessary below here, I hope.

function pretty_time(ss, dd, hh, mm, ret, f) {
	ss += 0;
	if(ss < 0) {
		ret = "-";
		ss = -ss;
	} else {
		ret = "";
	}
	mm = int(ss / 60); ss -= (mm * 60);
	hh = int(mm / 60); mm -= (hh * 60);
	dd = int(hh / 24); hh -= (dd * 24);

	if(dd != 0) {
		ret = sprintf("%s%d+", ret, dd);
	}
	if(ret != "" || hh != 0) {
		ret = sprintf("%s%02d:", ret, hh);
	}
	if(ret != "" || mm != 0) {
		ret = sprintf("%s%02d:", ret, mm);
	}
	ss = sprintf("%06.3f", ss); sub(/0+$/, "", ss); sub(/\.$/, "", ss);
	if(ret != "" || (ss + 0) != 0) {
		ret = sprintf("%s%s", ret, ss);
	} else {
		ret = "0";
	}
	return ret;
}

function abs(a) {
	a += 0;
	if(a < 0) {
		return -a;
	}
	else {
		return a;
	}
}

function round(a, p) {
	p += 0;
	if(p == 0) {
		return int(a + 0.5);
	} else {
		return int((a / p) + 0.5) * p;
	}
}

BEGIN {
	f1 = "";
	tderr = tderr / 1000;
	tdmin = td - tderr;
	tdmax = td + tderr;
	freq_diff = freq_diff * 65536;
	ntp_delay = ntp_delay * 1000;
	c = 0;
	ignored = 0;
	delta_o = 0;
	delta_t = 0;
	sum_f2 = 0;
	min_f2 = "init";
	max_f2 = "init";
	prev_f2 = "init";
}

/^[0-9]/ {
	if(f1 == "") {
		t1 = $1 * 86400 + $2;
		o1 = $5;
		f1 = $7;

		t2 = ts = t1;
		o2 = o1;
		fs = f1;

		c++;
	} else {
		t2 = $1 * 86400 + $2;
		o2 = $5;

		td = t2 - t1;
		od = o2 - o1;
		if($3 > ntp_delay) {
			print "Ignoring: ntp delay (" $3 / 1000 " ms) out of spec in line " FNR ".";
			ignored++;
		} else {
			if(td < tdmin || td > tdmax) {
				print "Ignoring: time difference (" pretty_time(td) ") out of spec in line " FNR ".";
				ignored++;
			} else {
				f2 = f1 + (65536 * od / td);
				if(prev_f2 == "init") {prev_f2 = f2;}
				if(abs(f2 - prev_f2) > freq_diff) {
					print "Ignoring: target frequency adjustment (" (f2 - prev_f2) / 65536 " ppm) out of spec in line " FNR ".";
					ignored++;
				} else {
					# weighted average
					sum_f2 += f2 * td;
					delta_o += od;
					delta_t += td;

					# minimum / maximum
					if(min_f2 == "init") { min_f2 = f2; }
					else { if(f2 < min_f2) { min_f2 = f2; }}
					if(max_f2 == "init") { max_f2 = f2; }
					else { if(f2 > max_f2) { max_f2 = f2; }}

					c++;
				}
				prev_f2 = f2;
			}
		}

		t1 = t2;
		o1 = o2;
		f1 = $7;
	}
}

END {
	if(ignored > 0) {
		print "";
	}
	print "total time:",pretty_time(round(delta_t));
	print "number of samples:",c;
	if(ignored > 0) {
		print "ignored samples:", ignored, "(" round(100 * ignored / (ignored + c)) "%)";
	}
	if(delta_t >= tdmin) {
		print "";
		slope = delta_o / delta_t;
		f2 = sum_f2 / delta_t;
		print "delta-t", delta_t, "seconds"
		print "delta-o", delta_o, "useconds"
		print "slope:", slope, "ppm";
		print "old frequency:", fs, "(" fs / 65536,"ppm)";
		#print "min frequency:", int(min_f2 + .5), "(" min_f2 / 65536,"ppm)";
		#print "max frequency:", int(max_f2 + .5), "(" max_f2 / 65536,"ppm)";
		print "new frequency:", int(f2 + .5), "(" f2 / 65536,"ppm)";
	}
}

