You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iceraven-browser/app/src/main/java/org/mozilla/fenix/perf/ProfilerMarkerFactProcessor.kt

83 lines
3.8 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.Handler
import android.os.Looper
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import mozilla.components.concept.base.profiler.Profiler
import mozilla.components.support.base.facts.Action
import mozilla.components.support.base.facts.Fact
import mozilla.components.support.base.facts.FactProcessor
/**
* A fact processor that adds Gecko profiler markers for [Fact]s matching a specific format.
* We look for the following format:
* ```
* Fact(
* action = Action.IMPLEMENTATION_DETAIL
* item = <marker name>
* )
* ```
*
* This allows us to add profiler markers from android-components code. Using the Fact API for this
* purpose, rather than calling [Profiler.addMarker] directly inside components, has trade-offs. Its
* downsides are that it is less explicit and tooling does not work as well on it. However, we felt
* it was worthwhile because:
*
* 1. we don't know what profiler markers are useful so we want to be able to iterate quickly.
* Adding dependencies on the Profiler and landing these changes across two repos hinders that
* 2. we want to instrument the code as close to specific method calls as possible (e.g.
* GeckoSession.loadUrl) but it's not always easy to do so (e.g. in the previous example, passing a
* Profiler reference to GeckoEngineSession is difficult because GES is not a global dependency)
* 3. we can only add Profiler markers from the main thread so adding markers will become more
* difficult if we have to understand the threading needs of each Profiler call site
*
* An additional benefit with having this infrastructure is that it's easy to add Profiler markers
* for local debugging.
*
* That being said, if we find a location where it would be valuable to have a long term Profiler
* marker, we should consider instrumenting it via the [Profiler] API.
*/
class ProfilerMarkerFactProcessor @VisibleForTesting(otherwise = PRIVATE) constructor(
// We use a provider to defer accessing the profiler until we need it, because the property is a
// child of the engine property and we don't want to initialize it earlier than we intend to.
private val profilerProvider: () -> Profiler?,
private val mainHandler: Handler = Handler(Looper.getMainLooper()),
private val getMyLooper: () -> Looper? = { Looper.myLooper() }
) : FactProcessor {
override fun process(fact: Fact) {
if (fact.action != Action.IMPLEMENTATION_DETAIL) {
return
}
val markerName = fact.item
// Java profiler markers can only be added from the main thread so, for now, we push all
// markers to the the main thread (which also groups all the markers together,
// making it easier to read).
val profiler = profilerProvider()
if (getMyLooper() == mainHandler.looper) {
profiler?.addMarker(markerName)
} else {
// To reduce the performance burden, we could early return if the profiler isn't active.
// However, this would change the performance characteristics from when the profiler is
// active and when it's inactive so we always post instead.
val now = profiler?.getProfilerTime()
mainHandler.post {
// We set now to both start and end time because we want a marker of without duration
// and if end is omitted, the duration is created implicitly.
profiler?.addMarker(markerName, now, now, null)
}
}
}
companion object {
fun create(profilerProvider: () -> Profiler?) = ProfilerMarkerFactProcessor(profilerProvider)
}
}