1
0
Fork 0

For #8803: add Stat and test.

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.
master
Michael Comella 2020-04-03 16:46:48 -07:00 committed by Michael Comella
parent a0c4b33b0f
commit 7f618a6a7c
2 changed files with 111 additions and 0 deletions

View File

@ -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
}

View File

@ -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
}