50

I'm looking for a way to get an absolute, always-incrementing system uptime on iOS.

It should return the time since the device was last rebooted, and not be affected by changes to the system date.

All the methods I can find either pause when the device is asleep (CACurrentMediaTime, [NSProcessInfo systemUptime], mach_absolute_time) or are changed when the system date changes (sysctl/KERN_BOOTTIME).

Any ideas?

ricardopereira
  • 11,118
  • 5
  • 63
  • 81
Russell Quinn
  • 1,330
  • 1
  • 11
  • 14

6 Answers6

54

I think I worked it out.

time() carries on incrementing while the device is asleep, but of course can be manipulated by the operating system or user. However, the Kernel boottime (a timestamp of when the system last booted) also changes when the system clock is changed, therefore even though both these values are not fixed, the offset between them is.

#include <sys/sysctl.h>

- (time_t)uptime
{
    struct timeval boottime;
    int mib[2] = {CTL_KERN, KERN_BOOTTIME};
    size_t size = sizeof(boottime);
    time_t now;
    time_t uptime = -1;

    (void)time(&now);

    if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0) {
        uptime = now - boottime.tv_sec;
    }

    return uptime;
}
Russell Quinn
  • 1,330
  • 1
  • 11
  • 14
  • 1
    Well done mate. I was going to have to go for a server date / local date offset compare before this... So much appreciated. – Magoo Dec 19 '14 at 22:21
  • Is there a race condition if you happen to call this at the exact moment after the system time has updated, but before the boot time is updated, or is there something that guarantees they'll both update atomically? – Jon Colverson Jan 27 '16 at 16:53
  • 1
    I'm not sure. This has been used in a production app for a few years now and I've never seen a race condition problem. So, anecdotally, it seems fine. – Russell Quinn Jan 28 '16 at 17:23
  • 4
    Yes, there is a race condition. The "never seen a race condition problem" isn't valid because: 1. Your application may not actually depend on the monotonicity of the clock 2. You may not have a mechanism for actually capturing such issues in the field (i.e. your users are hitting it, you just don't know) 3. You don't have a large install base of users or your install base is not representative of other users (e.g. your users never change their manual clock). 4. Your users may simply have gotten lucky and never hit this. – Vitali Nov 08 '16 at 22:56
30

As said in one of the comments, POSIX's clock_gettime() was implemented in iOS 10 and macOS 10.12. When used with the CLOCK_MONOTONIC argument, it does seem to return the uptime value. However, this is not guaranteed by the documentation:

For this clock, the value returned by clock_gettime() represents the amount of time (in seconds and nanoseconds) since an unspecified point in the past (for example, system start-up time, or the Epoch). This point does not change after system start-up time.

And an extract from the corresponding macOS man page:

CLOCK_MONOTONIC clock that increments monotonically, tracking the time since an arbitrary point, and will continue to increment while the system is asleep.

CLOCK_MONOTONIC_RAW clock that increments monotonically, tracking the time since an arbitrary point like CLOCK_MONOTONIC. However, this clock is unaffected by frequency or time adjustments. It should not be compared to other system time sources.

CLOCK_MONOTONIC_RAW_APPROX like CLOCK_MONOTONIC_RAW, but reads a value cached by the system at context switch. This can be read faster, but at a loss of accuracy as it may return values that are milliseconds old.

CLOCK_UPTIME_RAW clock that increments monotonically, in the same manner as CLOCK_MONOTONIC_RAW, but that does not increment while the system is asleep. The returned value is identical to the result of mach_absolute_time() after the appropriate mach_timebase conversion is applied.

Note that CLOCK_MONOTONIC returns with microsecond precision while CLOCK_MONOTONIC_RAW with nanosecond precision.

Swift code:

func uptime() -> timespec {

    var uptime = timespec()
    if 0 != clock_gettime(CLOCK_MONOTONIC_RAW, &uptime) {
        fatalError("Could not execute clock_gettime, errno: \(errno)")
    }

    return uptime
}

print(uptime()) // timespec(tv_sec: 636705, tv_nsec: 750700397)

For those still interested in getting the kernel's boot time in Swift:

func kernelBootTime() -> timeval {

    var mib = [ CTL_KERN, KERN_BOOTTIME ]
    var bootTime = timeval()
    var bootTimeSize = MemoryLayout<timeval>.size

    if 0 != sysctl(&mib, UInt32(mib.count), &bootTime, &bootTimeSize, nil, 0) {
        fatalError("Could not get boot time, errno: \(errno)")
    }

    return bootTime
}

print(kernelBootTime()) // timeval(tv_sec: 1499259206, tv_usec: 122778)
nyg
  • 2,380
  • 3
  • 25
  • 40
  • How come `CLOCK_MONOTONIC_RAW` gives up time? The documentation states it's from an unspecified arbitrary point in time – Gregory Pakosz Jun 05 '20 at 15:23
  • I see the documentation above here: https://www.unix.com/man-page/mojave/3/clock_getres/. It appears to be for macOS Mojave. – William Grand Jul 06 '20 at 21:19
16

There's a race condition in all examples listed: if there's a time change (NTP or user-modified), the code will race and the clock is no longer monotonic.

The correct implementation is:

#include <sys/sysctl.h>

static int64_t us_since_boot() {
    struct timeval boottime;
    int mib[2] = {CTL_KERN, KERN_BOOTTIME};
    size_t size = sizeof(boottime);
    int rc = sysctl(mib, 2, &boottime, &size, NULL, 0);
    if (rc != 0) {
      return 0;
    }
    return (int64_t)boottime.tv_sec * 1000000 + (int64_t)boottime.tv_usec;
}

- (int64_t)us_uptime
{
    int64_t before_now;
    int64_t after_now;
    struct timeval now;

    after_now = us_since_boot();
    do {
        before_now = after_now;
        gettimeofday(&now, NULL);
        after_now = us_since_boot();
    } while (after_now != before_now);

    return (int64_t)now.tv_sec * 1000000 + (int64_t)now.tv_usec - before_now;
}
d12frosted
  • 1,280
  • 12
  • 33
Vitali
  • 3,411
  • 2
  • 24
  • 25
  • 4
    I've tested this and it appears to work like a charm. I can't find any flaws in it. Thanks! For the record, finally there's a non-awkward solution for OS10 and up. The clock_gettime API is finally available, and using CLOCK_MONOTONIC should support the requirements for a monotonic timer that increases at wall clock rate with a decent accuracy. – Klaas van Aarsen Nov 16 '16 at 23:14
  • 1
    This is great except for the function name `us_since_boot`. It's just a boot time, not elapsed time since boot. – Keewon Seo Feb 03 '17 at 08:57
  • Note that there is possible overflow when doing `boottime.tv_sec * 1000000` or `now.tv_sec * 1000000` (on 32-bit platforms it's easy to catch). In order to avoid this issue, you have to do a direct cast. – d12frosted Nov 10 '17 at 12:52
  • @KeewonSeo you're right. It's probably better to call it boot_timestamp(). – Vitali Jul 12 '18 at 23:39
  • 1
    @IlikeSerena you're right. CLOCK_MONOTONIC_RAW_APPROX is probably the better API from a performance perspective or CLOCK_MONOTONIC_RAW if you need the absolute accurate timestamp (I worked with high-precision timestamps & I think one would be hard-pressed to find a scenario where RAW is the better choice). It sounds like CLOCK_MONOTONIC might still be affected by time/frequency adjustements. – Vitali Jul 12 '18 at 23:43
4

If higher precision is needed then below is a modified version of accepted answer.

#include <sys/sysctl.h>

- (NSTimeInterval)uptime
{
    struct timeval boottime;
    int mib[2] = {CTL_KERN, KERN_BOOTTIME};
    size_t size = sizeof(boottime);

    struct timeval now;
    struct timezone tz;
    gettimeofday(&now, &tz);

    double uptime = -1;

    if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0)
    {
        uptime = now.tv_sec - boottime.tv_sec;
        uptime += (double)(now.tv_usec - boottime.tv_usec) / 1000000.0;
    }
    return uptime;
}
Leszek Szary
  • 9,763
  • 4
  • 55
  • 62
2

I'd comment on Leszek's answer, but not enough rep... Leszek's solution returns seconds.

The following is a modified version of Leszek's answer if anyone needs millisecond precision.

#include <sys/sysctl.h>

+ (long long int)uptime 
{
    struct timeval boottime;
    int mib[2] = {CTL_KERN, KERN_BOOTTIME};
    size_t size = sizeof(boottime);

    struct timeval now;
    struct timezone tz;
    gettimeofday(&now, &tz);

    long long int uptime = -1;

    if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0)
    {
        uptime = ((long long int)(now.tv_sec - boottime.tv_sec)) * 1000;
        uptime += (now.tv_usec - boottime.tv_usec) / 1000;
    }
    return uptime;
}
Boris
  • 316
  • 2
  • 10
-6

Why not using systemUptime?

systemUptime Returns how long it has been since the computer has been restarted.

  • (NSTimeInterval)systemUptime Return Value An NSTimeInterval indicating how long since the computer has been restarted.

Availability Available in iOS 4.0 and later. Declared In NSProcessInfo.h

I have tested and proved that at least on iPhone 5 with iOS 7.1.1, systemUptime DOES NOT STOP when your phone is locked. So anyone don't believe this can test it yourself.

Owen Zhao
  • 3,205
  • 1
  • 26
  • 43
  • In my test with iOS 7.1.x. It won't stop when device is locked and the screen is off. – Owen Zhao May 12 '14 at 12:41
  • it's affected by sleep/awake, really. Maybe you tested it with connected cable which prevents sleep. Or some other reason block sleeping your device while you tested... – Maxim Kholyavkin Jun 27 '14 at 14:30
  • Yep, I re-tested and it stops, sorry. – Russell Quinn Jul 03 '14 at 19:46
  • 4
    From: http://devetc.org/code/2014/01/21/timers-clocks-and-cocoa.html Monotonic time is basically a counter that gets incremented when a physical timer signals the CPU via a timer interrupt… Several functions include the conversion to seconds: -[NSProcessInfo systemUptime], CACurrentMediaTime(), and others… However since the CPU increments this counter, the monotonic clock stops when the CPU is powered down — which includes when the system is “sleeping”. – Russell Quinn Jul 03 '14 at 19:50
  • @Speakus thank you so much! you are right systemUpTime is restarted with unplugged cable! I have been struggled with this all the day and I hadn't been noticed about it... – Marta Tenés Sep 07 '16 at 13:53