Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find out if the linux kernel will insert a leap second at the end of the month

Tags:

c

linux

time

Suppose my program runs on a linux machine that is properly configured to handle leap seconds. How that configuration is done exactly (NTP, config file) should not be relevant to this question.

In effect, the kernel will insert an additional second or skip over a second at the UTC end of the month. This has an effect on the time value read by gettimeofday(2). The last UTC second of the month is either repeated or skipped. Example readings of the time during a leap second are listed here.

My question: How can I find out, in a C/C++ program, if a leap second will occur at the end of the month, and in which direction. So how do I implement the following function on linux

int leap_seconds_scheduled_for_end_of_month() {
  if (/*kernel_will_insert_extra_second?*/)
    return 1;
  if (/*kernel_will_skip_over_last_second?*/)
    return -1;
  return 0;
}

It is ok if the result is incorrect if the end of the month is far in the future. (far, for my purposes, is >=2 seconds) The answer has to be correct(*) during the last second before a possible leap second, i.e. 23:59:58 UTC on the last day of the month. It is not sufficient if I learn about the leap second after it has occurred, as I have to prepare for it in advance.

I have been trying to find any leap second indicator in sysfs or procfs, but so far have been unsuccessful.

(*) Of course, if the kernel itself learns about the leap second only a fraction before it occurs, e.g. due to an outage of the NTP service during the whole last month, the answer cannot be correct then. That's ok.

like image 563
Ludwig Schulze Avatar asked Oct 05 '14 12:10

Ludwig Schulze


3 Answers

Thanks to Sam Varshavchik and richvdh who have both provided very useful answers to the question. Last night, a leap second has occurred, and I now have example readings using both of their methods running on several computers.

For readability, I am posting the data in a separate answer instead of a comment. The following C program has been used to read the time and and determine if a leap second will occur:

#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <string.h>
#include <sys/timex.h>
#include <unistd.h>

/* This C function implements Sam Varshavchik's method */
int leap_seconds_scheduled_for_end_of_month() {
  char ntpq_output[1024];
  /* The path to ntpq might have to be adjusted for your system */
  FILE * ntpq_stream = popen("/usr/bin/ntpq -c lassoc -c 'mrv &1 &999 leap,srcadr,stratum'","r");
  memset(ntpq_output,0,1024);
  fread(ntpq_output,1,1022,ntpq_stream);
  pclose(ntpq_stream);
  /* This finds the first leap=xx occurrence in ntpq's output. If 
     multiple upstream ntp servers are configured, there will be one
     line of text for each of them. It would be good to check that
     the stratum=xx value in the same line is not 16 (which means
     invalid). If it is, better use the leap=xx value from another 
     output line. Not done here for simplicity. */ 
  char * leap_bits = strstr(ntpq_output,"leap=");
  if (leap_bits == NULL)
    return 0;
  leap_bits += 5;
  if (leap_bits[0] == '0' && leap_bits[1] == '1')
    return 1;
  if (leap_bits[0] == '1' && leap_bits[1] == '0')
    return -1;
  return 0;
}

/* This function prints the following data in a single line of text:
   gettimeofday, clock_gettime for CLOCK_MONOTONIC, adjtimex (richvdh's
   solution), and Sam's solution */
print_current_data() {
  struct timeval tv = {0,0};
  gettimeofday(&tv,NULL);
  printf("%u.%06u ", (unsigned)tv.tv_sec, (unsigned)tv.tv_usec);

  struct timespec ts = {0,0};
  clock_gettime(CLOCK_MONOTONIC, &ts);
  printf("%u.%09u ", (unsigned)ts.tv_sec, (unsigned)ts.tv_nsec);

  struct timex buf;
  buf.modes = 0;
  printf("%d ", adjtimex(&buf));

  printf("%2d\n", leap_seconds_scheduled_for_end_of_month());
}

/* main just calls print_current_data repeatedly. 
   If we are far away from the leap second, it sleeps between 
   printouts to reduce the ammount of data. */ 
int main(int argc, char ** argv) {
  /* this is the time_t value for the second after the 2015 leap second */
  time_t post_leap = 1435708800;
  for(;;) {
    time_t now = time(NULL);
    print_current_data();
    if (now < (post_leap-60)) {
      sleep(30);
    } else if (now < (post_leap - 10)) {
      sleep(5);
    } else if (now < (post_leap - 2)) {
      usleep(500000);
    } else if (now <= (post_leap + 1)) {
    } else if (now > (post_leap + 120)) {
      return 0;
    } else if (now > (post_leap + 1)) {
      usleep(500000);
    }
  }

  return 0;
}

And here the data from last night. To not drown you in the data, I insert only the lines before and after an interesting change has occured, and indicate the omission with [... skipping to xxx].

Each data line contains the following fields:
gettimeofday, clock_gettime for CLOCK_MONOTONIC, adjtimex (richvdh's solution), and Sam's solution (invoke ntpq and parse output).

System 1: Cubieboard running Linaro 13.04 with Kernel 3.0.62

ntp package is version 1:4.2.6.p3+dfsg-1ubuntu5

$ ./print_leap 
1435703927.419452 7971483.087902293 1  1
[... skipping to the second before the leap second]
1435708798.992813 7976354.661146951 1  1
1435708799.014143 7976354.682476687 1  1
[... skipping to the leap second]
1435708799.982292 7976355.650624344 1  1
1435708799.007776 7976355.676111361 3  1
[... skipping to the second after the leap second]
1435708799.985337 7976356.653668890 3  1
1435708800.007414 7976356.675746846 4  1
[... skipping to a change in the adjtimex reading]
1435708844.685062 7976401.353401529 4  1
1435708845.204844 7976401.873191115 0  1
[... skipping to the last programmatic reading]
1435708921.124692 7976477.793033504 0  1

In a manual check about 4.5 hours after the leap second, Sam's method is also back to 0.

System 2: Cubieboard 2 running Debian 8 with Kernel 3.4.107-cubietruck

ntp package is version 1:4.2.6.p5+dfsg-7

$ ./print_leap
1435703847.373559 948687.514617591 1  1
[... skipping to the second before the leap second]
1435708798.998980 953639.139771365 1  1
1435708799.028375 953639.169165960 1  1
[... skipping to the leap second]
1435708799.986449 953640.127238940 1  1
1435708799.020017 953640.160809364 3  1
[... skipping to the second after the leap second]
1435708799.984912 953641.125702281 3  1
1435708800.012875 953641.153666113 4  1
[... skipping to the last programmatic reading]
1435708921.268660 953762.409472677 4  1
$ ./print_leap  # manual reading several hours later
1435725362.080253 970203.221195342 0  0

System 3: Raspberrypi model B running Raspbian 7 with Kernel 3.12.35+

ntp package is version 1:4.2.6.p5+dfsg-2+deb7u1

$ ./print_leap 
1435704085.032222 313606.064883453 1  1
[... skipping to the second before the leap second]
1435708798.969855 318320.002146274 1  1
1435708799.040573 318320.072865002 1  1
[... skipping to the leap second]
1435708799.996887 318321.029180271 1  1
1435708799.071282 318321.103573880 3  1
[... skipping to the second after the leap second]
1435708799.945609 318321.977898784 3  1
1435708800.017116 318322.049407486 4  1
[... skipping to the last programmatic reading]
1435708921.136882 318443.169189210 4  1
$ ./print_leap  # manual reading several hours later 
1435732962.263988 342484.296617478 0  0

System 4: Intel NUC DN2820 running Ubuntu 15.04 with Kernel 3.19.0-20-generic

ntp package is version 1:4.2.6.p5+dfsg-3ubuntu6

Note the glitch at the start of the leap second:

nuc@nuc1:~$ ./print_leap 
1435703986.534020 1305198.740735208 1  1
[... skipping to the second before the leap second]
1435708798.991478 1310011.198081950 1  1
1435708799.000905 1310011.207509156 1  1
[... skipping to the leap second]
1435708799.989309 1310012.195913702 1  1
1435708800.000738 1310012.207343097 1  1
1435708799.016079 1310012.222683886 3  1
[... skipping to the second after the leap second]
1435708799.999616 1310013.206220581 3  1
1435708800.012446 1310013.219050861 4  1
[... skipping to the last programmatic reading]
1435708921.047985 1310134.254602197 4  1
nuc@nuc1:~$ ./print_leap  # manual reading several hours later
1435725807.234545 1327020.441295352 0  0

System 5: Intel NUC DN2820 running Ubuntu 14.04 with Kernel 3.13.0-53-generic

ntp package is version 1:4.2.6.p5+dfsg-3ubuntu2.14.04.3

nuc@nuc2:~$ ./print_leap 
1435704031.137881 323125.995674014 1  1
[... skipping to the second before the leap second]
1435708798.995646 327893.853350053 1  1
1435708799.007936 327893.865640804 1  1
[... skipping to the leap second]
1435708799.995589 327894.853293338 1  1
1435708799.007527 327894.865231774 3  1
[... skipping to the second after the leap second]
1435708799.998111 327895.855815619 3  1
1435708800.013461 327895.871165134 4  1
[... skipping to the last programmatic reading]
1435708921.282149 328017.139858311 4  1
nuc@nuc2:~$ ./print_leap  # manual reading several hours later 
1435725557.303859 344653.161689304 0  0

System 6: Lenovo E330 Laptop running Ubuntu 14.04 with Kernel 3.13.0-55-generic

ntp package is version 1:4.2.6.p5+dfsg-3ubuntu2.14.04.3

Note the inconsistency after the end of the leap second.

$ ./print_leap 
1435706912.426035 82874.419021628 1  1
[... skipping to the second before the leap second]
1435708798.929115 84760.922063075 1  1
1435708799.013439 84761.006388727 1  1
[... skipping to the leap second]
1435708799.981243 84761.974190967 1  1
1435708799.049374 84762.042323977 3  1
[... skipping to the second after the leap second]
1435708799.913464 84762.906413341 3  1
1435708800.000183 84762.993132942 3  1
1435708800.477323 84763.470272391 4  1
[... skipping to a change in the ntpq reading]
1435708840.094271 84803.087225581 4  1
1435708840.618538 84803.611493081 4  0
[... skipping to the last programmatic reading]
1435708921.042020 84884.034975833 4  0
$ ./print_leap  # manual reading several hours later 
1435724912.944500 100875.937487693 0  0

System 7: Dell server with AMD Phenom CPU running Ubuntu 14.04 with Kernel 3.13.0-53-generic

ntp package is version 1:4.2.6.p5+dfsg-3ubuntu2.14.04.3

Note the glitches at the start and at the end of the leap second

$ ./print_leap
1435704125.933343 2210517.393979537 1  1
[... skipping to the second before the leap second]
1435708798.998012 2215190.458598770 1  1
1435708799.002893 2215190.463480413 1  1
[... skipping to the leap second]
1435708799.994690 2215191.455273816 1  1
1435708800.001917 2215191.462501431 1  1
1435708799.013505 2215191.474092236 3  1
[... skipping to the second after the leap second]
1435708799.992212 2215192.452796670 3  1
1435708800.000210 2215192.460794341 3  1
1435708800.006819 2215192.467403224 4  1
[... skipping to the last programmatic reading]
1435708921.323808 2215313.784400730 4  1
$ ./print_leap  # manual reading several hours later
1435726838.319240 2233230.779876616 0  0

System 8: Virtual server running Debian 6, using the host's kernel 2.6.32-028stab094.3

No ntp package is installed in the guest system. NTP may or may not run on the host. The host provides the time to the guest system.

$ ./print_leap 
sh: /usr/bin/ntpq: No such file or directory
1435704206.353115 114299811.979591258 1  0
[... skipping to the second before the leap second]
sh: /usr/bin/ntpq: No such file or directory
1435708798.994303 114304404.620727643 1  0
sh: /usr/bin/ntpq: No such file or directory
1435708799.000291 114304404.626715249 1  0
[... skipping to the leap second]
sh: /usr/bin/ntpq: No such file or directory
1435708799.999445 114304405.625868168 1  0
sh: /usr/bin/ntpq: No such file or directory
1435708799.004273 114304405.630696209 3  0
[... skipping to the second after the leap second]
sh: /usr/bin/ntpq: No such file or directory
1435708799.998234 114304406.624656721 3  0
sh: /usr/bin/ntpq: No such file or directory
1435708800.003723 114304406.630146580 4  0
[... skipping to the last programmatic reading]
sh: /usr/bin/ntpq: No such file or directory
1435708921.185876 114304527.812316105 4  0
$ ./print_leap  # manual reading several hours later
sh: /usr/bin/ntpq: No such file or directory
1435727243.250130 114322849.876607481 0  0

System 9: Virtual server running Debian 8, using the host's kernel 2.6.32-37-pve

No ntp package is installed in the guest system. NTP may or may not run on the host. The host provides the time to the guest system.

$ ./print_leap 
sh: 1: /usr/bin/ntpq: not found
1435704263.521402 627240.363389922 1  0
[... skipping to the second before the leap second]
sh: 1: /usr/bin/ntpq: not found
1435708798.991150 631775.833122648 1  0
sh: 1: /usr/bin/ntpq: not found
1435708799.062267 631775.904240598 1  0
[... skipping to the leap second]
sh: 1: /usr/bin/ntpq: not found
1435708799.986215 631776.828187732 1  0
sh: 1: /usr/bin/ntpq: not found
1435708799.061678 631776.903650529 3  0
[... skipping to the second after the leap second]
sh: 1: /usr/bin/ntpq: not found
1435708799.987007 631777.828979335 3  0
sh: 1: /usr/bin/ntpq: not found
1435708800.061094 631777.903067236 4  0
[... skipping to the last programmatic reading]
sh: 1: /usr/bin/ntpq: not found
1435708921.380148 631899.222124765 4  0
$ ./print_leap  # manual reading several hours later
sh: 1: /usr/bin/ntpq: not found
1435727152.545742 650130.387735253 0  0

NTP Public Pool problem

Some servers in the NTP pool did not announce the leap second. In the case of a little bad luck, when all your upstream servers in the pool fail to announce the leap second, you will never know about it. Therefore, it is mandatory to inform your local NTP daemon about the leap seconds using config files if a local application requires leap second warnings.

Glitches / Inconsistencies

When querying current time and current leap second status sequentially, it is clear that the values might be inconsistent because the time that has passed between the two function calls.

However, additional inconsistencies that cannot be explained by the time that has passed between invocations have been observed. Two computers, systems 4 and 7, have started the leap second with a time value of 1435708800 returned by gettimeofday before switching back to 1435708799 in the next reading. Two computers, systems 6 and 7, start the second after the leap second with the adjtimex value still indicating that we are inside the leap second.

Conclusion

Both suggested methods work well for the indended purpose: knowing in advance if a leap second will occur, and when, on a properly configured linux system.

This advance knowledge enables applications to determine the delta between CLOCK_REALTIME and CLOCK_MONOTONIC and then use CLOCK_MONOTONIC near the leap second to determine time.

None of the methods helps to determine time near the leap second if this preparation has not been made. But this is not what I have asked.

Both methods require the additional knowledge that leap seconds will only occur at the end of an UTC month to do accurate planning. NTP leap second warnings from the previous month seem to carry over into the first few minutes of the next UTC month before they evaporate. Therefore, they should only be interpreted when an UTC month end is in the near future.

like image 145
Ludwig Schulze Avatar answered Nov 10 '22 11:11

Ludwig Schulze


The kernel keeps a flag which defines whether it will add (or remove) a leap-second at the end of the current (UTC) day. Where this is supported, it is set by ntpd when there is less than one day remaining to the leap second.

So you can basically get a day's warning of the leap-second by inspecting this flag, assuming that (a) your ntp daemon gets notice of it from its sources, and (b) your ntp daemon correctly determines that your kernel supports this.

I don't think the flag is exposed via sysfs or procfs; however it is easy to retrieve from C via the adjtimex syscall:

#include <sys/timex.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    struct timex buf;
    int res;

    buf.modes = 0;

    res = adjtimex(&buf);
    if(res < 0) {
        perror("Error calling adjtimex");
        return 1;
    }

    printf("clock status: %i\n", res);
    return 0;
}

The result is 1 or 2 if a leap second is on its way, as given by adjtimex(2):

On success, adjtimex() returns the clock state:

#define TIME_OK   0 /* clock synchronized */
#define TIME_INS  1 /* insert leap second */
#define TIME_DEL  2 /* delete leap second */
#define TIME_OOP  3 /* leap second in progress */
#define TIME_WAIT 4 /* leap second has occurred */
#define TIME_BAD  5 /* clock not synchronized */
like image 36
richvdh Avatar answered Nov 10 '22 12:11

richvdh


AFAIK, the kernel does not keep advance notice of a leap second being scheduled. This knowledge is maintained by the NTP daemon, and when it's time to fudge the system clock, ntpd issues the adjtimex(2) syscall, to adjust the system clock.

To poke your ntpd daemon:

$ /usr/sbin/ntpq -c 'lassoc' -c "mrv &1 &999 leap,srcadr,stratum"

ind assid status  conf reach auth condition  last_event cnt
===========================================================
  1  5159  80a3   yes    no  none    reject unreachable 10
  2  5160  968a   yes   yes  none  sys.peer    sys_peer  8
srcadr=LOCAL(0), leap=00, stratum=10

srcadr=timeserver.example.com, leap=00, stratum=4

When you see "01" set for leap, you have a leap second headed your way. The "srcaddr" line is going to be repeated for every source your ntp server is configured for, so you might have multiple entries, there (my example returns the dummy local system cl

So, for a C++ solution, you'll either have to do an ugly fork/exec of /usr/sbin/ntpq, and capture the output, or dig up the socket protocol ntpq uses to talk to the NTP daemon, and do it yourself, to grab the response from the daemon.

like image 43
Sam Varshavchik Avatar answered Nov 10 '22 12:11

Sam Varshavchik