Getting iOS system uptime, that doesn't pause when asleep
Asked Answered
A

6

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?

Airburst answered 19/9, 2012 at 4:15 Comment(0)
A
55

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;
}
Airburst answered 19/9, 2012 at 7:41 Comment(4)
Well done mate. I was going to have to go for a server date / local date offset compare before this... So much appreciated.Soler
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?Bilow
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.Airburst
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.Megaron
V
31

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)
Visible answered 12/7, 2017 at 21:47 Comment(2)
How come CLOCK_MONOTONIC_RAW gives up time? The documentation states it's from an unspecified arbitrary point in timeBastien
I see the documentation above here: unix.com/man-page/mojave/3/clock_getres. It appears to be for macOS Mojave.Spleenful
M
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;
}
Megaron answered 8/11, 2016 at 22:54 Comment(5)
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.Makeweight
This is great except for the function name us_since_boot. It's just a boot time, not elapsed time since boot.Frog
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.Sparkman
@KeewonSeo you're right. It's probably better to call it boot_timestamp().Megaron
@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.Megaron
B
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;
}
Burdock answered 18/3, 2016 at 17:50 Comment(0)
M
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;
}
Meathead answered 30/1, 2017 at 15:10 Comment(0)
D
-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.

Damalis answered 7/5, 2014 at 4:19 Comment(5)
In my test with iOS 7.1.x. It won't stop when device is locked and the screen is off.Damalis
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...Splat
Yep, I re-tested and it stops, sorry.Airburst
From: 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”.Airburst
@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...Epaulet

© 2022 - 2024 — McMap. All rights reserved.