70 lines
3.3 KiB
Kotlin
70 lines
3.3 KiB
Kotlin
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package org.mozilla.fenix.perf
|
|
|
|
import android.os.SystemClock
|
|
import android.system.Os
|
|
import android.system.OsConstants
|
|
import androidx.annotation.VisibleForTesting
|
|
import androidx.annotation.VisibleForTesting.PRIVATE
|
|
import java.io.File
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
private const val FIELD_POS_STARTTIME = 21 // starttime naming matches field in man page.
|
|
|
|
/**
|
|
* Functionality from stat on the proc pseudo-filesystem common to unix systems. /proc contains
|
|
* information related to active processes. /proc/$pid/stat contains information about the status of
|
|
* the process by the given process id (pid).
|
|
*
|
|
* See the man page - `man 5 proc` - on linux for more information:
|
|
* http://man7.org/linux/man-pages/man5/proc.5.html
|
|
*/
|
|
open class Stat {
|
|
|
|
/**
|
|
* @throws [java.io.FileNotFoundException]
|
|
*/
|
|
@VisibleForTesting(otherwise = PRIVATE)
|
|
open fun getStatText(pid: Int): String = File("/proc/$pid/stat").readText()
|
|
|
|
// See `man 3 sysconf` for details on Os.sysconf and OsConstants:
|
|
// http://man7.org/linux/man-pages/man3/sysconf.3.html
|
|
open val clockTicksPerSecond: Long get() = Os.sysconf(OsConstants._SC_CLK_TCK)
|
|
private val nanosPerClockTick = TimeUnit.SECONDS.toNanos(1).let { nanosPerSecond ->
|
|
// We use nanos per clock tick, rather than clock ticks per nanos, to mitigate float/double
|
|
// rounding errors: this way we can use integer values and divide the larger value by the smaller one.
|
|
nanosPerSecond / clockTicksPerSecond.toDouble()
|
|
}
|
|
|
|
/**
|
|
* Gets the process start time since system boot in ticks, including time spent in suspension/deep sleep.
|
|
* This value can be compared against [SystemClock.elapsedRealtimeNanos]: you can convert between
|
|
* measurements using [convertTicksToNanos] and [convertNanosToTicks].
|
|
*
|
|
* Ticks are "an arbitrary unit for measuring internal system time": https://superuser.com/a/101202
|
|
* They are not aligned with CPU frequency and do not change at runtime but can theoretically
|
|
* change between devices. On the Pixel 2, one tick is equivalent to one centisecond.
|
|
*
|
|
* We confirmed that this measurement and elapsedRealtimeNanos both include suspension time, and
|
|
* are thus comparable, by* looking at their source:
|
|
* - /proc/pid/stat starttime is set using boottime:
|
|
* https://github.com/torvalds/linux/blob/79e178a57dae819ae724065b47c25720494cc9f2/fs/proc/array.c#L536
|
|
* - elapsedRealtimeNanos is set using boottime:
|
|
* https://cs.android.com/android/platform/superproject/+/master:system/core/libutils/SystemClock.cpp;l=60-68;drc=bab16584ce0525742b5370682c9132b2002ee110
|
|
*
|
|
* Perf note: this call reads from the pseudo-filesystem using the java File APIs, which isn't
|
|
* likely to be a very optimized call path.
|
|
*
|
|
* Implementation inspired by https://stackoverflow.com/a/42195623.
|
|
*/
|
|
fun getProcessStartTimeTicks(pid: Int): Long {
|
|
return getStatText(pid).split(' ')[FIELD_POS_STARTTIME].toLong()
|
|
}
|
|
|
|
fun convertTicksToNanos(ticks: Long): Double = ticks * nanosPerClockTick
|
|
fun convertNanosToTicks(nanos: Long): Double = nanos / nanosPerClockTick
|
|
}
|