зеркало из https://github.com/mozilla/glean.git
[UniFFI] Migrate timespan metric implementation
This commit is contained in:
Родитель
c5f9686bab
Коммит
dd8056d2aa
|
@ -4,14 +4,7 @@
|
|||
|
||||
package mozilla.telemetry.glean.private
|
||||
|
||||
import android.os.SystemClock
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.sun.jna.StringArray
|
||||
import mozilla.telemetry.glean.Dispatchers
|
||||
import mozilla.telemetry.glean.rust.LibGleanFFI
|
||||
import mozilla.telemetry.glean.rust.toBoolean
|
||||
import mozilla.telemetry.glean.rust.toByte
|
||||
import mozilla.telemetry.glean.testing.ErrorType
|
||||
|
||||
/**
|
||||
* This implements the developer facing API for recording timespans.
|
||||
|
@ -21,184 +14,30 @@ import mozilla.telemetry.glean.testing.ErrorType
|
|||
*
|
||||
* The timespans API exposes the [start], [stop] and [cancel] methods.
|
||||
*/
|
||||
class TimespanMetricType internal constructor(
|
||||
private var handle: Long,
|
||||
private val disabled: Boolean,
|
||||
private val sendInPings: List<String>
|
||||
) {
|
||||
/**
|
||||
* The public constructor used by automatically generated metrics.
|
||||
*/
|
||||
constructor(
|
||||
disabled: Boolean,
|
||||
category: String,
|
||||
lifetime: Lifetime,
|
||||
name: String,
|
||||
sendInPings: List<String>,
|
||||
timeUnit: TimeUnit = TimeUnit.Minute
|
||||
) : this(handle = 0, disabled = disabled, sendInPings = sendInPings) {
|
||||
val ffiPingsList = StringArray(sendInPings.toTypedArray(), "utf-8")
|
||||
this.handle = LibGleanFFI.INSTANCE.glean_new_timespan_metric(
|
||||
category = category,
|
||||
name = name,
|
||||
send_in_pings = ffiPingsList,
|
||||
send_in_pings_len = sendInPings.size,
|
||||
lifetime = lifetime.ordinal,
|
||||
disabled = disabled.toByte(),
|
||||
time_unit = timeUnit.ordinal
|
||||
)
|
||||
typealias TimespanMetricType = mozilla.telemetry.glean.internal.TimespanMetric
|
||||
|
||||
/**
|
||||
* Convenience method to simplify measuring a function or block of code
|
||||
*
|
||||
* If the measured function throws, the measurement is canceled and the exception rethrown.
|
||||
*/
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
fun <U> TimespanMetricType.measure(funcToMeasure: () -> U): U {
|
||||
this.start()
|
||||
|
||||
val returnValue = try {
|
||||
funcToMeasure()
|
||||
} catch (e: Exception) {
|
||||
this.cancel()
|
||||
throw e
|
||||
}
|
||||
|
||||
/**
|
||||
* Start tracking time for the provided metric.
|
||||
* This records an error if it’s already tracking time (i.e. start was already
|
||||
* called with no corresponding [stop]): in that case the original
|
||||
* start time will be preserved.
|
||||
*/
|
||||
fun start() {
|
||||
if (disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
val startTime = SystemClock.elapsedRealtimeNanos()
|
||||
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
Dispatchers.API.launch {
|
||||
LibGleanFFI.INSTANCE.glean_timespan_set_start(this@TimespanMetricType.handle, startTime)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop tracking time for the provided metric.
|
||||
* Sets the metric to the elapsed time, but does not overwrite an already
|
||||
* existing value.
|
||||
* This will record an error if no [start] was called or there is an already
|
||||
* existing value.
|
||||
*/
|
||||
fun stop() {
|
||||
if (disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
val stopTime = SystemClock.elapsedRealtimeNanos()
|
||||
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
Dispatchers.API.launch {
|
||||
LibGleanFFI.INSTANCE.glean_timespan_set_stop(this@TimespanMetricType.handle, stopTime)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to simplify measuring a function or block of code
|
||||
*
|
||||
* If the measured function throws, the measurement is canceled and the exception rethrown.
|
||||
*/
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
fun <U> measure(funcToMeasure: () -> U): U {
|
||||
start()
|
||||
|
||||
val returnValue = try {
|
||||
funcToMeasure()
|
||||
} catch (e: Exception) {
|
||||
cancel()
|
||||
throw e
|
||||
}
|
||||
|
||||
stop()
|
||||
return returnValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort a previous [start] call. No error is recorded if no [start] was called.
|
||||
*/
|
||||
fun cancel() {
|
||||
if (disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
Dispatchers.API.launch {
|
||||
LibGleanFFI.INSTANCE.glean_timespan_cancel(this@TimespanMetricType.handle)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set the timespan value, in nanoseconds.
|
||||
*
|
||||
* This API should only be used if your library or application requires recording
|
||||
* times in a way that can not make use of [start]/[stop]/[cancel].
|
||||
*
|
||||
* [setRawNanos] does not overwrite a running timer or an already existing value.
|
||||
*
|
||||
* @param elapsedNanos The elapsed time to record, in nanoseconds.
|
||||
*/
|
||||
fun setRawNanos(elapsedNanos: Long) {
|
||||
if (disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
Dispatchers.API.launch {
|
||||
LibGleanFFI.INSTANCE.glean_timespan_set_raw_nanos(
|
||||
this@TimespanMetricType.handle,
|
||||
elapsedNanos)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether a value is stored for the metric for testing purposes only
|
||||
*
|
||||
* @param pingName represents the name of the ping to retrieve the metric for.
|
||||
* Defaults to the first value in `sendInPings`.
|
||||
* @return true if metric value exists, otherwise false
|
||||
*/
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
@JvmOverloads
|
||||
fun testHasValue(pingName: String = sendInPings.first()): Boolean {
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
Dispatchers.API.assertInTestingMode()
|
||||
|
||||
return LibGleanFFI
|
||||
.INSTANCE.glean_timespan_test_has_value(this.handle, pingName)
|
||||
.toBoolean()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stored value for testing purposes only
|
||||
*
|
||||
* @param pingName represents the name of the ping to retrieve the metric for.
|
||||
* Defaults to the first value in `sendInPings`.
|
||||
* @return value of the stored metric
|
||||
* @throws [NullPointerException] if no value is stored
|
||||
*/
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
@JvmOverloads
|
||||
fun testGetValue(pingName: String = sendInPings.first()): Long {
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
Dispatchers.API.assertInTestingMode()
|
||||
|
||||
if (!testHasValue(pingName)) {
|
||||
throw NullPointerException("Metric has no value")
|
||||
}
|
||||
return LibGleanFFI.INSTANCE.glean_timespan_test_get_value(this.handle, pingName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of errors recorded for the given metric.
|
||||
*
|
||||
* @param errorType The type of the error recorded.
|
||||
* @param pingName represents the name of the ping to retrieve the metric for.
|
||||
* Defaults to the first value in `sendInPings`.
|
||||
* @return the number of errors recorded for the metric.
|
||||
*/
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
@JvmOverloads
|
||||
fun testGetNumRecordedErrors(errorType: ErrorType, pingName: String = sendInPings.first()): Int {
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
Dispatchers.API.assertInTestingMode()
|
||||
|
||||
return LibGleanFFI.INSTANCE.glean_timespan_test_get_num_recorded_errors(
|
||||
this.handle, errorType.ordinal, pingName
|
||||
)
|
||||
}
|
||||
this.stop()
|
||||
return returnValue
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
@JvmOverloads
|
||||
fun TimespanMetricType.testHasValue(pingName: String? = null): Boolean {
|
||||
return this.testGetValue(pingName) != null
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import mozilla.telemetry.glean.testing.GleanTestRule
|
|||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -28,13 +29,13 @@ class TimespanMetricTypeTest {
|
|||
@Test
|
||||
fun `The API must record to its storage engine`() {
|
||||
// Define a timespan metric, which will be stored in "store1"
|
||||
val metric = TimespanMetricType(
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = false,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.Application,
|
||||
lifetime = Lifetime.APPLICATION,
|
||||
name = "timespan_metric",
|
||||
sendInPings = listOf("store1"),
|
||||
timeUnit = TimeUnit.Millisecond
|
||||
), timeUnit = TimeUnit.MILLISECOND
|
||||
)
|
||||
|
||||
// Record a timespan.
|
||||
|
@ -43,19 +44,19 @@ class TimespanMetricTypeTest {
|
|||
|
||||
// Check that data was properly recorded.
|
||||
assertTrue(metric.testHasValue())
|
||||
assertTrue(metric.testGetValue() >= 0)
|
||||
assertTrue(metric.testGetValue()!! >= 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `The API should not record if the metric is disabled`() {
|
||||
// Define a timespan metric, which will be stored in "store1"
|
||||
val metric = TimespanMetricType(
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = true,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.Application,
|
||||
lifetime = Lifetime.APPLICATION,
|
||||
name = "timespan_metric",
|
||||
sendInPings = listOf("store1"),
|
||||
timeUnit = TimeUnit.Millisecond
|
||||
), timeUnit = TimeUnit.MILLISECOND
|
||||
)
|
||||
|
||||
// Record a timespan.
|
||||
|
@ -73,13 +74,13 @@ class TimespanMetricTypeTest {
|
|||
@Test
|
||||
fun `The API must correctly cancel`() {
|
||||
// Define a timespan metric, which will be stored in "store1"
|
||||
val metric = TimespanMetricType(
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = false,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.Application,
|
||||
lifetime = Lifetime.APPLICATION,
|
||||
name = "timespan_metric",
|
||||
sendInPings = listOf("store1"),
|
||||
timeUnit = TimeUnit.Millisecond
|
||||
), timeUnit = TimeUnit.MILLISECOND
|
||||
)
|
||||
|
||||
// Record a timespan.
|
||||
|
@ -90,32 +91,33 @@ class TimespanMetricTypeTest {
|
|||
// Check that data was not recorded.
|
||||
assertFalse("The API should not record a counter if metric is cancelled",
|
||||
metric.testHasValue())
|
||||
assertEquals(1, metric.testGetNumRecordedErrors(ErrorType.InvalidState))
|
||||
assertEquals(1, metric.testGetNumRecordedErrors(ErrorType.INVALID_STATE))
|
||||
}
|
||||
|
||||
@Test(expected = NullPointerException::class)
|
||||
// TODO: Fixme: should we continue throwing an exception instead?
|
||||
@Test //(expected = NullPointerException::class)
|
||||
fun `testGetValue() throws NullPointerException if nothing is stored`() {
|
||||
val metric = TimespanMetricType(
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = false,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.Application,
|
||||
lifetime = Lifetime.APPLICATION,
|
||||
name = "timespan_metric",
|
||||
sendInPings = listOf("store1"),
|
||||
timeUnit = TimeUnit.Millisecond
|
||||
), timeUnit = TimeUnit.MILLISECOND
|
||||
)
|
||||
metric.testGetValue()
|
||||
assertNull(metric.testGetValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `The API saves to secondary pings`() {
|
||||
// Define a timespan metric, which will be stored in "store1" and "store2"
|
||||
val metric = TimespanMetricType(
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = false,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.Application,
|
||||
lifetime = Lifetime.APPLICATION,
|
||||
name = "timespan_metric",
|
||||
sendInPings = listOf("store1", "store2"),
|
||||
timeUnit = TimeUnit.Millisecond
|
||||
), timeUnit = TimeUnit.MILLISECOND
|
||||
)
|
||||
|
||||
// Record a timespan.
|
||||
|
@ -124,19 +126,19 @@ class TimespanMetricTypeTest {
|
|||
|
||||
// Check that data was properly recorded in the second ping.
|
||||
assertTrue(metric.testHasValue("store2"))
|
||||
assertTrue(metric.testGetValue("store2") >= 0)
|
||||
assertTrue(metric.testGetValue("store2")!! >= 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Records an error if started twice`() {
|
||||
// Define a timespan metric, which will be stored in "store1" and "store2"
|
||||
val metric = TimespanMetricType(
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = false,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.Application,
|
||||
lifetime = Lifetime.APPLICATION,
|
||||
name = "timespan_metric",
|
||||
sendInPings = listOf("store1", "store2"),
|
||||
timeUnit = TimeUnit.Millisecond
|
||||
), timeUnit = TimeUnit.MILLISECOND
|
||||
)
|
||||
|
||||
// Record a timespan.
|
||||
|
@ -146,20 +148,20 @@ class TimespanMetricTypeTest {
|
|||
|
||||
// Check that data was properly recorded in the second ping.
|
||||
assertTrue(metric.testHasValue("store2"))
|
||||
assertTrue(metric.testGetValue("store2") >= 0)
|
||||
assertEquals(1, metric.testGetNumRecordedErrors(ErrorType.InvalidState))
|
||||
assertTrue(metric.testGetValue("store2")!! >= 0)
|
||||
assertEquals(1, metric.testGetNumRecordedErrors(ErrorType.INVALID_STATE))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Value unchanged if stopped twice`() {
|
||||
// Define a timespan metric, which will be stored in "store1" and "store2"
|
||||
val metric = TimespanMetricType(
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = false,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.Application,
|
||||
lifetime = Lifetime.APPLICATION,
|
||||
name = "timespan_metric",
|
||||
sendInPings = listOf("store1"),
|
||||
timeUnit = TimeUnit.Nanosecond
|
||||
), timeUnit = TimeUnit.NANOSECOND
|
||||
)
|
||||
|
||||
// Record a timespan.
|
||||
|
@ -177,52 +179,51 @@ class TimespanMetricTypeTest {
|
|||
fun `test setRawNanos`() {
|
||||
val timespanNanos = 6 * 1000000000L
|
||||
|
||||
val metric = TimespanMetricType(
|
||||
false,
|
||||
"telemetry",
|
||||
Lifetime.Ping,
|
||||
"explicit_timespan",
|
||||
listOf("store1"),
|
||||
timeUnit = TimeUnit.Second
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = false,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.PING,
|
||||
name = "explicit_timespan",
|
||||
sendInPings = listOf("store1"),
|
||||
), timeUnit = TimeUnit.SECOND
|
||||
)
|
||||
|
||||
metric.setRawNanos(timespanNanos)
|
||||
assertEquals(6, metric.testGetValue())
|
||||
assertEquals(6, metric.testGetValue()!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test setRawNanos followed by other API`() {
|
||||
val timespanNanos = 6 * 1000000000L
|
||||
|
||||
val metric = TimespanMetricType(
|
||||
false,
|
||||
"telemetry",
|
||||
Lifetime.Ping,
|
||||
"explicit_timespan_1",
|
||||
listOf("store1"),
|
||||
timeUnit = TimeUnit.Second
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = false,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.PING,
|
||||
name = "explicit_timespan_1",
|
||||
sendInPings = listOf("store1"),
|
||||
), timeUnit = TimeUnit.SECOND
|
||||
)
|
||||
|
||||
metric.setRawNanos(timespanNanos)
|
||||
assertEquals(6, metric.testGetValue())
|
||||
assertEquals(6, metric.testGetValue()!!)
|
||||
|
||||
metric.start()
|
||||
metric.stop()
|
||||
val value = metric.testGetValue()
|
||||
assertEquals(6, value)
|
||||
assertEquals(6, metric.testGetValue()!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setRawNanos does not overwrite value`() {
|
||||
val timespanNanos = 6 * 1000000000L
|
||||
|
||||
val metric = TimespanMetricType(
|
||||
false,
|
||||
"telemetry",
|
||||
Lifetime.Ping,
|
||||
"explicit_timespan_1",
|
||||
listOf("store1"),
|
||||
timeUnit = TimeUnit.Second
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = false,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.PING,
|
||||
name = "explicit_timespan_2",
|
||||
sendInPings = listOf("store1"),
|
||||
), timeUnit = TimeUnit.SECOND
|
||||
)
|
||||
|
||||
metric.start()
|
||||
|
@ -231,20 +232,20 @@ class TimespanMetricTypeTest {
|
|||
|
||||
metric.setRawNanos(timespanNanos)
|
||||
|
||||
assertEquals(value, metric.testGetValue())
|
||||
assertEquals(value, metric.testGetValue()!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setRawNanos does nothing when timer is running`() {
|
||||
val timespanNanos = 1000000000L
|
||||
|
||||
val metric = TimespanMetricType(
|
||||
false,
|
||||
"telemetry",
|
||||
Lifetime.Ping,
|
||||
"explicit_timespan",
|
||||
listOf("store1"),
|
||||
timeUnit = TimeUnit.Second
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = false,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.PING,
|
||||
name = "explicit_timespan_3",
|
||||
sendInPings = listOf("store1"),
|
||||
), timeUnit = TimeUnit.SECOND
|
||||
)
|
||||
|
||||
metric.start()
|
||||
|
@ -253,19 +254,19 @@ class TimespanMetricTypeTest {
|
|||
|
||||
// If setRawNanos worked, (which it's not supposed to in this case), it would
|
||||
// have recorded 1000000000 ns == 1s. Make sure it's not that.
|
||||
assertNotEquals(1, metric.testGetValue())
|
||||
assertNotEquals(1, metric.testGetValue()!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `measure function correctly measures values`() {
|
||||
// Define a timespan metric, which will be stored in "store1"
|
||||
val metric = TimespanMetricType(
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = false,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.Application,
|
||||
lifetime = Lifetime.APPLICATION,
|
||||
name = "timespan_metric",
|
||||
sendInPings = listOf("store1"),
|
||||
timeUnit = TimeUnit.Millisecond
|
||||
), timeUnit = TimeUnit.MILLISECOND
|
||||
)
|
||||
|
||||
// Create a function to measure, which also returns a value to test that we properly pass
|
||||
|
@ -285,18 +286,18 @@ class TimespanMetricTypeTest {
|
|||
|
||||
// Check that data was properly recorded.
|
||||
assertTrue("Metric must have a value", metric.testHasValue())
|
||||
assertTrue("Metric value must be greater than zero", metric.testGetValue() >= 0)
|
||||
assertTrue("Metric value must be greater than zero", metric.testGetValue()!! >= 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `measure function does not change behavior with early return`() {
|
||||
val metric = TimespanMetricType(
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = false,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.Ping,
|
||||
lifetime = Lifetime.PING,
|
||||
name = "inlined",
|
||||
sendInPings = listOf("store1"),
|
||||
timeUnit = TimeUnit.Nanosecond
|
||||
), timeUnit = TimeUnit.NANOSECOND
|
||||
)
|
||||
|
||||
// We define a function that measures the whole function call runtime
|
||||
|
@ -315,19 +316,19 @@ class TimespanMetricTypeTest {
|
|||
assertEquals("Test value must match", 17, res)
|
||||
|
||||
assertTrue("Metric must have a value", metric.testHasValue())
|
||||
assertTrue("Metric value must be greater than zero", metric.testGetValue() >= 0)
|
||||
assertTrue("Metric value must be greater than zero", metric.testGetValue()!! >= 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `measure function bubbles up exceptions and timing is canceled`() {
|
||||
// Define a timespan metric, which will be stored in "store1"
|
||||
val metric = TimespanMetricType(
|
||||
val metric = TimespanMetricType(CommonMetricData(
|
||||
disabled = false,
|
||||
category = "telemetry",
|
||||
lifetime = Lifetime.Application,
|
||||
lifetime = Lifetime.APPLICATION,
|
||||
name = "timespan_metric",
|
||||
sendInPings = listOf("store1"),
|
||||
timeUnit = TimeUnit.Millisecond
|
||||
), timeUnit = TimeUnit.MILLISECOND
|
||||
)
|
||||
|
||||
// Create a function that will throw a NPE
|
||||
|
|
|
@ -126,6 +126,17 @@ enum Lifetime {
|
|||
"User",
|
||||
};
|
||||
|
||||
enum ErrorType {
|
||||
// For when the value to be recorded does not match the metric-specific restrictions
|
||||
"InvalidValue",
|
||||
// For when the label of a labeled metric does not match the restrictions
|
||||
"InvalidLabel",
|
||||
// For when the metric caught an invalid state while recording
|
||||
"InvalidState",
|
||||
// For when the value to be recorded overflows the metric-specific upper range
|
||||
"InvalidOverflow",
|
||||
};
|
||||
|
||||
dictionary CommonMetricData {
|
||||
string category;
|
||||
string name;
|
||||
|
@ -149,3 +160,38 @@ interface PingType {
|
|||
constructor(string name, boolean include_client_id, boolean send_if_empty, sequence<string> reason_codes);
|
||||
void submit(optional string? reason = null);
|
||||
};
|
||||
|
||||
// Different resolutions supported by the time related metric types
|
||||
// (e.g. DatetimeMetric).
|
||||
enum TimeUnit {
|
||||
// Represents nanosecond precision.
|
||||
"Nanosecond",
|
||||
// Represents microsecond precision.
|
||||
"Microsecond",
|
||||
// Represents millisecond precision.
|
||||
"Millisecond",
|
||||
// Represents second precision.
|
||||
"Second",
|
||||
// Represents minute precision.
|
||||
"Minute",
|
||||
// Represents hour precision.
|
||||
"Hour",
|
||||
// Represents day precision.
|
||||
"Day",
|
||||
};
|
||||
|
||||
interface TimespanMetric {
|
||||
constructor(CommonMetricData meta, TimeUnit time_unit);
|
||||
|
||||
void start();
|
||||
|
||||
void stop();
|
||||
|
||||
void cancel();
|
||||
|
||||
void set_raw_nanos(i64 elapsed);
|
||||
|
||||
i64? test_get_value(optional string? ping_name = null);
|
||||
|
||||
i32 test_get_num_recorded_errors(ErrorType error, optional string? ping_name = null);
|
||||
};
|
||||
|
|
|
@ -48,7 +48,7 @@ pub use crate::core::Glean;
|
|||
use crate::core_metrics::ClientInfoMetrics;
|
||||
pub use crate::error::{Error, ErrorKind, Result};
|
||||
pub use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
|
||||
pub use crate::metrics::{CounterMetric, PingType, RecordedExperiment};
|
||||
pub use crate::metrics::{CounterMetric, PingType, RecordedExperiment, TimeUnit, TimespanMetric};
|
||||
pub use crate::upload::{PingRequest, PingUploadTask, UploadResult};
|
||||
|
||||
const GLEAN_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
|
|
@ -687,14 +687,14 @@ fn test_change_metric_type_runtime() {
|
|||
timespan_metric.set_stop(&glean, duration);
|
||||
|
||||
assert_eq!(
|
||||
timespan_metric.get_value(&glean, Some(ping_name)).unwrap(),
|
||||
timespan_metric.get_value(&glean, ping_name).unwrap(),
|
||||
60,
|
||||
"Expected properly deserialized time"
|
||||
);
|
||||
|
||||
// We expect old data to be lost forever. See the following bug comment
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1621757#c1 for more context.
|
||||
assert_eq!(None, string_metric.test_get_value(&glean, ping_name));
|
||||
assert_eq!(None, string_metric.get_value(&glean, ping_name));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::error_recording::{record_error, ErrorType};
|
||||
use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
|
||||
use crate::metrics::time_unit::TimeUnit;
|
||||
use crate::metrics::Metric;
|
||||
use crate::metrics::MetricType;
|
||||
|
@ -173,6 +174,25 @@ impl TimespanMetric {
|
|||
crate::launch_with_glean(move |glean| metric.set_raw_sync(glean, elapsed));
|
||||
}
|
||||
|
||||
/// Explicitly sets the timespan value.
|
||||
///
|
||||
/// This API should only be used if your library or application requires
|
||||
/// recording times in a way that can not make use of
|
||||
/// [`set_start`](TimespanMetric::set_start)/[`set_stop`](TimespanMetric::set_stop)/[`cancel`](TimespanMetric::cancel).
|
||||
///
|
||||
/// Care should be taken using this if the ping lifetime might contain more
|
||||
/// than one timespan measurement. To be safe,
|
||||
/// [`set_raw`](TimespanMetric::set_raw) should generally be followed by
|
||||
/// sending a custom ping containing the timespan.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `elapsed_nanos` - The elapsed time to record, in nanoseconds.
|
||||
pub fn set_raw_nanos(&self, elapsed_nanos: i64) {
|
||||
let elapsed = Duration::from_nanos(elapsed_nanos.try_into().unwrap_or(0));
|
||||
self.set_raw(elapsed)
|
||||
}
|
||||
|
||||
/// Set raw but sync
|
||||
pub fn set_raw_sync(&self, glean: &Glean, elapsed: Duration) {
|
||||
if !self.meta.should_record() {
|
||||
|
@ -229,14 +249,25 @@ impl TimespanMetric {
|
|||
/// Gets the currently stored value as an integer.
|
||||
///
|
||||
/// This doesn't clear the stored value.
|
||||
pub fn test_get_value(&self, ping_name: Option<String>) -> Option<u64> {
|
||||
pub fn test_get_value(&self, ping_name: Option<String>) -> Option<i64> {
|
||||
crate::block_on_dispatcher();
|
||||
crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref()))
|
||||
crate::core::with_glean(|glean| {
|
||||
self.get_value(glean, ping_name.as_deref()).map(|val| {
|
||||
val.try_into()
|
||||
.expect("Timespan can't be represented as i64")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the current value
|
||||
pub fn get_value(&self, glean: &Glean, ping_name: Option<&str>) -> Option<u64> {
|
||||
let queried_ping_name = ping_name.unwrap_or_else(|| &self.meta.send_in_pings[0]);
|
||||
pub fn get_value<'a, S: Into<Option<&'a str>>>(
|
||||
&self,
|
||||
glean: &Glean,
|
||||
ping_name: S,
|
||||
) -> Option<u64> {
|
||||
let queried_ping_name = ping_name
|
||||
.into()
|
||||
.unwrap_or_else(|| &self.meta().send_in_pings[0]);
|
||||
|
||||
match StorageManager.snapshot_metric_for_test(
|
||||
glean.storage(),
|
||||
|
@ -248,4 +279,26 @@ impl TimespanMetric {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// **Exported for test purposes.**
|
||||
///
|
||||
/// Gets the number of recorded errors for the given metric and error type.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `error` - The type of error
|
||||
/// * `ping_name` - represents the optional name of the ping to retrieve the
|
||||
/// metric for. Defaults to the first value in `send_in_pings`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The number of errors reported.
|
||||
pub fn test_get_num_recorded_errors(&self, error: ErrorType, ping_name: Option<String>) -> i32 {
|
||||
crate::block_on_dispatcher();
|
||||
|
||||
crate::core::with_glean_mut(|glean| {
|
||||
test_get_num_recorded_errors(glean, self.meta(), error, ping_name.as_deref())
|
||||
.unwrap_or(0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ fn serializer_should_correctly_serialize_timespans() {
|
|||
metric.set_stop(&glean, duration);
|
||||
|
||||
let val = metric
|
||||
.get_value(&glean, Some("store1"))
|
||||
.get_value(&glean, "store1")
|
||||
.expect("Value should be stored");
|
||||
assert_eq!(duration, val, "Recorded timespan should be positive.");
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ fn single_elapsed_time_must_be_recorded() {
|
|||
metric.set_stop(&glean, duration);
|
||||
|
||||
let val = metric
|
||||
.get_value(&glean, Some("store1"))
|
||||
.get_value(&glean, "store1")
|
||||
.expect("Value should be stored");
|
||||
assert_eq!(duration, val, "Recorded timespan should be positive.");
|
||||
}
|
||||
|
@ -119,13 +119,13 @@ fn second_timer_run_is_skipped() {
|
|||
test_get_num_recorded_errors(&glean, metric.meta(), ErrorType::InvalidState, None).is_err()
|
||||
);
|
||||
|
||||
let first_value = metric.get_value(&glean, Some("store1")).unwrap();
|
||||
let first_value = metric.get_value(&glean, "store1").unwrap();
|
||||
assert_eq!(duration, first_value);
|
||||
|
||||
metric.set_start(&glean, 0);
|
||||
metric.set_stop(&glean, duration * 2);
|
||||
|
||||
let second_value = metric.get_value(&glean, Some("store1")).unwrap();
|
||||
let second_value = metric.get_value(&glean, "store1").unwrap();
|
||||
assert_eq!(second_value, first_value);
|
||||
|
||||
// Make sure that the error has been recorded: we had a stored value, the
|
||||
|
@ -168,7 +168,7 @@ fn recorded_time_conforms_to_resolution() {
|
|||
ns_metric.set_start(&glean, 0);
|
||||
ns_metric.set_stop(&glean, duration);
|
||||
|
||||
let ns_value = ns_metric.get_value(&glean, Some("store1")).unwrap();
|
||||
let ns_value = ns_metric.get_value(&glean, "store1").unwrap();
|
||||
assert_eq!(duration, ns_value);
|
||||
|
||||
// 1 minute in nanoseconds
|
||||
|
@ -176,7 +176,7 @@ fn recorded_time_conforms_to_resolution() {
|
|||
minute_metric.set_start(&glean, 0);
|
||||
minute_metric.set_stop(&glean, duration_minute);
|
||||
|
||||
let minute_value = minute_metric.get_value(&glean, Some("store1")).unwrap();
|
||||
let minute_value = minute_metric.get_value(&glean, "store1").unwrap();
|
||||
assert_eq!(1, minute_value);
|
||||
}
|
||||
|
||||
|
@ -201,7 +201,7 @@ fn cancel_does_not_store() {
|
|||
metric.set_start(&glean, 0);
|
||||
metric.cancel();
|
||||
|
||||
assert_eq!(None, metric.get_value(&glean, Some("store1")));
|
||||
assert_eq!(None, metric.get_value(&glean, "store1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -224,10 +224,10 @@ fn nothing_stored_before_stop() {
|
|||
|
||||
metric.set_start(&glean, 0);
|
||||
|
||||
assert_eq!(None, metric.get_value(&glean, Some("store1")));
|
||||
assert_eq!(None, metric.get_value(&glean, "store1"));
|
||||
|
||||
metric.set_stop(&glean, duration);
|
||||
assert_eq!(duration, metric.get_value(&glean, Some("store1")).unwrap());
|
||||
assert_eq!(duration, metric.get_value(&glean, "store1").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -250,7 +250,7 @@ fn set_raw_time() {
|
|||
metric.set_raw_sync(&glean, time);
|
||||
|
||||
let time_in_ns = time.as_nanos() as u64;
|
||||
assert_eq!(Some(time_in_ns), metric.get_value(&glean, Some("store1")));
|
||||
assert_eq!(Some(time_in_ns), metric.get_value(&glean, "store1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -276,7 +276,7 @@ fn set_raw_time_does_nothing_when_timer_running() {
|
|||
metric.set_stop(&glean, 60);
|
||||
|
||||
// We expect the start/stop value, not the raw value.
|
||||
assert_eq!(Some(60), metric.get_value(&glean, Some("store1")));
|
||||
assert_eq!(Some(60), metric.get_value(&glean, "store1"));
|
||||
|
||||
// Make sure that the error has been recorded
|
||||
assert_eq!(
|
||||
|
@ -319,7 +319,7 @@ fn timespan_is_not_tracked_across_upload_toggle() {
|
|||
metric.set_stop(&glean, 200);
|
||||
|
||||
// Nothing should have been recorded.
|
||||
assert_eq!(None, metric.get_value(&glean, Some("store1")));
|
||||
assert_eq!(None, metric.get_value(&glean, "store1"));
|
||||
|
||||
// Make sure that the error has been recorded
|
||||
assert_eq!(
|
||||
|
@ -345,7 +345,7 @@ fn time_cannot_go_backwards() {
|
|||
// Time cannot go backwards.
|
||||
metric.set_start(&glean, 10);
|
||||
metric.set_stop(&glean, 0);
|
||||
assert!(metric.get_value(&glean, Some("test1")).is_none());
|
||||
assert!(metric.get_value(&glean, "test1").is_none());
|
||||
assert_eq!(
|
||||
Ok(1),
|
||||
test_get_num_recorded_errors(&glean, metric.meta(), ErrorType::InvalidValue, None),
|
||||
|
|
Загрузка…
Ссылка в новой задаче