We need to access the data in stat to get the process start time, so we
can calculate the time from process start until application.init for the
frameworkStart probe.
pull/600/head
Michael Comella 4 years ago committed by Michael Comella
parent 3556b5a10f
commit 162a93a190

@ -0,0 +1,66 @@
/* 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 {
@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
}

@ -0,0 +1,45 @@
/* 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 org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
private const val STAT_CONTENTS = "32250 (a.fennec_aurora) S 831 831 0 0 -1 1077952832 670949 0 184936 0 15090 5387 0 0 20 0 119 0 166636813 9734365184 24664 18446744073709551615 1 1 0 0 0 0 4612 4097 1073792254 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0"
private const val CLOCK_TICKS_PER_SECOND = 100L // actual value on the Pixel 2.
class StatTest {
private lateinit var stat: StatTestImpl
@Before
fun setUp() {
stat = StatTestImpl()
}
@Test
fun `WHEN getting the process start time THEN the correct value is returned`() {
val actual = stat.getProcessStartTimeTicks(pid = -1) // pid behavior is overridden.
assertEquals(166636813, actual) // expected value calculated by hand.
}
@Test
fun `WHEN converting ticks to nanos THEN the correct value is returned`() {
val actual = stat.convertTicksToNanos(166_636_813)
assertEquals(1_666_368_130_000_000.0, actual, 0.0) // expected value calculated by hand.
}
@Test
fun `WHEN converting nanos to ticks THEN the correct value is returned`() {
val actual = stat.convertNanosToTicks(1_666_368_135_432_102)
assertEquals(166_636_813.5432102, actual, 0.0) // expected value calculated by hand.
}
}
class StatTestImpl : Stat() {
override fun getStatText(pid: Int): String = STAT_CONTENTS
override val clockTicksPerSecond: Long get() = CLOCK_TICKS_PER_SECOND
}
Loading…
Cancel
Save