Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | // |
michael@0 | 8 | // Implement TimeStamp::Now() with POSIX clocks. |
michael@0 | 9 | // |
michael@0 | 10 | // The "tick" unit for POSIX clocks is simply a nanosecond, as this is |
michael@0 | 11 | // the smallest unit of time representable by struct timespec. That |
michael@0 | 12 | // doesn't mean that a nanosecond is the resolution of TimeDurations |
michael@0 | 13 | // obtained with this API; see TimeDuration::Resolution; |
michael@0 | 14 | // |
michael@0 | 15 | |
michael@0 | 16 | #include <sys/syscall.h> |
michael@0 | 17 | #include <time.h> |
michael@0 | 18 | #include <unistd.h> |
michael@0 | 19 | |
michael@0 | 20 | #if defined(__DragonFly__) || defined(__FreeBSD__) \ |
michael@0 | 21 | || defined(__NetBSD__) || defined(__OpenBSD__) |
michael@0 | 22 | #include <sys/param.h> |
michael@0 | 23 | #include <sys/sysctl.h> |
michael@0 | 24 | #endif |
michael@0 | 25 | |
michael@0 | 26 | #if defined(__DragonFly__) || defined(__FreeBSD__) |
michael@0 | 27 | #include <sys/user.h> |
michael@0 | 28 | #endif |
michael@0 | 29 | |
michael@0 | 30 | #if defined(__NetBSD__) |
michael@0 | 31 | #undef KERN_PROC |
michael@0 | 32 | #define KERN_PROC KERN_PROC2 |
michael@0 | 33 | #define KINFO_PROC struct kinfo_proc2 |
michael@0 | 34 | #else |
michael@0 | 35 | #define KINFO_PROC struct kinfo_proc |
michael@0 | 36 | #endif |
michael@0 | 37 | |
michael@0 | 38 | #if defined(__DragonFly__) |
michael@0 | 39 | #define KP_START_SEC kp_start.tv_sec |
michael@0 | 40 | #define KP_START_USEC kp_start.tv_usec |
michael@0 | 41 | #elif defined(__FreeBSD__) |
michael@0 | 42 | #define KP_START_SEC ki_start.tv_sec |
michael@0 | 43 | #define KP_START_USEC ki_start.tv_usec |
michael@0 | 44 | #else |
michael@0 | 45 | #define KP_START_SEC p_ustart_sec |
michael@0 | 46 | #define KP_START_USEC p_ustart_usec |
michael@0 | 47 | #endif |
michael@0 | 48 | |
michael@0 | 49 | #include "mozilla/TimeStamp.h" |
michael@0 | 50 | #include "nsCRT.h" |
michael@0 | 51 | #include "prprf.h" |
michael@0 | 52 | #include "prthread.h" |
michael@0 | 53 | #include "nsDebug.h" |
michael@0 | 54 | |
michael@0 | 55 | // Estimate of the smallest duration of time we can measure. |
michael@0 | 56 | static uint64_t sResolution; |
michael@0 | 57 | static uint64_t sResolutionSigDigs; |
michael@0 | 58 | |
michael@0 | 59 | static const uint16_t kNsPerUs = 1000; |
michael@0 | 60 | static const uint64_t kNsPerMs = 1000000; |
michael@0 | 61 | static const uint64_t kNsPerSec = 1000000000; |
michael@0 | 62 | static const double kNsPerMsd = 1000000.0; |
michael@0 | 63 | static const double kNsPerSecd = 1000000000.0; |
michael@0 | 64 | |
michael@0 | 65 | static uint64_t |
michael@0 | 66 | TimespecToNs(const struct timespec& ts) |
michael@0 | 67 | { |
michael@0 | 68 | uint64_t baseNs = uint64_t(ts.tv_sec) * kNsPerSec; |
michael@0 | 69 | return baseNs + uint64_t(ts.tv_nsec); |
michael@0 | 70 | } |
michael@0 | 71 | |
michael@0 | 72 | static uint64_t |
michael@0 | 73 | ClockTimeNs() |
michael@0 | 74 | { |
michael@0 | 75 | struct timespec ts; |
michael@0 | 76 | // this can't fail: we know &ts is valid, and TimeStamp::Startup() |
michael@0 | 77 | // checks that CLOCK_MONOTONIC is supported (and aborts if not) |
michael@0 | 78 | clock_gettime(CLOCK_MONOTONIC, &ts); |
michael@0 | 79 | |
michael@0 | 80 | // tv_sec is defined to be relative to an arbitrary point in time, |
michael@0 | 81 | // but it would be madness for that point in time to be earlier than |
michael@0 | 82 | // the Epoch. So we can safely assume that even if time_t is 32 |
michael@0 | 83 | // bits, tv_sec won't overflow while the browser is open. Revisit |
michael@0 | 84 | // this argument if we're still building with 32-bit time_t around |
michael@0 | 85 | // the year 2037. |
michael@0 | 86 | return TimespecToNs(ts); |
michael@0 | 87 | } |
michael@0 | 88 | |
michael@0 | 89 | static uint64_t |
michael@0 | 90 | ClockResolutionNs() |
michael@0 | 91 | { |
michael@0 | 92 | // NB: why not rely on clock_getres()? Two reasons: (i) it might |
michael@0 | 93 | // lie, and (ii) it might return an "ideal" resolution that while |
michael@0 | 94 | // theoretically true, could never be measured in practice. Since |
michael@0 | 95 | // clock_gettime() likely involves a system call on your platform, |
michael@0 | 96 | // the "actual" timing resolution shouldn't be lower than syscall |
michael@0 | 97 | // overhead. |
michael@0 | 98 | |
michael@0 | 99 | uint64_t start = ClockTimeNs(); |
michael@0 | 100 | uint64_t end = ClockTimeNs(); |
michael@0 | 101 | uint64_t minres = (end - start); |
michael@0 | 102 | |
michael@0 | 103 | // 10 total trials is arbitrary: what we're trying to avoid by |
michael@0 | 104 | // looping is getting unlucky and being interrupted by a context |
michael@0 | 105 | // switch or signal, or being bitten by paging/cache effects |
michael@0 | 106 | for (int i = 0; i < 9; ++i) { |
michael@0 | 107 | start = ClockTimeNs(); |
michael@0 | 108 | end = ClockTimeNs(); |
michael@0 | 109 | |
michael@0 | 110 | uint64_t candidate = (start - end); |
michael@0 | 111 | if (candidate < minres) |
michael@0 | 112 | minres = candidate; |
michael@0 | 113 | } |
michael@0 | 114 | |
michael@0 | 115 | if (0 == minres) { |
michael@0 | 116 | // measurable resolution is either incredibly low, ~1ns, or very |
michael@0 | 117 | // high. fall back on clock_getres() |
michael@0 | 118 | struct timespec ts; |
michael@0 | 119 | if (0 == clock_getres(CLOCK_MONOTONIC, &ts)) { |
michael@0 | 120 | minres = TimespecToNs(ts); |
michael@0 | 121 | } |
michael@0 | 122 | } |
michael@0 | 123 | |
michael@0 | 124 | if (0 == minres) { |
michael@0 | 125 | // clock_getres probably failed. fall back on NSPR's resolution |
michael@0 | 126 | // assumption |
michael@0 | 127 | minres = 1 * kNsPerMs; |
michael@0 | 128 | } |
michael@0 | 129 | |
michael@0 | 130 | return minres; |
michael@0 | 131 | } |
michael@0 | 132 | |
michael@0 | 133 | namespace mozilla { |
michael@0 | 134 | |
michael@0 | 135 | double |
michael@0 | 136 | TimeDuration::ToSeconds() const |
michael@0 | 137 | { |
michael@0 | 138 | return double(mValue) / kNsPerSecd; |
michael@0 | 139 | } |
michael@0 | 140 | |
michael@0 | 141 | double |
michael@0 | 142 | TimeDuration::ToSecondsSigDigits() const |
michael@0 | 143 | { |
michael@0 | 144 | // don't report a value < mResolution ... |
michael@0 | 145 | int64_t valueSigDigs = sResolution * (mValue / sResolution); |
michael@0 | 146 | // and chop off insignificant digits |
michael@0 | 147 | valueSigDigs = sResolutionSigDigs * (valueSigDigs / sResolutionSigDigs); |
michael@0 | 148 | return double(valueSigDigs) / kNsPerSecd; |
michael@0 | 149 | } |
michael@0 | 150 | |
michael@0 | 151 | TimeDuration |
michael@0 | 152 | TimeDuration::FromMilliseconds(double aMilliseconds) |
michael@0 | 153 | { |
michael@0 | 154 | return TimeDuration::FromTicks(aMilliseconds * kNsPerMsd); |
michael@0 | 155 | } |
michael@0 | 156 | |
michael@0 | 157 | TimeDuration |
michael@0 | 158 | TimeDuration::Resolution() |
michael@0 | 159 | { |
michael@0 | 160 | return TimeDuration::FromTicks(int64_t(sResolution)); |
michael@0 | 161 | } |
michael@0 | 162 | |
michael@0 | 163 | static bool gInitialized = false; |
michael@0 | 164 | |
michael@0 | 165 | nsresult |
michael@0 | 166 | TimeStamp::Startup() |
michael@0 | 167 | { |
michael@0 | 168 | if (gInitialized) |
michael@0 | 169 | return NS_OK; |
michael@0 | 170 | |
michael@0 | 171 | struct timespec dummy; |
michael@0 | 172 | if (0 != clock_gettime(CLOCK_MONOTONIC, &dummy)) |
michael@0 | 173 | NS_RUNTIMEABORT("CLOCK_MONOTONIC is absent!"); |
michael@0 | 174 | |
michael@0 | 175 | sResolution = ClockResolutionNs(); |
michael@0 | 176 | |
michael@0 | 177 | // find the number of significant digits in sResolution, for the |
michael@0 | 178 | // sake of ToSecondsSigDigits() |
michael@0 | 179 | for (sResolutionSigDigs = 1; |
michael@0 | 180 | !(sResolutionSigDigs == sResolution |
michael@0 | 181 | || 10*sResolutionSigDigs > sResolution); |
michael@0 | 182 | sResolutionSigDigs *= 10); |
michael@0 | 183 | |
michael@0 | 184 | gInitialized = true; |
michael@0 | 185 | |
michael@0 | 186 | return NS_OK; |
michael@0 | 187 | } |
michael@0 | 188 | |
michael@0 | 189 | void |
michael@0 | 190 | TimeStamp::Shutdown() |
michael@0 | 191 | { |
michael@0 | 192 | } |
michael@0 | 193 | |
michael@0 | 194 | TimeStamp |
michael@0 | 195 | TimeStamp::Now(bool aHighResolution) |
michael@0 | 196 | { |
michael@0 | 197 | return TimeStamp(ClockTimeNs()); |
michael@0 | 198 | } |
michael@0 | 199 | |
michael@0 | 200 | #if defined(LINUX) || defined(ANDROID) |
michael@0 | 201 | |
michael@0 | 202 | // Calculates the amount of jiffies that have elapsed since boot and up to the |
michael@0 | 203 | // starttime value of a specific process as found in its /proc/*/stat file. |
michael@0 | 204 | // Returns 0 if an error occurred. |
michael@0 | 205 | |
michael@0 | 206 | static uint64_t |
michael@0 | 207 | JiffiesSinceBoot(const char *aFile) |
michael@0 | 208 | { |
michael@0 | 209 | char stat[512]; |
michael@0 | 210 | |
michael@0 | 211 | FILE *f = fopen(aFile, "r"); |
michael@0 | 212 | if (!f) |
michael@0 | 213 | return 0; |
michael@0 | 214 | |
michael@0 | 215 | int n = fread(&stat, 1, sizeof(stat) - 1, f); |
michael@0 | 216 | |
michael@0 | 217 | fclose(f); |
michael@0 | 218 | |
michael@0 | 219 | if (n <= 0) |
michael@0 | 220 | return 0; |
michael@0 | 221 | |
michael@0 | 222 | stat[n] = 0; |
michael@0 | 223 | |
michael@0 | 224 | long long unsigned startTime = 0; // instead of uint64_t to keep GCC quiet |
michael@0 | 225 | char *s = strrchr(stat, ')'); |
michael@0 | 226 | |
michael@0 | 227 | if (!s) |
michael@0 | 228 | return 0; |
michael@0 | 229 | |
michael@0 | 230 | int rv = sscanf(s + 2, |
michael@0 | 231 | "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u " |
michael@0 | 232 | "%*u %*u %*u %*d %*d %*d %*d %*d %*d %llu", |
michael@0 | 233 | &startTime); |
michael@0 | 234 | |
michael@0 | 235 | if (rv != 1 || !startTime) |
michael@0 | 236 | return 0; |
michael@0 | 237 | |
michael@0 | 238 | return startTime; |
michael@0 | 239 | } |
michael@0 | 240 | |
michael@0 | 241 | // Computes the interval that has elapsed between the thread creation and the |
michael@0 | 242 | // process creation by comparing the starttime fields in the respective |
michael@0 | 243 | // /proc/*/stat files. The resulting value will be a good approximation of the |
michael@0 | 244 | // process uptime. This value will be stored at the address pointed by aTime; |
michael@0 | 245 | // if an error occurred 0 will be stored instead. |
michael@0 | 246 | |
michael@0 | 247 | static void |
michael@0 | 248 | ComputeProcessUptimeThread(void *aTime) |
michael@0 | 249 | { |
michael@0 | 250 | uint64_t *uptime = static_cast<uint64_t *>(aTime); |
michael@0 | 251 | long hz = sysconf(_SC_CLK_TCK); |
michael@0 | 252 | |
michael@0 | 253 | *uptime = 0; |
michael@0 | 254 | |
michael@0 | 255 | if (!hz) |
michael@0 | 256 | return; |
michael@0 | 257 | |
michael@0 | 258 | char threadStat[40]; |
michael@0 | 259 | sprintf(threadStat, "/proc/self/task/%d/stat", (pid_t) syscall(__NR_gettid)); |
michael@0 | 260 | |
michael@0 | 261 | uint64_t threadJiffies = JiffiesSinceBoot(threadStat); |
michael@0 | 262 | uint64_t selfJiffies = JiffiesSinceBoot("/proc/self/stat"); |
michael@0 | 263 | |
michael@0 | 264 | if (!threadJiffies || !selfJiffies) |
michael@0 | 265 | return; |
michael@0 | 266 | |
michael@0 | 267 | *uptime = ((threadJiffies - selfJiffies) * kNsPerSec) / hz; |
michael@0 | 268 | } |
michael@0 | 269 | |
michael@0 | 270 | // Computes and returns the process uptime in us on Linux & its derivatives. |
michael@0 | 271 | // Returns 0 if an error was encountered. |
michael@0 | 272 | |
michael@0 | 273 | uint64_t |
michael@0 | 274 | TimeStamp::ComputeProcessUptime() |
michael@0 | 275 | { |
michael@0 | 276 | uint64_t uptime = 0; |
michael@0 | 277 | PRThread *thread = PR_CreateThread(PR_USER_THREAD, |
michael@0 | 278 | ComputeProcessUptimeThread, |
michael@0 | 279 | &uptime, |
michael@0 | 280 | PR_PRIORITY_NORMAL, |
michael@0 | 281 | PR_GLOBAL_THREAD, |
michael@0 | 282 | PR_JOINABLE_THREAD, |
michael@0 | 283 | 0); |
michael@0 | 284 | |
michael@0 | 285 | PR_JoinThread(thread); |
michael@0 | 286 | |
michael@0 | 287 | return uptime / kNsPerUs; |
michael@0 | 288 | } |
michael@0 | 289 | |
michael@0 | 290 | #elif defined(__DragonFly__) || defined(__FreeBSD__) \ |
michael@0 | 291 | || defined(__NetBSD__) || defined(__OpenBSD__) |
michael@0 | 292 | |
michael@0 | 293 | // Computes and returns the process uptime in us on various BSD flavors. |
michael@0 | 294 | // Returns 0 if an error was encountered. |
michael@0 | 295 | |
michael@0 | 296 | uint64_t |
michael@0 | 297 | TimeStamp::ComputeProcessUptime() |
michael@0 | 298 | { |
michael@0 | 299 | struct timespec ts; |
michael@0 | 300 | int rv = clock_gettime(CLOCK_REALTIME, &ts); |
michael@0 | 301 | |
michael@0 | 302 | if (rv == -1) { |
michael@0 | 303 | return 0; |
michael@0 | 304 | } |
michael@0 | 305 | |
michael@0 | 306 | int mib[] = { |
michael@0 | 307 | CTL_KERN, |
michael@0 | 308 | KERN_PROC, |
michael@0 | 309 | KERN_PROC_PID, |
michael@0 | 310 | getpid(), |
michael@0 | 311 | #if defined(__NetBSD__) || defined(__OpenBSD__) |
michael@0 | 312 | sizeof(KINFO_PROC), |
michael@0 | 313 | 1, |
michael@0 | 314 | #endif |
michael@0 | 315 | }; |
michael@0 | 316 | u_int mibLen = sizeof(mib) / sizeof(mib[0]); |
michael@0 | 317 | |
michael@0 | 318 | KINFO_PROC proc; |
michael@0 | 319 | size_t bufferSize = sizeof(proc); |
michael@0 | 320 | rv = sysctl(mib, mibLen, &proc, &bufferSize, nullptr, 0); |
michael@0 | 321 | |
michael@0 | 322 | if (rv == -1) |
michael@0 | 323 | return 0; |
michael@0 | 324 | |
michael@0 | 325 | uint64_t startTime = ((uint64_t)proc.KP_START_SEC * kNsPerSec) |
michael@0 | 326 | + (proc.KP_START_USEC * kNsPerUs); |
michael@0 | 327 | uint64_t now = ((uint64_t)ts.tv_sec * kNsPerSec) + ts.tv_nsec; |
michael@0 | 328 | |
michael@0 | 329 | if (startTime > now) |
michael@0 | 330 | return 0; |
michael@0 | 331 | |
michael@0 | 332 | return (now - startTime) / kNsPerUs; |
michael@0 | 333 | } |
michael@0 | 334 | |
michael@0 | 335 | #else |
michael@0 | 336 | |
michael@0 | 337 | uint64_t |
michael@0 | 338 | TimeStamp::ComputeProcessUptime() |
michael@0 | 339 | { |
michael@0 | 340 | return 0; |
michael@0 | 341 | } |
michael@0 | 342 | |
michael@0 | 343 | #endif |
michael@0 | 344 | |
michael@0 | 345 | } // namespace mozilla |