From 8962041ae952e3b1a43f9c3fca174ec78f40e359 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Tue, 12 May 2026 02:01:27 +0100 Subject: [PATCH 01/32] RUM-13949 Add timeseries JSON schemas and regenerate API surface ### What does this PR do? Adds JSON schemas for the CPU and memory timeseries event types (`timeseries-cpu-schema.json`, `timeseries-memory-schema.json`), registers them in the model-generation Gradle task, and updates the generated `api/apiSurface` and `api/dd-sdk-android-rum.api` files to reflect the new `RumTimeseriesCpuEvent`, `RumTimeseriesMemoryEvent`, and `TimeseriesConfiguration` public surface. ### Motivation The timeseries feature needs a well-defined wire format (JSON schemas) and generated Kotlin model classes before any collector or serializer code can reference them. ### Additional Notes The generated Kotlin model classes (`RumTimeseriesCpuEvent`, `RumTimeseriesMemoryEvent`) are produced at build time from these schemas via the existing `generateRumModelsFromJson` task and are not included in this commit. --- features/dd-sdk-android-rum/api/apiSurface | 158 ++++++ .../api/dd-sdk-android-rum.api | 452 ++++++++++++++++++ .../generate_rum_models.gradle.kts | 4 +- .../main/json/rum/timeseries-cpu-schema.json | 162 +++++++ .../json/rum/timeseries-memory-schema.json | 167 +++++++ 5 files changed, 942 insertions(+), 1 deletion(-) create mode 100644 features/dd-sdk-android-rum/src/main/json/rum/timeseries-cpu-schema.json create mode 100644 features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index a6e090cb4e..ef74222fd0 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -90,6 +90,8 @@ data class com.datadog.android.rum.RumConfiguration fun setSlowFramesConfiguration(com.datadog.android.rum.configuration.SlowFramesConfiguration?): Builder fun trackAnonymousUser(Boolean): Builder fun setAppStartupActivityPredicate(com.datadog.android.rum.startup.AppStartupActivityPredicate): Builder + fun enableTimeseries(com.datadog.android.rum.timeseries.TimeseriesConfiguration = TimeseriesConfiguration()): Builder + fun disableTimeseries(): Builder fun build(): RumConfiguration enum com.datadog.android.rum.RumErrorSource - NETWORK @@ -304,6 +306,14 @@ class com.datadog.android.rum.resource.RumResourceInputStream : java.io.InputStr override fun close() interface com.datadog.android.rum.startup.AppStartupActivityPredicate fun shouldTrackStartup(android.app.Activity): Boolean +class com.datadog.android.rum.timeseries.TimeseriesConfiguration + constructor(Int = DEFAULT_BUFFER_SIZE, Long = DEFAULT_INTERVAL_MS, Boolean = false) + val bufferSize: Int + val intervalMs: Long + companion object + const val DEFAULT_BUFFER_SIZE: Int + const val DEFAULT_INTERVAL_MS: Long + const val MIN_INTERVAL_MS: Long open class com.datadog.android.rum.tracking.AcceptAllActivities : ComponentPredicate override fun accept(android.app.Activity): Boolean override fun getViewName(android.app.Activity): String? @@ -1724,6 +1734,154 @@ data class com.datadog.android.rum.model.ResourceEvent fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): OperationType +data class com.datadog.android.rum.model.RumTimeseriesCpuEvent + constructor(Dd, Application, Session, Source, kotlin.Long, kotlin.String? = null, kotlin.String? = null, Timeseries) + val type: kotlin.String + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): RumTimeseriesCpuEvent + fun fromJsonObject(com.google.gson.JsonObject): RumTimeseriesCpuEvent + class Dd + val formatVersion: kotlin.Long + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Dd + fun fromJsonObject(com.google.gson.JsonObject): Dd + data class Application + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Application + fun fromJsonObject(com.google.gson.JsonObject): Application + data class Session + constructor(kotlin.String, Type) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Session + fun fromJsonObject(com.google.gson.JsonObject): Session + data class Timeseries + constructor(kotlin.String, Schema, kotlin.Long, kotlin.Long, kotlin.collections.List) + val name: kotlin.String + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Timeseries + fun fromJsonObject(com.google.gson.JsonObject): Timeseries + data class Data + constructor(kotlin.Long, DataPoint) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Data + fun fromJsonObject(com.google.gson.JsonObject): Data + data class DataPoint + constructor(kotlin.Number) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): DataPoint + fun fromJsonObject(com.google.gson.JsonObject): DataPoint + enum Source + constructor(kotlin.String) + - ANDROID + - IOS + - BROWSER + - FLUTTER + - REACT_NATIVE + - ROKU + - UNITY + - KOTLIN_MULTIPLATFORM + - ELECTRON + - RUM_CPP + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Source + enum Type + constructor(kotlin.String) + - USER + - SYNTHETICS + - CI_TEST + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Type + enum Schema + constructor(kotlin.String) + - OBJECT + - DELTA_SCALAR + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Schema +data class com.datadog.android.rum.model.RumTimeseriesMemoryEvent + constructor(Dd, Application, Session, Source, kotlin.Long, kotlin.String? = null, kotlin.String? = null, Timeseries) + val type: kotlin.String + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): RumTimeseriesMemoryEvent + fun fromJsonObject(com.google.gson.JsonObject): RumTimeseriesMemoryEvent + class Dd + val formatVersion: kotlin.Long + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Dd + fun fromJsonObject(com.google.gson.JsonObject): Dd + data class Application + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Application + fun fromJsonObject(com.google.gson.JsonObject): Application + data class Session + constructor(kotlin.String, Type) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Session + fun fromJsonObject(com.google.gson.JsonObject): Session + data class Timeseries + constructor(kotlin.String, Schema, kotlin.Long, kotlin.Long, kotlin.collections.List) + val name: kotlin.String + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Timeseries + fun fromJsonObject(com.google.gson.JsonObject): Timeseries + data class Data + constructor(kotlin.Long, DataPoint) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Data + fun fromJsonObject(com.google.gson.JsonObject): Data + data class DataPoint + constructor(kotlin.Number, kotlin.Number) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): DataPoint + fun fromJsonObject(com.google.gson.JsonObject): DataPoint + enum Source + constructor(kotlin.String) + - ANDROID + - IOS + - BROWSER + - FLUTTER + - REACT_NATIVE + - ROKU + - UNITY + - KOTLIN_MULTIPLATFORM + - ELECTRON + - RUM_CPP + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Source + enum Type + constructor(kotlin.String) + - USER + - SYNTHETICS + - CI_TEST + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Type + enum Schema + constructor(kotlin.String) + - OBJECT + - DELTA_OBJECT + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Schema data class com.datadog.android.rum.model.ViewEvent constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, ViewEventSession, ViewEventSource? = null, ViewEventView, Usr? = null, Account? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Container? = null, Context? = null, Privacy? = null) val type: kotlin.String diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index f991d80b43..76884da4fa 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -105,7 +105,11 @@ public final class com/datadog/android/rum/RumConfiguration$Builder { public fun (Ljava/lang/String;)V public final fun build ()Lcom/datadog/android/rum/RumConfiguration; public final fun collectAccessibility (Z)Lcom/datadog/android/rum/RumConfiguration$Builder; + public final fun disableTimeseries ()Lcom/datadog/android/rum/RumConfiguration$Builder; public final fun disableUserInteractionTracking ()Lcom/datadog/android/rum/RumConfiguration$Builder; + public final fun enableTimeseries ()Lcom/datadog/android/rum/RumConfiguration$Builder; + public final fun enableTimeseries (Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration;)Lcom/datadog/android/rum/RumConfiguration$Builder; + public static synthetic fun enableTimeseries$default (Lcom/datadog/android/rum/RumConfiguration$Builder;Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration;ILjava/lang/Object;)Lcom/datadog/android/rum/RumConfiguration$Builder; public final fun setActionEventMapper (Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder; public final fun setAppStartupActivityPredicate (Lcom/datadog/android/rum/startup/AppStartupActivityPredicate;)Lcom/datadog/android/rum/RumConfiguration$Builder; public final fun setErrorEventMapper (Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder; @@ -4896,6 +4900,436 @@ public final class com/datadog/android/rum/model/ResourceEvent$Worker$Companion public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ResourceEvent$Worker; } +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Companion; + public fun (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries;)V + public synthetic fun (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd; + public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; + public final fun component3 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; + public final fun component4 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public final fun component5 ()J + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; + public final fun copy (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent; + public final fun getApplication ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; + public final fun getDate ()J + public final fun getDd ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd; + public final fun getService ()Ljava/lang/String; + public final fun getSession ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; + public final fun getSource ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public final fun getTimeseries ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; + public final fun getType ()Ljava/lang/String; + public final fun getVersion ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Application { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Application$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Data { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data$Companion; + public fun (JLcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint;)V + public final fun component1 ()J + public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; + public final fun copy (JLcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data;JLcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data; + public final fun getDataPoint ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; + public final fun getTimestamp ()J + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Data$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint$Companion; + public fun (Ljava/lang/Number;)V + public final fun component1 ()Ljava/lang/Number; + public final fun copy (Ljava/lang/Number;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; + public final fun getCpuUsage ()Ljava/lang/Number; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd; + public final fun getFormatVersion ()J + public final fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema : java/lang/Enum { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema$Companion; + public static final field DELTA_SCALAR Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; + public static final field OBJECT Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; + public static fun values ()[Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Session { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session$Companion; + public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; + public final fun getId ()Ljava/lang/String; + public final fun getType ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Session$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Source : java/lang/Enum { + public static final field ANDROID Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public static final field BROWSER Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source$Companion; + public static final field ELECTRON Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public static final field FLUTTER Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public static final field IOS Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public static final field KOTLIN_MULTIPLATFORM Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public static final field REACT_NATIVE Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public static final field ROKU Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public static final field RUM_CPP Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public static final field UNITY Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public static fun values ()[Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Source$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries$Companion; + public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema;JJLjava/util/List;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; + public final fun component3 ()J + public final fun component4 ()J + public final fun component5 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema;JJLjava/util/List;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema;JJLjava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; + public final fun getData ()Ljava/util/List; + public final fun getEnd ()J + public final fun getId ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getSchema ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; + public final fun getStart ()J + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Type : java/lang/Enum { + public static final field CI_TEST Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type$Companion; + public static final field SYNTHETICS Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; + public static final field USER Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; + public static fun values ()[Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; +} + +public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Type$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Companion; + public fun (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries;)V + public synthetic fun (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd; + public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; + public final fun component3 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; + public final fun component4 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public final fun component5 ()J + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; + public final fun copy (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent; + public final fun getApplication ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; + public final fun getDate ()J + public final fun getDd ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd; + public final fun getService ()Ljava/lang/String; + public final fun getSession ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; + public final fun getSource ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public final fun getTimeseries ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; + public final fun getType ()Ljava/lang/String; + public final fun getVersion ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data$Companion; + public fun (JLcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint;)V + public final fun component1 ()J + public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; + public final fun copy (JLcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data;JLcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data; + public final fun getDataPoint ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; + public final fun getTimestamp ()J + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint$Companion; + public fun (Ljava/lang/Number;Ljava/lang/Number;)V + public final fun component1 ()Ljava/lang/Number; + public final fun component2 ()Ljava/lang/Number; + public final fun copy (Ljava/lang/Number;Ljava/lang/Number;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint;Ljava/lang/Number;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; + public final fun getMemoryMax ()Ljava/lang/Number; + public final fun getMemoryPercent ()Ljava/lang/Number; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd; + public final fun getFormatVersion ()J + public final fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema : java/lang/Enum { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema$Companion; + public static final field DELTA_OBJECT Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; + public static final field OBJECT Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; + public static fun values ()[Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session$Companion; + public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; + public final fun getId ()Ljava/lang/String; + public final fun getType ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source : java/lang/Enum { + public static final field ANDROID Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public static final field BROWSER Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source$Companion; + public static final field ELECTRON Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public static final field FLUTTER Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public static final field IOS Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public static final field KOTLIN_MULTIPLATFORM Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public static final field REACT_NATIVE Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public static final field ROKU Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public static final field RUM_CPP Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public static final field UNITY Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public static fun values ()[Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries { + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries$Companion; + public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema;JJLjava/util/List;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; + public final fun component3 ()J + public final fun component4 ()J + public final fun component5 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema;JJLjava/util/List;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema;JJLjava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; + public final fun getData ()Ljava/util/List; + public final fun getEnd ()J + public final fun getId ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getSchema ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; + public final fun getStart ()J + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type : java/lang/Enum { + public static final field CI_TEST Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; + public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type$Companion; + public static final field SYNTHETICS Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; + public static final field USER Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; + public static fun values ()[Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; +} + +public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; +} + public final class com/datadog/android/rum/model/ViewEvent { public static final field Companion Lcom/datadog/android/rum/model/ViewEvent$Companion; public fun (JLcom/datadog/android/rum/model/ViewEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource;Lcom/datadog/android/rum/model/ViewEvent$ViewEventView;Lcom/datadog/android/rum/model/ViewEvent$Usr;Lcom/datadog/android/rum/model/ViewEvent$Account;Lcom/datadog/android/rum/model/ViewEvent$Connectivity;Lcom/datadog/android/rum/model/ViewEvent$Display;Lcom/datadog/android/rum/model/ViewEvent$Synthetics;Lcom/datadog/android/rum/model/ViewEvent$CiTest;Lcom/datadog/android/rum/model/ViewEvent$Os;Lcom/datadog/android/rum/model/ViewEvent$Device;Lcom/datadog/android/rum/model/ViewEvent$Dd;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Container;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Privacy;)V @@ -8199,6 +8633,24 @@ public abstract interface class com/datadog/android/rum/startup/AppStartupActivi public abstract fun shouldTrackStartup (Landroid/app/Activity;)Z } +public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration { + public static final field Companion Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Companion; + public static final field DEFAULT_BUFFER_SIZE I + public static final field DEFAULT_INTERVAL_MS J + public static final field MIN_INTERVAL_MS J + public fun ()V + public fun (I)V + public fun (IJ)V + public fun (IJZ)V + public synthetic fun (IJZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getBufferSize ()I + public final fun getCollectInBackground ()Z + public final fun getIntervalMs ()J +} + +public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Companion { +} + public class com/datadog/android/rum/tracking/AcceptAllActivities : com/datadog/android/rum/tracking/ComponentPredicate { public fun ()V public fun accept (Landroid/app/Activity;)Z diff --git a/features/dd-sdk-android-rum/generate_rum_models.gradle.kts b/features/dd-sdk-android-rum/generate_rum_models.gradle.kts index 73a9457678..4b1dce9ea4 100644 --- a/features/dd-sdk-android-rum/generate_rum_models.gradle.kts +++ b/features/dd-sdk-android-rum/generate_rum_models.gradle.kts @@ -28,6 +28,8 @@ createJsonModelsGenerationTask("generateRumModelsFromJson") { "view-schema.json" to "ViewEvent", "long_task-schema.json" to "LongTaskEvent", "vital-app-launch-schema.json" to "VitalAppLaunchEvent", - "vital-operation-step-schema.json" to "VitalOperationStepEvent" + "vital-operation-step-schema.json" to "VitalOperationStepEvent", + "timeseries-memory-schema.json" to "RumTimeseriesMemoryEvent", + "timeseries-cpu-schema.json" to "RumTimeseriesCpuEvent" ) } diff --git a/features/dd-sdk-android-rum/src/main/json/rum/timeseries-cpu-schema.json b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-cpu-schema.json new file mode 100644 index 0000000000..7d08a7dfbd --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-cpu-schema.json @@ -0,0 +1,162 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "rum/timeseries-cpu-schema.json", + "title": "RumTimeseriesCpuEvent", + "type": "object", + "description": "Schema for a CPU timeseries event.", + "required": ["_dd", "application", "date", "session", "source", "timeseries", "type"], + "properties": { + "type": { + "type": "string", + "description": "RUM event type", + "const": "timeseries", + "readOnly": true + }, + "_dd": { + "type": "object", + "description": "Internal properties", + "required": ["format_version"], + "properties": { + "format_version": { + "type": "integer", + "const": 2, + "description": "Version of the RUM event format", + "readOnly": true + } + }, + "readOnly": true + }, + "application": { + "type": "object", + "description": "Application properties", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "description": "UUID of the application", + "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", + "readOnly": true + } + }, + "readOnly": true + }, + "session": { + "type": "object", + "description": "Session properties", + "required": ["id", "type"], + "properties": { + "id": { + "type": "string", + "description": "UUID of the session", + "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", + "readOnly": true + }, + "type": { + "type": "string", + "description": "Type of the session", + "enum": ["user", "synthetics", "ci_test"], + "readOnly": true + } + }, + "readOnly": true + }, + "source": { + "type": "string", + "description": "The source of this event", + "enum": [ + "android", + "ios", + "browser", + "flutter", + "react-native", + "roku", + "unity", + "kotlin-multiplatform", + "electron", + "rum-cpp" + ], + "readOnly": true + }, + "date": { + "type": "integer", + "description": "Start of the event in ms from epoch", + "minimum": 0, + "readOnly": true + }, + "service": { + "type": "string", + "description": "The service name for this application" + }, + "version": { + "type": "string", + "description": "The version for this application" + }, + "timeseries": { + "type": "object", + "description": "CPU timeseries properties", + "required": ["id", "name", "schema", "start", "end", "data"], + "properties": { + "id": { + "type": "string", + "description": "UUID of the timeseries batch", + "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", + "readOnly": true + }, + "name": { + "type": "string", + "description": "Name identifying the timeseries metric", + "const": "cpu", + "readOnly": true + }, + "schema": { + "type": "string", + "description": "Wire-shape discriminator for the data field", + "enum": ["object", "delta-scalar"], + "readOnly": true + }, + "start": { + "type": "integer", + "description": "Timestamp of the first sample in nanoseconds from epoch", + "readOnly": true + }, + "end": { + "type": "integer", + "description": "Timestamp of the last sample in nanoseconds from epoch", + "readOnly": true + }, + "data": { + "type": "array", + "description": "Array of CPU data points", + "items": { + "type": "object", + "description": "A single CPU data point", + "required": ["timestamp", "data_point"], + "properties": { + "timestamp": { + "type": "integer", + "description": "Sample timestamp in nanoseconds from epoch", + "readOnly": true + }, + "data_point": { + "type": "object", + "description": "CPU measurements for this sample", + "required": ["cpu_usage"], + "properties": { + "cpu_usage": { + "type": "number", + "description": "CPU usage as a percentage (0.0 to 100.0)", + "readOnly": true + } + }, + "readOnly": true + } + }, + "readOnly": true + }, + "readOnly": true + } + }, + "readOnly": true + } + } +} diff --git a/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json new file mode 100644 index 0000000000..7d79da7026 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json @@ -0,0 +1,167 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "rum/timeseries-memory-schema.json", + "title": "RumTimeseriesMemoryEvent", + "type": "object", + "description": "Schema for a memory timeseries event.", + "required": ["_dd", "application", "date", "session", "source", "timeseries", "type"], + "properties": { + "type": { + "type": "string", + "description": "RUM event type", + "const": "timeseries", + "readOnly": true + }, + "_dd": { + "type": "object", + "description": "Internal properties", + "required": ["format_version"], + "properties": { + "format_version": { + "type": "integer", + "const": 2, + "description": "Version of the RUM event format", + "readOnly": true + } + }, + "readOnly": true + }, + "application": { + "type": "object", + "description": "Application properties", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "description": "UUID of the application", + "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", + "readOnly": true + } + }, + "readOnly": true + }, + "session": { + "type": "object", + "description": "Session properties", + "required": ["id", "type"], + "properties": { + "id": { + "type": "string", + "description": "UUID of the session", + "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", + "readOnly": true + }, + "type": { + "type": "string", + "description": "Type of the session", + "enum": ["user", "synthetics", "ci_test"], + "readOnly": true + } + }, + "readOnly": true + }, + "source": { + "type": "string", + "description": "The source of this event", + "enum": [ + "android", + "ios", + "browser", + "flutter", + "react-native", + "roku", + "unity", + "kotlin-multiplatform", + "electron", + "rum-cpp" + ], + "readOnly": true + }, + "date": { + "type": "integer", + "description": "Start of the event in ms from epoch", + "minimum": 0, + "readOnly": true + }, + "service": { + "type": "string", + "description": "The service name for this application" + }, + "version": { + "type": "string", + "description": "The version for this application" + }, + "timeseries": { + "type": "object", + "description": "Memory timeseries properties", + "required": ["id", "name", "schema", "start", "end", "data"], + "properties": { + "id": { + "type": "string", + "description": "UUID of the timeseries batch", + "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", + "readOnly": true + }, + "name": { + "type": "string", + "description": "Name identifying the timeseries metric", + "const": "memory", + "readOnly": true + }, + "schema": { + "type": "string", + "description": "Wire-shape discriminator for the data field", + "enum": ["object", "delta-object"], + "readOnly": true + }, + "start": { + "type": "integer", + "description": "Timestamp of the first sample in nanoseconds from epoch", + "readOnly": true + }, + "end": { + "type": "integer", + "description": "Timestamp of the last sample in nanoseconds from epoch", + "readOnly": true + }, + "data": { + "type": "array", + "description": "Array of memory data points", + "items": { + "type": "object", + "description": "A single memory data point", + "required": ["timestamp", "data_point"], + "properties": { + "timestamp": { + "type": "integer", + "description": "Sample timestamp in nanoseconds from epoch", + "readOnly": true + }, + "data_point": { + "type": "object", + "description": "Memory measurements for this sample", + "required": ["memory_max", "memory_percent"], + "properties": { + "memory_max": { + "type": "number", + "description": "Physical memory footprint of the process in bytes", + "readOnly": true + }, + "memory_percent": { + "type": "number", + "description": "Memory footprint as a percentage of total device RAM", + "readOnly": true + } + }, + "readOnly": true + } + }, + "readOnly": true + }, + "readOnly": true + } + }, + "readOnly": true + } + } +} From 96392b6a17e48a24647f9f6eefdc2a571e3ed71e Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Wed, 13 May 2026 14:41:38 +0100 Subject: [PATCH 02/32] RUM-13949: Regenerate API surface for timeseries-only changes on this branch --- features/dd-sdk-android-rum/api/apiSurface | 10 --------- .../api/dd-sdk-android-rum.api | 22 ------------------- 2 files changed, 32 deletions(-) diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index ef74222fd0..c2b15c440f 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -90,8 +90,6 @@ data class com.datadog.android.rum.RumConfiguration fun setSlowFramesConfiguration(com.datadog.android.rum.configuration.SlowFramesConfiguration?): Builder fun trackAnonymousUser(Boolean): Builder fun setAppStartupActivityPredicate(com.datadog.android.rum.startup.AppStartupActivityPredicate): Builder - fun enableTimeseries(com.datadog.android.rum.timeseries.TimeseriesConfiguration = TimeseriesConfiguration()): Builder - fun disableTimeseries(): Builder fun build(): RumConfiguration enum com.datadog.android.rum.RumErrorSource - NETWORK @@ -306,14 +304,6 @@ class com.datadog.android.rum.resource.RumResourceInputStream : java.io.InputStr override fun close() interface com.datadog.android.rum.startup.AppStartupActivityPredicate fun shouldTrackStartup(android.app.Activity): Boolean -class com.datadog.android.rum.timeseries.TimeseriesConfiguration - constructor(Int = DEFAULT_BUFFER_SIZE, Long = DEFAULT_INTERVAL_MS, Boolean = false) - val bufferSize: Int - val intervalMs: Long - companion object - const val DEFAULT_BUFFER_SIZE: Int - const val DEFAULT_INTERVAL_MS: Long - const val MIN_INTERVAL_MS: Long open class com.datadog.android.rum.tracking.AcceptAllActivities : ComponentPredicate override fun accept(android.app.Activity): Boolean override fun getViewName(android.app.Activity): String? diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index 76884da4fa..a746ce9664 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -105,11 +105,7 @@ public final class com/datadog/android/rum/RumConfiguration$Builder { public fun (Ljava/lang/String;)V public final fun build ()Lcom/datadog/android/rum/RumConfiguration; public final fun collectAccessibility (Z)Lcom/datadog/android/rum/RumConfiguration$Builder; - public final fun disableTimeseries ()Lcom/datadog/android/rum/RumConfiguration$Builder; public final fun disableUserInteractionTracking ()Lcom/datadog/android/rum/RumConfiguration$Builder; - public final fun enableTimeseries ()Lcom/datadog/android/rum/RumConfiguration$Builder; - public final fun enableTimeseries (Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration;)Lcom/datadog/android/rum/RumConfiguration$Builder; - public static synthetic fun enableTimeseries$default (Lcom/datadog/android/rum/RumConfiguration$Builder;Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration;ILjava/lang/Object;)Lcom/datadog/android/rum/RumConfiguration$Builder; public final fun setActionEventMapper (Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder; public final fun setAppStartupActivityPredicate (Lcom/datadog/android/rum/startup/AppStartupActivityPredicate;)Lcom/datadog/android/rum/RumConfiguration$Builder; public final fun setErrorEventMapper (Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder; @@ -8633,24 +8629,6 @@ public abstract interface class com/datadog/android/rum/startup/AppStartupActivi public abstract fun shouldTrackStartup (Landroid/app/Activity;)Z } -public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration { - public static final field Companion Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Companion; - public static final field DEFAULT_BUFFER_SIZE I - public static final field DEFAULT_INTERVAL_MS J - public static final field MIN_INTERVAL_MS J - public fun ()V - public fun (I)V - public fun (IJ)V - public fun (IJZ)V - public synthetic fun (IJZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getBufferSize ()I - public final fun getCollectInBackground ()Z - public final fun getIntervalMs ()J -} - -public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Companion { -} - public class com/datadog/android/rum/tracking/AcceptAllActivities : com/datadog/android/rum/tracking/ComponentPredicate { public fun ()V public fun accept (Landroid/app/Activity;)Z From 7291ea8418e843dfbb56f212c498956736c1c5fa Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Wed, 13 May 2026 15:21:56 +0100 Subject: [PATCH 03/32] RUM-13949: Drop Rum prefix from timeseries model names Aligns timeseries model class names with the existing convention used by ActionEvent, ErrorEvent, ViewEvent etc. (the inputNameMapping in generate_rum_models.gradle.kts already strips the Rum prefix that appears in the JSON schema titles). - generate_rum_models.gradle.kts: map both schemas to Timeseries{Memory,Cpu}Event. - features/dd-sdk-android-rum/api: regenerate apiSurface and api dump. --- features/dd-sdk-android-rum/api/apiSurface | 12 +- .../api/dd-sdk-android-rum.api | 472 +++++++++--------- .../generate_rum_models.gradle.kts | 4 +- 3 files changed, 244 insertions(+), 244 deletions(-) diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index c2b15c440f..67f6240b97 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -1724,13 +1724,13 @@ data class com.datadog.android.rum.model.ResourceEvent fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): OperationType -data class com.datadog.android.rum.model.RumTimeseriesCpuEvent +data class com.datadog.android.rum.model.TimeseriesCpuEvent constructor(Dd, Application, Session, Source, kotlin.Long, kotlin.String? = null, kotlin.String? = null, Timeseries) val type: kotlin.String fun toJson(): com.google.gson.JsonElement companion object - fun fromJson(kotlin.String): RumTimeseriesCpuEvent - fun fromJsonObject(com.google.gson.JsonObject): RumTimeseriesCpuEvent + fun fromJson(kotlin.String): TimeseriesCpuEvent + fun fromJsonObject(com.google.gson.JsonObject): TimeseriesCpuEvent class Dd val formatVersion: kotlin.Long fun toJson(): com.google.gson.JsonElement @@ -1798,13 +1798,13 @@ data class com.datadog.android.rum.model.RumTimeseriesCpuEvent fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Schema -data class com.datadog.android.rum.model.RumTimeseriesMemoryEvent +data class com.datadog.android.rum.model.TimeseriesMemoryEvent constructor(Dd, Application, Session, Source, kotlin.Long, kotlin.String? = null, kotlin.String? = null, Timeseries) val type: kotlin.String fun toJson(): com.google.gson.JsonElement companion object - fun fromJson(kotlin.String): RumTimeseriesMemoryEvent - fun fromJsonObject(com.google.gson.JsonObject): RumTimeseriesMemoryEvent + fun fromJson(kotlin.String): TimeseriesMemoryEvent + fun fromJsonObject(com.google.gson.JsonObject): TimeseriesMemoryEvent class Dd val formatVersion: kotlin.Long fun toJson(): com.google.gson.JsonElement diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index a746ce9664..77bdcbdd54 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -4896,30 +4896,30 @@ public final class com/datadog/android/rum/model/ResourceEvent$Worker$Companion public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ResourceEvent$Worker; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Companion; - public fun (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries;)V - public synthetic fun (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd; - public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; - public final fun component3 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; - public final fun component4 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Companion; + public fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;)V + public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; + public final fun component3 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; + public final fun component4 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; public final fun component5 ()J public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/lang/String; - public final fun component8 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; - public final fun copy (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent; + public final fun component8 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; + public final fun copy (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent; - public final fun getApplication ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; + public final fun getApplication ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; public final fun getDate ()J - public final fun getDd ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd; + public final fun getDd ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; public final fun getService ()Ljava/lang/String; - public final fun getSession ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; - public final fun getSource ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; - public final fun getTimeseries ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; + public final fun getSession ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; + public final fun getSource ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public final fun getTimeseries ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; public final fun getType ()Ljava/lang/String; public final fun getVersion ()Ljava/lang/String; public fun hashCode ()I @@ -4927,213 +4927,213 @@ public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent { public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Application { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application$Companion; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Application { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application$Companion; public fun (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; public final fun getId ()Ljava/lang/String; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Application$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Application; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Application$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Data { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data$Companion; - public fun (JLcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint;)V +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Data { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data$Companion; + public fun (JLcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint;)V public final fun component1 ()J - public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; - public final fun copy (JLcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data;JLcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; + public final fun copy (JLcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data;JLcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data; - public final fun getDataPoint ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; + public final fun getDataPoint ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; public final fun getTimestamp ()J public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Data$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Data; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Data$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint$Companion; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint$Companion; public fun (Ljava/lang/Number;)V public final fun component1 ()Ljava/lang/Number; - public final fun copy (Ljava/lang/Number;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; + public final fun copy (Ljava/lang/Number;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; public final fun getCpuUsage ()Ljava/lang/Number; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$DataPoint; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd$Companion; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Dd { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd$Companion; public fun ()V - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; public final fun getFormatVersion ()J public final fun toJson ()Lcom/google/gson/JsonElement; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Dd; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Dd$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema : java/lang/Enum { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema$Companion; - public static final field DELTA_SCALAR Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; - public static final field OBJECT Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Schema : java/lang/Enum { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema$Companion; + public static final field DELTA_SCALAR Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; + public static final field OBJECT Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; public final fun toJson ()Lcom/google/gson/JsonElement; - public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; - public static fun values ()[Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Schema$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Session { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session$Companion; - public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type;)V +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Session { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session$Companion; + public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type;)V public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; - public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; public final fun getId ()Ljava/lang/String; - public final fun getType ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; + public final fun getType ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Session$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Session; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Session$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Source : java/lang/Enum { - public static final field ANDROID Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; - public static final field BROWSER Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source$Companion; - public static final field ELECTRON Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; - public static final field FLUTTER Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; - public static final field IOS Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; - public static final field KOTLIN_MULTIPLATFORM Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; - public static final field REACT_NATIVE Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; - public static final field ROKU Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; - public static final field RUM_CPP Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; - public static final field UNITY Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Source : java/lang/Enum { + public static final field ANDROID Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field BROWSER Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source$Companion; + public static final field ELECTRON Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field FLUTTER Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field IOS Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field KOTLIN_MULTIPLATFORM Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field REACT_NATIVE Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field ROKU Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field RUM_CPP Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field UNITY Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; public final fun toJson ()Lcom/google/gson/JsonElement; - public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; - public static fun values ()[Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Source$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Source; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Source$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries$Companion; - public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema;JJLjava/util/List;)V +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries$Companion; + public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema;JJLjava/util/List;)V public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; public final fun component3 ()J public final fun component4 ()J public final fun component5 ()Ljava/util/List; - public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema;JJLjava/util/List;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema;JJLjava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema;JJLjava/util/List;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema;JJLjava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; public final fun getData ()Ljava/util/List; public final fun getEnd ()J public final fun getId ()Ljava/lang/String; public final fun getName ()Ljava/lang/String; - public final fun getSchema ()Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Schema; + public final fun getSchema ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; public final fun getStart ()J public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Timeseries; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Type : java/lang/Enum { - public static final field CI_TEST Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type$Companion; - public static final field SYNTHETICS Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; - public static final field USER Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Type : java/lang/Enum { + public static final field CI_TEST Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type$Companion; + public static final field SYNTHETICS Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; + public static final field USER Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; public final fun toJson ()Lcom/google/gson/JsonElement; - public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; - public static fun values ()[Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; } -public final class com/datadog/android/rum/model/RumTimeseriesCpuEvent$Type$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesCpuEvent$Type; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Type$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Companion; - public fun (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries;)V - public synthetic fun (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd; - public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; - public final fun component3 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; - public final fun component4 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Companion; + public fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;)V + public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; + public final fun component3 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; + public final fun component4 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; public final fun component5 ()J public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/lang/String; - public final fun component8 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; - public final fun copy (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent; + public final fun component8 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; + public final fun copy (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent; - public final fun getApplication ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; + public final fun getApplication ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; public final fun getDate ()J - public final fun getDd ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd; + public final fun getDd ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; public final fun getService ()Ljava/lang/String; - public final fun getSession ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; - public final fun getSource ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; - public final fun getTimeseries ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; + public final fun getSession ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; + public final fun getSource ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public final fun getTimeseries ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; public final fun getType ()Ljava/lang/String; public final fun getVersion ()Ljava/lang/String; public fun hashCode ()I @@ -5141,63 +5141,63 @@ public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent { public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application$Companion; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Application { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application$Companion; public fun (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; public final fun getId ()Ljava/lang/String; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Application; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Application$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data$Companion; - public fun (JLcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint;)V +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Data { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data$Companion; + public fun (JLcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint;)V public final fun component1 ()J - public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; - public final fun copy (JLcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data;JLcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; + public final fun copy (JLcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data;JLcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data; - public final fun getDataPoint ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; + public final fun getDataPoint ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; public final fun getTimestamp ()J public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Data; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Data$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint$Companion; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint$Companion; public fun (Ljava/lang/Number;Ljava/lang/Number;)V public final fun component1 ()Ljava/lang/Number; public final fun component2 ()Ljava/lang/Number; - public final fun copy (Ljava/lang/Number;Ljava/lang/Number;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint;Ljava/lang/Number;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; + public final fun copy (Ljava/lang/Number;Ljava/lang/Number;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint;Ljava/lang/Number;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; public final fun getMemoryMax ()Ljava/lang/Number; public final fun getMemoryPercent ()Ljava/lang/Number; public fun hashCode ()I @@ -5205,125 +5205,125 @@ public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPo public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$DataPoint; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd$Companion; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Dd { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd$Companion; public fun ()V - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; public final fun getFormatVersion ()J public final fun toJson ()Lcom/google/gson/JsonElement; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Dd; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Dd$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema : java/lang/Enum { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema$Companion; - public static final field DELTA_OBJECT Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; - public static final field OBJECT Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Schema : java/lang/Enum { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema$Companion; + public static final field DELTA_OBJECT Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; + public static final field OBJECT Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; public final fun toJson ()Lcom/google/gson/JsonElement; - public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; - public static fun values ()[Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Schema$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session$Companion; - public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type;)V +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Session { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session$Companion; + public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type;)V public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; - public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; public final fun getId ()Ljava/lang/String; - public final fun getType ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; + public final fun getType ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Session; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Session$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source : java/lang/Enum { - public static final field ANDROID Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; - public static final field BROWSER Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source$Companion; - public static final field ELECTRON Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; - public static final field FLUTTER Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; - public static final field IOS Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; - public static final field KOTLIN_MULTIPLATFORM Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; - public static final field REACT_NATIVE Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; - public static final field ROKU Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; - public static final field RUM_CPP Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; - public static final field UNITY Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Source : java/lang/Enum { + public static final field ANDROID Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public static final field BROWSER Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source$Companion; + public static final field ELECTRON Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public static final field FLUTTER Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public static final field IOS Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public static final field KOTLIN_MULTIPLATFORM Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public static final field REACT_NATIVE Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public static final field ROKU Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public static final field RUM_CPP Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public static final field UNITY Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; public final fun toJson ()Lcom/google/gson/JsonElement; - public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; - public static fun values ()[Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Source; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Source$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries { - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries$Companion; - public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema;JJLjava/util/List;)V +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries$Companion; + public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema;JJLjava/util/List;)V public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; public final fun component3 ()J public final fun component4 ()J public final fun component5 ()Ljava/util/List; - public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema;JJLjava/util/List;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries;Ljava/lang/String;Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema;JJLjava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema;JJLjava/util/List;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema;JJLjava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; public final fun getData ()Ljava/util/List; public final fun getEnd ()J public final fun getId ()Ljava/lang/String; public final fun getName ()Ljava/lang/String; - public final fun getSchema ()Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Schema; + public final fun getSchema ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; public final fun getStart ()J public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Timeseries; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type : java/lang/Enum { - public static final field CI_TEST Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; - public static final field Companion Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type$Companion; - public static final field SYNTHETICS Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; - public static final field USER Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Type : java/lang/Enum { + public static final field CI_TEST Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type$Companion; + public static final field SYNTHETICS Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; + public static final field USER Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; public final fun toJson ()Lcom/google/gson/JsonElement; - public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; - public static fun values ()[Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; } -public final class com/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/RumTimeseriesMemoryEvent$Type; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Type$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; } public final class com/datadog/android/rum/model/ViewEvent { diff --git a/features/dd-sdk-android-rum/generate_rum_models.gradle.kts b/features/dd-sdk-android-rum/generate_rum_models.gradle.kts index 4b1dce9ea4..e274a62021 100644 --- a/features/dd-sdk-android-rum/generate_rum_models.gradle.kts +++ b/features/dd-sdk-android-rum/generate_rum_models.gradle.kts @@ -29,7 +29,7 @@ createJsonModelsGenerationTask("generateRumModelsFromJson") { "long_task-schema.json" to "LongTaskEvent", "vital-app-launch-schema.json" to "VitalAppLaunchEvent", "vital-operation-step-schema.json" to "VitalOperationStepEvent", - "timeseries-memory-schema.json" to "RumTimeseriesMemoryEvent", - "timeseries-cpu-schema.json" to "RumTimeseriesCpuEvent" + "timeseries-memory-schema.json" to "TimeseriesMemoryEvent", + "timeseries-cpu-schema.json" to "TimeseriesCpuEvent" ) } From f9bfcd3e2ffa75c0c6f0c830bc051447e8105a76 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Wed, 27 May 2026 12:42:23 +0100 Subject: [PATCH 04/32] RUM-13949: Implement timeseries pipeline infrastructure - Add Pipeline, Buffer, DataPoint, Timeseries, TimeseriesFactory types - Add RumSessionScopeTimeseries and RumSessionScopeTimeseriesFactory - Wire timeseries start/stop into RumSessionScope (renewSession/stopSession) - Add NoOpTimeseries and NoOpTimeseriesFactory - Add unit tests for all new types --- detekt_custom_safe_calls.yml | 10 + .../android/rum/internal/RumSessionTypeExt.kt | 13 + .../internal/domain/scope/RumSessionScope.kt | 30 +- .../android/rum/internal/timeseries/Buffer.kt | 16 + .../rum/internal/timeseries/DataPoint.kt | 9 + .../internal/timeseries/DeltaCompression.kt | 28 + .../rum/internal/timeseries/Pipeline.kt | 43 ++ .../timeseries/RumSessionScopeTimeseries.kt | 131 +++++ .../RumSessionScopeTimeseriesFactory.kt | 31 + .../rum/internal/timeseries/Timeseries.kt | 17 + .../internal/timeseries/TimeseriesFactory.kt | 15 + .../timeseries/provider/DataPointsReader.kt | 28 + .../timeseries/provider/VitalReaderWrapper.kt | 19 + .../timeseries/serializer/JsonSerializer.kt | 14 + .../rum/internal/RumSessionTypeExtTest.kt | 157 ++++++ .../domain/scope/RumSessionScopeTest.kt | 208 ++++++- .../rum/internal/timeseries/BufferTest.kt | 118 ++++ .../rum/internal/timeseries/PipelineTest.kt | 212 +++++++ .../RumSessionScopeTimeseriesFactoryTest.kt | 108 ++++ .../RumSessionScopeTimeseriesTest.kt | 531 ++++++++++++++++++ .../VitalReaderDataPointsReaderTest.kt | 94 ++++ .../android/rum/utils/forge/Configurator.kt | 1 + .../utils/forge/DataPointForgeryFactory.kt | 18 + .../java/com/datadog/benchmark/EndPoint.kt | 11 + 24 files changed, 1858 insertions(+), 4 deletions(-) create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Buffer.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DataPoint.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DeltaCompression.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactory.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesFactory.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/DataPointsReader.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/VitalReaderWrapper.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumSessionTypeExtTest.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/BufferTest.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesTest.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/VitalReaderDataPointsReaderTest.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/DataPointForgeryFactory.kt diff --git a/detekt_custom_safe_calls.yml b/detekt_custom_safe_calls.yml index 243c0efb2d..813d493d92 100644 --- a/detekt_custom_safe_calls.yml +++ b/detekt_custom_safe_calls.yml @@ -419,6 +419,7 @@ datadog: # endregion # region Gson - "com.google.gson.JsonArray.add(com.google.gson.JsonElement?)" + - "com.google.gson.JsonArray.add(kotlin.Number?)" - "com.google.gson.JsonArray.add(kotlin.String?)" - "com.google.gson.JsonArray.addAll(com.google.gson.JsonArray?)" - "com.google.gson.JsonArray.any(kotlin.Function1)" @@ -451,7 +452,10 @@ datadog: - "com.google.gson.JsonPrimitive.constructor(kotlin.String?)" # endregion # region Java Collections + - "java.util.ArrayList.add(com.datadog.android.rum.internal.timeseries.DataPoint)" + - "java.util.ArrayList.clear()" - "java.util.ArrayList.forEach(kotlin.Function1)" + - "java.util.ArrayList.toList()" - "java.util.Deque.peekLast()" - "java.util.Deque.toList()" - "java.util.HashSet.add(kotlin.String)" @@ -585,6 +589,7 @@ datadog: - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.android.api.SdkCore?, com.datadog.android.api.SdkCore?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.android.flags.internal.repository.DefaultFlagsRepository.FlagsState?, com.datadog.android.flags.internal.repository.DefaultFlagsRepository.FlagsState?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI?, com.datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI?)" + - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.android.rum.internal.timeseries.RumSessionScopeTimeseries.State?, com.datadog.android.rum.internal.timeseries.RumSessionScopeTimeseries.State?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.android.trace.api.tracer.DatadogTracer?, com.datadog.android.trace.api.tracer.DatadogTracer?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(kotlin.String?, kotlin.String?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(kotlin.collections.Set?, kotlin.collections.Set?)" @@ -597,10 +602,12 @@ datadog: - "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.flags.model.FlagsClientState?)" - "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.flags.model.ProviderContext?)" - "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.rum.internal.domain.RumContext?)" + - "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.rum.internal.timeseries.RumSessionScopeTimeseries.State?)" - "java.util.concurrent.atomic.AtomicReference.constructor(kotlin.collections.Map?)" - "java.util.concurrent.atomic.AtomicReference.constructor(kotlin.String?)" - "java.util.concurrent.atomic.AtomicReference.constructor(kotlin.collections.Set?)" - "java.util.concurrent.atomic.AtomicReference.get()" + - "java.util.concurrent.atomic.AtomicReference.getAndSet(com.datadog.android.rum.internal.timeseries.RumSessionScopeTimeseries.State?)" - "java.util.concurrent.atomic.AtomicReference.set(android.app.Application.ActivityLifecycleCallbacks?)" - "java.util.concurrent.atomic.AtomicReference.set(com.datadog.android.api.FeatureEventReceiver?)" - "java.util.concurrent.atomic.AtomicReference.set(com.datadog.android.api.SdkCore?)" @@ -778,6 +785,7 @@ datadog: - "kotlin.ByteArray.joinToString(kotlin.CharSequence, kotlin.CharSequence, kotlin.CharSequence, kotlin.Int, kotlin.CharSequence, kotlin.Function1?)" - "kotlin.arrayOf(kotlin.Array)" - "kotlin.collections.arrayListOf()" + - "kotlin.collections.ArrayList(kotlin.Int)" - "kotlin.collections.ArrayList(kotlin.collections.MutableCollection?)" - "kotlin.collections.buildMap(kotlin.Function1)" - "kotlin.collections.Collection.flatten()" @@ -850,6 +858,7 @@ datadog: - "kotlin.collections.List.subList(kotlin.Int, kotlin.Int)" - "kotlin.collections.List.sumOf(kotlin.Function1)" - "kotlin.collections.List.take(kotlin.Int)" + - "kotlin.collections.List.takeIf(kotlin.Function1)" - "kotlin.collections.List.toCharArray()" - "kotlin.collections.List.toList()" - "kotlin.collections.List.toMap()" @@ -895,6 +904,7 @@ datadog: - "kotlin.collections.MutableList.add(com.datadog.android.rum.internal.domain.scope.RumSessionScope)" - "kotlin.collections.MutableList.add(com.datadog.android.rum.internal.domain.scope.RumScope)" - "kotlin.collections.MutableList.add(com.datadog.android.rum.internal.domain.scope.RumViewScope)" + - "kotlin.collections.MutableList.add(com.datadog.android.rum.internal.timeseries.DataPoint)" - "kotlin.collections.MutableList.add(com.datadog.android.rum.internal.vitals.FrameStateListener)" - "kotlin.collections.MutableList.add(com.datadog.android.rum.model.ActionEvent.Type)" - "kotlin.collections.MutableList.add(com.datadog.android.sessionreplay.compose.internal.data.Parameter)" diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumSessionTypeExt.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumSessionTypeExt.kt index e2b12f316d..49105fdc95 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumSessionTypeExt.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumSessionTypeExt.kt @@ -11,6 +11,8 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.model.VitalAppLaunchEvent import com.datadog.android.rum.model.VitalOperationStepEvent @@ -49,6 +51,7 @@ internal fun RumSessionType.toLongTask(): LongTaskEvent.LongTaskEventSessionType RumSessionType.USER -> LongTaskEvent.LongTaskEventSessionType.USER } } + internal fun RumSessionType.toVital(): VitalOperationStepEvent.VitalOperationStepEventSessionType { return when (this) { RumSessionType.SYNTHETICS -> VitalOperationStepEvent.VitalOperationStepEventSessionType.SYNTHETICS @@ -62,3 +65,13 @@ internal fun RumSessionType.toVitalAppLaunch(): VitalAppLaunchEvent.VitalAppLaun RumSessionType.USER -> VitalAppLaunchEvent.VitalAppLaunchEventSessionType.USER } } + +internal fun RumSessionType.toTimeseriesMemorySessionType(): TimeseriesMemoryEvent.Type = when (this) { + RumSessionType.SYNTHETICS -> TimeseriesMemoryEvent.Type.SYNTHETICS + RumSessionType.USER -> TimeseriesMemoryEvent.Type.USER +} + +internal fun RumSessionType.toTimeseriesCpuSessionType(): TimeseriesCpuEvent.Type = when (this) { + RumSessionType.SYNTHETICS -> TimeseriesCpuEvent.Type.SYNTHETICS + RumSessionType.USER -> TimeseriesCpuEvent.Type.USER +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt index 7e4b131f0e..3d70ade4c8 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt @@ -28,6 +28,10 @@ import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollect import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.startup.RumSessionScopeStartupManager +import com.datadog.android.rum.internal.timeseries.NoOpTimeseries +import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesFactory +import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.TimeseriesFactory import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -58,11 +62,14 @@ internal class RumSessionScope( displayInfoProvider: InfoProvider, private val sessionInactivityNanos: Long = DEFAULT_SESSION_INACTIVITY_NS, private val sessionMaxDurationNanos: Long = DEFAULT_SESSION_MAX_DURATION_NS, - rumSessionTypeOverride: RumSessionType?, + private val rumSessionTypeOverride: RumSessionType?, private val rumSessionScopeStartupManagerFactory: () -> RumSessionScopeStartupManager, - insightsCollector: InsightsCollector + insightsCollector: InsightsCollector, + private val timeseriesFactory: TimeseriesFactory = NoOpTimeseriesFactory() ) : RumScope { + private var timeseries: Timeseries = NoOpTimeseries() + internal var sessionId = RumContext.NULL_UUID internal var sessionState: State = State.NOT_TRACKED internal val sessionSampleRate: Float = sessionSampler.getSampleRate() ?: RumContext.SAMPLE_ALL_RATE @@ -199,6 +206,8 @@ internal class RumSessionScope( } } + timeseries.onViewTypeUpdate(rumContext.viewType) + return if (isSessionComplete()) { null } else { @@ -225,8 +234,23 @@ internal class RumSessionScope( // region Internal + private fun startTimeseries() { + timeseries = timeseriesFactory.create( + sessionId, + parentScope.getRumContext().applicationId, + rumSessionTypeOverride ?: RumSessionType.USER + ) + timeseries.onSessionStart() + } + + private fun stopTimeseries() { + timeseries.onSessionStop() + timeseries = NoOpTimeseries() + } + private fun stopSession() { isActive = false + stopTimeseries() sessionEndedMetricDispatcher.onSessionStopped(sessionId) } @@ -281,6 +305,7 @@ internal class RumSessionScope( } private fun renewSession(time: Time, reason: StartReason) { + stopTimeseries() val newSessionId = UUID.randomUUID().toString() val keepSession = sessionSampler.sample(newSessionId) startReason = reason @@ -296,6 +321,7 @@ internal class RumSessionScope( ntpOffsetAtStartMs = sdkCore.time.serverTimeOffsetMs, backgroundEventTracking = backgroundTrackingEnabled ) + startTimeseries() } sessionListener?.onSessionStarted(sessionId, !keepSession) } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Buffer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Buffer.kt new file mode 100644 index 0000000000..5b7243d47a --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Buffer.kt @@ -0,0 +1,16 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ +package com.datadog.android.rum.internal.timeseries + +internal class Buffer(private val size: Int) { + private val items = ArrayList>(size) + + fun add(dataPoint: DataPoint) = items.add(dataPoint) + + fun isFull(): Boolean = items.size >= size + + fun drain() = items.toList().also { items.clear() } +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DataPoint.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DataPoint.kt new file mode 100644 index 0000000000..1ef50c7836 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DataPoint.kt @@ -0,0 +1,9 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries + +internal data class DataPoint(val timestampNs: Long, val value: T) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DeltaCompression.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DeltaCompression.kt new file mode 100644 index 0000000000..817fa2c4dc --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DeltaCompression.kt @@ -0,0 +1,28 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ +package com.datadog.android.rum.internal.timeseries + +import com.google.gson.JsonArray +import kotlin.math.roundToLong + +internal object DeltaCompression { + + fun roundToLongSafely(value: Double, replaceNaNWith: Long = 0L, scale: Long = SCALE): Long = try { + (value * scale).roundToLong() + } catch (_: IllegalArgumentException) { + replaceNaNWith + } + + fun List.mapToDeltaCompressed(extractor: (T) -> Long) = JsonArray(size).also { + for (i in indices) { + @Suppress("UnsafeThirdPartyFunctionCall") // NoSuchElementException could not be thrown here + it.add(if (i == 0) extractor(get(i)) else extractor(get(i)) - extractor(get(i - 1))) + } + } + + const val PRECISION = 4 + const val SCALE: Long = 10_000L // 10^4 +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt new file mode 100644 index 0000000000..727211b6f5 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt @@ -0,0 +1,43 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries + +import androidx.annotation.WorkerThread +import com.datadog.android.api.feature.Feature +import com.datadog.android.api.feature.FeatureSdkCore +import com.datadog.android.api.storage.DataWriter +import com.datadog.android.api.storage.EventType +import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader +import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer +import com.google.gson.JsonObject + +internal class Pipeline( + private val sdkCore: FeatureSdkCore, + private val reader: DataPointsReader, + private val buffer: Buffer, + private val serializer: JsonSerializer, + private val dataWriter: DataWriter +) { + val intervalMs: Long get() = reader.intervalMs + + @WorkerThread + fun execute() { + reader.read()?.let(buffer::add) + if (buffer.isFull()) flush() + } + + @WorkerThread + fun flush() = buffer.drain() + .takeIf { it.isNotEmpty() } + ?.let(serializer::serialize) + ?.let(::write) + + private fun write(json: JsonObject) = sdkCore.getFeature(Feature.RUM_FEATURE_NAME) + ?.withWriteContext { _, writeScope -> + writeScope { dataWriter.write(it, json, EventType.DEFAULT) } + } +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt new file mode 100644 index 0000000000..3ca5941a2e --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt @@ -0,0 +1,131 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries + +import androidx.annotation.WorkerThread +import com.datadog.android.api.InternalLogger +import com.datadog.android.core.internal.utils.scheduleSafe +import com.datadog.android.rum.internal.domain.scope.RumViewType +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicReference + +/** + * Per-session timeseries collector. + * + * Lifecycle invariants (enforced by [onSessionStart]/[onSessionStop] idempotency): + * - Each instance is single-use: exactly one [onSessionStart] followed by exactly one [onSessionStop]. + * - Multiple [onSessionStart] / [onSessionStop] calls are safe — duplicate calls are no-ops. + * - After [onSessionStop], the instance must not be restarted; create a new instance. + * + * Background sampling: + * - When [collectInBackground] is `false`, [onViewTypeUpdate] pauses sampling on leaving foreground + * and resumes it on returning to foreground. + * + * Threading: + * - [onSessionStart] / [onSessionStop] / [onViewTypeUpdate] are called from the RUM event-handler thread. + * - Sampling tasks run on a [scheduledExecutorService] shared with other RUM components + * (owned by the SDK core); the instance neither owns nor shuts it down. + * - [Pipeline] is responsible for its own thread safety via internal synchronization. + * - Duplicate-chain prevention: each sampling chain carries a generation number. + * When [startSampling] starts a new generation, any in-flight or queued ticks from the + * previous generation self-terminate on their first check. + */ +internal class RumSessionScopeTimeseries( + private val internalLogger: InternalLogger, + private val pipelines: List>, + private val collectInBackground: Boolean, + internal val scheduledExecutorService: ScheduledExecutorService +) : Timeseries { + + private enum class State { IDLE, RUNNING, SUSPENDED, STOPPED } + private val state = AtomicReference(State.IDLE) + + // Incremented on every start/resume. Ticks carrying a stale generation self-terminate. + private val currentGeneration = AtomicInteger(0) + + @Volatile + private var currentViewType: RumViewType? = null + + @WorkerThread + override fun onSessionStart() { + if (state.compareAndSet(State.IDLE, State.RUNNING)) { + startSampling() + } + } + + @WorkerThread + override fun onSessionStop() { + if (state.getAndSet(State.STOPPED) != State.STOPPED) { + pipelines.forEach { pipeline -> + try { + synchronized(pipeline, pipeline::flush) + } catch (@Suppress("TooGenericExceptionCaught") t: Throwable) { + internalLogger.log( + level = InternalLogger.Level.ERROR, + targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), + messageBuilder = { ERROR_FLUSH_FAILED }, + throwable = t + ) + } + } + } + } + + @WorkerThread + override fun onViewTypeUpdate(viewType: RumViewType) { + val isEnterForeground = currentViewType != RumViewType.FOREGROUND && viewType == RumViewType.FOREGROUND + currentViewType = viewType + if (!collectInBackground) { + if (isEnterForeground && state.compareAndSet(State.SUSPENDED, State.RUNNING)) { + startSampling() + } else if (viewType != RumViewType.FOREGROUND) { + state.compareAndSet(State.RUNNING, State.SUSPENDED) + } + } + } + + private fun startSampling() { + val generation = currentGeneration.incrementAndGet() + pipelines.forEach { schedulePipeline(it, generation) } + } + + private fun schedulePipeline(pipeline: Pipeline<*>, generation: Int) { + scheduledExecutorService.scheduleSafe( + OPERATION_NAME, + pipeline.intervalMs, + TimeUnit.MILLISECONDS, + internalLogger + ) { + if (!isActive(generation)) return@scheduleSafe + try { + synchronized(pipeline) { + if (isActive(generation)) pipeline.execute() + } + } catch (@Suppress("TooGenericExceptionCaught") t: Throwable) { + internalLogger.log( + level = InternalLogger.Level.ERROR, + targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), + messageBuilder = { ERROR_SAMPLING_FAILED }, + throwable = t + ) + } finally { + if (isActive(generation)) schedulePipeline(pipeline, generation) + } + } + } + + private fun isActive(generation: Int): Boolean = + state.get() == State.RUNNING && currentGeneration.get() == generation + + internal companion object { + internal const val OPERATION_NAME = "Timeseries sampling" + internal const val ERROR_SAMPLING_FAILED = "Timeseries sampling iteration failed; rescheduling next sample." + internal const val ERROR_FLUSH_FAILED = "Timeseries flush on session stop failed." + } +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactory.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactory.kt new file mode 100644 index 0000000000..8313ec0453 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactory.kt @@ -0,0 +1,31 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries + +import com.datadog.android.api.InternalLogger +import com.datadog.android.rum.RumSessionType +import java.util.concurrent.ScheduledExecutorService + +internal class RumSessionScopeTimeseriesFactory( + private val internalLogger: InternalLogger, + private val collectInBackground: Boolean, + private val scheduledExecutorService: ScheduledExecutorService, + private val pipelinesProvider: ( + sessionId: String, + applicationId: String, + sessionType: RumSessionType + ) -> List> +) : TimeseriesFactory { + + override fun create(sessionId: String, applicationId: String, sessionType: RumSessionType) = + RumSessionScopeTimeseries( + internalLogger = internalLogger, + collectInBackground = collectInBackground, + scheduledExecutorService = scheduledExecutorService, + pipelines = pipelinesProvider(sessionId, applicationId, sessionType) + ) +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt new file mode 100644 index 0000000000..db01671297 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt @@ -0,0 +1,17 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries + +import com.datadog.android.rum.internal.domain.scope.RumViewType +import com.datadog.tools.annotation.NoOpImplementation + +@NoOpImplementation +internal interface Timeseries { + fun onSessionStart() + fun onSessionStop() + fun onViewTypeUpdate(viewType: RumViewType) +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesFactory.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesFactory.kt new file mode 100644 index 0000000000..151037f53a --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesFactory.kt @@ -0,0 +1,15 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries + +import com.datadog.android.rum.RumSessionType +import com.datadog.tools.annotation.NoOpImplementation + +@NoOpImplementation +internal interface TimeseriesFactory { + fun create(sessionId: String, applicationId: String, sessionType: RumSessionType): Timeseries +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/DataPointsReader.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/DataPointsReader.kt new file mode 100644 index 0000000000..f936571168 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/DataPointsReader.kt @@ -0,0 +1,28 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.provider + +import androidx.annotation.WorkerThread +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.internal.timeseries.DataPoint +import java.util.concurrent.TimeUnit + +internal abstract class DataPointsReader(private val timeProvider: TimeProvider) { + + abstract val intervalMs: Long + + @WorkerThread + protected abstract fun readValue(): T? + + @WorkerThread + open fun read(): DataPoint? = readValue()?.let { value -> + DataPoint( + value = value, + timestampNs = TimeUnit.MILLISECONDS.toNanos(timeProvider.getDeviceTimestampMillis()) + ) + } +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/VitalReaderWrapper.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/VitalReaderWrapper.kt new file mode 100644 index 0000000000..3afd3ccf1b --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/VitalReaderWrapper.kt @@ -0,0 +1,19 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.provider + +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.internal.vitals.VitalReader + +internal class VitalReaderWrapper( + private val vitalReader: VitalReader, + timeProvider: TimeProvider, + override val intervalMs: Long +) : DataPointsReader(timeProvider) { + + override fun readValue(): Double? = vitalReader.readVitalData() +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt new file mode 100644 index 0000000000..6bf484b9e3 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt @@ -0,0 +1,14 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.serializer + +import com.datadog.android.rum.internal.timeseries.DataPoint +import com.google.gson.JsonObject + +internal fun interface JsonSerializer { + fun serialize(dataPoints: List>): JsonObject? +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumSessionTypeExtTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumSessionTypeExtTest.kt new file mode 100644 index 0000000000..f5283f6b4e --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumSessionTypeExtTest.kt @@ -0,0 +1,157 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal + +import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.model.ActionEvent +import com.datadog.android.rum.model.ErrorEvent +import com.datadog.android.rum.model.LongTaskEvent +import com.datadog.android.rum.model.ResourceEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent +import com.datadog.android.rum.model.ViewEvent +import com.datadog.android.rum.model.VitalAppLaunchEvent +import com.datadog.android.rum.model.VitalOperationStepEvent +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource + +internal class RumSessionTypeExtTest { + + @ParameterizedTest + @MethodSource("toActionMappings") + fun `M map session type W toAction()`(input: RumSessionType, expected: ActionEvent.ActionEventSessionType) { + assertThat(input.toAction()).isEqualTo(expected) + } + + @ParameterizedTest + @MethodSource("toResourceMappings") + fun `M map session type W toResource()`(input: RumSessionType, expected: ResourceEvent.ResourceEventSessionType) { + assertThat(input.toResource()).isEqualTo(expected) + } + + @ParameterizedTest + @MethodSource("toErrorMappings") + fun `M map session type W toError()`(input: RumSessionType, expected: ErrorEvent.ErrorEventSessionType) { + assertThat(input.toError()).isEqualTo(expected) + } + + @ParameterizedTest + @MethodSource("toViewMappings") + fun `M map session type W toView()`(input: RumSessionType, expected: ViewEvent.ViewEventSessionType) { + assertThat(input.toView()).isEqualTo(expected) + } + + @ParameterizedTest + @MethodSource("toLongTaskMappings") + fun `M map session type W toLongTask()`(input: RumSessionType, expected: LongTaskEvent.LongTaskEventSessionType) { + assertThat(input.toLongTask()).isEqualTo(expected) + } + + @ParameterizedTest + @MethodSource("toVitalMappings") + fun `M map session type W toVital()`( + input: RumSessionType, + expected: VitalOperationStepEvent.VitalOperationStepEventSessionType + ) { + assertThat(input.toVital()).isEqualTo(expected) + } + + @ParameterizedTest + @MethodSource("toVitalAppLaunchMappings") + fun `M map session type W toVitalAppLaunch()`( + input: RumSessionType, + expected: VitalAppLaunchEvent.VitalAppLaunchEventSessionType + ) { + assertThat(input.toVitalAppLaunch()).isEqualTo(expected) + } + + @ParameterizedTest + @MethodSource("toTimeseriesMemorySessionTypeMappings") + fun `M map session type W toTimeseriesMemorySessionType()`( + input: RumSessionType, + expected: TimeseriesMemoryEvent.Type + ) { + assertThat(input.toTimeseriesMemorySessionType()).isEqualTo(expected) + } + + @ParameterizedTest + @MethodSource("toTimeseriesCpuSessionTypeMappings") + fun `M map session type W toTimeseriesCpuSessionType()`(input: RumSessionType, expected: TimeseriesCpuEvent.Type) { + assertThat(input.toTimeseriesCpuSessionType()).isEqualTo(expected) + } + + companion object { + + @JvmStatic + fun toActionMappings(): List = listOf( + Arguments.of(RumSessionType.USER, ActionEvent.ActionEventSessionType.USER), + Arguments.of(RumSessionType.SYNTHETICS, ActionEvent.ActionEventSessionType.SYNTHETICS) + ) + + @JvmStatic + fun toResourceMappings(): List = listOf( + Arguments.of(RumSessionType.USER, ResourceEvent.ResourceEventSessionType.USER), + Arguments.of(RumSessionType.SYNTHETICS, ResourceEvent.ResourceEventSessionType.SYNTHETICS) + ) + + @JvmStatic + fun toErrorMappings(): List = listOf( + Arguments.of(RumSessionType.USER, ErrorEvent.ErrorEventSessionType.USER), + Arguments.of(RumSessionType.SYNTHETICS, ErrorEvent.ErrorEventSessionType.SYNTHETICS) + ) + + @JvmStatic + fun toViewMappings(): List = listOf( + Arguments.of(RumSessionType.USER, ViewEvent.ViewEventSessionType.USER), + Arguments.of(RumSessionType.SYNTHETICS, ViewEvent.ViewEventSessionType.SYNTHETICS) + ) + + @JvmStatic + fun toLongTaskMappings(): List = listOf( + Arguments.of(RumSessionType.USER, LongTaskEvent.LongTaskEventSessionType.USER), + Arguments.of(RumSessionType.SYNTHETICS, LongTaskEvent.LongTaskEventSessionType.SYNTHETICS) + ) + + @JvmStatic + fun toVitalMappings(): List = listOf( + Arguments.of( + RumSessionType.USER, + VitalOperationStepEvent.VitalOperationStepEventSessionType.USER + ), + Arguments.of( + RumSessionType.SYNTHETICS, + VitalOperationStepEvent.VitalOperationStepEventSessionType.SYNTHETICS + ) + ) + + @JvmStatic + fun toVitalAppLaunchMappings(): List = listOf( + Arguments.of( + RumSessionType.USER, + VitalAppLaunchEvent.VitalAppLaunchEventSessionType.USER + ), + Arguments.of( + RumSessionType.SYNTHETICS, + VitalAppLaunchEvent.VitalAppLaunchEventSessionType.SYNTHETICS + ) + ) + + @JvmStatic + fun toTimeseriesMemorySessionTypeMappings(): List = listOf( + Arguments.of(RumSessionType.USER, TimeseriesMemoryEvent.Type.USER), + Arguments.of(RumSessionType.SYNTHETICS, TimeseriesMemoryEvent.Type.SYNTHETICS) + ) + + @JvmStatic + fun toTimeseriesCpuSessionTypeMappings(): List = listOf( + Arguments.of(RumSessionType.USER, TimeseriesCpuEvent.Type.USER), + Arguments.of(RumSessionType.SYNTHETICS, TimeseriesCpuEvent.Type.SYNTHETICS) + ) + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt index 68f350725e..8373efd71e 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt @@ -39,6 +39,9 @@ import com.datadog.android.rum.internal.startup.RumSessionScopeStartupManager import com.datadog.android.rum.internal.startup.RumStartupScenario import com.datadog.android.rum.internal.startup.RumTTIDInfo import com.datadog.android.rum.internal.startup.testRumStartupScenarios +import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesFactory +import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.TimeseriesFactory import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -66,9 +69,11 @@ import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq +import org.mockito.kotlin.inOrder import org.mockito.kotlin.isA import org.mockito.kotlin.mock import org.mockito.kotlin.never @@ -196,12 +201,19 @@ internal class RumSessionScopeTest { @Mock private lateinit var mockRumSessionScopeStartupManager: RumSessionScopeStartupManager + @Mock + private lateinit var mockTimeseriesFactory: TimeseriesFactory + + @Mock + private lateinit var mockTimeseries: Timeseries + private lateinit var stubTimeProvider: StubTimeProvider @BeforeEach fun `set up`(forge: Forge) { stubTimeProvider = StubTimeProvider(elapsedTimeNs = TEST_INACTIVITY_NS + 1) fakeInitialViewEvent = forge.startViewEvent() + fakeParentContext = fakeParentContext.copy(viewType = RumViewType.NONE) whenever(mockParentScope.getRumContext()).doAnswer { fakeParentContext } whenever(mockChildScope.handleEvent(any(), any(), any(), any())) doReturn mockChildScope @@ -1744,6 +1756,196 @@ internal class RumSessionScopeTest { // endregion + // region Timeseries + + @Test + fun `M start timeseries via factory W renewSession { keepSession = true }`(forge: Forge) { + // Given + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + + // When + testedScope.handleEvent( + forge.startViewEvent(), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // Then + verify(mockTimeseriesFactory).create( + testedScope.sessionId, + fakeParentContext.applicationId, + fakeRumSessionType ?: RumSessionType.USER + ) + verify(mockTimeseries).onSessionStart() + } + + @Test + fun `M not start timeseries W renewSession { keepSession = false }`(forge: Forge) { + // Given + whenever(mockSessionSampler.sample(any())).thenReturn(false) + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + + // When + testedScope.handleEvent( + forge.startViewEvent(), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // Then + verify(mockTimeseriesFactory, never()).create(any(), any(), any()) + verify(mockTimeseries, never()).onSessionStart() + } + + @Test + fun `M stop previous timeseries W renewSession { another tracked session }`(forge: Forge) { + // Given + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + testedScope.handleEvent( + forge.startViewEvent(), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // When — second session via inactivity expiration + advanceTimeByMs(TEST_INACTIVITY_MS) + testedScope.handleEvent( + forge.startViewEvent(), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // Then + verify(mockTimeseries).onSessionStop() + verify(mockTimeseriesFactory, times(2)).create(any(), any(), any()) + verify(mockTimeseries, times(2)).onSessionStart() + } + + @Test + fun `M stop timeseries W handleEvent { StopSession }`(forge: Forge) { + // Given + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + testedScope.handleEvent( + forge.startViewEvent(), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // When + testedScope.handleEvent( + RumRawEvent.StopSession(), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // Then + verify(mockTimeseries).onSessionStop() + } + + @Test + fun `M not interact with timeseries W renewSession { default NoOp factory }`(forge: Forge) { + // When + val result = testedScope.handleEvent( + forge.startViewEvent(), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // Then + assertThat(result).isSameAs(testedScope) + verifyNoInteractions(mockTimeseries, mockTimeseriesFactory) + } + + @Test + fun `M notify timeseries of view type W handleEvent`(forge: Forge) { + // Given + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + testedScope.handleEvent( + forge.startViewEvent(), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // When + testedScope.handleEvent( + forge.addErrorEvent(), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // Then — called once per handleEvent invocation + verify(mockTimeseries, times(2)).onViewTypeUpdate(any()) + } + + @Test + fun `M pass NONE W onViewTypeUpdate { no active view }`(forge: Forge) { + // Given — activeView is null by default (mockChildScope.activeView not stubbed) + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // When + testedScope.handleEvent(forge.addErrorEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then + verify(mockTimeseries, times(2)).onViewTypeUpdate(RumViewType.NONE) + } + + @Test + fun `M pass active view type W onViewTypeUpdate { foreground view active }`(forge: Forge) { + // Given + val mockViewScope = mock() + val fakeRumContext = forge.getForgery().copy(viewType = RumViewType.FOREGROUND) + whenever(mockChildScope.activeView) doReturn mockViewScope + whenever(mockViewScope.getRumContext()) doReturn fakeRumContext + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // When + testedScope.handleEvent(forge.addErrorEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then — at least the last call carries FOREGROUND + verify(mockTimeseries, atLeastOnce()).onViewTypeUpdate(RumViewType.FOREGROUND) + } + + @Test + fun `M pass view type to fresh timeseries W renewSession { session renewed }`(forge: Forge) { + // Given — active foreground view throughout + val mockViewScope = mock() + val fakeRumContext = forge.getForgery().copy(viewType = RumViewType.FOREGROUND) + whenever(mockChildScope.activeView) doReturn mockViewScope + whenever(mockViewScope.getRumContext()) doReturn fakeRumContext + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // When — expire session and start a new one + advanceTimeByMs(TEST_INACTIVITY_MS) + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then — second timeseries (same factory mock) also receives FOREGROUND right after start() + val inOrder = inOrder(mockTimeseries) + inOrder.verify(mockTimeseries, times(2)).onSessionStart() + inOrder.verify(mockTimeseries).onViewTypeUpdate(RumViewType.FOREGROUND) + } + + // endregion + // region Internal private fun advanceTimeByMs(ms: Long) { @@ -1760,7 +1962,8 @@ internal class RumSessionScopeTest { private fun initializeTestedScope( sessionSampler: Sampler = mockSessionSampler, withMockChildScope: Boolean = true, - backgroundTrackingEnabled: Boolean? = null + backgroundTrackingEnabled: Boolean? = null, + timeseriesFactory: TimeseriesFactory = NoOpTimeseriesFactory() ) { testedScope = RumSessionScope( parentScope = mockParentScope, @@ -1786,7 +1989,8 @@ internal class RumSessionScopeTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumSessionScopeStartupManagerFactory = { mockRumSessionScopeStartupManager }, - insightsCollector = mockInsightsCollector + insightsCollector = mockInsightsCollector, + timeseriesFactory = timeseriesFactory ) if (withMockChildScope) { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/BufferTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/BufferTest.kt new file mode 100644 index 0000000000..a480423d92 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/BufferTest.kt @@ -0,0 +1,118 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries + +import com.datadog.android.rum.utils.forge.Configurator +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.IntForgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions + +@Extensions( + ExtendWith(ForgeExtension::class) +) +@ForgeConfiguration(Configurator::class) +internal class BufferTest { + + @Test + fun `M return false W isFull() { buffer is empty }`(@IntForgery(min = 1, max = 256) fakeSize: Int) { + // Given + val testedBuffer = Buffer(fakeSize) + + // When / Then + assertThat(testedBuffer.isFull()).isFalse() + } + + @Test + fun `M return false W isFull() { items below capacity }`( + @IntForgery(min = 2, max = 256) fakeSize: Int, + forge: Forge + ) { + // Given + val testedBuffer = Buffer(fakeSize) + repeat(fakeSize - 1) { testedBuffer.add(forge.getForgery>()) } + + // When / Then + assertThat(testedBuffer.isFull()).isFalse() + } + + @Test + fun `M return true W isFull() { items at capacity }`(@IntForgery(min = 1, max = 256) fakeSize: Int, forge: Forge) { + // Given + val testedBuffer = Buffer(fakeSize) + repeat(fakeSize) { testedBuffer.add(forge.getForgery>()) } + + // When / Then + assertThat(testedBuffer.isFull()).isTrue() + } + + @Test + fun `M return true W isFull() { items beyond capacity }`( + @IntForgery(min = 1, max = 64) fakeSize: Int, + @IntForgery(min = 1, max = 8) fakeOverflow: Int, + forge: Forge + ) { + // Given + val testedBuffer = Buffer(fakeSize) + repeat(fakeSize + fakeOverflow) { testedBuffer.add(forge.getForgery>()) } + + // When / Then + assertThat(testedBuffer.isFull()).isTrue() + } + + @Test + fun `M return null W drain() { buffer is empty }`(@IntForgery(min = 1, max = 256) fakeSize: Int) { + // Given + val testedBuffer = Buffer(fakeSize) + + // When + val result = testedBuffer.drain() + + // Then + assertThat(result).isEmpty() + } + + @Test + fun `M return all items and clear W drain() { buffer not empty }`( + @IntForgery(min = 1, max = 16) fakeSize: Int, + forge: Forge + ) { + // Given + val testedBuffer = Buffer(fakeSize) + val fakePoints = (0 until fakeSize).map { forge.getForgery>() } + fakePoints.forEach { testedBuffer.add(it) } + + // When + val drained = testedBuffer.drain() + + // Then + assertThat(drained).containsExactlyElementsOf(fakePoints) + assertThat(testedBuffer.isFull()).isFalse() + assertThat(testedBuffer.drain()).isEmpty() + } + + @Test + fun `M return immutable snapshot W drain() { caller cannot affect internal state }`( + @IntForgery(min = 1, max = 16) fakeSize: Int, + forge: Forge + ) { + // Given + val testedBuffer = Buffer(fakeSize) + repeat(fakeSize) { testedBuffer.add(forge.getForgery>()) } + val drained = testedBuffer.drain() + + // When - the buffer should be cleared after drain + testedBuffer.add(forge.getForgery>()) + + // Then - drained list size unchanged regardless of further mutations + assertThat(drained).hasSize(fakeSize) + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt new file mode 100644 index 0000000000..6e5b984cd9 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt @@ -0,0 +1,212 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries + +import com.datadog.android.api.context.DatadogContext +import com.datadog.android.api.feature.EventWriteScope +import com.datadog.android.api.feature.Feature +import com.datadog.android.api.feature.FeatureScope +import com.datadog.android.api.feature.FeatureSdkCore +import com.datadog.android.api.storage.DataWriter +import com.datadog.android.api.storage.EventBatchWriter +import com.datadog.android.api.storage.EventType +import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader +import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer +import com.datadog.android.rum.utils.forge.Configurator +import com.google.gson.JsonObject +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.IntForgery +import fr.xgouchet.elmyr.annotation.LongForgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(Configurator::class) +internal class PipelineTest { + + @Mock + lateinit var mockReader: DataPointsReader + + @Mock + lateinit var mockSerializer: JsonSerializer + + @Mock + lateinit var mockSdkCore: FeatureSdkCore + + @Mock + lateinit var mockDataWriter: DataWriter + + @Mock + lateinit var mockRumFeatureScope: FeatureScope + + @Mock + lateinit var mockEventWriteScope: EventWriteScope + + @Mock + lateinit var mockEventBatchWriter: EventBatchWriter + + @IntForgery(min = 2, max = 10) + var fakeBufferSize: Int = 0 + + private lateinit var buffer: Buffer + private lateinit var testedPipeline: Pipeline + + @BeforeEach + fun `set up`() { + whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope + whenever(mockRumFeatureScope.withWriteContext(any(), any())) doAnswer { + it.getArgument<(DatadogContext, EventWriteScope) -> Unit>(it.arguments.lastIndex) + .invoke(mock(), mockEventWriteScope) + } + whenever(mockEventWriteScope.invoke(any())) doAnswer { + it.getArgument<(EventBatchWriter) -> Unit>(0).invoke(mockEventBatchWriter) + } + + buffer = Buffer(fakeBufferSize) + testedPipeline = Pipeline(mockSdkCore, mockReader, buffer, mockSerializer, mockDataWriter) + } + + @Test + fun `M expose reader intervalMs W intervalMs`(@LongForgery(min = 1L) fakeIntervalMs: Long) { + // Given + whenever(mockReader.intervalMs) doReturn fakeIntervalMs + + // When / Then + assertThat(testedPipeline.intervalMs).isEqualTo(fakeIntervalMs) + } + + // region execute() + + @Test + fun `M append sample to buffer W execute()`(forge: Forge) { + // Given + val fakePoint = forge.getForgery>() + whenever(mockReader.read()) doReturn fakePoint + + // When + testedPipeline.execute() + + // Then + assertThat(buffer.drain()).containsExactly(fakePoint) + } + + @Test + fun `M not add to buffer W execute() { reader returns null }`() { + // Given + whenever(mockReader.read()) doReturn null + + // When + testedPipeline.execute() + + // Then + assertThat(buffer.drain()).isEmpty() + } + + // endregion + + // region flush() + + @Test + fun `M flush W execute() { buffer fills to capacity }`(forge: Forge) { + // Given + val fakePoint = forge.getForgery>() + val fakeJson = JsonObject().apply { addProperty("k", "v") } + whenever(mockReader.read()) doReturn fakePoint + whenever(mockSerializer.serialize(any())) doReturn fakeJson + repeat(fakeBufferSize - 1) { testedPipeline.execute() } + + // When + testedPipeline.execute() + + // Then + verify(mockDataWriter).write(mockEventBatchWriter, fakeJson, EventType.DEFAULT) + assertThat(buffer.drain()).isEmpty() + } + + @Test + fun `M not flush W execute() { buffer not full }`(forge: Forge) { + // Given + whenever(mockReader.read()) doReturn forge.getForgery>() + + // When + repeat(fakeBufferSize - 1) { testedPipeline.execute() } + + // Then + verifyNoInteractions(mockDataWriter) + } + + @Test + fun `M not flush W execute() { serializer returns null }`(forge: Forge) { + // Given + whenever(mockReader.read()) doReturn forge.getForgery>() + whenever(mockSerializer.serialize(any())) doReturn null + repeat(fakeBufferSize - 1) { testedPipeline.execute() } + + // When + testedPipeline.execute() + + // Then + verifyNoInteractions(mockDataWriter) + } + + // endregion + + // region flush() + + @Test + fun `M write partial buffer W flush() { buffer has data }`(forge: Forge) { + // Given + val fakePoint = forge.getForgery>() + val fakeJson = JsonObject().apply { addProperty("k", "v") } + whenever(mockReader.read()) doReturn fakePoint + whenever(mockSerializer.serialize(any())) doReturn fakeJson + val sampleCount = fakeBufferSize - 1 + repeat(sampleCount) { testedPipeline.execute() } + + // When + testedPipeline.flush() + + // Then + val captor = argumentCaptor>>() + verify(mockSerializer).serialize(captor.capture()) + assertThat(captor.firstValue).hasSize(sampleCount).allMatch { it == fakePoint } + verify(mockDataWriter).write(mockEventBatchWriter, fakeJson, EventType.DEFAULT) + assertThat(buffer.drain()).isEmpty() + } + + @Test + fun `M not write W flush() { buffer empty }`() { + // When + testedPipeline.flush() + + // Then + verifyNoInteractions(mockDataWriter) + } + + // endregion +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt new file mode 100644 index 0000000000..9301b3cbd0 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt @@ -0,0 +1,108 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries + +import com.datadog.android.api.InternalLogger +import com.datadog.android.api.feature.FeatureSdkCore +import com.datadog.android.api.storage.DataWriter +import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader +import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer +import com.datadog.android.rum.utils.forge.Configurator +import fr.xgouchet.elmyr.annotation.StringForgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness +import java.util.concurrent.ScheduledExecutorService + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(Configurator::class) +internal class RumSessionScopeTimeseriesFactoryTest { + + @Mock + lateinit var mockSdkCore: FeatureSdkCore + + @Mock + lateinit var mockInternalLogger: InternalLogger + + @Mock + lateinit var mockWriter: DataWriter + + @Mock + lateinit var mockExecutor: ScheduledExecutorService + + @Test + fun `M build RumSessionScopeTimeseries with provided pipelines W create()`( + @StringForgery fakeSessionId: String, + @StringForgery fakeApplicationId: String + ) { + // Given + val fakeSessionType = RumSessionType.USER + val pipelineA = mockPipeline() + val pipelineB = mockPipeline() + val provider: (String, String, RumSessionType) -> List> = + mock<(String, String, RumSessionType) -> List>>().apply { + whenever(this(fakeSessionId, fakeApplicationId, fakeSessionType)) + .thenReturn(listOf(pipelineA, pipelineB)) + } + val testedFactory = RumSessionScopeTimeseriesFactory( + internalLogger = mockInternalLogger, + collectInBackground = false, + scheduledExecutorService = mockExecutor, + pipelinesProvider = provider + ) + + // When + val timeseries = testedFactory.create(fakeSessionId, fakeApplicationId, fakeSessionType) + + // Then + verify(provider).invoke(fakeSessionId, fakeApplicationId, fakeSessionType) + assertThat(timeseries.scheduledExecutorService).isSameAs(mockExecutor) + } + + @Test + fun `M propagate collectInBackground flag W create()`( + @StringForgery fakeSessionId: String, + @StringForgery fakeApplicationId: String + ) { + // Given + val emptyProvider: (String, String, RumSessionType) -> List> = { _, _, _ -> emptyList() } + val testedFactory = RumSessionScopeTimeseriesFactory( + internalLogger = mockInternalLogger, + collectInBackground = true, + scheduledExecutorService = mockExecutor, + pipelinesProvider = emptyProvider + ) + + // When + val timeseries = testedFactory.create(fakeSessionId, fakeApplicationId, RumSessionType.USER) + + // Then — collectInBackground is private; tick behavior is covered by RumSessionScopeTimeseriesTest. + // Here we just assert the instance was built without throwing. + assertThat(timeseries).isNotNull + } + + private fun mockPipeline(): Pipeline<*> { + val reader = mock>() + val serializer = mock>() + return Pipeline(mockSdkCore, reader, Buffer(1), serializer, mockWriter) + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesTest.kt new file mode 100644 index 0000000000..41a004ea3a --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesTest.kt @@ -0,0 +1,531 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries + +import com.datadog.android.api.InternalLogger +import com.datadog.android.api.context.DatadogContext +import com.datadog.android.api.feature.EventWriteScope +import com.datadog.android.api.feature.Feature +import com.datadog.android.api.feature.FeatureScope +import com.datadog.android.api.feature.FeatureSdkCore +import com.datadog.android.api.storage.DataWriter +import com.datadog.android.api.storage.EventBatchWriter +import com.datadog.android.api.storage.EventType +import com.datadog.android.rum.internal.domain.scope.RumViewType +import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader +import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer +import com.datadog.android.rum.utils.forge.Configurator +import com.datadog.android.utils.verifyLog +import com.google.gson.JsonObject +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.IntForgery +import fr.xgouchet.elmyr.annotation.LongForgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(Configurator::class) +internal class RumSessionScopeTimeseriesTest { + + private lateinit var testedTimeseries: RumSessionScopeTimeseries + + @Mock + lateinit var mockDataWriter: DataWriter + + @Mock + lateinit var mockInternalLogger: InternalLogger + + @Mock + lateinit var mockSdkCore: FeatureSdkCore + + @Mock + lateinit var mockRumFeatureScope: FeatureScope + + @Mock + lateinit var mockEventWriteScope: EventWriteScope + + @Mock + lateinit var mockEventBatchWriter: EventBatchWriter + + @Mock + lateinit var mockExecutor: ScheduledExecutorService + + @Mock + lateinit var mockReaderA: DataPointsReader + + @Mock + lateinit var mockReaderB: DataPointsReader + + @Mock + lateinit var mockSerializerA: JsonSerializer + + @Mock + lateinit var mockSerializerB: JsonSerializer + + private lateinit var bufferA: Buffer + private lateinit var bufferB: Buffer + + private lateinit var pipelineA: Pipeline + private lateinit var pipelineB: Pipeline + + @LongForgery(min = 1L, max = 60_000L) + var fakeIntervalAMs: Long = 0L + + @LongForgery(min = 60_001L, max = 120_000L) + var fakeIntervalBMs: Long = 0L + + @IntForgery(min = 2, max = 16) + var fakeBufferSize: Int = 0 + + @BeforeEach + fun `set up`() { + whenever(mockReaderA.intervalMs) doReturn fakeIntervalAMs + whenever(mockReaderB.intervalMs) doReturn fakeIntervalBMs + whenever(mockExecutor.schedule(any(), any(), any())) doReturn mock>() + whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger + whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope + whenever(mockRumFeatureScope.withWriteContext(any(), any())) doAnswer { inv -> + inv.getArgument<(DatadogContext, EventWriteScope) -> Unit>(inv.arguments.lastIndex) + .invoke(mock(), mockEventWriteScope) + } + whenever(mockEventWriteScope.invoke(any())) doAnswer { inv -> + inv.getArgument<(EventBatchWriter) -> Unit>(0).invoke(mockEventBatchWriter) + } + + bufferA = Buffer(fakeBufferSize) + bufferB = Buffer(fakeBufferSize) + pipelineA = Pipeline(mockSdkCore, mockReaderA, bufferA, mockSerializerA, mockDataWriter) + pipelineB = Pipeline(mockSdkCore, mockReaderB, bufferB, mockSerializerB, mockDataWriter) + + testedTimeseries = RumSessionScopeTimeseries( + pipelines = listOf(pipelineA, pipelineB), + internalLogger = mockInternalLogger, + collectInBackground = false, + scheduledExecutorService = mockExecutor + ) + // Seed FOREGROUND so sampling tests don't suspend on first tick + testedTimeseries.onViewTypeUpdate(RumViewType.FOREGROUND) + } + + @Test + fun `M schedule one runnable per pipeline W onSessionStart()`() { + // When + testedTimeseries.onSessionStart() + + // Then + verify(mockExecutor).schedule(any(), eq(fakeIntervalAMs), eq(TimeUnit.MILLISECONDS)) + verify(mockExecutor).schedule(any(), eq(fakeIntervalBMs), eq(TimeUnit.MILLISECONDS)) + } + + @Test + fun `M not write events W onSessionStop() { never started }`() { + // When + testedTimeseries.onSessionStop() + + // Then + verify(mockDataWriter, never()).write(any(), any(), any()) + } + + @Test + fun `M not shut down shared executor W onSessionStop()`() { + // Given + testedTimeseries.onSessionStart() + + // When + testedTimeseries.onSessionStop() + + // Then — executor is owned by the SDK core and reused across components + verify(mockExecutor, never()).shutdown() + verify(mockExecutor, never()).shutdownNow() + } + + @Test + fun `M not write events W onSessionStart()+onSessionStop() { buffers empty }`() { + // Given + testedTimeseries.onSessionStart() + + // When + testedTimeseries.onSessionStop() + + // Then + verify(mockDataWriter, never()).write(any(), any(), any()) + } + + @Test + fun `M log error and continue W onSessionStop() { serializer throws on pipelineA }`(forge: Forge) { + // Given + val fakeError = RuntimeException("serializer failure") + val fakeJson = JsonObject().apply { addProperty("k", "v") } + whenever(mockReaderA.read()) doReturn forge.getForgery>() + whenever(mockReaderB.read()) doReturn forge.getForgery>() + whenever(mockSerializerA.serialize(any())) doThrow fakeError + whenever(mockSerializerB.serialize(any())) doReturn fakeJson + testedTimeseries.onSessionStart() + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + val runnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) + runnableA.run() + runnableB.run() + + // When + testedTimeseries.onSessionStop() + + // Then — error logged for pipelineA, pipelineB still flushed + mockInternalLogger.verifyLog( + InternalLogger.Level.ERROR, + targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), + RumSessionScopeTimeseries.ERROR_FLUSH_FAILED, + fakeError + ) + verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) + } + + @Test + fun `M flush partial buffer W onSessionStop() { buffer below batch size }`(forge: Forge) { + // Given + val fakeJson = JsonObject().apply { addProperty("k", "v") } + whenever(mockReaderA.read()) doReturn forge.getForgery>() + whenever(mockSerializerA.serialize(any())) doReturn fakeJson + testedTimeseries.onSessionStart() + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + runnableA.run() + + // When + testedTimeseries.onSessionStop() + + // Then + verify(mockSerializerA).serialize(any()) + verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) + } + + @Test + fun `M flush batch W sample tick fills buffer { single pipeline }`(forge: Forge) { + // Given + val element = forge.getForgery>() + val dataPoints = List(fakeBufferSize) { element } + val fakeJson = JsonObject().apply { addProperty("a", "1") } + whenever(mockReaderA.read()) doReturn element + whenever(mockSerializerA.serialize(dataPoints)) doReturn fakeJson + + testedTimeseries.onSessionStart() + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + + // When + repeat(fakeBufferSize) { runnableA.run() } + + // Then + verify(mockSerializerA).serialize(any()) + verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) + } + + @Test + fun `M reschedule sample W sample tick runs`(forge: Forge) { + // Given + whenever(mockReaderA.read()) doReturn forge.getForgery>() + testedTimeseries.onSessionStart() + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + + // When + runnableA.run() + + // Then - 1 schedule from start() + 1 reschedule from sample tick + verify(mockExecutor, times(2)) + .schedule(any(), eq(fakeIntervalAMs), eq(TimeUnit.MILLISECONDS)) + } + + @Test + fun `M not write events W sample tick { buffer not full }`(forge: Forge) { + // Given + whenever(mockReaderA.read()) doReturn forge.getForgery>() + testedTimeseries.onSessionStart() + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + + // When + runnableA.run() + + // Then + verify(mockSerializerA, never()).serialize(any()) + verify(mockDataWriter, never()).write(any(), any(), any()) + } + + @Test + fun `M discard pre-existing data W onSessionStart() { buffer not empty }`(forge: Forge) { + // Given - start() drains all pipelines and ignores any serialized result + bufferA.add(forge.getForgery>()) + + // When + testedTimeseries.onSessionStart() + + // Then - even though serializer may be invoked, no event reaches the writer + verify(mockDataWriter, never()).write(any(), any(), any()) + } + + @Test + fun `M skip writer W sample tick { serializer returns null }`(forge: Forge) { + // Given - fill the buffer so Pipeline.drain() reaches the serializer; serializer returns null + whenever(mockReaderA.read()) doReturn forge.getForgery>() + whenever(mockSerializerA.serialize(any())) doReturn null + testedTimeseries.onSessionStart() + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + + // When + repeat(fakeBufferSize) { runnableA.run() } + + // Then + verify(mockSerializerA).serialize(any()) + verify(mockDataWriter, never()).write(any(), any(), any()) + } + + @Test + fun `M not start twice W onSessionStart() { called twice }`() { + // When + testedTimeseries.onSessionStart() + testedTimeseries.onSessionStart() + + // Then — executor.schedule called exactly once per pipeline (not doubled) + verify(mockExecutor).schedule( + any(), + eq(fakeIntervalAMs), + eq(TimeUnit.MILLISECONDS) + ) + verify(mockExecutor).schedule( + any(), + eq(fakeIntervalBMs), + eq(TimeUnit.MILLISECONDS) + ) + } + + @Test + fun `M not throw W onSessionStop() { called twice }`() { + // Given + testedTimeseries.onSessionStart() + + // When + Then — must not throw + testedTimeseries.onSessionStop() + testedTimeseries.onSessionStop() + } + + @Test + fun `M not start after stop W onSessionStart() { called after onSessionStop() }`() { + // Given + testedTimeseries.onSessionStart() + testedTimeseries.onSessionStop() + + // When + testedTimeseries.onSessionStart() + + // Then — no additional schedules beyond the original start + verify(mockExecutor).schedule( + any(), + eq(fakeIntervalAMs), + eq(TimeUnit.MILLISECONDS) + ) + verify(mockExecutor).schedule( + any(), + eq(fakeIntervalBMs), + eq(TimeUnit.MILLISECONDS) + ) + } + + @Test + fun `M continue scheduling W sample tick { reader throws }`() { + // Given + val fakeError = RuntimeException("reader failure") + whenever(mockReaderA.read()) doThrow fakeError + testedTimeseries.onSessionStart() + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + + // When + runnableA.run() + + // Then — 1 from start() + 1 rescheduled via finally after exception + verify(mockExecutor, times(2)) + .schedule( + any(), + eq(fakeIntervalAMs), + eq(TimeUnit.MILLISECONDS) + ) + } + + @Test + fun `M log error W sample tick { reader throws }`() { + // Given + val fakeError = RuntimeException("reader failure") + whenever(mockReaderA.read()) doThrow fakeError + testedTimeseries.onSessionStart() + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + + // When + runnableA.run() + + // Then + mockInternalLogger.verifyLog( + InternalLogger.Level.ERROR, + targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), + RumSessionScopeTimeseries.ERROR_SAMPLING_FAILED, + fakeError + ) + } + + @Test + fun `M not reschedule W stopped { sample tick skips try block }`() { + // Given + testedTimeseries.onSessionStart() + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + testedTimeseries.onSessionStop() + + // When + runnableA.run() + + // Then — no additional schedule after the one from start() + verify(mockExecutor).schedule( + any(), + eq(fakeIntervalAMs), + eq(TimeUnit.MILLISECONDS) + ) + } + + @Test + fun `M suspend chain W sample tick { background, collectInBackground = false }`(forge: Forge) { + // Given + whenever(mockReaderA.read()) doReturn forge.getForgery>() + testedTimeseries.onSessionStart() + testedTimeseries.onViewTypeUpdate(RumViewType.BACKGROUND) + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + + // When + runnableA.run() + + // Then — sample skipped AND no reschedule (chain suspended) + verify(mockReaderA, never()).read() + verify(mockExecutor).schedule( + any(), + eq(fakeIntervalAMs), + eq(TimeUnit.MILLISECONDS) + ) + } + + @Test + fun `M sample W sample tick { background, collectInBackground = true }`(forge: Forge) { + // Given + whenever(mockReaderA.read()) doReturn forge.getForgery>() + val bgTimeseries = RumSessionScopeTimeseries( + pipelines = listOf(pipelineA, pipelineB), + internalLogger = mockInternalLogger, + collectInBackground = true, + scheduledExecutorService = mockExecutor + ) + bgTimeseries.onSessionStart() + bgTimeseries.onViewTypeUpdate(RumViewType.BACKGROUND) + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + + // When + runnableA.run() + + // Then — sample taken even in background + verify(mockReaderA).read() + } + + @Test + fun `M resume scheduling W onViewTypeUpdate() { background → foreground }`() { + // Given + testedTimeseries.onSessionStart() + // Transition to background: suspends chains + testedTimeseries.onViewTypeUpdate(RumViewType.BACKGROUND) + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + runnableA.run() // State is already SUSPENDED; tick exits without reschedule + val schedulesAfterSuspend = 1 // only the one from start() + + // When — app returns to foreground + testedTimeseries.onViewTypeUpdate(RumViewType.FOREGROUND) + + // Then — scheduling resumed (one new schedule per pipeline) + verify(mockExecutor, times(schedulesAfterSuspend + 1)) + .schedule(any(), eq(fakeIntervalAMs), eq(TimeUnit.MILLISECONDS)) + } + + @Test + fun `M not sample W stale tick fires after resume { old generation tick self-terminates }`(forge: Forge) { + // Regression: when pipeline B's tick (long interval) is still pending at the moment + // resumeSampling() fires, it must self-terminate rather than duplicate the chain. + // + // Given + whenever(mockReaderB.read()) doReturn forge.getForgery>() + testedTimeseries.onSessionStart() + + // Pipeline A suspends the instance (short interval fires first in background) + testedTimeseries.onViewTypeUpdate(RumViewType.BACKGROUND) + val oldRunnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + val oldRunnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) + oldRunnableA.run() // State is already SUSPENDED (set by onViewTypeUpdate); B's tick is still queued + + // Resume creates a new generation and schedules fresh ticks for both pipelines + testedTimeseries.onViewTypeUpdate(RumViewType.FOREGROUND) + + // When — B's old (stale) runnable fires despite the new generation + oldRunnableB.run() + + // Then — stale tick must not sample and must not reschedule + verify(mockReaderB, never()).read() + // Total B schedules: 1 from start() + 1 from resumeSampling = 2; old tick adds nothing + verify(mockExecutor, times(2)) + .schedule(any(), eq(fakeIntervalBMs), eq(TimeUnit.MILLISECONDS)) + } + + @Test + fun `M not sample or reschedule W stale tick fires { different generation, state is RUNNING }`() { + // A stale tick (old generation) must self-terminate via the generation check + // even when state is currently RUNNING. Ticks never write state. + // + // Given + testedTimeseries.onSessionStart() + testedTimeseries.onViewTypeUpdate(RumViewType.BACKGROUND) + val oldRunnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) + // Resume: state = RUNNING, gen = 2; A and B each get a new schedule + testedTimeseries.onViewTypeUpdate(RumViewType.FOREGROUND) + + // When — B's stale gen-1 tick fires while state is RUNNING + oldRunnableB.run() + + // Then — stale tick neither samples nor produces an extra schedule + verify(mockReaderB, never()).read() + // B schedules: 1 from start() + 1 from resume = 2; stale tick adds nothing + verify(mockExecutor, times(2)) + .schedule(any(), eq(fakeIntervalBMs), eq(TimeUnit.MILLISECONDS)) + } + + private fun captureScheduledRunnableForInterval(intervalMs: Long): Runnable { + val captor = argumentCaptor() + verify(mockExecutor).schedule(captor.capture(), eq(intervalMs), eq(TimeUnit.MILLISECONDS)) + return captor.firstValue + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/VitalReaderDataPointsReaderTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/VitalReaderDataPointsReaderTest.kt new file mode 100644 index 0000000000..3650eec407 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/VitalReaderDataPointsReaderTest.kt @@ -0,0 +1,94 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries + +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.internal.timeseries.provider.VitalReaderWrapper +import com.datadog.android.rum.internal.vitals.VitalReader +import com.datadog.android.rum.utils.forge.Configurator +import fr.xgouchet.elmyr.annotation.DoubleForgery +import fr.xgouchet.elmyr.annotation.LongForgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness +import java.util.concurrent.TimeUnit + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(Configurator::class) +internal class VitalReaderDataPointsReaderTest { + + @LongForgery(min = 1L) + var fakeTimestamp: Long = 0L + + @Mock + lateinit var mockTimeProvider: TimeProvider + + @BeforeEach + fun `set up`() { + whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn fakeTimestamp + } + + @Test + fun `M return sample W read() { reader has value }`( + @DoubleForgery(min = 0.001) fakeValue: Double, + @LongForgery(min = 1L) fakeIntervalMs: Long + ) { + // Given + val mockVitalReader: VitalReader = mock { + on { readVitalData() } doReturn fakeValue + } + val testedReader = VitalReaderWrapper(mockVitalReader, mockTimeProvider, fakeIntervalMs) + + // When + val result = testedReader.read() + + // Then + checkNotNull(result) + assertThat(result.timestampNs).isEqualTo(TimeUnit.MILLISECONDS.toNanos(fakeTimestamp)) + assertThat(result.value).isEqualTo(fakeValue) + } + + @Test + fun `M return null W read() { reader has no value }`(@LongForgery(min = 1L) fakeIntervalMs: Long) { + // Given + val mockVitalReader: VitalReader = mock { + on { readVitalData() } doReturn null + } + val testedReader = VitalReaderWrapper(mockVitalReader, mockTimeProvider, fakeIntervalMs) + + // When + val result = testedReader.read() + + // Then + assertThat(result).isNull() + } + + @Test + fun `M expose intervalMs W intervalMs { construction value }`(@LongForgery(min = 1L) fakeIntervalMs: Long) { + // Given + val mockVitalReader: VitalReader = mock() + val testedReader = VitalReaderWrapper(mockVitalReader, mockTimeProvider, fakeIntervalMs) + + // When / Then + assertThat(testedReader.intervalMs).isEqualTo(fakeIntervalMs) + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt index 16fa387995..c06ed6f021 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt @@ -54,6 +54,7 @@ internal class Configurator : BaseConfigurator() { forge.addFactory(SlowFramesConfigurationForgeryFactory()) forge.addFactory(DisplayInfoForgeryFactory()) forge.addFactory(BatteryInfoForgeryFactory()) + forge.addFactory(DataPointForgeryFactory()) // Telemetry schema models forge.addFactory(TelemetryDebugEventForgeryFactory()) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/DataPointForgeryFactory.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/DataPointForgeryFactory.kt new file mode 100644 index 0000000000..58c2769cd3 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/DataPointForgeryFactory.kt @@ -0,0 +1,18 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.utils.forge + +import com.datadog.android.rum.internal.timeseries.DataPoint +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +internal class DataPointForgeryFactory : ForgeryFactory> { + override fun getForgery(forge: Forge): DataPoint = DataPoint( + timestampNs = forge.aPositiveLong(), + value = forge.aDouble() + ) +} diff --git a/tools/benchmark/src/main/java/com/datadog/benchmark/EndPoint.kt b/tools/benchmark/src/main/java/com/datadog/benchmark/EndPoint.kt index 3e232ab237..9ff046f194 100644 --- a/tools/benchmark/src/main/java/com/datadog/benchmark/EndPoint.kt +++ b/tools/benchmark/src/main/java/com/datadog/benchmark/EndPoint.kt @@ -14,26 +14,37 @@ enum class EndPoint( private val traces: String ) { + /** US1 datacenter (datadoghq.com). */ US1( metrics = "https://api.datadoghq.com/", traces = "https://browser-intake-datadoghq.com/" ), + + /** US3 datacenter (us3.datadoghq.com). */ US3( metrics = "https://api.us3.datadoghq.com/", traces = "https://browser-intake-us3-datadoghq.com/" ), + + /** US5 datacenter (us5.datadoghq.com). */ US5( metrics = "https://api.us5.datadoghq.com/", traces = "https://browser-intake-us5-datadoghq.com/" ), + + /** EU1 datacenter (datadoghq.eu). */ EU1( metrics = "https://api.datadoghq.eu/", traces = "https://public-trace-http-intake.logs.datadoghq.eu/" ), + + /** AP1 datacenter (ap1.datadoghq.com). */ AP1( metrics = "https://api.ap1.datadoghq.com/", traces = "https://browser-intake-ap1-datadoghq.com/" ), + + /** AP2 datacenter (ap2.datadoghq.com). */ AP2( metrics = "https://api.ap2.datadoghq.com/", traces = "https://browser-intake-ap2-datadoghq.com/" From dd92529f58df697c31ed629a6748517f5af4e436 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Tue, 12 May 2026 02:02:04 +0100 Subject: [PATCH 05/32] RUM-13949: Adds `CpuDatapointReader`, which reads CPU-usage metrics from the existing `CpuVitalReader` vital and wraps them as `DataPoint` values; and `CpuEventSerializer`, which converts a batch of CPU `DataPoint`s into a `RumTimeseriesCpuEvent` JSON object ready for ingestion. --- detekt_custom_safe_calls.yml | 2 + .../android/rum/internal/RumFeature.kt | 3 +- .../internal/timeseries/DeltaCompression.kt | 3 +- .../timeseries/provider/CpuDatapointReader.kt | 59 ++++ .../serializer/CpuEventSerializer.kt | 94 ++++++ .../rum/internal/vitals/CPUVitalReader.kt | 37 +-- .../rum/internal/vitals/CpuStatReader.kt | 64 ++++ .../rum/assertj/TimeseriesCpuEventAssert.kt | 158 ++++++++++ .../provider/CpuDatapointReaderTest.kt | 208 +++++++++++++ .../serializer/CpuEventSerializerTest.kt | 204 ++++++++++++ .../rum/internal/vitals/CPUVitalReaderTest.kt | 149 +-------- .../rum/internal/vitals/CpuStatReaderTest.kt | 294 ++++++++++++++++++ 12 files changed, 1102 insertions(+), 173 deletions(-) create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReader.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/vitals/CpuStatReader.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReaderTest.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/vitals/CpuStatReaderTest.kt diff --git a/detekt_custom_safe_calls.yml b/detekt_custom_safe_calls.yml index 813d493d92..627d7db73c 100644 --- a/detekt_custom_safe_calls.yml +++ b/detekt_custom_safe_calls.yml @@ -1135,6 +1135,7 @@ datadog: - "kotlin.Char.titlecase(java.util.Locale)" - "kotlin.CharArray.constructor(kotlin.Int, kotlin.Function1)" - "kotlin.Double.coerceAtMost(kotlin.Double)" + - "kotlin.Double.coerceIn(kotlin.Double, kotlin.Double)" - "kotlin.Double.isNaN()" - "kotlin.Double.pow(kotlin.Int)" - "kotlin.Double.pow(kotlin.Double)" @@ -1168,6 +1169,7 @@ datadog: - "kotlin.IntArray.constructor(kotlin.Int)" - "kotlin.IntArray.joinToString(kotlin.CharSequence, kotlin.CharSequence, kotlin.CharSequence, kotlin.Int, kotlin.CharSequence, kotlin.Function1?)" - "kotlin.IntArray.toSet()" + - "kotlin.Long.coerceAtLeast(kotlin.Long)" - "kotlin.Long.coerceIn(kotlin.Long, kotlin.Long)" - "kotlin.Long.hashCode()" - "kotlin.Long.or(kotlin.Long)" diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index 28dfd32983..11d261bcdc 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -88,6 +88,7 @@ import com.datadog.android.rum.internal.tracking.NoOpUserActionTrackingStrategy import com.datadog.android.rum.internal.tracking.UserActionTrackingStrategy import com.datadog.android.rum.internal.vitals.AggregatingVitalMonitor import com.datadog.android.rum.internal.vitals.CPUVitalReader +import com.datadog.android.rum.internal.vitals.CpuStatReader import com.datadog.android.rum.internal.vitals.FPSVitalListener import com.datadog.android.rum.internal.vitals.FrameStateListener import com.datadog.android.rum.internal.vitals.FrameStatesAggregator @@ -581,7 +582,7 @@ internal class RumFeature( cpuVitalMonitor = AggregatingVitalMonitor() initializeVitalMonitor( - CPUVitalReader(internalLogger = sdkCore.internalLogger), + CPUVitalReader(CpuStatReader(internalLogger = sdkCore.internalLogger)), cpuVitalMonitor, frequency.periodInMs ) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DeltaCompression.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DeltaCompression.kt index 817fa2c4dc..2cac530389 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DeltaCompression.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DeltaCompression.kt @@ -10,7 +10,7 @@ import kotlin.math.roundToLong internal object DeltaCompression { - fun roundToLongSafely(value: Double, replaceNaNWith: Long = 0L, scale: Long = SCALE): Long = try { + fun roundToLongSafely(value: Double, scale: Long, replaceNaNWith: Long = 0L): Long = try { (value * scale).roundToLong() } catch (_: IllegalArgumentException) { replaceNaNWith @@ -24,5 +24,4 @@ internal object DeltaCompression { } const val PRECISION = 4 - const val SCALE: Long = 10_000L // 10^4 } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReader.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReader.kt new file mode 100644 index 0000000000..d48d4fe76f --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReader.kt @@ -0,0 +1,59 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.provider + +import androidx.annotation.WorkerThread +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.internal.vitals.CpuStatReader + +/** + * Reads CPU usage as a percentage of total device capacity by computing successive + * deltas of the utime+stime CPU time provided by [CpuStatReader], divided by the number of + * available processors. + * + * CLK_TCK = 100 Hz on Android bionic: 100 ticks/s per core. Dividing by the core + * count normalizes the result to [0, 100] relative to total device CPU capacity, + * so multi-threaded work is represented correctly without artificial clamping. + * The first call always returns null. + */ +internal class CpuDatapointReader( + private val cpuStatReader: CpuStatReader, + private val cpuTimeProvider: TimeProvider, + override val intervalMs: Long, + private val availableProcessors: Int = Runtime.getRuntime().availableProcessors() +) : DataPointsReader(cpuTimeProvider) { + + private var lastUtime: Double? = null + private var lastNs: Long? = null + + @WorkerThread + override fun readValue() = cpuStatReader.readActiveTime()?.let { currentCpuTicks -> + val nowNs = cpuTimeProvider.getDeviceElapsedTimeNanos() + val (prevCpuTicks, prevNs) = updateValues(currentCpuTicks, nowNs) + if (prevCpuTicks == null || prevNs == null) return null + val elapsedMs = ((nowNs - prevNs) / NS_PER_MS).coerceAtLeast(1L) + val corePercent = (currentCpuTicks - prevCpuTicks) * MS_PER_SECOND / elapsedMs + (corePercent / availableProcessors.coerceAtLeast(1)).coerceIn(0.0, MAX_CPU_PERCENT) + } + + private fun updateValues( + cpuTicks: Double, + nowNs: Long + ): Pair { + val prevCpuTicks = lastUtime + val prevNs = lastNs + lastUtime = cpuTicks + lastNs = nowNs + return prevCpuTicks to prevNs + } + + companion object { + private const val MS_PER_SECOND = 1000.0 + private const val NS_PER_MS = 1_000_000L + private const val MAX_CPU_PERCENT = 100.0 + } +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt new file mode 100644 index 0000000000..8bfee46a94 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt @@ -0,0 +1,94 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.serializer + +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.timeseries.DataPoint +import com.datadog.android.rum.internal.timeseries.DeltaCompression +import com.datadog.android.rum.internal.timeseries.DeltaCompression.mapToDeltaCompressed +import com.datadog.android.rum.internal.timeseries.DeltaCompression.roundToLongSafely +import com.datadog.android.rum.internal.toTimeseriesCpuSessionType +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.google.gson.JsonObject +import java.util.UUID +import kotlin.math.pow + +internal class CpuEventSerializer( + private val sessionId: String, + private val applicationId: String, + private val sessionType: RumSessionType, + private val timeProvider: TimeProvider, + private val useDeltaCompression: Boolean = false, + private val precision: Int = DeltaCompression.PRECISION +) : JsonSerializer { + + override fun serialize(dataPoints: List>): JsonObject? { + if (dataPoints.isEmpty()) return null + val data = dataPoints.map { sample -> + TimeseriesCpuEvent.Data( + timestamp = sample.timestampNs, + dataPoint = TimeseriesCpuEvent.DataPoint(sample.value) + ) + } + val start = data.firstOrNull()?.timestamp ?: 0L + val end = data.lastOrNull()?.timestamp ?: 0L + val deltaEncoded = if (useDeltaCompression) encodeDelta(data) else null + val schema = if (deltaEncoded != null) { + TimeseriesCpuEvent.Schema.DELTA_SCALAR + } else { + TimeseriesCpuEvent.Schema.OBJECT + } + val json = TimeseriesCpuEvent( + dd = TimeseriesCpuEvent.Dd(), + application = TimeseriesCpuEvent.Application(id = applicationId), + session = TimeseriesCpuEvent.Session( + id = sessionId, + type = sessionType.toTimeseriesCpuSessionType() + ), + source = TimeseriesCpuEvent.Source.ANDROID, + date = timeProvider.getDeviceTimestampMillis(), + service = null, + version = null, + timeseries = TimeseriesCpuEvent.Timeseries( + id = UUID.randomUUID().toString(), + schema = schema, + start = start, + end = end, + data = data + ) + ).toJson() as JsonObject + + if (deltaEncoded != null) { + val timeseriesJson = json.getAsJsonObject("timeseries") + timeseriesJson.remove("data") + timeseriesJson.add("data", deltaEncoded) + } + return json + } + + private fun encodeDelta(data: List): JsonObject? { + if (data.size <= 1) return null + + val scale = 10.0.pow(precision.toDouble()).toLong() + val ts = data.mapToDeltaCompressed { it.timestamp } + val cpuUsageArray = data.mapToDeltaCompressed { + roundToLongSafely(it.dataPoint.cpuUsage.toDouble(), scale = scale) + } + + return JsonObject().apply { + addProperty("precision", precision) + addProperty("resolution", RESOLUTION_NS) + add("ts", ts) + add("value", cpuUsageArray) + } + } + + companion object { + private const val RESOLUTION_NS = "ns" + } +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/vitals/CPUVitalReader.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/vitals/CPUVitalReader.kt index a7f570e230..aedaed9eb7 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/vitals/CPUVitalReader.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/vitals/CPUVitalReader.kt @@ -6,41 +6,16 @@ package com.datadog.android.rum.internal.vitals -import com.datadog.android.api.InternalLogger -import com.datadog.android.core.internal.persistence.file.canReadSafe -import com.datadog.android.core.internal.persistence.file.existsSafe -import com.datadog.android.core.internal.persistence.file.readTextSafe -import java.io.File +import androidx.annotation.WorkerThread /** - * Reads the CPU `utime` based on the `/proc/self/stat` file. - * cf. documentation https://man7.org/linux/man-pages/man5/procfs.5.html + * Reads the CPU `utime` based on the `/proc/self/stat` file, delegating the file access and + * parsing to [CpuStatReader]. */ internal class CPUVitalReader( - internal val statFile: File = STAT_FILE, - internal val internalLogger: InternalLogger + private val cpuStatReader: CpuStatReader ) : VitalReader { - @Suppress("ReturnCount") - override fun readVitalData(): Double? { - if (!(statFile.existsSafe(internalLogger) && statFile.canReadSafe(internalLogger))) { - return null - } - - val stat = statFile.readTextSafe(internalLogger = internalLogger) ?: return null - val tokens = stat.split(' ') - return if (tokens.size > UTIME_IDX) { - tokens[UTIME_IDX].toDoubleOrNull() - } else { - null - } - } - - companion object { - - private const val STAT_PATH = "/proc/self/stat" - internal val STAT_FILE = File(STAT_PATH) - - private const val UTIME_IDX = 13 - } + @WorkerThread + override fun readVitalData(): Double? = cpuStatReader.readUserTime() } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/vitals/CpuStatReader.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/vitals/CpuStatReader.kt new file mode 100644 index 0000000000..673cb41848 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/vitals/CpuStatReader.kt @@ -0,0 +1,64 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.vitals + +import androidx.annotation.WorkerThread +import com.datadog.android.api.InternalLogger +import com.datadog.android.core.internal.persistence.file.canReadSafe +import com.datadog.android.core.internal.persistence.file.existsSafe +import com.datadog.android.core.internal.persistence.file.readTextSafe +import java.io.File + +/** + * Reads the process' CPU time counters from the `/proc/self/stat` file. + * cf. documentation https://man7.org/linux/man-pages/man5/procfs.5.html + */ +internal class CpuStatReader( + internal val statFile: File = STAT_FILE, + private val internalLogger: InternalLogger +) { + + /** + * Cumulative user-mode CPU time (`utime`, field 14 of `/proc/self/stat`) in clock ticks, + * or `null` when the file is unavailable or the field can't be parsed. + */ + @WorkerThread + fun readUserTime(): Double? = readTokens()?.getOrNull(UTIME_IDX)?.toDoubleOrNull() + + /** + * Cumulative active CPU time (`utime` + `stime`, fields 14 & 15) in clock ticks. Falls back + * to `utime` alone when `stime` is absent; returns `null` when the file is unavailable, + * `utime` is missing/unparseable, or `stime` is present but unparseable. + */ + @WorkerThread + @Suppress("ReturnCount") + fun readActiveTime(): Double? { + val tokens = readTokens() ?: return null + return when { + tokens.size <= UTIME_IDX -> null + tokens.size <= STIME_IDX -> tokens[UTIME_IDX].toDoubleOrNull() + else -> { + val utime = tokens[UTIME_IDX].toDoubleOrNull() ?: return null + val stime = tokens[STIME_IDX].toDoubleOrNull() ?: return null + utime + stime + } + } + } + + @WorkerThread + private fun readTokens(): List? { + if (!(statFile.existsSafe(internalLogger) && statFile.canReadSafe(internalLogger))) return null + return statFile.readTextSafe(internalLogger = internalLogger)?.split(' ') + } + + companion object { + private const val STAT_PATH = "/proc/self/stat" + internal val STAT_FILE = File(STAT_PATH) + private const val UTIME_IDX = 13 + private const val STIME_IDX = 14 + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt new file mode 100644 index 0000000000..6eddc5ce28 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt @@ -0,0 +1,158 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.assertj + +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import org.assertj.core.api.AbstractObjectAssert +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.data.Offset +import org.junit.jupiter.api.assertDoesNotThrow +import java.util.UUID + +internal class TimeseriesCpuEventAssert private constructor( + actual: TimeseriesCpuEvent, + private val json: JsonObject +) : AbstractObjectAssert( + actual, + TimeseriesCpuEventAssert::class.java +) { + + fun hasApplicationId(expected: String): TimeseriesCpuEventAssert = apply { + assertThat(actual.application.id) + .overridingErrorMessage { + "Expected event to have application.id $expected but was ${actual.application.id}" + } + .isEqualTo(expected) + } + + fun hasSessionId(expected: String): TimeseriesCpuEventAssert = apply { + assertThat(actual.session.id) + .overridingErrorMessage { "Expected event to have session.id $expected but was ${actual.session.id}" } + .isEqualTo(expected) + } + + fun hasSessionType(expected: TimeseriesCpuEvent.Type): TimeseriesCpuEventAssert = apply { + assertThat(actual.session.type) + .overridingErrorMessage { "Expected event to have session.type $expected but was ${actual.session.type}" } + .isEqualTo(expected) + } + + fun hasDate(expected: Long): TimeseriesCpuEventAssert = apply { + assertThat(actual.date) + .overridingErrorMessage { "Expected event to have date $expected but was ${actual.date}" } + .isEqualTo(expected) + } + + fun hasTimeseriesSchema(expected: TimeseriesCpuEvent.Schema): TimeseriesCpuEventAssert = apply { + assertThat(actual.timeseries.schema) + .overridingErrorMessage { + "Expected event to have timeseries.schema $expected but was ${actual.timeseries.schema}" + } + .isEqualTo(expected) + } + + fun hasTimeseriesStart(expected: Long): TimeseriesCpuEventAssert = apply { + assertThat(actual.timeseries.start) + .overridingErrorMessage { + "Expected event to have timeseries.start $expected but was ${actual.timeseries.start}" + } + .isEqualTo(expected) + } + + fun hasTimeseriesEnd(expected: Long): TimeseriesCpuEventAssert = apply { + assertThat(actual.timeseries.end) + .overridingErrorMessage { + "Expected event to have timeseries.end $expected but was ${actual.timeseries.end}" + } + .isEqualTo(expected) + } + + fun hasValidTimeseriesId(): TimeseriesCpuEventAssert = apply { + assertDoesNotThrow("Expected timeseries.id to be a valid UUID but was ${actual.timeseries.id}") { + UUID.fromString(actual.timeseries.id) + } + } + + fun hasTimeseriesDataCount(expected: Int): TimeseriesCpuEventAssert = apply { + assertThat(actual.timeseries.data) + .overridingErrorMessage { + "Expected event to have timeseries.data of size $expected but was ${actual.timeseries.data.size}" + } + .hasSize(expected) + } + + fun hasCpuUsage(expected: Double, offset: Offset, position: Int = 0): TimeseriesCpuEventAssert = apply { + val cpuUsage = actual.timeseries.data.getOrNull(position)?.dataPoint?.cpuUsage?.toDouble() + assertThat(cpuUsage) + .overridingErrorMessage { "Expected first data point cpu_usage to be close to $expected but was $cpuUsage" } + .isNotNull() + .isCloseTo(expected, offset) + } + + // region Delta Compression + + // Delta compression is not GA — these assertions cover the delta-scalar wire + // format used during dogfooding only. This section will be revisited or removed before GA. + // Use assertThatDelta(JsonObject) to enter this path. + + fun hasDeltaPrecision(expected: Int): TimeseriesCpuEventAssert = apply { + val precision = json.timeseries.getAsJsonObject("data").get("precision").asInt + assertThat(precision) + .overridingErrorMessage { "Expected delta data to have precision $expected but was $precision" } + .isEqualTo(expected) + } + + fun hasDeltaResolution(expected: String): TimeseriesCpuEventAssert = apply { + val resolution = json.timeseries.getAsJsonObject("data").get("resolution").asString + assertThat(resolution) + .overridingErrorMessage { "Expected delta data to have resolution $expected but was $resolution" } + .isEqualTo(expected) + } + + fun hasDeltaTsValues(vararg expected: Long): TimeseriesCpuEventAssert = apply { + val tsArray = json.timeseries.getAsJsonObject("data").get("ts").asJsonArray + expected.forEachIndexed { i, v -> + assertThat(tsArray[i].asLong) + .overridingErrorMessage { "Expected delta ts[$i] to be $v but was ${tsArray[i].asLong}" } + .isEqualTo(v) + } + } + + fun hasDeltaValueAt(index: Int, expected: Long): TimeseriesCpuEventAssert = apply { + val valueArray = json.timeseries.getAsJsonObject("data").get("value").asJsonArray + val actual = valueArray[index].asLong + assertThat(actual) + .overridingErrorMessage { "Expected delta value[$index] to be $expected but was $actual" } + .isEqualTo(expected) + } + + // endregion + + companion object { + private val JsonObject.timeseries: JsonObject get() = getAsJsonObject("timeseries") + + internal fun assertThat(event: TimeseriesCpuEvent): TimeseriesCpuEventAssert = + TimeseriesCpuEventAssert(event, event.toJson() as JsonObject) + + internal fun assertThat(json: JsonObject): TimeseriesCpuEventAssert = + assertThat(TimeseriesCpuEvent.fromJsonObject(json)) + + /** + * Delta encoding replaces timeseries.data with a JsonObject in-place, + * making fromJsonObject fail. A patched copy (empty data array) is used to construct + * the typed model; the original json is retained for delta assertions. + */ + internal fun assertThatDelta(json: JsonObject): TimeseriesCpuEventAssert { + val patchedJson = json.deepCopy().also { + it.getAsJsonObject("timeseries").add("data", JsonArray()) + } + return TimeseriesCpuEventAssert(TimeseriesCpuEvent.fromJsonObject(patchedJson), json) + } + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReaderTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReaderTest.kt new file mode 100644 index 0000000000..00aed2e75f --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReaderTest.kt @@ -0,0 +1,208 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.provider + +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.internal.timeseries.DataPoint +import com.datadog.android.rum.internal.vitals.CpuStatReader +import com.datadog.android.rum.utils.forge.Configurator +import fr.xgouchet.elmyr.annotation.DoubleForgery +import fr.xgouchet.elmyr.annotation.IntForgery +import fr.xgouchet.elmyr.annotation.LongForgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.data.Offset +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doReturnConsecutively +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness +import java.util.concurrent.TimeUnit + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(Configurator::class) +internal class CpuDatapointReaderTest { + + @LongForgery(min = 100L, max = 60_000L) + var fakeIntervalMs: Long = 0L + + @LongForgery(min = 1_000L, max = 1_000_000L) + var fakeStartTimestampMs: Long = 0L + + @IntForgery(min = 1, max = 10_000) + var fakeBaselineTicks: Int = 0 + + val mockCpuStatReader: CpuStatReader = mock() + + val mockTimeProvider: TimeProvider = mock { + on { getDeviceTimestampMillis() } doAnswer { fakeStartTimestampMs } + on { getDeviceElapsedTimeNanos() } doAnswer { fakeStartTimestampMs * NS_PER_MS } + } + + private fun buildReader(availableProcessors: Int = 1) = CpuDatapointReader( + cpuStatReader = mockCpuStatReader, + cpuTimeProvider = mockTimeProvider, + intervalMs = fakeIntervalMs, + availableProcessors = availableProcessors + ) + + /** + * Primes the reader with [firstTicks] (returns null — no baseline yet), advances the clock + * by [elapsedMs], then returns the second sample built from [secondTicks] (or `null` to + * simulate the stat read failing between samples). Elapsed time is stubbed as a sequence so + * the prime read sees the start instant and the second read sees start + [elapsedMs]. + */ + private fun readSecondSample( + elapsedMs: Long, + secondTicks: Double?, + availableProcessors: Int = 1, + firstTicks: Double = fakeBaselineTicks.toDouble() + ): DataPoint? { + whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).doReturn( + fakeStartTimestampMs * NS_PER_MS, + (fakeStartTimestampMs + elapsedMs) * NS_PER_MS + ) + whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn (fakeStartTimestampMs + elapsedMs) + whenever(mockCpuStatReader.readActiveTime()).doReturnConsecutively(listOf(firstTicks, secondTicks)) + return buildReader(availableProcessors).run { + read() + read() + } + } + + @Test + fun `M return null W read() {first sample}`() { + // Given + whenever(mockCpuStatReader.readActiveTime()) doReturn fakeBaselineTicks.toDouble() + + // When + val result = buildReader().read() + + // Then + assertThat(result).isNull() + } + + @Test + fun `M return null W read() {stat read returns null}`() { + // Given + whenever(mockCpuStatReader.readActiveTime()) doReturn null + + // When + val result = buildReader().read() + + // Then + assertThat(result).isNull() + } + + @Test + fun `M return null W read() {stat read fails on second sample}`( + @LongForgery(min = 500L, max = 5000L) fakeElapsedMs: Long + ) { + // When + val result = readSecondSample(elapsedMs = fakeElapsedMs, secondTicks = null) + + // Then + assertThat(result).isNull() + } + + @Test + fun `M return 0 W read() {zero elapsed and no tick delta}`() { + // When — both samples at the same nano instant with unchanged ticks. + // Without the elapsedMs floor this would be 0.0 / 0.0 = NaN (NaN survives coerceIn). + val result = readSecondSample(elapsedMs = 0L, secondTicks = fakeBaselineTicks.toDouble()) + + // Then — the 1 ms floor keeps the result finite + checkNotNull(result) + assertThat(result.value).isEqualTo(0.0) + } + + @Test + fun `M return cpu percent W read() {second sample}`( + @DoubleForgery(min = 1.0, max = 80.0) fakeCpuPercent: Double, + @LongForgery(min = 500L, max = 5000L) fakeElapsedMs: Long + ) { + // Given + val deltaTicks = (fakeCpuPercent * fakeElapsedMs / 1000.0).toInt().coerceAtLeast(2) + + // When + val result = readSecondSample( + elapsedMs = fakeElapsedMs, + secondTicks = (fakeBaselineTicks + deltaTicks).toDouble() + ) + + // Then + checkNotNull(result) + val expectedPercent = deltaTicks * 1000.0 / fakeElapsedMs + assertThat(result.value).isCloseTo(expectedPercent, Offset.offset(0.1)) + assertThat(result.timestampNs).isEqualTo(TimeUnit.MILLISECONDS.toNanos(fakeStartTimestampMs + fakeElapsedMs)) + } + + @Test + fun `M return 0 W read() {ticks unchanged}`(@LongForgery(min = 500L, max = 5000L) fakeElapsedMs: Long) { + // When + val result = readSecondSample(elapsedMs = fakeElapsedMs, secondTicks = fakeBaselineTicks.toDouble()) + + // Then + checkNotNull(result) + assertThat(result.value).isEqualTo(0.0) + } + + @Test + fun `M clamp to 100 W read() {spike exceeds total capacity}`( + @LongForgery(min = 500L, max = 2000L) fakeElapsedMs: Long + ) { + // Given — 200 ticks/s on one core with availableProcessors=1 → 200% → clamped to 100 + val deltaTicks = (200.0 * fakeElapsedMs / 1000.0).toInt() + + // When + val result = readSecondSample( + elapsedMs = fakeElapsedMs, + secondTicks = (fakeBaselineTicks + deltaTicks).toDouble() + ) + + // Then + checkNotNull(result) + assertThat(result.value).isEqualTo(100.0) + } + + @Test + fun `M normalise by core count W read() {multi-core usage}`( + @IntForgery(min = 1, max = 8) fakeCoreCount: Int, + @LongForgery(min = 500L, max = 5000L) fakeElapsedMs: Long, + @DoubleForgery(min = 1.0, max = 50.0) fakePerCorePct: Double + ) { + // Given — deltaTicks = fakePerCorePct% on ONE core → fakePerCorePct/fakeCoreCount % of total + val deltaTicks = (fakePerCorePct * fakeElapsedMs / 1000.0).toInt().coerceAtLeast(1) + + // When + val result = readSecondSample( + elapsedMs = fakeElapsedMs, + availableProcessors = fakeCoreCount, + secondTicks = (fakeBaselineTicks + deltaTicks).toDouble() + ) + + // Then + checkNotNull(result) + val expectedPercent = (deltaTicks * 1000.0 / fakeElapsedMs) / fakeCoreCount + assertThat(result.value).isCloseTo(expectedPercent, Offset.offset(0.1)) + } + + private companion object { + private const val NS_PER_MS = 1_000_000L + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt new file mode 100644 index 0000000000..0e3cd6db7e --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt @@ -0,0 +1,204 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.serializer + +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.assertj.TimeseriesCpuEventAssert.Companion.assertThat +import com.datadog.android.rum.assertj.TimeseriesCpuEventAssert.Companion.assertThatDelta +import com.datadog.android.rum.internal.timeseries.DataPoint +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.utils.forge.Configurator +import com.google.gson.JsonObject +import fr.xgouchet.elmyr.annotation.DoubleForgery +import fr.xgouchet.elmyr.annotation.IntForgery +import fr.xgouchet.elmyr.annotation.LongForgery +import fr.xgouchet.elmyr.annotation.StringForgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.data.Offset +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.mock +import org.mockito.quality.Strictness +import kotlin.math.pow +import kotlin.math.roundToLong + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(Configurator::class) +internal class CpuEventSerializerTest { + + @LongForgery(min = 1L) + var fakeNowMs: Long = 0L + + @StringForgery + lateinit var fakeSessionId: String + + @StringForgery + lateinit var fakeApplicationId: String + + @IntForgery(min = 2, max = 9) + var fakePrecision: Int = 0 + + val fakeScale: Long get() = 10.0.pow(fakePrecision.toDouble()).toLong() + + val mockTimeProvider: TimeProvider = mock { + on { getDeviceTimestampMillis() } doAnswer { fakeNowMs } + } + + private fun testedSerializer( + useDeltaCompression: Boolean = false, + sessionType: RumSessionType = RumSessionType.USER, + precision: Int = fakePrecision + ) = CpuEventSerializer( + sessionId = fakeSessionId, + applicationId = fakeApplicationId, + sessionType = sessionType, + timeProvider = mockTimeProvider, + useDeltaCompression = useDeltaCompression, + precision = precision + ) + + @Test + fun `M return null W serialize() { empty input }`() { + // When + val result = testedSerializer().serialize(emptyList()) + + // Then + assertThat(result).isNull() + } + + @Test + fun `M produce object schema W serialize() { compression off }`( + @DoubleForgery(min = 0.0, max = 100.0) fakeCpuValue: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // Given + val samples = listOf( + DataPoint(fakeTs, fakeCpuValue), + DataPoint(fakeTs + 1L, fakeCpuValue + 0.1) + ) + + // When + val json = testedSerializer().serialize(samples).failIfNull() + + // Then + assertThat(json) + .hasDate(fakeNowMs) + .hasValidTimeseriesId() + .hasTimeseriesDataCount(2) + .hasTimeseriesStart(fakeTs) + .hasTimeseriesEnd(fakeTs + 1L) + .hasApplicationId(fakeApplicationId) + .hasSessionType(TimeseriesCpuEvent.Type.USER) + .hasTimeseriesSchema(TimeseriesCpuEvent.Schema.OBJECT) + } + + @Test + fun `M map session type W serialize() { synthetics }`( + @DoubleForgery(min = 0.0, max = 100.0) fakeCpuValue: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // Given + val samples = listOf(DataPoint(fakeTs, fakeCpuValue)) + + // When + val json = testedSerializer(sessionType = RumSessionType.SYNTHETICS) + .serialize(samples) + .failIfNull() + + // Then + assertThat(json).hasSessionType(TimeseriesCpuEvent.Type.SYNTHETICS) + } + + @Test + fun `M produce delta-scalar schema W serialize() { compression on, multi-sample }`( + @DoubleForgery(min = 0.001, max = 100.0) fakeFirstCpu: Double, + @DoubleForgery(min = 0.001, max = 100.0) fakeSecondCpu: Double, + @DoubleForgery(min = 0.001, max = 100.0) fakeThirdCpu: Double, + @LongForgery(min = 1L, max = 1_000_000L) fakeTs1: Long, + @LongForgery(min = 1L, max = 1_000L) fakeTimestampStep: Long + ) { + // Given + val fakeTs2 = fakeTs1 + fakeTimestampStep + val fakeTs3 = fakeTs2 + fakeTimestampStep + val samples = listOf( + DataPoint(fakeTs1, fakeFirstCpu), + DataPoint(fakeTs2, fakeSecondCpu), + DataPoint(fakeTs3, fakeThirdCpu) + ) + + // When + val json = testedSerializer(useDeltaCompression = true) + .serialize(samples) + .failIfNull() + + // Then + val scaled = listOf(fakeFirstCpu, fakeSecondCpu, fakeThirdCpu).map { (it * fakeScale).roundToLong() } + assertThatDelta(json) + .hasTimeseriesSchema(TimeseriesCpuEvent.Schema.DELTA_SCALAR) + .hasDeltaPrecision(fakePrecision) + .hasDeltaResolution(RESOLUTION_NS) + .hasDeltaTsValues(fakeTs1, fakeTimestampStep, fakeTimestampStep) + .hasDeltaValueAt(0, scaled[0]) + .hasDeltaValueAt(1, scaled[1] - scaled[0]) + .hasDeltaValueAt(2, scaled[2] - scaled[1]) + } + + @Test + fun `M fall back to object schema W serialize() { compression on, single sample }`( + @DoubleForgery(min = 0.001, max = 100.0) fakeCpu: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // When + val json = testedSerializer(useDeltaCompression = true) + .serialize(listOf(DataPoint(fakeTs, fakeCpu))) + .failIfNull() + + // Then + assertThat(json) + .hasTimeseriesSchema(TimeseriesCpuEvent.Schema.OBJECT) + .hasTimeseriesDataCount(1) + } + + @Test + fun `M preserve cpu_usage W serialize() { object schema }`( + @DoubleForgery(min = 1.0, max = 100.0) fakeCpu: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // Given + val samples = listOf( + DataPoint(fakeTs, fakeCpu), + DataPoint(fakeTs + 1L, fakeCpu) + ) + + // When + val json = testedSerializer().serialize(samples).failIfNull() + + // Then + assertThat(json) + .hasCpuUsage(fakeCpu, Offset.offset(CPU_OFFSET), position = 0) + } + + private companion object { + private const val CPU_OFFSET: Double = 0.0001 + private const val RESOLUTION_NS: String = "ns" + private const val NON_NULL_JSON_ERROR: String = "expected non-null json" + private fun JsonObject?.failIfNull(): JsonObject { + if (this == null) error(NON_NULL_JSON_ERROR) else return this + } + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/vitals/CPUVitalReaderTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/vitals/CPUVitalReaderTest.kt index 1a3b4d4414..abadf83ece 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/vitals/CPUVitalReaderTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/vitals/CPUVitalReaderTest.kt @@ -6,158 +6,53 @@ package com.datadog.android.rum.internal.vitals -import com.datadog.android.rum.utils.forge.Configurator -import fr.xgouchet.elmyr.annotation.IntForgery -import fr.xgouchet.elmyr.annotation.StringForgery -import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.annotation.DoubleForgery import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions -import org.junit.jupiter.api.io.TempDir +import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.mockito.quality.Strictness -import java.io.File @Extensions( ExtendWith(MockitoExtension::class), ExtendWith(ForgeExtension::class) ) @MockitoSettings(strictness = Strictness.LENIENT) -@ForgeConfiguration(Configurator::class) internal class CPUVitalReaderTest { lateinit var testedReader: VitalReader - @TempDir - lateinit var tempDir: File - - lateinit var fakeFile: File - - @IntForgery(1) - var fakePid: Int = 0 - - @StringForgery(regex = "\\(\\w+\\)") - lateinit var fakeCommand: String - - @StringForgery(regex = "[RSDZTtWXxKWP]") - lateinit var fakeState: String - - @IntForgery(1) - var fakePpid: Int = 0 - - @IntForgery(1) - var fakePgrp: Int = 0 - - @IntForgery(1) - var fakeSession: Int = 0 - - @IntForgery(1) - var fakeTtyNr: Int = 0 - - @IntForgery(1) - var fakeTpgid: Int = 0 - - @IntForgery(1) - var fakeFlags: Int = 0 - - @IntForgery(1) - var fakeMinFlt: Int = 0 - - @IntForgery(1) - var fakeCMinFlt: Int = 0 - - @IntForgery(1) - var fakeMajFlt: Int = 0 - - @IntForgery(1) - var fakeCMajFlt: Int = 0 - - @IntForgery(1) - var fakeUtime: Int = 0 - - @IntForgery(1) - var fakeStime: Int = 0 - - @IntForgery(1) - var fakeCUtime: Int = 0 - - @IntForgery(1) - var fakeCStime: Int = 0 - - @IntForgery(-100, -2) - var fakePriority: Int = 0 - - lateinit var fakeStatContent: String + @Mock + lateinit var mockCpuStatReader: CpuStatReader @BeforeEach fun `set up`() { - fakeFile = File(tempDir, "stat") - fakeStatContent = generateFakeContent() - testedReader = CPUVitalReader(fakeFile, internalLogger = mock()) - } - - @Test - fun `M read unix stats file W init()`() { - // When - val testedReader = CPUVitalReader(internalLogger = mock()) - - // Then - assertThat(testedReader.statFile).isEqualTo(CPUVitalReader.STAT_FILE) - } - - @Test - fun `M read correct data W readVitalData()`() { - // Given - fakeFile.writeText(fakeStatContent) - - // When - val result = testedReader.readVitalData() - - // Then - assertThat(result).isEqualTo(fakeUtime.toDouble()) + testedReader = CPUVitalReader(mockCpuStatReader) } @Test - fun `M read correct data W readVitalData() {multiple times}`( - @IntForgery(1) utimes: List - ) { + fun `M return user time W readVitalData()`(@DoubleForgery fakeUserTime: Double) { // Given - val results = mutableListOf() - - // When - utimes.forEach { utime -> - fakeUtime = utime - fakeFile.writeText(generateFakeContent()) - val result = testedReader.readVitalData() - results.add(result!!) - } + whenever(mockCpuStatReader.readUserTime()) doReturn fakeUserTime - // Then - assertThat(results).isEqualTo(utimes.map { utime -> utime.toDouble() }) - } - - @Test - fun `M return null W readVitalData() {file doesn't exist}`() { // When val result = testedReader.readVitalData() // Then - assertThat(result).isNull() + assertThat(result).isEqualTo(fakeUserTime) } @Test - fun `M return null W readVitalData() {file isn't readable}`() { + fun `M return null W readVitalData() {no cpu data}`() { // Given - val restrictedFile = mock() - whenever(restrictedFile.exists()) doReturn true - whenever(restrictedFile.canRead()) doReturn false + whenever(mockCpuStatReader.readUserTime()) doReturn null // When val result = testedReader.readVitalData() @@ -165,28 +60,4 @@ internal class CPUVitalReaderTest { // Then assertThat(result).isNull() } - - @Test - fun `M return null W readVitalData() {file has invalid data}`( - @StringForgery content: String - ) { - // Given - fakeFile.writeText(content) - - // When - val result = testedReader.readVitalData() - - // Then - assertThat(result).isNull() - } - - private fun generateFakeContent(): String { - return listOf( - fakePid, fakeCommand, fakeState, fakePpid, fakePgrp, - fakeSession, fakeTtyNr, fakeTpgid, fakeFlags, - fakeMinFlt, fakeCMinFlt, fakeMajFlt, fakeCMajFlt, - fakeUtime, fakeStime, fakeCUtime, fakeCStime, - fakePriority - ).joinToString(" ") - } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/vitals/CpuStatReaderTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/vitals/CpuStatReaderTest.kt new file mode 100644 index 0000000000..4dd0917e25 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/vitals/CpuStatReaderTest.kt @@ -0,0 +1,294 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.vitals + +import com.datadog.android.rum.utils.forge.Configurator +import fr.xgouchet.elmyr.annotation.IntForgery +import fr.xgouchet.elmyr.annotation.StringForgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.junit.jupiter.api.io.TempDir +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.quality.Strictness +import java.io.File + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(Configurator::class) +internal class CpuStatReaderTest { + + lateinit var testedReader: CpuStatReader + + @TempDir + lateinit var tempDir: File + + lateinit var fakeFile: File + + // /proc/self/stat fields (indices 0–12 before utime at index 13) + @IntForgery(1) + var fakePid: Int = 0 + + @StringForgery(regex = "\\(\\w+\\)") + lateinit var fakeCommand: String + + @StringForgery(regex = "[RSDZTtWXxKWP]") + lateinit var fakeState: String + + @IntForgery(1) + var fakePpid: Int = 0 + + @IntForgery(1) + var fakePgrp: Int = 0 + + @IntForgery(1) + var fakeSession: Int = 0 + + @IntForgery(1) + var fakeTtyNr: Int = 0 + + @IntForgery(1) + var fakeTpgid: Int = 0 + + @IntForgery(1) + var fakeFlags: Int = 0 + + @IntForgery(1) + var fakeMinFlt: Int = 0 + + @IntForgery(1) + var fakeCMinFlt: Int = 0 + + @IntForgery(1) + var fakeMajFlt: Int = 0 + + @IntForgery(1) + var fakeCMajFlt: Int = 0 + + @IntForgery(min = 1, max = 10_000) + var fakeUtime: Int = 0 + + @IntForgery(min = 1, max = 10_000) + var fakeStime: Int = 0 + + @BeforeEach + fun `set up`() { + fakeFile = File(tempDir, "stat") + testedReader = CpuStatReader(fakeFile, internalLogger = mock()) + } + + @Test + fun `M use default stat file W init()`() { + // When + val reader = CpuStatReader(internalLogger = mock()) + + // Then + assertThat(reader.statFile).isEqualTo(CpuStatReader.STAT_FILE) + } + + @Test + fun `M read utime W readUserTime()`() { + // Given + fakeFile.writeText(generateStatContent(fakeUtime)) + + // When + val result = testedReader.readUserTime() + + // Then + assertThat(result).isEqualTo(fakeUtime.toDouble()) + } + + @Test + fun `M read utime W readUserTime() {multiple times}`(@IntForgery(1) utimes: List) { + // Given + val results = mutableListOf() + + // When + utimes.forEach { utime -> + fakeFile.writeText(generateStatContent(utime)) + results.add(testedReader.readUserTime()) + } + + // Then + assertThat(results).isEqualTo(utimes.map { it.toDouble() }) + } + + @Test + fun `M ignore stime W readUserTime() {non-numeric stime}`( + @StringForgery(regex = "[a-z]{3}") fakeNonNumeric: String + ) { + // Given — utime is well-formed, only stime is garbage; utime must still be returned + fakeFile.writeText(generateStatContent(utime = fakeUtime, stime = fakeNonNumeric)) + + // When + val result = testedReader.readUserTime() + + // Then + assertThat(result).isEqualTo(fakeUtime.toDouble()) + } + + @Test + fun `M return null W readUserTime() {file doesn't exist}`() { + // When + val result = testedReader.readUserTime() + + // Then + assertThat(result).isNull() + } + + @Test + fun `M return null W readUserTime() {file isn't readable}`() { + // Given + val restrictedFile = mock { + on { exists() } doReturn true + on { canRead() } doReturn false + } + val reader = CpuStatReader(restrictedFile, internalLogger = mock()) + + // When + val result = reader.readUserTime() + + // Then + assertThat(result).isNull() + } + + @Test + fun `M return null W readUserTime() {invalid content}`(@StringForgery fakeContent: String) { + // Given + fakeFile.writeText(fakeContent) + + // When + val result = testedReader.readUserTime() + + // Then + assertThat(result).isNull() + } + + @Test + fun `M return null W readUserTime() {non-numeric utime}`( + @StringForgery(regex = "[a-z]{3}") fakeNonNumeric: String + ) { + // Given + fakeFile.writeText(generateStatContent(utime = fakeNonNumeric)) + + // When + val result = testedReader.readUserTime() + + // Then + assertThat(result).isNull() + } + + @Test + fun `M read utime plus stime W readActiveTime()`() { + // Given + fakeFile.writeText(generateStatContent(fakeUtime)) + + // When + val result = testedReader.readActiveTime() + + // Then + assertThat(result).isEqualTo((fakeUtime + fakeStime).toDouble()) + } + + @Test + fun `M use only utime W readActiveTime() {stime field absent}`() { + // Given — content with exactly 14 tokens (utime at [13], no stime at [14]) + fakeFile.writeText(generateStatContent(fakeUtime, includeStime = false)) + + // When + val result = testedReader.readActiveTime() + + // Then + assertThat(result).isEqualTo(fakeUtime.toDouble()) + } + + @Test + fun `M return null W readActiveTime() {non-numeric utime}`( + @StringForgery(regex = "[a-z]{3}") fakeNonNumeric: String + ) { + // Given + fakeFile.writeText(generateStatContent(utime = fakeNonNumeric)) + + // When + val result = testedReader.readActiveTime() + + // Then + assertThat(result).isNull() + } + + @Test + fun `M return null W readActiveTime() {non-numeric stime}`( + @StringForgery(regex = "[a-z]{3}") fakeNonNumeric: String + ) { + // Given — 15 tokens, numeric utime but non-numeric stime + fakeFile.writeText(generateStatContent(utime = fakeUtime, stime = fakeNonNumeric)) + + // When + val result = testedReader.readActiveTime() + + // Then + assertThat(result).isNull() + } + + @Test + fun `M return null W readActiveTime() {file doesn't exist}`() { + // When + val result = testedReader.readActiveTime() + + // Then + assertThat(result).isNull() + } + + @Test + fun `M return null W readActiveTime() {file isn't readable}`() { + // Given + val restrictedFile = mock { + on { exists() } doReturn true + on { canRead() } doReturn false + } + val reader = CpuStatReader(restrictedFile, internalLogger = mock()) + + // When + val result = reader.readActiveTime() + + // Then + assertThat(result).isNull() + } + + @Test + fun `M return null W readActiveTime() {invalid content}`(@StringForgery fakeContent: String) { + // Given + fakeFile.writeText(fakeContent) + + // When + val result = testedReader.readActiveTime() + + // Then + assertThat(result).isNull() + } + + private fun generateStatContent(utime: Any, stime: Any = fakeStime, includeStime: Boolean = true): String { + val fields = mutableListOf( + fakePid, fakeCommand, fakeState, fakePpid, fakePgrp, + fakeSession, fakeTtyNr, fakeTpgid, fakeFlags, + fakeMinFlt, fakeCMinFlt, fakeMajFlt, fakeCMajFlt, + utime + ) + if (includeStime) fields.add(stime) + return fields.joinToString(" ") + } +} From 1ca0085dca1158e0d0a400d19648c084db8f2a7a Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Tue, 12 May 2026 02:02:15 +0100 Subject: [PATCH 06/32] RUM-13949 Add memory timeseries serializer and session factory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `MemoryEventSerializer`, which converts a batch of memory `DataPoint`s (heap + native RSS) into a `RumTimeseriesMemoryEvent` JSON object. Introduces `TimeseriesConfiguration` as the public `@ExperimentalRumApi` configuration class (sampling intervals, batch size, background collection flag). Completes the pipeline with `RumSessionScopeTimeseriesFactory`, which wires CPU and memory readers, serializers, and the `TimeseriesConfiguration` together to create fully configured `RumSessionScopeTimeseries` instances for each new session. Completes the two metric types and provides the factory needed to tie the pipeline to the RUM session lifecycle. `MemoryEventSerializer` reports both heap and native RSS in bytes, normalised to a 0–100 percent scale using device `maxMemory`. `RumSessionScopeTimeseriesFactory` creates one pipeline per metric type and dispatches them through the same `Timeseries.Factory` interface. `TimeseriesConfiguration` exposes sampling interval, batch size, and a background-collection flag with sensible defaults. --- detekt_custom_safe_calls.yml | 1 + features/dd-sdk-android-rum/api/apiSurface | 8 + .../api/dd-sdk-android-rum.api | 18 ++ .../serializer/MemoryEventSerializer.kt | 99 ++++++ .../rum/timeseries/TimeseriesConfiguration.kt | 55 ++++ .../serializer/MemoryEventSerializerTest.kt | 294 ++++++++++++++++++ .../timeseries/TimeseriesConfigurationTest.kt | 121 +++++++ 7 files changed, 596 insertions(+) create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt diff --git a/detekt_custom_safe_calls.yml b/detekt_custom_safe_calls.yml index 627d7db73c..c8fa04b1b8 100644 --- a/detekt_custom_safe_calls.yml +++ b/detekt_custom_safe_calls.yml @@ -548,6 +548,7 @@ datadog: - "java.util.concurrent.ExecutorService.shutdown()" - "java.util.concurrent.ExecutorService.shutdownNow()" - "java.util.concurrent.Executors.newSingleThreadExecutor()" + - "java.util.concurrent.Executors.newSingleThreadScheduledExecutor(java.util.concurrent.ThreadFactory?)" - "java.util.concurrent.LinkedBlockingDeque.constructor()" - "java.util.concurrent.LinkedBlockingQueue.constructor()" - "java.util.concurrent.LinkedBlockingQueue.toArray()" diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index f1659c6b32..15d5ad321a 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -304,6 +304,14 @@ class com.datadog.android.rum.resource.RumResourceInputStream : java.io.InputStr override fun close() interface com.datadog.android.rum.startup.AppStartupActivityPredicate fun shouldTrackStartup(android.app.Activity): Boolean +class com.datadog.android.rum.timeseries.TimeseriesConfiguration + constructor(Int = DEFAULT_BUFFER_SIZE, Long = DEFAULT_INTERVAL_MS, Boolean = false) + val bufferSize: Int + val intervalMs: Long + companion object + const val DEFAULT_BUFFER_SIZE: Int + const val DEFAULT_INTERVAL_MS: Long + const val MIN_INTERVAL_MS: Long open class com.datadog.android.rum.tracking.AcceptAllActivities : ComponentPredicate override fun accept(android.app.Activity): Boolean override fun getViewName(android.app.Activity): String? diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index c4cf1522b6..7905f8b6bb 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -8637,6 +8637,24 @@ public abstract interface class com/datadog/android/rum/startup/AppStartupActivi public abstract fun shouldTrackStartup (Landroid/app/Activity;)Z } +public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration { + public static final field Companion Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Companion; + public static final field DEFAULT_BUFFER_SIZE I + public static final field DEFAULT_INTERVAL_MS J + public static final field MIN_INTERVAL_MS J + public fun ()V + public fun (I)V + public fun (IJ)V + public fun (IJZ)V + public synthetic fun (IJZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getBufferSize ()I + public final fun getCollectInBackground ()Z + public final fun getIntervalMs ()J +} + +public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Companion { +} + public class com/datadog/android/rum/tracking/AcceptAllActivities : com/datadog/android/rum/tracking/ComponentPredicate { public fun ()V public fun accept (Landroid/app/Activity;)Z diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt new file mode 100644 index 0000000000..967a9a0afc --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt @@ -0,0 +1,99 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.serializer + +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.timeseries.DataPoint +import com.datadog.android.rum.internal.timeseries.DeltaCompression +import com.datadog.android.rum.internal.timeseries.DeltaCompression.mapToDeltaCompressed +import com.datadog.android.rum.internal.timeseries.DeltaCompression.roundToLongSafely +import com.datadog.android.rum.internal.toTimeseriesMemorySessionType +import com.datadog.android.rum.model.TimeseriesMemoryEvent +import com.google.gson.JsonObject +import java.util.UUID + +internal class MemoryEventSerializer( + private val sessionId: String, + private val applicationId: String, + private val sessionType: RumSessionType, + private val totalRamBytes: Long, + private val timeProvider: TimeProvider, + private val useDeltaCompression: Boolean = false +) : JsonSerializer { + + override fun serialize(dataPoints: List>): JsonObject? { + if (totalRamBytes <= 0L || dataPoints.isEmpty()) return null + val data = dataPoints.map { sample -> + val memoryPercent = sample.value / totalRamBytes * PERCENT_FACTOR + TimeseriesMemoryEvent.Data( + timestamp = sample.timestampNs, + dataPoint = TimeseriesMemoryEvent.DataPoint(sample.value, memoryPercent) + ) + } + val start = data.firstOrNull()?.timestamp ?: 0L + val end = data.lastOrNull()?.timestamp ?: 0L + val deltaEncoded = if (useDeltaCompression) encodeDelta(data) else null + val schema = if (deltaEncoded != null) { + TimeseriesMemoryEvent.Schema.DELTA_OBJECT + } else { + TimeseriesMemoryEvent.Schema.OBJECT + } + val json = TimeseriesMemoryEvent( + dd = TimeseriesMemoryEvent.Dd(), + application = TimeseriesMemoryEvent.Application(id = applicationId), + session = TimeseriesMemoryEvent.Session( + id = sessionId, + type = sessionType.toTimeseriesMemorySessionType() + ), + source = TimeseriesMemoryEvent.Source.ANDROID, + date = timeProvider.getDeviceTimestampMillis(), + service = null, + version = null, + timeseries = TimeseriesMemoryEvent.Timeseries( + id = UUID.randomUUID().toString(), + schema = schema, + start = start, + end = end, + data = data + ) + ).toJson() as JsonObject + if (deltaEncoded != null) { + val timeseriesJson = json.getAsJsonObject("timeseries") + timeseriesJson.remove("data") + timeseriesJson.add("data", deltaEncoded) + } + return json + } + + private fun encodeDelta(data: List): JsonObject? { + if (data.size <= 1) return null + + val ts = data.mapToDeltaCompressed { it.timestamp } + + val memoryMaxArray = data.mapToDeltaCompressed { + roundToLongSafely(it.dataPoint.memoryMax.toDouble()) + } + + val memoryPercentArray = data.mapToDeltaCompressed { + roundToLongSafely(it.dataPoint.memoryPercent.toDouble()) + } + + return JsonObject().apply { + addProperty("precision", DeltaCompression.PRECISION) + addProperty("resolution", RESOLUTION_NS) + add("ts", ts) + add("memory_max", memoryMaxArray) + add("memory_percent", memoryPercentArray) + } + } + + companion object { + private const val PERCENT_FACTOR = 100.0 + private const val RESOLUTION_NS = "ns" + } +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt new file mode 100644 index 0000000000..f7568a5771 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt @@ -0,0 +1,55 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ +package com.datadog.android.rum.timeseries + +import com.datadog.android.rum.ExperimentalRumApi +import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.DEFAULT_BUFFER_SIZE +import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.DEFAULT_INTERVAL_MS +import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.MIN_INTERVAL_MS +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService + +/** + * Configuration for memory and CPU timeseries collection. + * + * @param bufferSize Number of samples accumulated per pipeline before sending a batch event. + * Must be > 0; out-of-range values fall back to [DEFAULT_BUFFER_SIZE]. + * Defaults to [DEFAULT_BUFFER_SIZE] (≈30 seconds at the default 1 s interval). + * @param intervalMs Sampling interval in milliseconds. Must be ≥ [MIN_INTERVAL_MS]; + * out-of-range values fall back to [DEFAULT_INTERVAL_MS]. + * Defaults to [DEFAULT_INTERVAL_MS]. + * @param collectInBackground Whether to keep sampling timeseries when the app is in background. + * Defaults to `false`. + */ + +class TimeseriesConfiguration +@JvmOverloads @ExperimentalRumApi +constructor( + bufferSize: Int = DEFAULT_BUFFER_SIZE, + intervalMs: Long = DEFAULT_INTERVAL_MS, + val collectInBackground: Boolean = false +) { + val bufferSize: Int = if (bufferSize > 0) bufferSize else DEFAULT_BUFFER_SIZE + val intervalMs: Long = if (intervalMs >= MIN_INTERVAL_MS) intervalMs else DEFAULT_INTERVAL_MS + + internal val executorFactory: () -> ScheduledExecutorService = { + Executors.newSingleThreadScheduledExecutor { runnable -> + @Suppress("UnsafeThirdPartyFunctionCall") // NPE cannot happen here + Thread(runnable, "datadog-timeseries").apply { isDaemon = true } + } + } + + companion object { + /** Default number of samples to accumulate before emitting a timeseries event. */ + const val DEFAULT_BUFFER_SIZE: Int = 30 + + /** Default sampling interval in milliseconds. */ + const val DEFAULT_INTERVAL_MS: Long = 1000L + + /** Minimum allowed sampling interval in milliseconds. */ + const val MIN_INTERVAL_MS: Long = 100L + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt new file mode 100644 index 0000000000..9e543c2460 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt @@ -0,0 +1,294 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.serializer + +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.timeseries.DataPoint +import com.datadog.android.rum.model.TimeseriesMemoryEvent +import com.datadog.android.rum.utils.forge.Configurator +import fr.xgouchet.elmyr.annotation.DoubleForgery +import fr.xgouchet.elmyr.annotation.LongForgery +import fr.xgouchet.elmyr.annotation.StringForgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.data.Offset +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness +import java.util.UUID +import kotlin.math.roundToLong + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(Configurator::class) +internal class MemoryEventSerializerTest { + + @Mock + lateinit var mockTimeProvider: TimeProvider + + @StringForgery + lateinit var fakeSessionId: String + + @StringForgery + lateinit var fakeApplicationId: String + + @LongForgery(min = 1_000_000_000L, max = 16_000_000_000L) + var fakeTotalRamBytes: Long = 0L + + @LongForgery(min = 1L) + var fakeNowMs: Long = 0L + + @BeforeEach + fun `set up`() { + whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn fakeNowMs + } + + @Test + fun `M return null W serialize() { empty input }`() { + // Given + val testedSerializer = MemoryEventSerializer( + sessionId = fakeSessionId, + applicationId = fakeApplicationId, + sessionType = RumSessionType.USER, + totalRamBytes = fakeTotalRamBytes, + timeProvider = mockTimeProvider + ) + + // When + val result = testedSerializer.serialize(emptyList()) + + // Then + assertThat(result).isNull() + } + + @Test + fun `M return null W serialize() { totalRamBytes is zero }`( + @DoubleForgery(min = 1.0) fakeMemory: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // Given + val testedSerializer = MemoryEventSerializer( + sessionId = fakeSessionId, + applicationId = fakeApplicationId, + sessionType = RumSessionType.USER, + totalRamBytes = 0L, + timeProvider = mockTimeProvider + ) + + // When + val result = testedSerializer.serialize(listOf(DataPoint(fakeTs, fakeMemory))) + + // Then + assertThat(result).isNull() + } + + @Test + fun `M return null W serialize() { totalRamBytes is negative }`( + @LongForgery(max = -1L) fakeNegativeRam: Long, + @DoubleForgery(min = 1.0) fakeMemory: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // Given + val testedSerializer = MemoryEventSerializer( + sessionId = fakeSessionId, + applicationId = fakeApplicationId, + sessionType = RumSessionType.USER, + totalRamBytes = fakeNegativeRam, + timeProvider = mockTimeProvider + ) + + // When + val result = testedSerializer.serialize(listOf(DataPoint(fakeTs, fakeMemory))) + + // Then + assertThat(result).isNull() + } + + @Test + fun `M produce object schema W serialize() { compression off }`( + @DoubleForgery(min = 1.0) fakeMemory: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // Given + val testedSerializer = MemoryEventSerializer( + sessionId = fakeSessionId, + applicationId = fakeApplicationId, + sessionType = RumSessionType.USER, + totalRamBytes = fakeTotalRamBytes, + timeProvider = mockTimeProvider, + useDeltaCompression = false + ) + val samples = listOf( + DataPoint(fakeTs, fakeMemory), + DataPoint(fakeTs + 1L, fakeMemory + 1.0) + ) + + // When + val json = testedSerializer.serialize(samples) ?: error(NON_NULL_JSON_ERROR) + + // Then + val timeseries = json.getAsJsonObject(KEY_TIMESERIES) + assertThat(timeseries.get(KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_OBJECT) + assertThat(timeseries.get(KEY_START).asLong).isEqualTo(fakeTs) + assertThat(timeseries.get(KEY_END).asLong).isEqualTo(fakeTs + 1L) + UUID.fromString(timeseries.get(KEY_ID).asString) + + val parsed = TimeseriesMemoryEvent.fromJsonObject(json) + assertThat(parsed.timeseries.data).hasSize(2) + val first = parsed.timeseries.data.first().dataPoint + assertThat(first.memoryMax.toDouble()).isCloseTo(fakeMemory, Offset.offset(MEMORY_OFFSET)) + assertThat(first.memoryPercent.toDouble()) + .isCloseTo(fakeMemory / fakeTotalRamBytes * PERCENT_FACTOR, Offset.offset(MEMORY_OFFSET)) + } + + @Test + fun `M map session type W serialize() { synthetics }`( + @DoubleForgery(min = 1.0) fakeMemory: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // Given + val testedSerializer = MemoryEventSerializer( + sessionId = fakeSessionId, + applicationId = fakeApplicationId, + sessionType = RumSessionType.SYNTHETICS, + totalRamBytes = fakeTotalRamBytes, + timeProvider = mockTimeProvider + ) + + // When + val json = testedSerializer.serialize( + listOf(DataPoint(fakeTs, fakeMemory), DataPoint(fakeTs + 1L, fakeMemory)) + ) ?: error(NON_NULL_JSON_ERROR) + + // Then + assertThat(json.getAsJsonObject(KEY_SESSION).get(KEY_TYPE).asString).isEqualTo(VALUE_TYPE_SYNTHETICS) + } + + @Test + fun `M produce delta-object schema W serialize() { compression on, multi-sample }`( + @DoubleForgery(min = 1.0, max = 1_000_000.0) fakeMem1: Double, + @DoubleForgery(min = 1.0, max = 1_000_000.0) fakeMem2: Double, + @DoubleForgery(min = 1.0, max = 1_000_000.0) fakeMem3: Double, + @LongForgery(min = 1L, max = 1_000_000L) fakeTs1: Long, + @LongForgery(min = 1L, max = 1_000L) fakeTimestampStep: Long + ) { + // Given + val testedSerializer = MemoryEventSerializer( + sessionId = fakeSessionId, + applicationId = fakeApplicationId, + sessionType = RumSessionType.USER, + totalRamBytes = fakeTotalRamBytes, + timeProvider = mockTimeProvider, + useDeltaCompression = true + ) + + val fakeTs2 = fakeTs1 + fakeTimestampStep + val fakeTs3 = fakeTs2 + fakeTimestampStep + + val samples = listOf( + DataPoint(fakeTs1, fakeMem1), + DataPoint(fakeTs2, fakeMem2), + DataPoint(fakeTs3, fakeMem3) + ) + + // When + val json = testedSerializer.serialize(samples) ?: error(NON_NULL_JSON_ERROR) + + // Then + val timeseries = json.getAsJsonObject(KEY_TIMESERIES) + assertThat(timeseries.get(KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_DELTA_OBJECT) + val data = timeseries.get(KEY_DATA).asJsonObject + assertThat(data.get(KEY_PRECISION).asInt).isEqualTo(EXPECTED_PRECISION) + assertThat(data.get(KEY_RESOLUTION).asString).isEqualTo(VALUE_RESOLUTION_NS) + + val tsArray = data.get(KEY_TS).asJsonArray + assertThat(tsArray[0].asLong).isEqualTo(fakeTs1) + assertThat(tsArray[1].asLong).isEqualTo(fakeTimestampStep) + assertThat(tsArray[2].asLong).isEqualTo(fakeTimestampStep) + + val maxArr = data.get(KEY_MEMORY_MAX).asJsonArray + val pctArr = data.get(KEY_MEMORY_PERCENT).asJsonArray + val scaledMax = listOf(fakeMem1, fakeMem2, fakeMem3).map { (it * SCALE).roundToLong() } + val scaledPct = listOf(fakeMem1, fakeMem2, fakeMem3) + .map { (it / fakeTotalRamBytes * PERCENT_FACTOR * SCALE).roundToLong() } + + assertThat(maxArr[0].asLong).isEqualTo(scaledMax[0]) + assertThat(maxArr[1].asLong).isEqualTo(scaledMax[1] - scaledMax[0]) + assertThat(maxArr[2].asLong).isEqualTo(scaledMax[2] - scaledMax[1]) + + assertThat(pctArr[0].asLong).isEqualTo(scaledPct[0]) + assertThat(pctArr[1].asLong).isEqualTo(scaledPct[1] - scaledPct[0]) + assertThat(pctArr[2].asLong).isEqualTo(scaledPct[2] - scaledPct[1]) + } + + @Test + fun `M fall back to object schema W serialize() { compression on, single sample }`( + @DoubleForgery(min = 1.0) fakeMemory: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // Given + val testedSerializer = MemoryEventSerializer( + sessionId = fakeSessionId, + applicationId = fakeApplicationId, + sessionType = RumSessionType.USER, + totalRamBytes = fakeTotalRamBytes, + timeProvider = mockTimeProvider, + useDeltaCompression = true + ) + + // When + val json = testedSerializer.serialize(listOf(DataPoint(fakeTs, fakeMemory))) + ?: error(NON_NULL_JSON_ERROR) + + // Then + val timeseries = json.getAsJsonObject(KEY_TIMESERIES) + assertThat(timeseries.get(KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_OBJECT) + assertThat(timeseries.get(KEY_DATA).isJsonArray).isTrue() + } + + private companion object { + private const val SCALE: Long = 10_000L + private const val PERCENT_FACTOR: Double = 100.0 + private const val EXPECTED_PRECISION: Int = 4 + private const val MEMORY_OFFSET: Double = 0.0001 + + // JSON keys + private const val KEY_TIMESERIES: String = "timeseries" + private const val KEY_SCHEMA: String = "schema" + private const val KEY_DATA: String = "data" + private const val KEY_SESSION: String = "session" + private const val KEY_TYPE: String = "type" + private const val KEY_ID: String = "id" + private const val KEY_START: String = "start" + private const val KEY_END: String = "end" + private const val KEY_PRECISION: String = "precision" + private const val KEY_RESOLUTION: String = "resolution" + private const val KEY_TS: String = "ts" + private const val KEY_MEMORY_MAX: String = "memory_max" + private const val KEY_MEMORY_PERCENT: String = "memory_percent" + + // JSON values + private const val VALUE_SCHEMA_OBJECT: String = "object" + private const val VALUE_SCHEMA_DELTA_OBJECT: String = "delta-object" + private const val VALUE_TYPE_SYNTHETICS: String = "synthetics" + private const val VALUE_RESOLUTION_NS: String = "ns" + + private const val NON_NULL_JSON_ERROR: String = "expected non-null json" + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt new file mode 100644 index 0000000000..45906ba575 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt @@ -0,0 +1,121 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.timeseries + +import com.datadog.android.rum.ExperimentalRumApi +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +@OptIn(ExperimentalRumApi::class) +internal class TimeseriesConfigurationTest { + + @Test + fun `M create thread named datadog-timeseries W executorFactory()`() { + // Given + val config = TimeseriesConfiguration() + val executor = config.executorFactory() + val capturedName = arrayOfNulls(1) + val latch = CountDownLatch(1) + + // When + executor.submit { + capturedName[0] = Thread.currentThread().name + latch.countDown() + } + latch.await(5, TimeUnit.SECONDS) + executor.shutdownNow() + + // Then + assertThat(capturedName[0]).isEqualTo("datadog-timeseries") + } + + @Test + fun `M create daemon thread W executorFactory()`() { + // Given + val config = TimeseriesConfiguration() + val executor = config.executorFactory() + val capturedDaemon = booleanArrayOf(false) + val latch = CountDownLatch(1) + + // When + executor.submit { + capturedDaemon[0] = Thread.currentThread().isDaemon + latch.countDown() + } + latch.await(5, TimeUnit.SECONDS) + executor.shutdownNow() + + // Then + assertThat(capturedDaemon[0]).isTrue() + } + + @Test + fun `M use defaults W constructed with no args`() { + // When + val config = TimeseriesConfiguration() + + // Then + assertThat(config.bufferSize).isEqualTo(TimeseriesConfiguration.DEFAULT_BUFFER_SIZE) + assertThat(config.intervalMs).isEqualTo(TimeseriesConfiguration.DEFAULT_INTERVAL_MS) + } + + @Test + fun `M use default bufferSize W constructed with non-positive bufferSize`() { + // When + val configZero = TimeseriesConfiguration(bufferSize = 0) + val configNegative = TimeseriesConfiguration(bufferSize = -1) + + // Then + assertThat(configZero.bufferSize).isEqualTo(TimeseriesConfiguration.DEFAULT_BUFFER_SIZE) + assertThat(configNegative.bufferSize).isEqualTo(TimeseriesConfiguration.DEFAULT_BUFFER_SIZE) + } + + @Test + fun `M use default intervalMs W constructed with intervalMs below minimum`() { + // When + val configBelowMin = TimeseriesConfiguration(intervalMs = TimeseriesConfiguration.MIN_INTERVAL_MS - 1) + val configZero = TimeseriesConfiguration(intervalMs = 0L) + + // Then + assertThat(configBelowMin.intervalMs).isEqualTo(TimeseriesConfiguration.DEFAULT_INTERVAL_MS) + assertThat(configZero.intervalMs).isEqualTo(TimeseriesConfiguration.DEFAULT_INTERVAL_MS) + } + + @Test + fun `M store provided values W constructed with valid args`() { + // Given + val fakeBufferSize = 10 + val fakeIntervalMs = 500L + + // When + val config = TimeseriesConfiguration(bufferSize = fakeBufferSize, intervalMs = fakeIntervalMs) + + // Then + assertThat(config.bufferSize).isEqualTo(fakeBufferSize) + assertThat(config.intervalMs).isEqualTo(fakeIntervalMs) + } + + @Test + fun `M default collectInBackground to false W constructed with no args`() { + // When + val config = TimeseriesConfiguration() + + // Then + assertThat(config.collectInBackground).isFalse() + } + + @Test + fun `M store collectInBackground W constructed with collectInBackground = true`() { + // When + val config = TimeseriesConfiguration(collectInBackground = true) + + // Then + assertThat(config.collectInBackground).isTrue() + } +} From 03d113a43423420d990e0f2c5e6933921cad60cc Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Fri, 29 May 2026 17:22:24 +0100 Subject: [PATCH 07/32] RUM-13949 Fix TimeseriesConfiguration and regenerate apiSurface - Remove executorFactory from TimeseriesConfiguration; executor is provided by RumFeature.vitalExecutorService at wiring time, not owned by the config class - Promote collectInBackground to a class-body property so the apiSurface generator picks it up (generator only reads propertyDeclaration nodes) - Regenerate apiSurface with val collectInBackground: Boolean included - Apply safe-cast fix in MemoryEventSerializer (as? JsonObject + null-safe chaining) - Remove executor tests from TimeseriesConfigurationTest Ref: RUM-13949 --- features/dd-sdk-android-rum/api/apiSurface | 1 + .../serializer/MemoryEventSerializer.kt | 10 +++-- .../rum/timeseries/TimeseriesConfiguration.kt | 17 +++----- .../timeseries/TimeseriesConfigurationTest.kt | 42 ------------------- 4 files changed, 12 insertions(+), 58 deletions(-) diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index 15d5ad321a..eeb29334c1 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -308,6 +308,7 @@ class com.datadog.android.rum.timeseries.TimeseriesConfiguration constructor(Int = DEFAULT_BUFFER_SIZE, Long = DEFAULT_INTERVAL_MS, Boolean = false) val bufferSize: Int val intervalMs: Long + val collectInBackground: Boolean companion object const val DEFAULT_BUFFER_SIZE: Int const val DEFAULT_INTERVAL_MS: Long diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt index 967a9a0afc..b5e5469d8b 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt @@ -61,11 +61,13 @@ internal class MemoryEventSerializer( end = end, data = data ) - ).toJson() as JsonObject + ).toJson() as? JsonObject + if (deltaEncoded != null) { - val timeseriesJson = json.getAsJsonObject("timeseries") - timeseriesJson.remove("data") - timeseriesJson.add("data", deltaEncoded) + json?.getAsJsonObject("timeseries")?.apply { + remove("data") + add("data", deltaEncoded) + } } return json } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt index f7568a5771..485905ba16 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt @@ -5,12 +5,11 @@ */ package com.datadog.android.rum.timeseries +import androidx.annotation.IntRange import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.DEFAULT_BUFFER_SIZE import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.DEFAULT_INTERVAL_MS import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.MIN_INTERVAL_MS -import java.util.concurrent.Executors -import java.util.concurrent.ScheduledExecutorService /** * Configuration for memory and CPU timeseries collection. @@ -28,19 +27,13 @@ import java.util.concurrent.ScheduledExecutorService class TimeseriesConfiguration @JvmOverloads @ExperimentalRumApi constructor( - bufferSize: Int = DEFAULT_BUFFER_SIZE, - intervalMs: Long = DEFAULT_INTERVAL_MS, - val collectInBackground: Boolean = false + @IntRange(from = 1, to = Int.MAX_VALUE.toLong()) bufferSize: Int = DEFAULT_BUFFER_SIZE, + @IntRange(from = MIN_INTERVAL_MS, to = Long.MAX_VALUE) intervalMs: Long = DEFAULT_INTERVAL_MS, + collectInBackground: Boolean = false ) { val bufferSize: Int = if (bufferSize > 0) bufferSize else DEFAULT_BUFFER_SIZE val intervalMs: Long = if (intervalMs >= MIN_INTERVAL_MS) intervalMs else DEFAULT_INTERVAL_MS - - internal val executorFactory: () -> ScheduledExecutorService = { - Executors.newSingleThreadScheduledExecutor { runnable -> - @Suppress("UnsafeThirdPartyFunctionCall") // NPE cannot happen here - Thread(runnable, "datadog-timeseries").apply { isDaemon = true } - } - } + val collectInBackground: Boolean = collectInBackground companion object { /** Default number of samples to accumulate before emitting a timeseries event. */ diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt index 45906ba575..949daefcdc 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt @@ -9,52 +9,10 @@ package com.datadog.android.rum.timeseries import com.datadog.android.rum.ExperimentalRumApi import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit @OptIn(ExperimentalRumApi::class) internal class TimeseriesConfigurationTest { - @Test - fun `M create thread named datadog-timeseries W executorFactory()`() { - // Given - val config = TimeseriesConfiguration() - val executor = config.executorFactory() - val capturedName = arrayOfNulls(1) - val latch = CountDownLatch(1) - - // When - executor.submit { - capturedName[0] = Thread.currentThread().name - latch.countDown() - } - latch.await(5, TimeUnit.SECONDS) - executor.shutdownNow() - - // Then - assertThat(capturedName[0]).isEqualTo("datadog-timeseries") - } - - @Test - fun `M create daemon thread W executorFactory()`() { - // Given - val config = TimeseriesConfiguration() - val executor = config.executorFactory() - val capturedDaemon = booleanArrayOf(false) - val latch = CountDownLatch(1) - - // When - executor.submit { - capturedDaemon[0] = Thread.currentThread().isDaemon - latch.countDown() - } - latch.await(5, TimeUnit.SECONDS) - executor.shutdownNow() - - // Then - assertThat(capturedDaemon[0]).isTrue() - } - @Test fun `M use defaults W constructed with no args`() { // When From d0b680ce2334f62f3c9a9ea284d5883fcbd79f14 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Mon, 8 Jun 2026 13:30:16 +0100 Subject: [PATCH 08/32] RUM-13949 Fix missing scale parameter in MemoryEventSerializer Pass the required `scale` argument to `roundToLongSafely` in the delta encoding path, matching the pattern already used in CpuEventSerializer. --- .../serializer/MemoryEventSerializer.kt | 11 +- .../serializer/MemoryEventSerializerTest.kt | 114 ++++++------------ 2 files changed, 44 insertions(+), 81 deletions(-) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt index b5e5469d8b..4f65aed41f 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt @@ -16,6 +16,7 @@ import com.datadog.android.rum.internal.toTimeseriesMemorySessionType import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.google.gson.JsonObject import java.util.UUID +import kotlin.math.pow internal class MemoryEventSerializer( private val sessionId: String, @@ -23,7 +24,8 @@ internal class MemoryEventSerializer( private val sessionType: RumSessionType, private val totalRamBytes: Long, private val timeProvider: TimeProvider, - private val useDeltaCompression: Boolean = false + private val useDeltaCompression: Boolean = false, + private val precision: Int = DeltaCompression.PRECISION ) : JsonSerializer { override fun serialize(dataPoints: List>): JsonObject? { @@ -75,18 +77,19 @@ internal class MemoryEventSerializer( private fun encodeDelta(data: List): JsonObject? { if (data.size <= 1) return null + val scale = 10.0.pow(precision.toDouble()).toLong() val ts = data.mapToDeltaCompressed { it.timestamp } val memoryMaxArray = data.mapToDeltaCompressed { - roundToLongSafely(it.dataPoint.memoryMax.toDouble()) + roundToLongSafely(it.dataPoint.memoryMax.toDouble(), scale = scale) } val memoryPercentArray = data.mapToDeltaCompressed { - roundToLongSafely(it.dataPoint.memoryPercent.toDouble()) + roundToLongSafely(it.dataPoint.memoryPercent.toDouble(), scale = scale) } return JsonObject().apply { - addProperty("precision", DeltaCompression.PRECISION) + addProperty("precision", precision) addProperty("resolution", RESOLUTION_NS) add("ts", ts) add("memory_max", memoryMaxArray) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt index 9e543c2460..89542c04dc 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt @@ -12,6 +12,7 @@ import com.datadog.android.rum.internal.timeseries.DataPoint import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.utils.forge.Configurator import fr.xgouchet.elmyr.annotation.DoubleForgery +import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -29,6 +30,7 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.util.UUID +import kotlin.math.pow import kotlin.math.roundToLong @Extensions( @@ -54,24 +56,35 @@ internal class MemoryEventSerializerTest { @LongForgery(min = 1L) var fakeNowMs: Long = 0L + @IntForgery(min = 2, max = 9) + var fakePrecision: Int = 0 + + val fakeScale: Long get() = 10.0.pow(fakePrecision.toDouble()).toLong() + @BeforeEach fun `set up`() { whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn fakeNowMs } + private fun testedSerializer( + useDeltaCompression: Boolean = false, + sessionType: RumSessionType = RumSessionType.USER, + totalRamBytes: Long = fakeTotalRamBytes, + precision: Int = fakePrecision + ) = MemoryEventSerializer( + sessionId = fakeSessionId, + applicationId = fakeApplicationId, + sessionType = sessionType, + totalRamBytes = totalRamBytes, + timeProvider = mockTimeProvider, + useDeltaCompression = useDeltaCompression, + precision = precision + ) + @Test fun `M return null W serialize() { empty input }`() { - // Given - val testedSerializer = MemoryEventSerializer( - sessionId = fakeSessionId, - applicationId = fakeApplicationId, - sessionType = RumSessionType.USER, - totalRamBytes = fakeTotalRamBytes, - timeProvider = mockTimeProvider - ) - // When - val result = testedSerializer.serialize(emptyList()) + val result = testedSerializer().serialize(emptyList()) // Then assertThat(result).isNull() @@ -82,17 +95,9 @@ internal class MemoryEventSerializerTest { @DoubleForgery(min = 1.0) fakeMemory: Double, @LongForgery(min = 1L) fakeTs: Long ) { - // Given - val testedSerializer = MemoryEventSerializer( - sessionId = fakeSessionId, - applicationId = fakeApplicationId, - sessionType = RumSessionType.USER, - totalRamBytes = 0L, - timeProvider = mockTimeProvider - ) - // When - val result = testedSerializer.serialize(listOf(DataPoint(fakeTs, fakeMemory))) + val result = testedSerializer(totalRamBytes = 0L) + .serialize(listOf(DataPoint(fakeTs, fakeMemory))) // Then assertThat(result).isNull() @@ -104,17 +109,9 @@ internal class MemoryEventSerializerTest { @DoubleForgery(min = 1.0) fakeMemory: Double, @LongForgery(min = 1L) fakeTs: Long ) { - // Given - val testedSerializer = MemoryEventSerializer( - sessionId = fakeSessionId, - applicationId = fakeApplicationId, - sessionType = RumSessionType.USER, - totalRamBytes = fakeNegativeRam, - timeProvider = mockTimeProvider - ) - // When - val result = testedSerializer.serialize(listOf(DataPoint(fakeTs, fakeMemory))) + val result = testedSerializer(totalRamBytes = fakeNegativeRam) + .serialize(listOf(DataPoint(fakeTs, fakeMemory))) // Then assertThat(result).isNull() @@ -126,21 +123,13 @@ internal class MemoryEventSerializerTest { @LongForgery(min = 1L) fakeTs: Long ) { // Given - val testedSerializer = MemoryEventSerializer( - sessionId = fakeSessionId, - applicationId = fakeApplicationId, - sessionType = RumSessionType.USER, - totalRamBytes = fakeTotalRamBytes, - timeProvider = mockTimeProvider, - useDeltaCompression = false - ) val samples = listOf( DataPoint(fakeTs, fakeMemory), DataPoint(fakeTs + 1L, fakeMemory + 1.0) ) // When - val json = testedSerializer.serialize(samples) ?: error(NON_NULL_JSON_ERROR) + val json = testedSerializer().serialize(samples) ?: error(NON_NULL_JSON_ERROR) // Then val timeseries = json.getAsJsonObject(KEY_TIMESERIES) @@ -162,19 +151,10 @@ internal class MemoryEventSerializerTest { @DoubleForgery(min = 1.0) fakeMemory: Double, @LongForgery(min = 1L) fakeTs: Long ) { - // Given - val testedSerializer = MemoryEventSerializer( - sessionId = fakeSessionId, - applicationId = fakeApplicationId, - sessionType = RumSessionType.SYNTHETICS, - totalRamBytes = fakeTotalRamBytes, - timeProvider = mockTimeProvider - ) - // When - val json = testedSerializer.serialize( - listOf(DataPoint(fakeTs, fakeMemory), DataPoint(fakeTs + 1L, fakeMemory)) - ) ?: error(NON_NULL_JSON_ERROR) + val json = testedSerializer(sessionType = RumSessionType.SYNTHETICS) + .serialize(listOf(DataPoint(fakeTs, fakeMemory), DataPoint(fakeTs + 1L, fakeMemory))) + ?: error(NON_NULL_JSON_ERROR) // Then assertThat(json.getAsJsonObject(KEY_SESSION).get(KEY_TYPE).asString).isEqualTo(VALUE_TYPE_SYNTHETICS) @@ -189,15 +169,6 @@ internal class MemoryEventSerializerTest { @LongForgery(min = 1L, max = 1_000L) fakeTimestampStep: Long ) { // Given - val testedSerializer = MemoryEventSerializer( - sessionId = fakeSessionId, - applicationId = fakeApplicationId, - sessionType = RumSessionType.USER, - totalRamBytes = fakeTotalRamBytes, - timeProvider = mockTimeProvider, - useDeltaCompression = true - ) - val fakeTs2 = fakeTs1 + fakeTimestampStep val fakeTs3 = fakeTs2 + fakeTimestampStep @@ -208,13 +179,13 @@ internal class MemoryEventSerializerTest { ) // When - val json = testedSerializer.serialize(samples) ?: error(NON_NULL_JSON_ERROR) + val json = testedSerializer(useDeltaCompression = true).serialize(samples) ?: error(NON_NULL_JSON_ERROR) // Then val timeseries = json.getAsJsonObject(KEY_TIMESERIES) assertThat(timeseries.get(KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_DELTA_OBJECT) val data = timeseries.get(KEY_DATA).asJsonObject - assertThat(data.get(KEY_PRECISION).asInt).isEqualTo(EXPECTED_PRECISION) + assertThat(data.get(KEY_PRECISION).asInt).isEqualTo(fakePrecision) assertThat(data.get(KEY_RESOLUTION).asString).isEqualTo(VALUE_RESOLUTION_NS) val tsArray = data.get(KEY_TS).asJsonArray @@ -224,9 +195,9 @@ internal class MemoryEventSerializerTest { val maxArr = data.get(KEY_MEMORY_MAX).asJsonArray val pctArr = data.get(KEY_MEMORY_PERCENT).asJsonArray - val scaledMax = listOf(fakeMem1, fakeMem2, fakeMem3).map { (it * SCALE).roundToLong() } + val scaledMax = listOf(fakeMem1, fakeMem2, fakeMem3).map { (it * fakeScale).roundToLong() } val scaledPct = listOf(fakeMem1, fakeMem2, fakeMem3) - .map { (it / fakeTotalRamBytes * PERCENT_FACTOR * SCALE).roundToLong() } + .map { (it / fakeTotalRamBytes * PERCENT_FACTOR * fakeScale).roundToLong() } assertThat(maxArr[0].asLong).isEqualTo(scaledMax[0]) assertThat(maxArr[1].asLong).isEqualTo(scaledMax[1] - scaledMax[0]) @@ -242,18 +213,9 @@ internal class MemoryEventSerializerTest { @DoubleForgery(min = 1.0) fakeMemory: Double, @LongForgery(min = 1L) fakeTs: Long ) { - // Given - val testedSerializer = MemoryEventSerializer( - sessionId = fakeSessionId, - applicationId = fakeApplicationId, - sessionType = RumSessionType.USER, - totalRamBytes = fakeTotalRamBytes, - timeProvider = mockTimeProvider, - useDeltaCompression = true - ) - // When - val json = testedSerializer.serialize(listOf(DataPoint(fakeTs, fakeMemory))) + val json = testedSerializer(useDeltaCompression = true) + .serialize(listOf(DataPoint(fakeTs, fakeMemory))) ?: error(NON_NULL_JSON_ERROR) // Then @@ -263,9 +225,7 @@ internal class MemoryEventSerializerTest { } private companion object { - private const val SCALE: Long = 10_000L private const val PERCENT_FACTOR: Double = 100.0 - private const val EXPECTED_PRECISION: Int = 4 private const val MEMORY_OFFSET: Double = 0.0001 // JSON keys From d734d6d84967cdd9629a3207601c447acadd1afd Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Tue, 9 Jun 2026 12:17:05 +0100 Subject: [PATCH 09/32] RUM-13949 Make TimeseriesConfiguration properties internal, fix linter - Make bufferSize/intervalMs/collectInBackground internal to avoid - UndocumentedPublicProperty detekt violations. Reorder import in - MemoryEventSerializerTest (ktlint). Regenerate dd-sdk-android-rum.api. - Replace the primary constructor with an internal constructor and a - public Builder class (setBufferSize/setIntervalMs/setCollectInBackground/build). - Update tests and regenerate API surface files. - Replace `?: error()` in // When blocks with `checkNotNull()` in // Then - Wrap bare UUID.fromString() call in assertDoesNotThrow { } - Remove unnecessary newSingleThreadScheduledExecutor entry from detekt_custom_safe_calls.yml Ref: RUM-13949 --- detekt_custom_safe_calls.yml | 1 - features/dd-sdk-android-rum/api/apiSurface | 9 ++- .../api/dd-sdk-android-rum.api | 14 ++-- .../rum/timeseries/TimeseriesConfiguration.kt | 74 +++++++++++++------ .../serializer/MemoryEventSerializerTest.kt | 19 ++--- .../timeseries/TimeseriesConfigurationTest.kt | 33 +++++---- 6 files changed, 94 insertions(+), 56 deletions(-) diff --git a/detekt_custom_safe_calls.yml b/detekt_custom_safe_calls.yml index c8fa04b1b8..627d7db73c 100644 --- a/detekt_custom_safe_calls.yml +++ b/detekt_custom_safe_calls.yml @@ -548,7 +548,6 @@ datadog: - "java.util.concurrent.ExecutorService.shutdown()" - "java.util.concurrent.ExecutorService.shutdownNow()" - "java.util.concurrent.Executors.newSingleThreadExecutor()" - - "java.util.concurrent.Executors.newSingleThreadScheduledExecutor(java.util.concurrent.ThreadFactory?)" - "java.util.concurrent.LinkedBlockingDeque.constructor()" - "java.util.concurrent.LinkedBlockingQueue.constructor()" - "java.util.concurrent.LinkedBlockingQueue.toArray()" diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index eeb29334c1..e8dfbeba34 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -305,10 +305,11 @@ class com.datadog.android.rum.resource.RumResourceInputStream : java.io.InputStr interface com.datadog.android.rum.startup.AppStartupActivityPredicate fun shouldTrackStartup(android.app.Activity): Boolean class com.datadog.android.rum.timeseries.TimeseriesConfiguration - constructor(Int = DEFAULT_BUFFER_SIZE, Long = DEFAULT_INTERVAL_MS, Boolean = false) - val bufferSize: Int - val intervalMs: Long - val collectInBackground: Boolean + class Builder + fun setBufferSize(Int): Builder + fun setIntervalMs(Long): Builder + fun setCollectInBackground(Boolean): Builder + fun build(): TimeseriesConfiguration companion object const val DEFAULT_BUFFER_SIZE: Int const val DEFAULT_INTERVAL_MS: Long diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index 7905f8b6bb..c78fdf7f7f 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -8642,14 +8642,14 @@ public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration { public static final field DEFAULT_BUFFER_SIZE I public static final field DEFAULT_INTERVAL_MS J public static final field MIN_INTERVAL_MS J +} + +public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder { public fun ()V - public fun (I)V - public fun (IJ)V - public fun (IJZ)V - public synthetic fun (IJZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getBufferSize ()I - public final fun getCollectInBackground ()Z - public final fun getIntervalMs ()J + public final fun build ()Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration; + public final fun setBufferSize (I)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; + public final fun setCollectInBackground (Z)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; + public final fun setIntervalMs (J)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; } public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Companion { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt index 485905ba16..9cd23fe4fb 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt @@ -7,33 +7,65 @@ package com.datadog.android.rum.timeseries import androidx.annotation.IntRange import com.datadog.android.rum.ExperimentalRumApi -import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.DEFAULT_BUFFER_SIZE -import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.DEFAULT_INTERVAL_MS -import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.MIN_INTERVAL_MS /** * Configuration for memory and CPU timeseries collection. * - * @param bufferSize Number of samples accumulated per pipeline before sending a batch event. - * Must be > 0; out-of-range values fall back to [DEFAULT_BUFFER_SIZE]. - * Defaults to [DEFAULT_BUFFER_SIZE] (≈30 seconds at the default 1 s interval). - * @param intervalMs Sampling interval in milliseconds. Must be ≥ [MIN_INTERVAL_MS]; - * out-of-range values fall back to [DEFAULT_INTERVAL_MS]. - * Defaults to [DEFAULT_INTERVAL_MS]. - * @param collectInBackground Whether to keep sampling timeseries when the app is in background. - * Defaults to `false`. + * Use [Builder] to create an instance. */ - -class TimeseriesConfiguration -@JvmOverloads @ExperimentalRumApi -constructor( - @IntRange(from = 1, to = Int.MAX_VALUE.toLong()) bufferSize: Int = DEFAULT_BUFFER_SIZE, - @IntRange(from = MIN_INTERVAL_MS, to = Long.MAX_VALUE) intervalMs: Long = DEFAULT_INTERVAL_MS, - collectInBackground: Boolean = false +class TimeseriesConfiguration internal constructor( + internal val bufferSize: Int, + internal val intervalMs: Long, + internal val collectInBackground: Boolean ) { - val bufferSize: Int = if (bufferSize > 0) bufferSize else DEFAULT_BUFFER_SIZE - val intervalMs: Long = if (intervalMs >= MIN_INTERVAL_MS) intervalMs else DEFAULT_INTERVAL_MS - val collectInBackground: Boolean = collectInBackground + + /** + * A Builder for [TimeseriesConfiguration]. + */ + @ExperimentalRumApi + class Builder { + + private var bufferSize: Int = DEFAULT_BUFFER_SIZE + private var intervalMs: Long = DEFAULT_INTERVAL_MS + private var collectInBackground: Boolean = false + + /** + * Sets the number of samples accumulated per pipeline before sending a batch event. + * Must be > 0; out-of-range values fall back to [DEFAULT_BUFFER_SIZE]. + * Defaults to [DEFAULT_BUFFER_SIZE] (≈30 seconds at the default 1 s interval). + */ + fun setBufferSize( + @IntRange(from = 1, to = Int.MAX_VALUE.toLong()) bufferSize: Int + ): Builder = apply { + this.bufferSize = if (bufferSize > 0) bufferSize else DEFAULT_BUFFER_SIZE + } + + /** + * Sets the sampling interval in milliseconds. + * Must be ≥ [MIN_INTERVAL_MS]; out-of-range values fall back to [DEFAULT_INTERVAL_MS]. + * Defaults to [DEFAULT_INTERVAL_MS]. + */ + fun setIntervalMs( + @IntRange(from = MIN_INTERVAL_MS, to = Long.MAX_VALUE) intervalMs: Long + ): Builder = apply { + this.intervalMs = if (intervalMs >= MIN_INTERVAL_MS) intervalMs else DEFAULT_INTERVAL_MS + } + + /** + * Sets whether to keep sampling timeseries when the app is in background. + * Defaults to `false`. + */ + fun setCollectInBackground(collectInBackground: Boolean): Builder = apply { + this.collectInBackground = collectInBackground + } + + /** Builds a [TimeseriesConfiguration] from the current builder state. */ + fun build(): TimeseriesConfiguration = TimeseriesConfiguration( + bufferSize = bufferSize, + intervalMs = intervalMs, + collectInBackground = collectInBackground + ) + } companion object { /** Default number of samples to accumulate before emitting a timeseries event. */ diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt index 89542c04dc..f932abfb11 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt @@ -21,6 +21,7 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.data.Offset import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions import org.mockito.Mock @@ -129,14 +130,15 @@ internal class MemoryEventSerializerTest { ) // When - val json = testedSerializer().serialize(samples) ?: error(NON_NULL_JSON_ERROR) + val result = testedSerializer().serialize(samples) // Then + val json = checkNotNull(result) val timeseries = json.getAsJsonObject(KEY_TIMESERIES) assertThat(timeseries.get(KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_OBJECT) assertThat(timeseries.get(KEY_START).asLong).isEqualTo(fakeTs) assertThat(timeseries.get(KEY_END).asLong).isEqualTo(fakeTs + 1L) - UUID.fromString(timeseries.get(KEY_ID).asString) + assertDoesNotThrow { UUID.fromString(timeseries.get(KEY_ID).asString) } val parsed = TimeseriesMemoryEvent.fromJsonObject(json) assertThat(parsed.timeseries.data).hasSize(2) @@ -152,11 +154,11 @@ internal class MemoryEventSerializerTest { @LongForgery(min = 1L) fakeTs: Long ) { // When - val json = testedSerializer(sessionType = RumSessionType.SYNTHETICS) + val result = testedSerializer(sessionType = RumSessionType.SYNTHETICS) .serialize(listOf(DataPoint(fakeTs, fakeMemory), DataPoint(fakeTs + 1L, fakeMemory))) - ?: error(NON_NULL_JSON_ERROR) // Then + val json = checkNotNull(result) assertThat(json.getAsJsonObject(KEY_SESSION).get(KEY_TYPE).asString).isEqualTo(VALUE_TYPE_SYNTHETICS) } @@ -179,9 +181,10 @@ internal class MemoryEventSerializerTest { ) // When - val json = testedSerializer(useDeltaCompression = true).serialize(samples) ?: error(NON_NULL_JSON_ERROR) + val result = testedSerializer(useDeltaCompression = true).serialize(samples) // Then + val json = checkNotNull(result) val timeseries = json.getAsJsonObject(KEY_TIMESERIES) assertThat(timeseries.get(KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_DELTA_OBJECT) val data = timeseries.get(KEY_DATA).asJsonObject @@ -214,11 +217,11 @@ internal class MemoryEventSerializerTest { @LongForgery(min = 1L) fakeTs: Long ) { // When - val json = testedSerializer(useDeltaCompression = true) + val result = testedSerializer(useDeltaCompression = true) .serialize(listOf(DataPoint(fakeTs, fakeMemory))) - ?: error(NON_NULL_JSON_ERROR) // Then + val json = checkNotNull(result) val timeseries = json.getAsJsonObject(KEY_TIMESERIES) assertThat(timeseries.get(KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_OBJECT) assertThat(timeseries.get(KEY_DATA).isJsonArray).isTrue() @@ -248,7 +251,5 @@ internal class MemoryEventSerializerTest { private const val VALUE_SCHEMA_DELTA_OBJECT: String = "delta-object" private const val VALUE_TYPE_SYNTHETICS: String = "synthetics" private const val VALUE_RESOLUTION_NS: String = "ns" - - private const val NON_NULL_JSON_ERROR: String = "expected non-null json" } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt index 949daefcdc..4ec7458ee2 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt @@ -14,9 +14,9 @@ import org.junit.jupiter.api.Test internal class TimeseriesConfigurationTest { @Test - fun `M use defaults W constructed with no args`() { + fun `M use defaults W built with no args`() { // When - val config = TimeseriesConfiguration() + val config = TimeseriesConfiguration.Builder().build() // Then assertThat(config.bufferSize).isEqualTo(TimeseriesConfiguration.DEFAULT_BUFFER_SIZE) @@ -24,10 +24,10 @@ internal class TimeseriesConfigurationTest { } @Test - fun `M use default bufferSize W constructed with non-positive bufferSize`() { + fun `M use default bufferSize W setBufferSize with non-positive value`() { // When - val configZero = TimeseriesConfiguration(bufferSize = 0) - val configNegative = TimeseriesConfiguration(bufferSize = -1) + val configZero = TimeseriesConfiguration.Builder().setBufferSize(0).build() + val configNegative = TimeseriesConfiguration.Builder().setBufferSize(-1).build() // Then assertThat(configZero.bufferSize).isEqualTo(TimeseriesConfiguration.DEFAULT_BUFFER_SIZE) @@ -35,10 +35,12 @@ internal class TimeseriesConfigurationTest { } @Test - fun `M use default intervalMs W constructed with intervalMs below minimum`() { + fun `M use default intervalMs W setIntervalMs below minimum`() { // When - val configBelowMin = TimeseriesConfiguration(intervalMs = TimeseriesConfiguration.MIN_INTERVAL_MS - 1) - val configZero = TimeseriesConfiguration(intervalMs = 0L) + val configBelowMin = TimeseriesConfiguration.Builder() + .setIntervalMs(TimeseriesConfiguration.MIN_INTERVAL_MS - 1) + .build() + val configZero = TimeseriesConfiguration.Builder().setIntervalMs(0L).build() // Then assertThat(configBelowMin.intervalMs).isEqualTo(TimeseriesConfiguration.DEFAULT_INTERVAL_MS) @@ -46,13 +48,16 @@ internal class TimeseriesConfigurationTest { } @Test - fun `M store provided values W constructed with valid args`() { + fun `M store provided values W built with valid args`() { // Given val fakeBufferSize = 10 val fakeIntervalMs = 500L // When - val config = TimeseriesConfiguration(bufferSize = fakeBufferSize, intervalMs = fakeIntervalMs) + val config = TimeseriesConfiguration.Builder() + .setBufferSize(fakeBufferSize) + .setIntervalMs(fakeIntervalMs) + .build() // Then assertThat(config.bufferSize).isEqualTo(fakeBufferSize) @@ -60,18 +65,18 @@ internal class TimeseriesConfigurationTest { } @Test - fun `M default collectInBackground to false W constructed with no args`() { + fun `M default collectInBackground to false W built with no args`() { // When - val config = TimeseriesConfiguration() + val config = TimeseriesConfiguration.Builder().build() // Then assertThat(config.collectInBackground).isFalse() } @Test - fun `M store collectInBackground W constructed with collectInBackground = true`() { + fun `M store collectInBackground W setCollectInBackground true`() { // When - val config = TimeseriesConfiguration(collectInBackground = true) + val config = TimeseriesConfiguration.Builder().setCollectInBackground(true).build() // Then assertThat(config.collectInBackground).isTrue() From 045a6305d8bd6d7349a227e7732a1fdf9ff7954c Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Tue, 12 May 2026 02:02:29 +0100 Subject: [PATCH 10/32] RUM-13949 Wire timeseries collection into RUM session lifecycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `enableTimeseries()`/`disableTimeseries()` builder methods on `RumConfiguration.Builder`. Wires the `Timeseries.Factory` through `RumFeature` → `DatadogRumMonitor` → `RumApplicationScope` → `RumSessionScope`, so that a new `RumSessionScopeTimeseries` collector is created and started for each sampled session and stopped on session end or SDK teardown. Also enables timeseries in the Kotlin sample app. Connects the standalone pipeline and public configuration API to the existing RUM session lifecycle, completing the end-to-end data flow from configuration through to event emission. Existing scope and monitor test scaffolding is updated to pass `timeseriesFactory = null` / a mock factory where required. The `RumFeature.onStop()` ordering test verifies the writer is still live when the final flush fires. --- features/dd-sdk-android-rum/api/apiSurface | 5 +- .../api/dd-sdk-android-rum.api | 5 +- .../kotlin/com/datadog/android/rum/Rum.kt | 3 +- .../datadog/android/rum/RumConfiguration.kt | 21 +++ .../android/rum/internal/RumFeature.kt | 151 +++++++++++++++--- .../domain/scope/RumApplicationScope.kt | 10 +- .../internal/domain/scope/RumSessionScope.kt | 11 +- .../rum/internal/monitor/DatadogRumMonitor.kt | 29 +++- .../timeseries/RumSessionScopeTimeseries.kt | 2 +- .../RumSessionScopeTimeseriesFactory.kt | 8 +- .../rum/internal/timeseries/Timeseries.kt | 6 + .../internal/timeseries/TimeseriesFactory.kt | 15 -- .../serializer/CpuEventSerializer.kt | 11 +- .../serializer/MemoryEventSerializer.kt | 11 +- .../rum/timeseries/TimeseriesConfiguration.kt | 21 ++- .../rum/RumConfigurationBuilderTest.kt | 38 +++++ .../android/rum/internal/RumFeatureTest.kt | 106 +++++++++++- ...pplicationScopeAttributePropagationTest.kt | 11 +- .../domain/scope/RumApplicationScopeTest.kt | 69 +++++++- ...RumSessionScopeAttributePropagationTest.kt | 11 +- .../domain/scope/RumSessionScopeTest.kt | 57 ++++++- .../internal/monitor/DatadogRumMonitorTest.kt | 29 ++-- ...yTest.kt => RumSessionScopeFactoryTest.kt} | 0 .../forge/ConfigurationRumForgeryFactory.kt | 6 +- .../android/sample/SampleApplication.kt | 2 + 25 files changed, 542 insertions(+), 96 deletions(-) delete mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesFactory.kt rename features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/{RumSessionScopeTimeseriesFactoryTest.kt => RumSessionScopeFactoryTest.kt} (100%) diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index e8dfbeba34..3531d55f1a 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -90,6 +90,7 @@ data class com.datadog.android.rum.RumConfiguration fun setSlowFramesConfiguration(com.datadog.android.rum.configuration.SlowFramesConfiguration?): Builder fun trackAnonymousUser(Boolean): Builder fun setAppStartupActivityPredicate(com.datadog.android.rum.startup.AppStartupActivityPredicate): Builder + fun setTimeseriesConfiguration(com.datadog.android.rum.timeseries.TimeseriesConfiguration?): Builder fun build(): RumConfiguration enum com.datadog.android.rum.RumErrorSource - NETWORK @@ -309,11 +310,9 @@ class com.datadog.android.rum.timeseries.TimeseriesConfiguration fun setBufferSize(Int): Builder fun setIntervalMs(Long): Builder fun setCollectInBackground(Boolean): Builder + fun setUseDeltaCompression(Boolean): Builder fun build(): TimeseriesConfiguration companion object - const val DEFAULT_BUFFER_SIZE: Int - const val DEFAULT_INTERVAL_MS: Long - const val MIN_INTERVAL_MS: Long open class com.datadog.android.rum.tracking.AcceptAllActivities : ComponentPredicate override fun accept(android.app.Activity): Boolean override fun getViewName(android.app.Activity): String? diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index c78fdf7f7f..cfc1e35ad1 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -117,6 +117,7 @@ public final class com/datadog/android/rum/RumConfiguration$Builder { public final fun setSessionSampleRate (F)Lcom/datadog/android/rum/RumConfiguration$Builder; public final fun setSlowFramesConfiguration (Lcom/datadog/android/rum/configuration/SlowFramesConfiguration;)Lcom/datadog/android/rum/RumConfiguration$Builder; public final fun setTelemetrySampleRate (F)Lcom/datadog/android/rum/RumConfiguration$Builder; + public final fun setTimeseriesConfiguration (Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration;)Lcom/datadog/android/rum/RumConfiguration$Builder; public final fun setViewEventMapper (Lcom/datadog/android/rum/event/ViewEventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder; public final fun setVitalEventMapper (Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder; public final fun setVitalEventMapper (Lcom/datadog/android/event/EventMapper;Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder; @@ -8639,9 +8640,6 @@ public abstract interface class com/datadog/android/rum/startup/AppStartupActivi public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration { public static final field Companion Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Companion; - public static final field DEFAULT_BUFFER_SIZE I - public static final field DEFAULT_INTERVAL_MS J - public static final field MIN_INTERVAL_MS J } public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder { @@ -8650,6 +8648,7 @@ public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Bu public final fun setBufferSize (I)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; public final fun setCollectInBackground (Z)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; public final fun setIntervalMs (J)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; + public final fun setUseDeltaCompression (Z)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; } public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Companion { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt index af7ad5dc96..bf18321483 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt @@ -171,7 +171,8 @@ object Rum { rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter ) }, - insightsCollector = rumFeature.insightsCollector + insightsCollector = rumFeature.insightsCollector, + timeseriesFactory = rumFeature.timeseriesFactory ) } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt index 7677258b68..9428f4969b 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt @@ -28,6 +28,7 @@ import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.model.VitalAppLaunchEvent import com.datadog.android.rum.model.VitalOperationStepEvent import com.datadog.android.rum.startup.AppStartupActivityPredicate +import com.datadog.android.rum.timeseries.TimeseriesConfiguration import com.datadog.android.rum.tracking.ActionTrackingStrategy import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy import com.datadog.android.rum.tracking.InteractionPredicate @@ -410,6 +411,26 @@ data class RumConfiguration internal constructor( return this } + /** + * Enables memory and CPU timeseries collection. + * + * When enabled, the SDK samples device memory (RSS) and CPU usage at + * [TimeseriesConfiguration.intervalMs] (default 1 s) and emits a batched + * timeseries event every [TimeseriesConfiguration.bufferSize] samples (default 30). + * Collection runs on a single background thread named `datadog-timeseries`. + * + * To disable, pass null. + * + * @param configuration optional fine-tuning of sampling interval and batch size. + */ + @ExperimentalRumApi + fun setTimeseriesConfiguration( + configuration: TimeseriesConfiguration? + ): Builder { + rumConfig = rumConfig.copy(timeseriesConfiguration = configuration) + return this + } + /** * Builds a [RumConfiguration] based on the current state of this Builder. */ diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index 11d261bcdc..637a819d23 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -82,6 +82,15 @@ import com.datadog.android.rum.internal.startup.RumAppStartupDetector import com.datadog.android.rum.internal.startup.RumStartupScenario import com.datadog.android.rum.internal.startup.RumTTIDInfo import com.datadog.android.rum.internal.thread.NoOpScheduledExecutorService +import com.datadog.android.rum.internal.timeseries.Buffer +import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesFactory +import com.datadog.android.rum.internal.timeseries.Pipeline +import com.datadog.android.rum.internal.timeseries.RumSessionScopeTimeseriesFactory +import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.provider.CpuDatapointReader +import com.datadog.android.rum.internal.timeseries.provider.VitalReaderWrapper +import com.datadog.android.rum.internal.timeseries.serializer.CpuEventSerializer +import com.datadog.android.rum.internal.timeseries.serializer.MemoryEventSerializer import com.datadog.android.rum.internal.tracking.JetpackViewAttributesProvider import com.datadog.android.rum.internal.tracking.NoOpInteractionPredicate import com.datadog.android.rum.internal.tracking.NoOpUserActionTrackingStrategy @@ -112,6 +121,7 @@ import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.model.VitalAppLaunchEvent import com.datadog.android.rum.model.VitalOperationStepEvent import com.datadog.android.rum.startup.AppStartupActivityPredicate +import com.datadog.android.rum.timeseries.TimeseriesConfiguration import com.datadog.android.rum.tracking.ActionTrackingStrategy import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy import com.datadog.android.rum.tracking.InteractionPredicate @@ -180,6 +190,7 @@ internal class RumFeature( internal var displayInfoProvider: InfoProvider = NoOpDisplayInfoProvider() internal val rumContextUpdateReceivers = mutableSetOf() internal var insightsCollector: InsightsCollector = NoOpInsightsCollector() + internal var timeseriesFactory: Timeseries.Factory = NoOpTimeseriesFactory() private val lateCrashEventHandler by lazy { lateCrashReporterFactory(sdkCore as InternalSdkCore) } internal var rumAppStartupDetector: RumAppStartupDetector? = null @@ -249,20 +260,27 @@ internal class RumFeature( configuration.longTaskTrackingStrategy?.let { longTaskTrackingStrategy = it } val frequency = configuration.vitalsMonitorUpdateFrequency - val slowFrameListenerConfiguration = configuration.slowFramesConfiguration - if (frequency != VitalsUpdateFrequency.NEVER || slowFrameListenerConfiguration != null) { - initializeVitalExecutorService(frequency) + val vitalsUpdatesRequired = frequency != VitalsUpdateFrequency.NEVER + + // The vital executor backs both the CPU/memory vital monitors and timeseries sampling. + if (vitalsUpdatesRequired || configuration.timeseriesConfiguration != null) initializeVitalExecutorService() + + if (vitalsUpdatesRequired) { + // CPU/memory monitors schedule on the vital executor, so they are only set up when vitals + // are enabled - the same condition that creates the executor above. initializeCpuVitalMonitor(frequency) initializeMemoryVitalMonitor(frequency) - if (!configuration.disableJankStats) { - initializeFrameStatesAggregator( - application = appContext as? Application, - listeners = listOfNotNull( - initializeSlowFrameListener(slowFrameListenerConfiguration), - initializeFPSVitalMonitor(frequency) - ) - ) - } + } + + val fpsVitalMonitor = initializeFPSVitalMonitor(frequency) + val slowFrameListener = initializeSlowFrameListener(configuration.slowFramesConfiguration) + if (fpsVitalMonitor != null || slowFrameListener != null) { + // The aggregator feeds two Choreographer-based listeners (no executor needed): the + // FPS frame-rate vital - gated on the vitals frequency, hence vitalsUpdatesRequired - and + // the slow-frames listener, gated on its own config. + (appContext as? Application)?.initializeFrameStatesAggregator( + listOfNotNull(slowFrameListener, fpsVitalMonitor) + ) } if (configuration.trackNonFatalAnrs) { @@ -273,6 +291,13 @@ internal class RumFeature( sessionListener = configuration.sessionListener + configuration.timeseriesConfiguration?.let { configuration -> + timeseriesFactory = createTimeseriesCollectingFactory( + configuration, + appContext.readTotalRamBytes(sdkCore.internalLogger) + ) + } + initRumAppStartupDetector() sdkCore.setEventReceiver(name, this) @@ -280,13 +305,13 @@ internal class RumFeature( initialized.set(true) } - private fun initializeFrameStatesAggregator(application: Application?, listeners: List) { + private fun Application.initializeFrameStatesAggregator(listeners: List) { frameStatesAggregator = FrameStatesAggregator(listeners, sdkCore.internalLogger) - application?.registerActivityLifecycleCallbacks(frameStatesAggregator) + registerActivityLifecycleCallbacks(frameStatesAggregator) } private fun initializeSlowFrameListener(slowFramesConfiguration: SlowFramesConfiguration?): FrameStateListener? { - slowFramesListener = if (slowFramesConfiguration != null) { + slowFramesListener = if (slowFramesConfiguration != null && !configuration.disableJankStats) { sdkCore.internalLogger.log( InternalLogger.Level.INFO, InternalLogger.Target.USER, @@ -337,6 +362,9 @@ internal class RumFeature( unregisterTrackingStrategies(appContext) + timeseriesFactory = NoOpTimeseriesFactory() + (GlobalRumMonitor.get(sdkCore) as? DatadogRumMonitor)?.stopActiveTimeseries() + dataWriter = NoOpDataWriter() viewTrackingStrategy = NoOpViewTrackingStrategy() @@ -409,7 +437,63 @@ internal class RumFeature( eventMetaSerializer = RumEventMetaSerializer(), sdkCore = sdkCore ) - } + + internal fun createTimeseriesCollectingFactory( + configuration: TimeseriesConfiguration, + totalRamBytes: Long + ): Timeseries.Factory = RumSessionScopeTimeseriesFactory( + internalLogger = sdkCore.internalLogger, + collectInBackground = configuration.collectInBackground, + scheduledExecutorService = vitalExecutorService, + pipelinesProvider = { applicationId, sessionId, sessionType -> + listOfNotNull( + Pipeline( + sdkCore = sdkCore, + reader = CpuDatapointReader( + cpuStatReader = CpuStatReader(internalLogger = sdkCore.internalLogger), + cpuTimeProvider = sdkCore.timeProvider, + intervalMs = configuration.intervalMs + ), + buffer = Buffer(configuration.bufferSize), + serializer = CpuEventSerializer( + sessionId = sessionId, + applicationId = applicationId, + sessionType = sessionType, + timeProvider = sdkCore.timeProvider, + useDeltaCompression = configuration.useDeltaCompression + ), + dataWriter = dataWriter + ), + if (totalRamBytes > 0L) { + Pipeline( + sdkCore = sdkCore, + reader = VitalReaderWrapper( + vitalReader = MemoryVitalReader(internalLogger = sdkCore.internalLogger), + timeProvider = sdkCore.timeProvider, + intervalMs = configuration.intervalMs + ), + buffer = Buffer(configuration.bufferSize), + serializer = MemoryEventSerializer( + sessionId = sessionId, + applicationId = applicationId, + sessionType = sessionType, + totalRamBytes = totalRamBytes, + timeProvider = sdkCore.timeProvider, + useDeltaCompression = configuration.useDeltaCompression + ), + dataWriter = dataWriter + ) + } else { + sdkCore.internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + { MEMORY_TIMESERIES_DISABLED_MESSAGE } + ) + null + } + ) + } + ) // region FeatureEventReceiver @@ -569,10 +653,7 @@ internal class RumFeature( longTaskTrackingStrategy.unregister(appContext) } - private fun initializeVitalExecutorService(frequency: VitalsUpdateFrequency) { - if (frequency == VitalsUpdateFrequency.NEVER) { - return - } + private fun initializeVitalExecutorService() { @Suppress("UnsafeThirdPartyFunctionCall") // pool size can't be <= 0 vitalExecutorService = sdkCore.createScheduledExecutorService("rum-vital") } @@ -600,7 +681,7 @@ internal class RumFeature( } private fun initializeFPSVitalMonitor(frequency: VitalsUpdateFrequency): FPSVitalListener? { - if (frequency == VitalsUpdateFrequency.NEVER) return null + if (frequency == VitalsUpdateFrequency.NEVER || configuration.disableJankStats) return null frameRateVitalMonitor = AggregatingVitalMonitor() return FPSVitalListener(frameRateVitalMonitor) @@ -767,7 +848,8 @@ internal class RumFeature( val collectAccessibility: Boolean, val disableJankStats: Boolean, val insightsCollector: InsightsCollector, - val appStartupActivityPredicate: AppStartupActivityPredicate + val appStartupActivityPredicate: AppStartupActivityPredicate, + val timeseriesConfiguration: TimeseriesConfiguration? ) internal companion object { @@ -821,7 +903,8 @@ internal class RumFeature( collectAccessibility = false, disableJankStats = false, insightsCollector = NoOpInsightsCollector(), - appStartupActivityPredicate = DefaultAppStartupActivityPredicate + appStartupActivityPredicate = DefaultAppStartupActivityPredicate, + timeseriesConfiguration = null ) internal const val EVENT_MESSAGE_PROPERTY = "message" @@ -849,12 +932,34 @@ internal class RumFeature( "Slow frames monitoring enabled." internal const val SLOW_FRAMES_MONITORING_DISABLED_MESSAGE = "Slow frames monitoring disabled." + internal const val MEMORY_TIMESERIES_DISABLED_MESSAGE = + "Unable to read total device memory; memory timeseries collection is disabled." internal const val RUM_FEATURE_NOT_YET_INITIALIZED = "RUM feature is not initialized yet, you need to register it with a" + " SDK instance by calling SdkCore#registerFeature method." internal const val FAILED_TO_ENABLE_JANK_STATS_TRACKING_MANUALLY = "Manually enabling JankStats tracking threw an exception." + @Suppress("TooGenericExceptionCaught") + internal fun Context.readTotalRamBytes(internalLogger: InternalLogger): Long = try { + getSystemServiceAs(Context.ACTIVITY_SERVICE) + // UnsafeThirdPartyFunctionCall: MemoryInfo() and getMemoryInfo() can throw + // RuntimeException on broken Android implementations; fall back to 0. + ?.run { + @Suppress("UnsafeThirdPartyFunctionCall") + ActivityManager.MemoryInfo().also { getMemoryInfo(it) }.totalMem + } + ?: 0L + } catch (e: RuntimeException) { + internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.MAINTAINER, + { "Failed to read total RAM via ActivityManager" }, + e + ) + 0L + } + private fun provideUserTrackingStrategy( touchTargetExtraAttributesProviders: Array, interactionPredicate: InteractionPredicate, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt index da42899374..ab820b1233 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt @@ -29,6 +29,7 @@ import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollect import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.startup.RumSessionScopeStartupManager +import com.datadog.android.rum.internal.timeseries.Timeseries import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -55,7 +56,8 @@ internal class RumApplicationScope( private val batteryInfoProvider: InfoProvider, private val displayInfoProvider: InfoProvider, private val rumSessionScopeStartupManagerFactory: () -> RumSessionScopeStartupManager, - private val insightsCollector: InsightsCollector + private val insightsCollector: InsightsCollector, + private val timeseriesFactory: Timeseries.Factory ) : RumScope, RumViewChangedListener { override val parentScope: RumScope? = null @@ -85,7 +87,8 @@ internal class RumApplicationScope( batteryInfoProvider = batteryInfoProvider, displayInfoProvider = displayInfoProvider, rumSessionScopeStartupManagerFactory = rumSessionScopeStartupManagerFactory, - insightsCollector = insightsCollector + insightsCollector = insightsCollector, + timeseriesFactory = timeseriesFactory ) ) @@ -207,7 +210,8 @@ internal class RumApplicationScope( batteryInfoProvider = batteryInfoProvider, displayInfoProvider = displayInfoProvider, rumSessionScopeStartupManagerFactory = rumSessionScopeStartupManagerFactory, - insightsCollector = insightsCollector + insightsCollector = insightsCollector, + timeseriesFactory = timeseriesFactory ) childScopes.add(newSession) if (event !is RumRawEvent.StartView) { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt index 3d70ade4c8..d8cf0aff24 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt @@ -31,7 +31,6 @@ import com.datadog.android.rum.internal.startup.RumSessionScopeStartupManager import com.datadog.android.rum.internal.timeseries.NoOpTimeseries import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesFactory import com.datadog.android.rum.internal.timeseries.Timeseries -import com.datadog.android.rum.internal.timeseries.TimeseriesFactory import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -65,7 +64,7 @@ internal class RumSessionScope( private val rumSessionTypeOverride: RumSessionType?, private val rumSessionScopeStartupManagerFactory: () -> RumSessionScopeStartupManager, insightsCollector: InsightsCollector, - private val timeseriesFactory: TimeseriesFactory = NoOpTimeseriesFactory() + private val timeseriesFactory: Timeseries.Factory = NoOpTimeseriesFactory() ) : RumScope { private var timeseries: Timeseries = NoOpTimeseries() @@ -236,14 +235,14 @@ internal class RumSessionScope( private fun startTimeseries() { timeseries = timeseriesFactory.create( - sessionId, - parentScope.getRumContext().applicationId, - rumSessionTypeOverride ?: RumSessionType.USER + sessionId = sessionId, + applicationId = parentScope.getRumContext().applicationId, + sessionType = rumSessionTypeOverride ?: RumSessionType.USER ) timeseries.onSessionStart() } - private fun stopTimeseries() { + internal fun stopTimeseries() { timeseries.onSessionStop() timeseries = NoOpTimeseries() } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt index 1c6362968a..b6fa16c1ba 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt @@ -64,6 +64,7 @@ import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.startup.RumSessionScopeStartupManager import com.datadog.android.rum.internal.startup.RumStartupScenario import com.datadog.android.rum.internal.startup.RumTTIDInfo +import com.datadog.android.rum.internal.timeseries.Timeseries import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -105,7 +106,8 @@ internal class DatadogRumMonitor( batteryInfoProvider: InfoProvider, displayInfoProvider: InfoProvider, private val rumSessionScopeStartupManagerFactory: () -> RumSessionScopeStartupManager, - insightsCollector: InsightsCollector + insightsCollector: InsightsCollector, + timeseriesFactory: Timeseries.Factory ) : RumMonitor, AdvancedRumMonitor { internal var rootScope = RumApplicationScope( @@ -128,7 +130,8 @@ internal class DatadogRumMonitor( batteryInfoProvider = batteryInfoProvider, displayInfoProvider = displayInfoProvider, rumSessionScopeStartupManagerFactory = rumSessionScopeStartupManagerFactory, - insightsCollector = insightsCollector + insightsCollector = insightsCollector, + timeseriesFactory = timeseriesFactory ) internal var debugListener: RumDebugListener? = null @@ -841,6 +844,28 @@ internal class DatadogRumMonitor( } } + /** + * Stops the timeseries collector on the currently active session, if any. + * + * Must be called from [RumFeature.onStop] **before** [GlobalRumMonitor.unregister], so that + * the sampling [java.util.concurrent.ScheduledExecutorService] is shut down while the monitor + * is still reachable. Without this call, the executor keeps running after SDK teardown and a + * subsequent [com.datadog.android.Datadog.initialize] would create a second + * `datadog-timeseries` thread in the same process. + * + * Must be called **before** [RumFeature.dataWriter] is replaced with a + * [com.datadog.android.rum.internal.storage.NoOpDataWriter]. [EventWriter.write] captures the + * writer eagerly at call time, so the final flush issued inside [stop] will reach the real + * writer only if this ordering is maintained. Note: the flush is best-effort — if the core SDK + * de-initializes and shuts down its context executor before the async write task fires, the + * write is silently skipped regardless. + */ + internal fun stopActiveTimeseries() { + synchronized(rootScope) { + rootScope.activeSession?.stopTimeseries() + } + } + internal fun handleEvent(event: RumRawEvent) { if (event is RumRawEvent.AddError && event.isFatal) { synchronized(rootScope) { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt index 3ca5941a2e..6055f3944c 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt @@ -38,7 +38,7 @@ import java.util.concurrent.atomic.AtomicReference */ internal class RumSessionScopeTimeseries( private val internalLogger: InternalLogger, - private val pipelines: List>, + internal val pipelines: List>, private val collectInBackground: Boolean, internal val scheduledExecutorService: ScheduledExecutorService ) : Timeseries { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactory.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactory.kt index 8313ec0453..9e42560294 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactory.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactory.kt @@ -15,17 +15,17 @@ internal class RumSessionScopeTimeseriesFactory( private val collectInBackground: Boolean, private val scheduledExecutorService: ScheduledExecutorService, private val pipelinesProvider: ( - sessionId: String, applicationId: String, + sessionId: String, sessionType: RumSessionType ) -> List> -) : TimeseriesFactory { +) : Timeseries.Factory { - override fun create(sessionId: String, applicationId: String, sessionType: RumSessionType) = + override fun create(applicationId: String, sessionId: String, sessionType: RumSessionType) = RumSessionScopeTimeseries( internalLogger = internalLogger, collectInBackground = collectInBackground, scheduledExecutorService = scheduledExecutorService, - pipelines = pipelinesProvider(sessionId, applicationId, sessionType) + pipelines = pipelinesProvider(applicationId, sessionId, sessionType) ) } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt index db01671297..655482944c 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt @@ -6,6 +6,7 @@ package com.datadog.android.rum.internal.timeseries +import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.internal.domain.scope.RumViewType import com.datadog.tools.annotation.NoOpImplementation @@ -14,4 +15,9 @@ internal interface Timeseries { fun onSessionStart() fun onSessionStop() fun onViewTypeUpdate(viewType: RumViewType) + + @NoOpImplementation + interface Factory { + fun create(applicationId: String, sessionId: String, sessionType: RumSessionType): Timeseries + } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesFactory.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesFactory.kt deleted file mode 100644 index 151037f53a..0000000000 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesFactory.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.rum.internal.timeseries - -import com.datadog.android.rum.RumSessionType -import com.datadog.tools.annotation.NoOpImplementation - -@NoOpImplementation -internal interface TimeseriesFactory { - fun create(sessionId: String, applicationId: String, sessionType: RumSessionType): Timeseries -} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt index 8bfee46a94..fa8ae47466 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt @@ -61,13 +61,16 @@ internal class CpuEventSerializer( end = end, data = data ) - ).toJson() as JsonObject + ).toJson() as? JsonObject + // + val timeseriesJson = json?.getAsJsonObject("timeseries") if (deltaEncoded != null) { - val timeseriesJson = json.getAsJsonObject("timeseries") - timeseriesJson.remove("data") - timeseriesJson.add("data", deltaEncoded) + timeseriesJson?.remove("data") + timeseriesJson?.add("data", deltaEncoded) } + timeseriesJson?.addProperty("count", data.size) + // return json } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt index 4f65aed41f..fc8ca5463b 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt @@ -64,13 +64,14 @@ internal class MemoryEventSerializer( data = data ) ).toJson() as? JsonObject - + // + val timeseriesJson = json?.getAsJsonObject("timeseries") if (deltaEncoded != null) { - json?.getAsJsonObject("timeseries")?.apply { - remove("data") - add("data", deltaEncoded) - } + timeseriesJson?.remove("data") + timeseriesJson?.add("data", deltaEncoded) } + timeseriesJson?.addProperty("count", data.size) + // return json } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt index 9cd23fe4fb..0b864abd64 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt @@ -16,7 +16,8 @@ import com.datadog.android.rum.ExperimentalRumApi class TimeseriesConfiguration internal constructor( internal val bufferSize: Int, internal val intervalMs: Long, - internal val collectInBackground: Boolean + internal val collectInBackground: Boolean, + internal val useDeltaCompression: Boolean ) { /** @@ -28,6 +29,7 @@ class TimeseriesConfiguration internal constructor( private var bufferSize: Int = DEFAULT_BUFFER_SIZE private var intervalMs: Long = DEFAULT_INTERVAL_MS private var collectInBackground: Boolean = false + private var useDeltaCompression: Boolean = false /** * Sets the number of samples accumulated per pipeline before sending a batch event. @@ -59,22 +61,31 @@ class TimeseriesConfiguration internal constructor( this.collectInBackground = collectInBackground } + /** + * Sets whether to encode each sample as a delta from the previous value instead of an + * absolute value. Reduces payload size when values change slowly. Defaults to `false`. + */ + fun setUseDeltaCompression(useDeltaCompression: Boolean): Builder = apply { + this.useDeltaCompression = useDeltaCompression + } + /** Builds a [TimeseriesConfiguration] from the current builder state. */ fun build(): TimeseriesConfiguration = TimeseriesConfiguration( bufferSize = bufferSize, intervalMs = intervalMs, - collectInBackground = collectInBackground + collectInBackground = collectInBackground, + useDeltaCompression = useDeltaCompression ) } companion object { /** Default number of samples to accumulate before emitting a timeseries event. */ - const val DEFAULT_BUFFER_SIZE: Int = 30 + internal const val DEFAULT_BUFFER_SIZE: Int = 30 /** Default sampling interval in milliseconds. */ - const val DEFAULT_INTERVAL_MS: Long = 1000L + internal const val DEFAULT_INTERVAL_MS: Long = 1000L /** Minimum allowed sampling interval in milliseconds. */ - const val MIN_INTERVAL_MS: Long = 100L + internal const val MIN_INTERVAL_MS: Long = 100L } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumConfigurationBuilderTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumConfigurationBuilderTest.kt index 246b12466c..05fb33144c 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumConfigurationBuilderTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumConfigurationBuilderTest.kt @@ -30,6 +30,7 @@ import com.datadog.android.rum.model.ResourceEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.model.VitalAppLaunchEvent import com.datadog.android.rum.model.VitalOperationStepEvent +import com.datadog.android.rum.timeseries.TimeseriesConfiguration import com.datadog.android.rum.tracking.ActionTrackingStrategy import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy import com.datadog.android.rum.tracking.InteractionPredicate @@ -65,6 +66,7 @@ import java.util.UUID ) @MockitoSettings(strictness = Strictness.LENIENT) @ForgeConfiguration(Configurator::class) +@OptIn(ExperimentalRumApi::class) internal class RumConfigurationBuilderTest { private lateinit var testedBuilder: RumConfiguration.Builder @@ -739,4 +741,40 @@ internal class RumConfigurationBuilderTest { assertThat(rumConfiguration.featureConfiguration.insightsCollector) .isInstanceOf(NoOpInsightsCollector::class.java) } + + @Test + fun `M store provided configuration W setTimeseriesConfiguration(config)`() { + // Given + val fakeConfig = TimeseriesConfiguration.Builder().setBufferSize(10).setIntervalMs(500L).build() + + // When + val rumConfiguration = testedBuilder.setTimeseriesConfiguration(fakeConfig).build() + + // Then + assertThat(rumConfiguration.featureConfiguration.timeseriesConfiguration) + .isSameAs(fakeConfig) + } + + @Test + fun `M store default configuration W setTimeseriesConfiguration(default)`() { + // When + val rumConfiguration = testedBuilder + .setTimeseriesConfiguration(TimeseriesConfiguration.Builder().build()) + .build() + + // Then + assertThat(rumConfiguration.featureConfiguration.timeseriesConfiguration).isNotNull + } + + @Test + fun `M nullify configuration W setTimeseriesConfiguration(null)`() { + // Given + testedBuilder.setTimeseriesConfiguration(TimeseriesConfiguration.Builder().build()) + + // When + val rumConfiguration = testedBuilder.setTimeseriesConfiguration(null).build() + + // Then + assertThat(rumConfiguration.featureConfiguration.timeseriesConfiguration).isNull() + } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt index 1f9a80bec4..fc651cef82 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt @@ -16,6 +16,7 @@ import android.content.res.Resources import android.os.Handler import com.datadog.android.api.InternalLogger import com.datadog.android.api.feature.FeatureContextUpdateReceiver +import com.datadog.android.api.storage.DataWriter import com.datadog.android.api.storage.NoOpDataWriter import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.feature.event.JvmCrash @@ -27,8 +28,10 @@ import com.datadog.android.internal.system.BuildSdkVersionProvider import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumErrorSource +import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.assertj.RumFeatureAssert import com.datadog.android.rum.configuration.VitalsUpdateFrequency +import com.datadog.android.rum.internal.RumFeature.Companion.MEMORY_TIMESERIES_DISABLED_MESSAGE import com.datadog.android.rum.internal.RumFeature.Companion.SLOW_FRAMES_MONITORING_DISABLED_MESSAGE import com.datadog.android.rum.internal.RumFeature.Companion.SLOW_FRAMES_MONITORING_ENABLED_MESSAGE import com.datadog.android.rum.internal.domain.InfoProvider @@ -42,9 +45,12 @@ import com.datadog.android.rum.internal.domain.display.DefaultDisplayInfoProvide import com.datadog.android.rum.internal.domain.event.RumEventMapper import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.monitor.AdvancedRumMonitor +import com.datadog.android.rum.internal.monitor.DatadogRumMonitor import com.datadog.android.rum.internal.monitor.NoOpAdvancedRumMonitor import com.datadog.android.rum.internal.startup.RumAppStartupDetector import com.datadog.android.rum.internal.thread.NoOpScheduledExecutorService +import com.datadog.android.rum.internal.timeseries.RumSessionScopeTimeseries +import com.datadog.android.rum.internal.timeseries.Timeseries import com.datadog.android.rum.internal.tracking.NoOpInteractionPredicate import com.datadog.android.rum.internal.tracking.NoOpUserActionTrackingStrategy import com.datadog.android.rum.internal.tracking.UserActionTrackingStrategy @@ -54,6 +60,7 @@ import com.datadog.android.rum.internal.vitals.FrameStateListener import com.datadog.android.rum.internal.vitals.FrameStatesAggregator import com.datadog.android.rum.internal.vitals.NoOpVitalMonitor import com.datadog.android.rum.internal.vitals.VitalReaderRunnable +import com.datadog.android.rum.timeseries.TimeseriesConfiguration import com.datadog.android.rum.tracking.InteractionPredicate import com.datadog.android.rum.tracking.NoOpTrackingStrategy import com.datadog.android.rum.tracking.NoOpViewTrackingStrategy @@ -726,7 +733,8 @@ internal class RumFeatureTest { fun `M not initialize the vital monitors W initialize { frequency = NEVER }`() { // Given fakeConfiguration = fakeConfiguration.copy( - vitalsMonitorUpdateFrequency = VitalsUpdateFrequency.NEVER + vitalsMonitorUpdateFrequency = VitalsUpdateFrequency.NEVER, + timeseriesConfiguration = null ) testedFeature = RumFeature( mockSdkCore, @@ -843,6 +851,92 @@ internal class RumFeatureTest { assertThat(testedFeature.dataWriter).isInstanceOf(NoOpDataWriter::class.java) } + @Test + fun `M flush timeseries before resetting writer W onStop()`() { + // Given + testedFeature.onInitialize(appContext.mockInstance) + val mockDatadogMonitor = mock() + GlobalRumMonitor.clear() + GlobalRumMonitor.registerIfAbsent(mockDatadogMonitor, mockSdkCore) + + var writerAtFlushTime: DataWriter? = null + doAnswer { + writerAtFlushTime = testedFeature.dataWriter + }.whenever(mockDatadogMonitor).stopActiveTimeseries() + + // When + testedFeature.onStop() + + // Then + assertThat(writerAtFlushTime).isNotNull + assertThat(writerAtFlushTime).isNotInstanceOf(NoOpDataWriter::class.java) + assertThat(testedFeature.dataWriter).isInstanceOf(NoOpDataWriter::class.java) + } + + @Test + fun `M log USER message W timeseries create() { total RAM unavailable }`( + @StringForgery fakeSessionId: String + ) { + // When + createTimeseries(totalRamBytes = 0L, sessionId = fakeSessionId) + + // Then + mockInternalLogger.verifyLog( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + MEMORY_TIMESERIES_DISABLED_MESSAGE + ) + } + + @Test + fun `M not collect memory W timeseries create() { total RAM unavailable }`( + @StringForgery fakeSessionId: String + ) { + // When + val timeseries = createTimeseries(totalRamBytes = 0L, sessionId = fakeSessionId) + + // Then: only the CPU pipeline is created; the memory pipeline is skipped. + check(timeseries is RumSessionScopeTimeseries) + assertThat(timeseries.pipelines).hasSize(1) + } + + @Test + fun `M collect memory W timeseries create() { total RAM available }`( + @StringForgery fakeSessionId: String, + @LongForgery(min = 1L) fakeTotalRamBytes: Long + ) { + // When + val timeseries = createTimeseries(totalRamBytes = fakeTotalRamBytes, sessionId = fakeSessionId) + + // Then: both the CPU and memory pipelines are created. + check(timeseries is RumSessionScopeTimeseries) + assertThat(timeseries.pipelines).hasSize(2) + } + + @Test + fun `M initialize vital executor W initialize { frequency = NEVER and timeseries enabled }`() { + // Given + @Suppress("OPT_IN_USAGE") + fakeConfiguration = fakeConfiguration.copy( + vitalsMonitorUpdateFrequency = VitalsUpdateFrequency.NEVER, + slowFramesConfiguration = null, + timeseriesConfiguration = TimeseriesConfiguration.Builder().build() + ) + testedFeature = RumFeature( + mockSdkCore, + fakeApplicationId.toString(), + fakeConfiguration, + lateCrashReporterFactory = { mockLateCrashReporter } + ) + + // When + testedFeature.onInitialize(appContext.mockInstance) + + // Then: timeseries sampling needs a real executor even when vitals are disabled. + assertThat(testedFeature.vitalExecutorService) + .isNotInstanceOf(NoOpScheduledExecutorService::class.java) + } + @Test fun `M remove associated monitor W onStop()`() { // Given @@ -893,7 +987,8 @@ internal class RumFeatureTest { fun `M not initialize vital executor W initialize { frequency = NEVER }()`() { // Given fakeConfiguration = fakeConfiguration.copy( - vitalsMonitorUpdateFrequency = VitalsUpdateFrequency.NEVER + vitalsMonitorUpdateFrequency = VitalsUpdateFrequency.NEVER, + timeseriesConfiguration = null ) testedFeature = RumFeature( mockSdkCore, @@ -1705,6 +1800,13 @@ internal class RumFeatureTest { ) } + // Builds a timeseries collector for the given total device RAM (in bytes), bypassing the + // ActivityManager read so the skip/collect/log behavior can be exercised deterministically. + private fun createTimeseries(totalRamBytes: Long, sessionId: String): Timeseries = + testedFeature + .createTimeseriesCollectingFactory(TimeseriesConfiguration.Builder().build(), totalRamBytes) + .create(fakeApplicationId.toString(), sessionId, RumSessionType.USER) + private fun Forge.anApplicationExitInfoList( mustInclude: Int? = null ): List { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt index 7f39dbeb9e..737ac478e4 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt @@ -27,6 +27,7 @@ import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollect import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter +import com.datadog.android.rum.internal.timeseries.Timeseries import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -136,6 +137,12 @@ internal class RumApplicationScopeAttributePropagationTest { @Mock lateinit var mockSessionSampler: Sampler + @Mock + lateinit var mockTimeseriesFactory: Timeseries.Factory + + @Mock + lateinit var mockTimeseries: Timeseries + lateinit var fakeEventTime: Time lateinit var fakeEvent: RumRawEvent @@ -199,6 +206,7 @@ internal class RumApplicationScopeAttributePropagationTest { fakeRumSessionType = forge.aNullable { aValueFrom(RumSessionType::class.java) } whenever(mockSessionSampler.getSampleRate()).thenReturn(fakeSampleRate) whenever(mockSessionSampler.sample(any())).thenReturn(true) + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries testedScope = RumApplicationScope( applicationId = fakeApplicationId, sdkCore = rumMonitor.mockSdkCore, @@ -219,7 +227,8 @@ internal class RumApplicationScopeAttributePropagationTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumSessionScopeStartupManagerFactory = mock(), - insightsCollector = mockInsightsCollector + insightsCollector = mockInsightsCollector, + timeseriesFactory = mockTimeseriesFactory ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt index da963967e0..1ce823d556 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt @@ -30,6 +30,7 @@ import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollect import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter +import com.datadog.android.rum.internal.timeseries.Timeseries import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -57,6 +58,7 @@ import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever @@ -139,6 +141,12 @@ internal class RumApplicationScopeTest { @Mock lateinit var mockSessionSampler: Sampler + @Mock + lateinit var mockTimeseriesFactory: Timeseries.Factory + + @Mock + lateinit var mockTimeseries: Timeseries + @StringForgery(regex = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}") lateinit var fakeApplicationId: String @@ -177,6 +185,7 @@ internal class RumApplicationScopeTest { whenever(mockSessionSampler.getSampleRate()).thenReturn(fakeSampleRate) whenever(mockSessionSampler.sample(any())).thenReturn(true) + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries testedScope = RumApplicationScope( applicationId = fakeApplicationId, @@ -198,7 +207,8 @@ internal class RumApplicationScopeTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumSessionScopeStartupManagerFactory = mock(), - insightsCollector = mockInsightsCollector + insightsCollector = mockInsightsCollector, + timeseriesFactory = mockTimeseriesFactory ) } @@ -214,6 +224,63 @@ internal class RumApplicationScopeTest { assertThat(childScope.firstPartyHostHeaderTypeResolver).isSameAs(mockResolver) } + @Test + fun `M propagate timeseriesFactory to initial child session scope W handleEvent { startView }`( + @StringForgery viewKey: String, + @StringForgery viewName: String + ) { + // When - delegating an interactive event triggers renewSession on the child session + testedScope.handleEvent( + RumRawEvent.StartView( + key = RumScopeKey.from(viewKey, viewName), + attributes = emptyMap() + ), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // Then - the child session uses the same factory we gave to the application scope + verify(mockTimeseriesFactory).create(eq(fakeApplicationId), any(), any()) + } + + @Test + fun `M propagate timeseriesFactory to a new child session W handleEvent { stop+startView }`( + @StringForgery viewKey: String, + @StringForgery viewName: String + ) { + // Given - first session that already used the factory + testedScope.handleEvent( + RumRawEvent.StartView( + key = RumScopeKey.from(viewKey, viewName), + attributes = emptyMap() + ), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + testedScope.handleEvent( + RumRawEvent.StopSession(), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // When - a brand new session is started; the application scope must reuse the factory + testedScope.handleEvent( + RumRawEvent.StartView( + key = RumScopeKey.from(viewKey, viewName), + attributes = emptyMap() + ), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // Then - factory.create is invoked once per tracked session (initial + new) + verify(mockTimeseriesFactory, times(2)).create(eq(fakeApplicationId), any(), any()) + } + @Test fun `M always return the same applicationId W getRumContext()`() { val context = testedScope.getRumContext() diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt index e0e1a3b5ab..5888e0570f 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt @@ -28,6 +28,7 @@ import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollect import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter +import com.datadog.android.rum.internal.timeseries.Timeseries import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -133,6 +134,12 @@ internal class RumSessionScopeAttributePropagationTest { @Mock lateinit var mockSessionSampler: Sampler + @Mock + lateinit var mockTimeseriesFactory: Timeseries.Factory + + @Mock + lateinit var mockTimeseries: Timeseries + lateinit var fakeParentAttributes: Map @Forgery @@ -171,6 +178,7 @@ internal class RumSessionScopeAttributePropagationTest { fakeRumSessionType = forge.aNullable { aValueFrom(RumSessionType::class.java) } whenever(mockSessionSampler.getSampleRate()).thenReturn(fakeSampleRate) whenever(mockSessionSampler.sample(any())).thenReturn(true) + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries testedScope = RumSessionScope( parentScope = mockParentScope, sdkCore = mockSdkCore, @@ -195,7 +203,8 @@ internal class RumSessionScopeAttributePropagationTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumSessionScopeStartupManagerFactory = mock(), - insightsCollector = mockInsightsCollector + insightsCollector = mockInsightsCollector, + timeseriesFactory = mockTimeseriesFactory ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt index 8373efd71e..c8ebf94fe0 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt @@ -41,7 +41,6 @@ import com.datadog.android.rum.internal.startup.RumTTIDInfo import com.datadog.android.rum.internal.startup.testRumStartupScenarios import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesFactory import com.datadog.android.rum.internal.timeseries.Timeseries -import com.datadog.android.rum.internal.timeseries.TimeseriesFactory import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -202,7 +201,7 @@ internal class RumSessionScopeTest { private lateinit var mockRumSessionScopeStartupManager: RumSessionScopeStartupManager @Mock - private lateinit var mockTimeseriesFactory: TimeseriesFactory + private lateinit var mockTimeseriesFactory: Timeseries.Factory @Mock private lateinit var mockTimeseries: Timeseries @@ -235,6 +234,7 @@ internal class RumSessionScopeTest { whenever(mockDisplayInfoProvider.getState()) doReturn fakeDisplayInfo whenever(mockSessionSampler.sample(any())).thenReturn(true) whenever(mockSessionSampler.getSampleRate()).thenReturn(100f) + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries fakeParentAttributes = forge.exhaustiveAttributes() whenever(mockParentScope.getCustomAttributes()) doReturn fakeParentAttributes @@ -1774,8 +1774,8 @@ internal class RumSessionScopeTest { // Then verify(mockTimeseriesFactory).create( - testedScope.sessionId, fakeParentContext.applicationId, + testedScope.sessionId, fakeRumSessionType ?: RumSessionType.USER ) verify(mockTimeseries).onSessionStart() @@ -1944,6 +1944,55 @@ internal class RumSessionScopeTest { inOrder.verify(mockTimeseries).onViewTypeUpdate(RumViewType.FOREGROUND) } + @Test + fun `M stop previous timeseries W renewSession { ResetSession renews tracked session }`(forge: Forge) { + // Given + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val firstTimeseries = mockTimeseries + val secondTimeseries: Timeseries = mock() + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn secondTimeseries + + // When + testedScope.handleEvent(RumRawEvent.ResetSession(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then + verify(firstTimeseries).onSessionStart() + verify(firstTimeseries).onSessionStop() + verify(secondTimeseries).onSessionStart() + } + + @Test + fun `M stop previous timeseries W renewSession { expired session then new interaction }`(forge: Forge) { + // Given + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + initializeTestedScope(backgroundTrackingEnabled = false, timeseriesFactory = mockTimeseriesFactory) + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val firstTimeseries = mockTimeseries + val secondTimeseries: Timeseries = mock() + whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn secondTimeseries + advanceTimeByMs(TEST_INACTIVITY_MS) + testedScope.handleEvent(mock(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // When + testedScope.handleEvent(forge.startActionEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then + verify(firstTimeseries).onSessionStart() + verify(firstTimeseries).onSessionStop() + verify(secondTimeseries).onSessionStart() + } + + @Test + fun `M not stop timeseries W handleEvent { StopSession, no tracked session }`() { + // When + testedScope.handleEvent(RumRawEvent.StopSession(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then + verifyNoInteractions(mockTimeseries) + } + // endregion // region Internal @@ -1963,7 +2012,7 @@ internal class RumSessionScopeTest { sessionSampler: Sampler = mockSessionSampler, withMockChildScope: Boolean = true, backgroundTrackingEnabled: Boolean? = null, - timeseriesFactory: TimeseriesFactory = NoOpTimeseriesFactory() + timeseriesFactory: Timeseries.Factory = NoOpTimeseriesFactory() ) { testedScope = RumSessionScope( parentScope = mockParentScope, diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt index 26cfd84023..9971e2ed1d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt @@ -54,6 +54,7 @@ import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.monitor.DatadogRumMonitor.Companion.OPERATION_ERROR_INVALID_NAME import com.datadog.android.rum.internal.monitor.DatadogRumMonitor.Companion.OPERATION_ERROR_INVALID_OPERATION_KEY import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter +import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesFactory import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -300,7 +301,8 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumSessionScopeStartupManagerFactory = mock(), - insightsCollector = mockInsightsCollector + insightsCollector = mockInsightsCollector, + timeseriesFactory = NoOpTimeseriesFactory() ) testedMonitor.rootScope = mockApplicationScope } @@ -332,7 +334,8 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumSessionScopeStartupManagerFactory = mock(), - insightsCollector = mockInsightsCollector + insightsCollector = mockInsightsCollector, + timeseriesFactory = NoOpTimeseriesFactory() ) // When @@ -407,7 +410,8 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumSessionScopeStartupManagerFactory = mock(), - insightsCollector = mockInsightsCollector + insightsCollector = mockInsightsCollector, + timeseriesFactory = NoOpTimeseriesFactory() ) testedMonitor.start() val mockCallback = mock<(String?) -> Unit>() @@ -450,7 +454,8 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumSessionScopeStartupManagerFactory = mock(), - insightsCollector = mockInsightsCollector + insightsCollector = mockInsightsCollector, + timeseriesFactory = NoOpTimeseriesFactory() ) testedMonitor.start() val mockCallback = mock<(String?) -> Unit>() @@ -1898,9 +1903,7 @@ internal class DatadogRumMonitorTest { } @Test - fun `M delegate event to rootScope W eventDropped {error}`( - @StringForgery viewId: String - ) { + fun `M delegate event to rootScope W eventDropped {error}`(@StringForgery viewId: String) { testedMonitor.eventDropped(viewId, StorageEvent.Error()) argumentCaptor { @@ -2033,7 +2036,8 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumSessionScopeStartupManagerFactory = mock(), - insightsCollector = mockInsightsCollector + insightsCollector = mockInsightsCollector, + timeseriesFactory = NoOpTimeseriesFactory() ) // When @@ -2073,7 +2077,8 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumSessionScopeStartupManagerFactory = mock(), - insightsCollector = mockInsightsCollector + insightsCollector = mockInsightsCollector, + timeseriesFactory = NoOpTimeseriesFactory() ) // When @@ -2114,7 +2119,8 @@ internal class DatadogRumMonitorTest { displayInfoProvider = mockDisplayInfoProvider, rumSessionTypeOverride = null, rumSessionScopeStartupManagerFactory = mock(), - insightsCollector = mockInsightsCollector + insightsCollector = mockInsightsCollector, + timeseriesFactory = NoOpTimeseriesFactory() ) whenever(mockExecutorService.isShutdown).thenReturn(true) @@ -2350,7 +2356,8 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumSessionScopeStartupManagerFactory = mock(), - insightsCollector = mockInsightsCollector + insightsCollector = mockInsightsCollector, + timeseriesFactory = NoOpTimeseriesFactory() ) testedMonitor.startView(key, name, attributes) // When diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeFactoryTest.kt similarity index 100% rename from features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt rename to features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeFactoryTest.kt diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/ConfigurationRumForgeryFactory.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/ConfigurationRumForgeryFactory.kt index 390c3cc0a7..89d752a810 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/ConfigurationRumForgeryFactory.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/ConfigurationRumForgeryFactory.kt @@ -6,6 +6,7 @@ package com.datadog.android.rum.utils.forge +import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.configuration.VitalsUpdateFrequency import com.datadog.android.rum.internal.RumFeature @@ -13,6 +14,7 @@ import com.datadog.android.rum.metric.interactiontonextview.NoOpLastInteractionI import com.datadog.android.rum.metric.interactiontonextview.TimeBasedInteractionIdentifier import com.datadog.android.rum.metric.networksettled.NoOpInitialResourceIdentifier import com.datadog.android.rum.metric.networksettled.TimeBasedInitialResourceIdentifier +import com.datadog.android.rum.timeseries.TimeseriesConfiguration import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy import com.datadog.android.rum.tracking.FragmentViewTrackingStrategy import com.datadog.android.rum.tracking.MixedViewTrackingStrategy @@ -21,6 +23,7 @@ import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeryFactory import org.mockito.kotlin.mock +@OptIn(ExperimentalRumApi::class) internal class ConfigurationRumForgeryFactory : ForgeryFactory { override fun getForgery(forge: Forge): RumFeature.Configuration { @@ -74,7 +77,8 @@ internal class ConfigurationRumForgeryFactory : collectAccessibility = forge.aBool(), disableJankStats = false, insightsCollector = mock(), - appStartupActivityPredicate = mock() + appStartupActivityPredicate = mock(), + timeseriesConfiguration = forge.aNullable { TimeseriesConfiguration.Builder().build() } ) } } diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt index 2a69f4fd3f..ca6f166459 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt @@ -40,6 +40,7 @@ import com.datadog.android.rum.RumConfiguration import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.configuration.RumNetworkInstrumentationConfiguration import com.datadog.android.rum.resource.ResourceHeadersExtractor +import com.datadog.android.rum.timeseries.TimeseriesConfiguration import com.datadog.android.rum.tracking.NavigationViewTrackingStrategy import com.datadog.android.sample.account.AccountFragment import com.datadog.android.sample.data.db.LocalDataSource @@ -427,6 +428,7 @@ class SampleApplication : Application() { .trackAnonymousUser(true) .enableComposeActionTracking() .collectAccessibility(true) + .setTimeseriesConfiguration(TimeseriesConfiguration.Builder().build()) .build() } From af31b3d9178b9862e0aed6a888a3ee5f20128481 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Thu, 11 Jun 2026 17:40:02 +0100 Subject: [PATCH 11/32] RUM-13949 Fix missing brace, opt-in annotation, and test filename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Restore closing `}` for `createDataWriter` accidentally dropped when `createTimeseriesCollectingFactory` was inserted after it - Add `@OptIn(ExperimentalRumApi::class)` to `createTimeseries` test helper to satisfy -Werror on the experimental API usage - Rename `RumSessionScopeFactoryTest.kt` → `RumSessionScopeTimeseriesFactoryTest.kt` to match contained class (ktlint filename rule) Ref: RUM-13949 --- .../main/kotlin/com/datadog/android/rum/internal/RumFeature.kt | 1 + .../kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt | 2 ++ ...peFactoryTest.kt => RumSessionScopeTimeseriesFactoryTest.kt} | 0 3 files changed, 3 insertions(+) rename features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/{RumSessionScopeFactoryTest.kt => RumSessionScopeTimeseriesFactoryTest.kt} (100%) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index 637a819d23..33b75eba00 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -437,6 +437,7 @@ internal class RumFeature( eventMetaSerializer = RumEventMetaSerializer(), sdkCore = sdkCore ) + } internal fun createTimeseriesCollectingFactory( configuration: TimeseriesConfiguration, diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt index fc651cef82..cf49e83270 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt @@ -26,6 +26,7 @@ import com.datadog.android.event.MapperSerializer import com.datadog.android.internal.flags.RumFlagEvaluationMessage import com.datadog.android.internal.system.BuildSdkVersionProvider import com.datadog.android.internal.telemetry.InternalTelemetryEvent +import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumSessionType @@ -1802,6 +1803,7 @@ internal class RumFeatureTest { // Builds a timeseries collector for the given total device RAM (in bytes), bypassing the // ActivityManager read so the skip/collect/log behavior can be exercised deterministically. + @OptIn(ExperimentalRumApi::class) private fun createTimeseries(totalRamBytes: Long, sessionId: String): Timeseries = testedFeature .createTimeseriesCollectingFactory(TimeseriesConfiguration.Builder().build(), totalRamBytes) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeFactoryTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt similarity index 100% rename from features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeFactoryTest.kt rename to features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt From bdd5f21899925e6d496a543195a3cd75c49a9e7e Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Tue, 12 May 2026 02:02:41 +0100 Subject: [PATCH 12/32] RUM-13949 Add end-to-end timeseries verification tests with CSV fixtures Adds a self-contained end-to-end test suite for the timeseries pipeline using CSV-driven input fixtures and golden JSON files, without any real Android API dependencies. Includes `CSVReader`, `CsvTimeseries` (a synchronous, executor-free test double mirroring `RumSessionScopeTimeseriesFactory`), `TimeseriesEndToEndTest`, and the accompanying CSV input and expected JSON fixture files. Provides regression coverage that the full memory and CPU batch outputs match the expected wire format exactly, catching any serialization regressions independently of mock-heavy unit tests. Fixtures use a fixed 10-sample / 2-batch scenario. Numeric comparisons use a small relative tolerance for floating-point memory_percent values. --- .../rum/internal/timeseries/csv/CSVReader.kt | 65 +++++ .../internal/timeseries/csv/CsvTimeseries.kt | 104 ++++++++ .../timeseries/csv/TimeseriesEndToEndTest.kt | 226 ++++++++++++++++++ .../timeseries/expected_cpu_batch1.json | 22 ++ .../timeseries/expected_cpu_batch2.json | 22 ++ .../timeseries/expected_memory_batch1.json | 22 ++ .../timeseries/expected_memory_batch2.json | 22 ++ .../fixtures/timeseries/input_memory_cpu.csv | 21 ++ 8 files changed, 504 insertions(+) create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CSVReader.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt create mode 100644 features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json create mode 100644 features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json create mode 100644 features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json create mode 100644 features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json create mode 100644 features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/input_memory_cpu.csv diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CSVReader.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CSVReader.kt new file mode 100644 index 0000000000..2944e34a7e --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CSVReader.kt @@ -0,0 +1,65 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.csv + +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.internal.timeseries.DataPoint +import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader + +/** + * A test-only [DataPointsReader] that replays pre-recorded samples from a CSV string. + * + * The CSV is expected to contain a header line plus rows in the form: + * `timestamp_nanos,metric_name,value`. Rows whose `metric_name` does not match the [metric] + * filter are skipped, as are blank or malformed rows. + */ +internal class CSVReader( + csvContent: String, + metric: String, + timeProvider: TimeProvider, + override val intervalMs: Long = DEFAULT_INTERVAL_MS +) : DataPointsReader(timeProvider) { + + private val samples: List> = parseCsv(csvContent, metric) + private var index: Int = 0 + + fun hasNext(): Boolean = index < samples.size + + /** Total number of samples available for the metric. */ + val size: Int get() = samples.size + + override fun read(): DataPoint { + check(hasNext()) { "CSVReader exhausted" } + return samples[index++] + } + + override fun readValue(): Double = error("CSVReader overrides read(); readValue() must not be called") + + private fun parseCsv(content: String, metric: String): List> { + return content.lineSequence() + .drop(1) + .mapNotNull { line -> + val trimmed = line.trim() + if (trimmed.isEmpty()) return@mapNotNull null + val cols = trimmed.split(",") + if (cols.size != COLUMN_COUNT) return@mapNotNull null + if (cols[METRIC_INDEX] != metric) return@mapNotNull null + val ts = cols[TIMESTAMP_INDEX].toLongOrNull() ?: return@mapNotNull null + val v = cols[VALUE_INDEX].toDoubleOrNull() ?: return@mapNotNull null + DataPoint(timestampNs = ts, value = v) + } + .toList() + } + + companion object { + const val DEFAULT_INTERVAL_MS: Long = 1_000L + private const val COLUMN_COUNT = 3 + private const val TIMESTAMP_INDEX = 0 + private const val METRIC_INDEX = 1 + private const val VALUE_INDEX = 2 + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt new file mode 100644 index 0000000000..987fb01ba6 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt @@ -0,0 +1,104 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.csv + +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.domain.scope.RumViewType +import com.datadog.android.rum.internal.timeseries.Buffer +import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.serializer.CpuEventSerializer +import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer +import com.datadog.android.rum.internal.timeseries.serializer.MemoryEventSerializer +import com.google.gson.JsonObject + +/** + * A test-only [Timeseries] that mirrors the wiring done by `RumSessionScopeTimeseriesFactory`, + * but pulls samples from CSV-backed readers instead of `VitalReaderWrapper` and drives the + * pipelines synchronously (no executor) so callers can assert on every emitted JSON. + */ +internal class CsvTimeseries(private val pipelines: List, JsonSerializer>>) : + Timeseries { + + private val emitted = mutableListOf() + + /** All JSON events produced by the pipelines so far. */ + val captured: List get() = emitted.toList() + + /** + * Walks each (reader, buffer, serializer) triple synchronously: samples until the reader is + * exhausted, flushing whenever the buffer is full, then emitting any final remainder. + */ + override fun onSessionStart() { + for ((reader, buffer, serializer) in pipelines) { + while (reader.hasNext()) { + buffer.add(reader.read()) + if (buffer.isFull()) { + buffer.drain().takeIf { it.isNotEmpty() }?.let(serializer::serialize)?.let(emitted::add) + } + } + buffer.drain().takeIf { it.isNotEmpty() }?.let(serializer::serialize)?.let(emitted::add) + } + } + + override fun onSessionStop() = Unit + + override fun onViewTypeUpdate(viewType: RumViewType) = Unit + + companion object { + + /** + * Builds a [CsvTimeseries] with the same shape as `RumSessionScopeTimeseriesFactory`: + * a memory pipeline followed by a CPU pipeline, each backed by [CSVReader] and the + * production [MemoryEventSerializer] / [CpuEventSerializer]. + */ + fun create( + csvContent: String, + sessionId: String, + applicationId: String, + sessionType: RumSessionType, + totalRamBytes: Long, + bufferSize: Int, + timeProvider: TimeProvider, + useDeltaCompression: Boolean = false + ): CsvTimeseries { + val memoryReader = CSVReader(csvContent, METRIC_MEMORY_USAGE, timeProvider) + val cpuReader = CSVReader(csvContent, METRIC_CPU_USAGE, timeProvider) + + return CsvTimeseries( + pipelines = listOf( + Triple( + memoryReader, + Buffer(bufferSize), + MemoryEventSerializer( + sessionId = sessionId, + applicationId = applicationId, + sessionType = sessionType, + totalRamBytes = totalRamBytes, + timeProvider = timeProvider, + useDeltaCompression = useDeltaCompression + ) + ), + Triple( + cpuReader, + Buffer(bufferSize), + CpuEventSerializer( + sessionId = sessionId, + applicationId = applicationId, + sessionType = sessionType, + timeProvider = timeProvider, + useDeltaCompression = useDeltaCompression + ) + ) + ) + ) + } + + const val METRIC_MEMORY_USAGE: String = "memory_usage" + const val METRIC_CPU_USAGE: String = "cpu_usage" + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt new file mode 100644 index 0000000000..58f01d2087 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt @@ -0,0 +1,226 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.csv + +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.utils.forge.Configurator +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.google.gson.JsonPrimitive +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness +import java.math.BigDecimal + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(Configurator::class) +internal class TimeseriesEndToEndTest { + + @Mock + lateinit var mockTimeProvider: TimeProvider + + private lateinit var csvContent: String + private lateinit var emitted: List + + @BeforeEach + fun `set up`() { + whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn FIXED_DATE_MS + + csvContent = loadResource("fixtures/timeseries/input_memory_cpu.csv") + val testedTimeseries = CsvTimeseries.create( + csvContent = csvContent, + sessionId = SESSION_ID, + applicationId = APPLICATION_ID, + sessionType = RumSessionType.USER, + totalRamBytes = TOTAL_RAM_BYTES, + bufferSize = BATCH_SIZE, + timeProvider = mockTimeProvider + ) + testedTimeseries.onSessionStart() + testedTimeseries.onSessionStop() + emitted = testedTimeseries.captured + } + + @Test + fun `M emit two memory and two cpu batches W full CSV is fed { batchSize=5, samples=10 }`() { + // Then + val memoryEvents = emitted.filter { metricNameOf(it) == NAME_MEMORY } + val cpuEvents = emitted.filter { metricNameOf(it) == NAME_CPU } + + assertThat(memoryEvents).hasSize(2) + assertThat(cpuEvents).hasSize(2) + } + + @Test + fun `M match expected fixture W memory batch 1`() { + val expected = loadFixture("fixtures/timeseries/expected_memory_batch1.json") + val actual = emitted.filter { metricNameOf(it) == NAME_MEMORY }[0] + assertJsonEquals(expected, maskTimeseriesId(actual)) + } + + @Test + fun `M match expected fixture W memory batch 2`() { + val expected = loadFixture("fixtures/timeseries/expected_memory_batch2.json") + val actual = emitted.filter { metricNameOf(it) == NAME_MEMORY }[1] + assertJsonEquals(expected, maskTimeseriesId(actual)) + } + + @Test + fun `M match expected fixture W cpu batch 1`() { + val expected = loadFixture("fixtures/timeseries/expected_cpu_batch1.json") + val actual = emitted.filter { metricNameOf(it) == NAME_CPU }[0] + assertJsonEquals(expected, maskTimeseriesId(actual)) + } + + @Test + fun `M match expected fixture W cpu batch 2`() { + val expected = loadFixture("fixtures/timeseries/expected_cpu_batch2.json") + val actual = emitted.filter { metricNameOf(it) == NAME_CPU }[1] + assertJsonEquals(expected, maskTimeseriesId(actual)) + } + + @Test + fun `M emit valid timeseries id W any batch`() { + // Each emitted event must carry a non-blank UUID; randomness is not asserted. + for (event in emitted) { + val id = event.getAsJsonObject("timeseries").get("id").asString + assertThat(java.util.UUID.fromString(id)).isNotNull + } + } + + @Test + fun `M produce monotonic start lt or eq to end W any batch`() { + for (event in emitted) { + val ts = event.getAsJsonObject("timeseries") + assertThat(ts.get("start").asLong).isLessThanOrEqualTo(ts.get("end").asLong) + } + } + + private fun metricNameOf(event: JsonObject): String = event.getAsJsonObject("timeseries").get("name").asString + + private fun maskTimeseriesId(actual: JsonObject): JsonObject { + val copy = actual.deepCopy() + copy.getAsJsonObject("timeseries").addProperty("id", MASKED_UUID) + return copy + } + + private fun loadFixture(path: String): JsonElement = JsonParser.parseString(loadResource(path)) + + private fun loadResource(path: String): String { + val stream = this::class.java.classLoader?.getResourceAsStream(path) + ?: error("Resource not found: $path") + return stream.bufferedReader().use { it.readText() } + } + + /** + * Structural JSON comparison that ignores key insertion order and treats numerically + * equal primitives as equal regardless of literal representation + * (e.g. `31233300` vs `3.12333E7`). + */ + private fun assertJsonEquals(expected: JsonElement, actual: JsonElement, path: String = "$") { + when { + expected is JsonObject && actual is JsonObject -> { + val expectedKeys: Set = expected.keySet() + val actualKeys: Set = actual.keySet() + assertThat(actualKeys.toList()) + .describedAs("Keys at %s", path) + .containsExactlyInAnyOrderElementsOf(expectedKeys.toList()) + for (key in expectedKeys) { + assertJsonEquals(expected.get(key), actual.get(key), "$path.$key") + } + } + expected is JsonArray && actual is JsonArray -> { + assertThat(actual.size()) + .describedAs("Array size at %s", path) + .isEqualTo(expected.size()) + for (i in 0 until expected.size()) { + assertJsonEquals(expected.get(i), actual.get(i), "$path[$i]") + } + } + expected is JsonPrimitive && actual is JsonPrimitive -> { + assertPrimitiveEquals(expected, actual, path) + } + expected is JsonNull && actual is JsonNull -> Unit + else -> assertThat(actual) + .describedAs("Type mismatch at %s", path) + .isEqualTo(expected) + } + } + + private fun assertPrimitiveEquals(expected: JsonPrimitive, actual: JsonPrimitive, path: String) { + when { + expected.isString || actual.isString -> + assertThat(actual.asString).describedAs("String at %s", path).isEqualTo(expected.asString) + expected.isBoolean || actual.isBoolean -> + assertThat(actual.asBoolean).describedAs("Boolean at %s", path).isEqualTo(expected.asBoolean) + else -> assertNumericPrimitivesEqual(expected, actual, path) + } + } + + // Numeric: integer-typed values must match exactly; floating-point values are compared + // with a small relative tolerance to absorb 64-bit FP rounding (e.g. computed + // memory_percent of 3.1233299999999997 vs expected 3.12333). + private fun assertNumericPrimitivesEqual(expected: JsonPrimitive, actual: JsonPrimitive, path: String) { + val expectedBd = expected.asString.toBigDecimalOrNull() + ?: error("Expected numeric value at $path, got: ${expected.asString}") + val actualBd = actual.asString.toBigDecimalOrNull() + ?: error("Actual numeric value at $path is not a number: ${actual.asString}") + + val isFloat = expected.asString.contains('.') || + expected.asString.contains('e', ignoreCase = true) || + actual.asString.contains('.') || + actual.asString.contains('e', ignoreCase = true) + + if (isFloat) { + val expectedDouble = expectedBd.toDouble() + val actualDouble = actualBd.toDouble() + assertThat(actualDouble) + .describedAs("Double at %s: expected=%s, actual=%s", path, expectedDouble, actualDouble) + .isCloseTo(expectedDouble, org.assertj.core.data.Offset.offset(FP_TOLERANCE)) + } else { + assertThat(actualBd.compareTo(expectedBd)) + .describedAs("Integer at %s: expected=%s, actual=%s", path, expectedBd, actualBd) + .isZero + } + } + + private fun String.toBigDecimalOrNull(): BigDecimal? = try { + BigDecimal(this) + } catch (e: NumberFormatException) { + null + } + + companion object { + private const val SESSION_ID: String = "fake-session-id" + private const val APPLICATION_ID: String = "fake-app-id" + private const val FIXED_DATE_MS: Long = 1_700_000_001_000L + private const val TOTAL_RAM_BYTES: Long = 1_000_000_000L + private const val BATCH_SIZE: Int = 5 + private const val NAME_MEMORY: String = "memory" + private const val NAME_CPU: String = "cpu" + private const val MASKED_UUID: String = "00000000-0000-0000-0000-000000000000" + private const val FP_TOLERANCE: Double = 1e-9 + } +} diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json new file mode 100644 index 0000000000..1cfd4a0256 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json @@ -0,0 +1,22 @@ +{ + "type": "timeseries", + "_dd": {"format_version": 2}, + "application": {"id": "fake-app-id"}, + "session": {"id": "fake-session-id", "type": "user"}, + "source": "android", + "date": 1700000001000, + "timeseries": { + "id": "00000000-0000-0000-0000-000000000000", + "name": "cpu", + "schema": "object", + "start": 1700000001000000000, + "end": 1700000005000000000, + "data": [ + {"timestamp": 1700000001000000000, "data_point": {"cpu_usage": 12.5}}, + {"timestamp": 1700000002000000000, "data_point": {"cpu_usage": 14.2}}, + {"timestamp": 1700000003000000000, "data_point": {"cpu_usage": 11.8}}, + {"timestamp": 1700000004000000000, "data_point": {"cpu_usage": 16.3}}, + {"timestamp": 1700000005000000000, "data_point": {"cpu_usage": 13.7}} + ] + } +} diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json new file mode 100644 index 0000000000..42365a43e2 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json @@ -0,0 +1,22 @@ +{ + "type": "timeseries", + "_dd": {"format_version": 2}, + "application": {"id": "fake-app-id"}, + "session": {"id": "fake-session-id", "type": "user"}, + "source": "android", + "date": 1700000001000, + "timeseries": { + "id": "00000000-0000-0000-0000-000000000000", + "name": "cpu", + "schema": "object", + "start": 1700000006000000000, + "end": 1700000010000000000, + "data": [ + {"timestamp": 1700000006000000000, "data_point": {"cpu_usage": 18.1}}, + {"timestamp": 1700000007000000000, "data_point": {"cpu_usage": 22.4}}, + {"timestamp": 1700000008000000000, "data_point": {"cpu_usage": 19.6}}, + {"timestamp": 1700000009000000000, "data_point": {"cpu_usage": 15.9}}, + {"timestamp": 1700000010000000000, "data_point": {"cpu_usage": 13.2}} + ] + } +} diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json new file mode 100644 index 0000000000..109a72dbf7 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json @@ -0,0 +1,22 @@ +{ + "type": "timeseries", + "_dd": {"format_version": 2}, + "application": {"id": "fake-app-id"}, + "session": {"id": "fake-session-id", "type": "user"}, + "source": "android", + "date": 1700000001000, + "timeseries": { + "id": "00000000-0000-0000-0000-000000000000", + "name": "memory", + "schema": "object", + "start": 1700000001000000000, + "end": 1700000005000000000, + "data": [ + {"timestamp": 1700000001000000000, "data_point": {"memory_max": 31233300, "memory_percent": 3.12333}}, + {"timestamp": 1700000002000000000, "data_point": {"memory_max": 31245500, "memory_percent": 3.12455}}, + {"timestamp": 1700000003000000000, "data_point": {"memory_max": 31300800, "memory_percent": 3.13008}}, + {"timestamp": 1700000004000000000, "data_point": {"memory_max": 31289100, "memory_percent": 3.12891}}, + {"timestamp": 1700000005000000000, "data_point": {"memory_max": 31350000, "memory_percent": 3.135}} + ] + } +} diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json new file mode 100644 index 0000000000..856be26246 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json @@ -0,0 +1,22 @@ +{ + "type": "timeseries", + "_dd": {"format_version": 2}, + "application": {"id": "fake-app-id"}, + "session": {"id": "fake-session-id", "type": "user"}, + "source": "android", + "date": 1700000001000, + "timeseries": { + "id": "00000000-0000-0000-0000-000000000000", + "name": "memory", + "schema": "object", + "start": 1700000006000000000, + "end": 1700000010000000000, + "data": [ + {"timestamp": 1700000006000000000, "data_point": {"memory_max": 31420200, "memory_percent": 3.14202}}, + {"timestamp": 1700000007000000000, "data_point": {"memory_max": 31510400, "memory_percent": 3.15104}}, + {"timestamp": 1700000008000000000, "data_point": {"memory_max": 31498700, "memory_percent": 3.14987}}, + {"timestamp": 1700000009000000000, "data_point": {"memory_max": 31550300, "memory_percent": 3.15503}}, + {"timestamp": 1700000010000000000, "data_point": {"memory_max": 31600100, "memory_percent": 3.16001}} + ] + } +} diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/input_memory_cpu.csv b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/input_memory_cpu.csv new file mode 100644 index 0000000000..992dead619 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/input_memory_cpu.csv @@ -0,0 +1,21 @@ +timestamp,metric,value +1700000001000000000,memory_usage,31233300 +1700000001000000000,cpu_usage,12.5 +1700000002000000000,memory_usage,31245500 +1700000002000000000,cpu_usage,14.2 +1700000003000000000,memory_usage,31300800 +1700000003000000000,cpu_usage,11.8 +1700000004000000000,memory_usage,31289100 +1700000004000000000,cpu_usage,16.3 +1700000005000000000,memory_usage,31350000 +1700000005000000000,cpu_usage,13.7 +1700000006000000000,memory_usage,31420200 +1700000006000000000,cpu_usage,18.1 +1700000007000000000,memory_usage,31510400 +1700000007000000000,cpu_usage,22.4 +1700000008000000000,memory_usage,31498700 +1700000008000000000,cpu_usage,19.6 +1700000009000000000,memory_usage,31550300 +1700000009000000000,cpu_usage,15.9 +1700000010000000000,memory_usage,31600100 +1700000010000000000,cpu_usage,13.2 From 6e1f3b8a34036ec8c6b4a50a8d7795c6e8b86e67 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Thu, 28 May 2026 13:47:40 +0100 Subject: [PATCH 13/32] RUM-13949: Add count field to e2e timeseries fixture expectations Add missing "count" field to all four expected JSON fixtures so they match the serializer output that now includes a sample count per batch. Ref: RUM-13949 --- .../test/resources/fixtures/timeseries/expected_cpu_batch1.json | 1 + .../test/resources/fixtures/timeseries/expected_cpu_batch2.json | 1 + .../resources/fixtures/timeseries/expected_memory_batch1.json | 1 + .../resources/fixtures/timeseries/expected_memory_batch2.json | 1 + 4 files changed, 4 insertions(+) diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json index 1cfd4a0256..3325d650ed 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json @@ -11,6 +11,7 @@ "schema": "object", "start": 1700000001000000000, "end": 1700000005000000000, + "count": 5, "data": [ {"timestamp": 1700000001000000000, "data_point": {"cpu_usage": 12.5}}, {"timestamp": 1700000002000000000, "data_point": {"cpu_usage": 14.2}}, diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json index 42365a43e2..c422d701eb 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json @@ -11,6 +11,7 @@ "schema": "object", "start": 1700000006000000000, "end": 1700000010000000000, + "count": 5, "data": [ {"timestamp": 1700000006000000000, "data_point": {"cpu_usage": 18.1}}, {"timestamp": 1700000007000000000, "data_point": {"cpu_usage": 22.4}}, diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json index 109a72dbf7..2801516bf6 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json @@ -11,6 +11,7 @@ "schema": "object", "start": 1700000001000000000, "end": 1700000005000000000, + "count": 5, "data": [ {"timestamp": 1700000001000000000, "data_point": {"memory_max": 31233300, "memory_percent": 3.12333}}, {"timestamp": 1700000002000000000, "data_point": {"memory_max": 31245500, "memory_percent": 3.12455}}, diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json index 856be26246..5428f37412 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json @@ -11,6 +11,7 @@ "schema": "object", "start": 1700000006000000000, "end": 1700000010000000000, + "count": 5, "data": [ {"timestamp": 1700000006000000000, "data_point": {"memory_max": 31420200, "memory_percent": 3.14202}}, {"timestamp": 1700000007000000000, "data_point": {"memory_max": 31510400, "memory_percent": 3.15104}}, From 05df854b93e848ae1a974b4160e907a07bea772c Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Thu, 18 Jun 2026 16:09:31 +0100 Subject: [PATCH 14/32] RUM-16325: Share knownTypes with OneOfPrimitiveOptionGenerator The oneOf-primitive option generator kept its own private knownTypes set that was never cleared between files, unlike every other generator which shares (and clears per file) the FileGenerator set. As a result, when two top-level models contained a structurally identical oneOf of primitives (e.g. the Path = oneOf[string,integer] from the shared _graphql schema, referenced by both ResourceEvent and ErrorEvent), the second model's option subclasses extended the first model's nested sealed class (ErrorEvent.String : ResourceEvent.Path()), which fails to compile. Wire the shared knownTypes set into OneOfPrimitiveOptionGenerator so each model resolves its own nested type. Add a dedicated regression test that generates two models sharing an identical oneOf-primitive and asserts the second does not leak a reference to the first's nested type. --- .../jsonschema/generator/FileGenerator.kt | 2 +- .../OneOfPrimitiveOptionGenerator.kt | 5 +- .../plugin/jsonschema/JsonSchemaReaderTest.kt | 5 +- .../plugin/jsonschema/ModelValidationTest.kt | 5 +- .../plugin/jsonschema/TestDefinitions.kt | 44 ++++ .../jsonschema/generator/FileGeneratorTest.kt | 8 +- .../CrossFileOneOfPrimitiveAForgeryFactory.kt | 24 ++ .../CrossFileOneOfPrimitiveBForgeryFactory.kt | 24 ++ .../example/forgery/ForgeryConfiguration.kt | 3 + .../OneOfPrimitiveRefForgeryFactory.kt | 29 +++ .../example/model/CrossFileOneOfPrimitiveA.kt | 208 +++++++++++++++++ .../example/model/CrossFileOneOfPrimitiveB.kt | 208 +++++++++++++++++ .../com/example/model/OneOfPrimitiveRef.kt | 216 ++++++++++++++++++ .../input/cross_file_one_of_primitive.json | 15 ++ .../input/cross_file_one_of_primitive_a.json | 9 + .../input/cross_file_one_of_primitive_b.json | 9 + .../resources/input/one_of_primitive_ref.json | 26 +++ 17 files changed, 834 insertions(+), 6 deletions(-) create mode 100644 buildSrc/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveAForgeryFactory.kt create mode 100644 buildSrc/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveBForgeryFactory.kt create mode 100644 buildSrc/src/test/kotlin/com/example/forgery/OneOfPrimitiveRefForgeryFactory.kt create mode 100644 buildSrc/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveA.kt create mode 100644 buildSrc/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveB.kt create mode 100644 buildSrc/src/test/kotlin/com/example/model/OneOfPrimitiveRef.kt create mode 100644 buildSrc/src/test/resources/input/cross_file_one_of_primitive.json create mode 100644 buildSrc/src/test/resources/input/cross_file_one_of_primitive_a.json create mode 100644 buildSrc/src/test/resources/input/cross_file_one_of_primitive_b.json create mode 100644 buildSrc/src/test/resources/input/one_of_primitive_ref.json diff --git a/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/FileGenerator.kt b/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/FileGenerator.kt index ea0acd3ea5..8b3afcde93 100644 --- a/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/FileGenerator.kt +++ b/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/FileGenerator.kt @@ -23,7 +23,7 @@ class FileGenerator( private val classGenerator = ClassGenerator(packageName, knownTypes) private val enumGenerator = EnumClassGenerator(packageName, knownTypes) - private val oneOfPrimitiveOptionGenerator = OneOfPrimitiveOptionGenerator(packageName) + private val oneOfPrimitiveOptionGenerator = OneOfPrimitiveOptionGenerator(packageName, knownTypes) private val multiClassGenerator = MultiClassGenerator( classGenerator = classGenerator, diff --git a/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/OneOfPrimitiveOptionGenerator.kt b/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/OneOfPrimitiveOptionGenerator.kt index bb8980192a..761dbdd875 100644 --- a/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/OneOfPrimitiveOptionGenerator.kt +++ b/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/generator/OneOfPrimitiveOptionGenerator.kt @@ -21,10 +21,11 @@ import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.jvm.throws class OneOfPrimitiveOptionGenerator( - packageName: String + packageName: String, + knownTypes: MutableSet = mutableSetOf() ) : TypeSpecGenerator( packageName, - mutableSetOf() + knownTypes ) { override fun generate( definition: TypeDefinition.Primitive, diff --git a/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/JsonSchemaReaderTest.kt b/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/JsonSchemaReaderTest.kt index 76d871b1d5..7993c1c68c 100644 --- a/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/JsonSchemaReaderTest.kt +++ b/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/JsonSchemaReaderTest.kt @@ -89,7 +89,10 @@ class JsonSchemaReaderTest( arrayOf("one_of_nested", WeirdCombo), arrayOf("required_for_other_all_of", RequiredForOtherAllOf), arrayOf("path_array_with_integer", PathArrayWithInteger), - arrayOf("path_array_with_number", PathArrayWithNumber) + arrayOf("path_array_with_number", PathArrayWithNumber), + arrayOf("one_of_primitive_ref", OneOfPrimitiveRef), + arrayOf("cross_file_one_of_primitive_a", CrossFileOneOfPrimitiveA), + arrayOf("cross_file_one_of_primitive_b", CrossFileOneOfPrimitiveB) ) } } diff --git a/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/ModelValidationTest.kt b/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/ModelValidationTest.kt index 7e73acd3cb..7cd24f2cc4 100644 --- a/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/ModelValidationTest.kt +++ b/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/ModelValidationTest.kt @@ -209,7 +209,10 @@ class ModelValidationTest( arrayOf("sets", OutputInfo("Video")), arrayOf("one_of_nested", OutputInfo("WeirdCombo")), arrayOf("path_array_with_integer", OutputInfo("PathArrayWithInteger")), - arrayOf("path_array_with_number", OutputInfo("PathArrayWithNumber")) + arrayOf("path_array_with_number", OutputInfo("PathArrayWithNumber")), + arrayOf("one_of_primitive_ref", OutputInfo("OneOfPrimitiveRef")), + arrayOf("cross_file_one_of_primitive_a", OutputInfo("CrossFileOneOfPrimitiveA")), + arrayOf("cross_file_one_of_primitive_b", OutputInfo("CrossFileOneOfPrimitiveB")) ) } } diff --git a/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/TestDefinitions.kt b/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/TestDefinitions.kt index f25595aa6c..ba24c5e765 100644 --- a/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/TestDefinitions.kt +++ b/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/TestDefinitions.kt @@ -1112,3 +1112,47 @@ val PathArrayWithNumber = TypeDefinition.Class( ), required = setOf("path") ) + +val OneOfPrimitivePath = TypeDefinition.OneOfClass( + name = "Path", + options = listOf( + TypeDefinition.OneOfClass.Option.Primitive( + primitive = TypeDefinition.Primitive( + type = JsonPrimitiveType.STRING, + description = "string element" + ) + ), + TypeDefinition.OneOfClass.Option.Primitive( + primitive = TypeDefinition.Primitive( + type = JsonPrimitiveType.INTEGER, + description = "integer element" + ) + ) + ), + description = "This is a definition of a path" +) + +val OneOfPrimitiveRef = TypeDefinition.Class( + name = "OneOfPrimitiveRef", + properties = listOf( + TypeProperty("from", OneOfPrimitivePath), + TypeProperty("to", OneOfPrimitivePath) + ), + required = setOf("from") +) + +val CrossFileOneOfPrimitiveA = TypeDefinition.Class( + name = "CrossFileOneOfPrimitiveA", + properties = listOf( + TypeProperty("path", OneOfPrimitivePath) + ), + required = setOf("path") +) + +val CrossFileOneOfPrimitiveB = TypeDefinition.Class( + name = "CrossFileOneOfPrimitiveB", + properties = listOf( + TypeProperty("path", OneOfPrimitivePath) + ), + required = setOf("path") +) diff --git a/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/generator/FileGeneratorTest.kt b/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/generator/FileGeneratorTest.kt index 37daddd0eb..d4abb9d8de 100644 --- a/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/generator/FileGeneratorTest.kt +++ b/buildSrc/src/test/kotlin/com/datadog/gradle/plugin/jsonschema/generator/FileGeneratorTest.kt @@ -13,6 +13,8 @@ import com.datadog.gradle.plugin.jsonschema.Book import com.datadog.gradle.plugin.jsonschema.Comment import com.datadog.gradle.plugin.jsonschema.Company import com.datadog.gradle.plugin.jsonschema.Conflict +import com.datadog.gradle.plugin.jsonschema.CrossFileOneOfPrimitiveA +import com.datadog.gradle.plugin.jsonschema.CrossFileOneOfPrimitiveB import com.datadog.gradle.plugin.jsonschema.Customer import com.datadog.gradle.plugin.jsonschema.DateTime import com.datadog.gradle.plugin.jsonschema.Delivery @@ -23,6 +25,7 @@ import com.datadog.gradle.plugin.jsonschema.Jacket import com.datadog.gradle.plugin.jsonschema.Location import com.datadog.gradle.plugin.jsonschema.Message import com.datadog.gradle.plugin.jsonschema.NoOpLogger +import com.datadog.gradle.plugin.jsonschema.OneOfPrimitiveRef import com.datadog.gradle.plugin.jsonschema.Opus import com.datadog.gradle.plugin.jsonschema.Order import com.datadog.gradle.plugin.jsonschema.Paper @@ -132,7 +135,10 @@ class FileGeneratorTest( arrayOf(Video, "Video"), arrayOf(WeirdCombo, "WeirdCombo"), arrayOf(PathArrayWithInteger, "PathArrayWithInteger"), - arrayOf(PathArrayWithNumber, "PathArrayWithNumber") + arrayOf(PathArrayWithNumber, "PathArrayWithNumber"), + arrayOf(OneOfPrimitiveRef, "OneOfPrimitiveRef"), + arrayOf(CrossFileOneOfPrimitiveA, "CrossFileOneOfPrimitiveA"), + arrayOf(CrossFileOneOfPrimitiveB, "CrossFileOneOfPrimitiveB") ) } } diff --git a/buildSrc/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveAForgeryFactory.kt b/buildSrc/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveAForgeryFactory.kt new file mode 100644 index 0000000000..068d3de755 --- /dev/null +++ b/buildSrc/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveAForgeryFactory.kt @@ -0,0 +1,24 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.example.forgery + +import com.example.model.CrossFileOneOfPrimitiveA +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +internal class CrossFileOneOfPrimitiveAForgeryFactory : ForgeryFactory { + override fun getForgery(forge: Forge): CrossFileOneOfPrimitiveA { + return CrossFileOneOfPrimitiveA( + path = forge.anElementFrom( + listOf( + CrossFileOneOfPrimitiveA.Path.String(forge.aString()), + CrossFileOneOfPrimitiveA.Path.Long(forge.aLong()) + ) + ) + ) + } +} diff --git a/buildSrc/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveBForgeryFactory.kt b/buildSrc/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveBForgeryFactory.kt new file mode 100644 index 0000000000..eea6fbefe6 --- /dev/null +++ b/buildSrc/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveBForgeryFactory.kt @@ -0,0 +1,24 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.example.forgery + +import com.example.model.CrossFileOneOfPrimitiveB +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +internal class CrossFileOneOfPrimitiveBForgeryFactory : ForgeryFactory { + override fun getForgery(forge: Forge): CrossFileOneOfPrimitiveB { + return CrossFileOneOfPrimitiveB( + path = forge.anElementFrom( + listOf( + CrossFileOneOfPrimitiveB.Path.String(forge.aString()), + CrossFileOneOfPrimitiveB.Path.Long(forge.aLong()) + ) + ) + ) + } +} diff --git a/buildSrc/src/test/kotlin/com/example/forgery/ForgeryConfiguration.kt b/buildSrc/src/test/kotlin/com/example/forgery/ForgeryConfiguration.kt index 69edf16b76..8578cf0ce6 100644 --- a/buildSrc/src/test/kotlin/com/example/forgery/ForgeryConfiguration.kt +++ b/buildSrc/src/test/kotlin/com/example/forgery/ForgeryConfiguration.kt @@ -40,5 +40,8 @@ internal class ForgeryConfiguration : ForgeConfigurator { forge.addFactory(WeirdComboForgeryFactory()) forge.addFactory(PathArrayWithIntegerForgeryFactory()) forge.addFactory(PathArrayWithNumberForgeryFactory()) + forge.addFactory(OneOfPrimitiveRefForgeryFactory()) + forge.addFactory(CrossFileOneOfPrimitiveAForgeryFactory()) + forge.addFactory(CrossFileOneOfPrimitiveBForgeryFactory()) } } diff --git a/buildSrc/src/test/kotlin/com/example/forgery/OneOfPrimitiveRefForgeryFactory.kt b/buildSrc/src/test/kotlin/com/example/forgery/OneOfPrimitiveRefForgeryFactory.kt new file mode 100644 index 0000000000..000a5e8575 --- /dev/null +++ b/buildSrc/src/test/kotlin/com/example/forgery/OneOfPrimitiveRefForgeryFactory.kt @@ -0,0 +1,29 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.example.forgery + +import com.example.model.OneOfPrimitiveRef +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +internal class OneOfPrimitiveRefForgeryFactory : ForgeryFactory { + override fun getForgery(forge: Forge): OneOfPrimitiveRef { + return OneOfPrimitiveRef( + from = forge.aPath(), + to = forge.aNullable { aPath() } + ) + } + + private fun Forge.aPath(): OneOfPrimitiveRef.Path { + return anElementFrom( + listOf( + OneOfPrimitiveRef.Path.String(aString()), + OneOfPrimitiveRef.Path.Long(aLong()) + ) + ) + } +} diff --git a/buildSrc/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveA.kt b/buildSrc/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveA.kt new file mode 100644 index 0000000000..cd669c459c --- /dev/null +++ b/buildSrc/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveA.kt @@ -0,0 +1,208 @@ +package com.example.model + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonParser +import com.google.gson.JsonPrimitive +import java.lang.IllegalStateException +import java.lang.NullPointerException +import java.lang.NumberFormatException +import java.lang.UnsupportedOperationException +import kotlin.String +import kotlin.jvm.JvmStatic +import kotlin.jvm.Throws + +/** + * @param path This is a definition of a path + */ +public data class CrossFileOneOfPrimitiveA( + public val path: Path, +) { + public fun toJson(): JsonElement { + val json = JsonObject() + json.add("path", path.toJson()) + return json + } + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: String): CrossFileOneOfPrimitiveA { + try { + val jsonObject = JsonParser.parseString(jsonString).asJsonObject + return fromJsonObject(jsonObject) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveA", + e + ) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonObject(jsonObject: JsonObject): CrossFileOneOfPrimitiveA { + try { + val path = jsonObject.get("path").let { + Path.fromJsonElement(it) + } + return CrossFileOneOfPrimitiveA(path) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveA", + e + ) + } catch (e: NumberFormatException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveA", + e + ) + } catch (e: NullPointerException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveA", + e + ) + } + } + } + + /** + * This is a definition of a path + */ + public sealed class Path { + public abstract fun toJson(): JsonElement + + /** + * string element + */ + public data class String( + public val item: kotlin.String, + ) : Path() { + override fun toJson(): JsonElement = JsonPrimitive(item) + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): String { + val jsonElement = JsonParser.parseString(jsonString) + try { + return fromJsonPrimitive(jsonElement.asJsonPrimitive) + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type String", e) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonPrimitive(jsonPrimitive: JsonPrimitive): String { + try { + if (jsonPrimitive.isString) { + return String(jsonPrimitive.asString) + } else { + throw JsonParseException("Can't convert jsonPrimitive to String") + } + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type String", e) + } catch (e: UnsupportedOperationException) { + throw JsonParseException("Unable to parse json into type String", e) + } + } + } + } + + /** + * integer element + */ + public data class Long( + public val item: kotlin.Long, + ) : Path() { + override fun toJson(): JsonElement = JsonPrimitive(item) + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): Long { + val jsonElement = JsonParser.parseString(jsonString) + try { + return fromJsonPrimitive(jsonElement.asJsonPrimitive) + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type Long", e) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonPrimitive(jsonPrimitive: JsonPrimitive): Long { + try { + if (jsonPrimitive.isNumber) { + return Long(jsonPrimitive.asLong) + } else { + throw JsonParseException("Can't convert jsonPrimitive to Long") + } + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type Long", e) + } catch (e: NumberFormatException) { + throw JsonParseException("Unable to parse json into type Long", e) + } catch (e: UnsupportedOperationException) { + throw JsonParseException("Unable to parse json into type Long", e) + } + } + } + } + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): Path { + try { + val jsonElement = JsonParser.parseString(jsonString) + return fromJsonElement(jsonElement) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into one of type Path", + e + ) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonElement(jsonElement: JsonElement): Path { + val errors = mutableListOf() + val asString = try { + if (jsonElement is JsonPrimitive) { + String.fromJsonPrimitive(jsonElement) + } else { + throw JsonParseException("Unable to parse json into type " + + "kotlin.String") + } + } catch (e: JsonParseException) { + errors.add(e) + null + } + val asLong = try { + if (jsonElement is JsonPrimitive) { + Long.fromJsonPrimitive(jsonElement) + } else { + throw JsonParseException("Unable to parse json into type " + + "kotlin.Long") + } + } catch (e: JsonParseException) { + errors.add(e) + null + } + val result = arrayOf( + asString, + asLong, + ).firstOrNull { it != null } + if (result == null) { + val message = "Unable to parse json into one of type \n" + "Path\n" + + errors.joinToString("\n") { it.message.toString() } + throw JsonParseException(message) + } + return result + } + } + } +} diff --git a/buildSrc/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveB.kt b/buildSrc/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveB.kt new file mode 100644 index 0000000000..650f48256f --- /dev/null +++ b/buildSrc/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveB.kt @@ -0,0 +1,208 @@ +package com.example.model + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonParser +import com.google.gson.JsonPrimitive +import java.lang.IllegalStateException +import java.lang.NullPointerException +import java.lang.NumberFormatException +import java.lang.UnsupportedOperationException +import kotlin.String +import kotlin.jvm.JvmStatic +import kotlin.jvm.Throws + +/** + * @param path This is a definition of a path + */ +public data class CrossFileOneOfPrimitiveB( + public val path: Path, +) { + public fun toJson(): JsonElement { + val json = JsonObject() + json.add("path", path.toJson()) + return json + } + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: String): CrossFileOneOfPrimitiveB { + try { + val jsonObject = JsonParser.parseString(jsonString).asJsonObject + return fromJsonObject(jsonObject) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveB", + e + ) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonObject(jsonObject: JsonObject): CrossFileOneOfPrimitiveB { + try { + val path = jsonObject.get("path").let { + Path.fromJsonElement(it) + } + return CrossFileOneOfPrimitiveB(path) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveB", + e + ) + } catch (e: NumberFormatException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveB", + e + ) + } catch (e: NullPointerException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveB", + e + ) + } + } + } + + /** + * This is a definition of a path + */ + public sealed class Path { + public abstract fun toJson(): JsonElement + + /** + * string element + */ + public data class String( + public val item: kotlin.String, + ) : Path() { + override fun toJson(): JsonElement = JsonPrimitive(item) + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): String { + val jsonElement = JsonParser.parseString(jsonString) + try { + return fromJsonPrimitive(jsonElement.asJsonPrimitive) + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type String", e) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonPrimitive(jsonPrimitive: JsonPrimitive): String { + try { + if (jsonPrimitive.isString) { + return String(jsonPrimitive.asString) + } else { + throw JsonParseException("Can't convert jsonPrimitive to String") + } + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type String", e) + } catch (e: UnsupportedOperationException) { + throw JsonParseException("Unable to parse json into type String", e) + } + } + } + } + + /** + * integer element + */ + public data class Long( + public val item: kotlin.Long, + ) : Path() { + override fun toJson(): JsonElement = JsonPrimitive(item) + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): Long { + val jsonElement = JsonParser.parseString(jsonString) + try { + return fromJsonPrimitive(jsonElement.asJsonPrimitive) + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type Long", e) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonPrimitive(jsonPrimitive: JsonPrimitive): Long { + try { + if (jsonPrimitive.isNumber) { + return Long(jsonPrimitive.asLong) + } else { + throw JsonParseException("Can't convert jsonPrimitive to Long") + } + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type Long", e) + } catch (e: NumberFormatException) { + throw JsonParseException("Unable to parse json into type Long", e) + } catch (e: UnsupportedOperationException) { + throw JsonParseException("Unable to parse json into type Long", e) + } + } + } + } + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): Path { + try { + val jsonElement = JsonParser.parseString(jsonString) + return fromJsonElement(jsonElement) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into one of type Path", + e + ) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonElement(jsonElement: JsonElement): Path { + val errors = mutableListOf() + val asString = try { + if (jsonElement is JsonPrimitive) { + String.fromJsonPrimitive(jsonElement) + } else { + throw JsonParseException("Unable to parse json into type " + + "kotlin.String") + } + } catch (e: JsonParseException) { + errors.add(e) + null + } + val asLong = try { + if (jsonElement is JsonPrimitive) { + Long.fromJsonPrimitive(jsonElement) + } else { + throw JsonParseException("Unable to parse json into type " + + "kotlin.Long") + } + } catch (e: JsonParseException) { + errors.add(e) + null + } + val result = arrayOf( + asString, + asLong, + ).firstOrNull { it != null } + if (result == null) { + val message = "Unable to parse json into one of type \n" + "Path\n" + + errors.joinToString("\n") { it.message.toString() } + throw JsonParseException(message) + } + return result + } + } + } +} diff --git a/buildSrc/src/test/kotlin/com/example/model/OneOfPrimitiveRef.kt b/buildSrc/src/test/kotlin/com/example/model/OneOfPrimitiveRef.kt new file mode 100644 index 0000000000..c3b8b3754d --- /dev/null +++ b/buildSrc/src/test/kotlin/com/example/model/OneOfPrimitiveRef.kt @@ -0,0 +1,216 @@ +package com.example.model + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonParser +import com.google.gson.JsonPrimitive +import java.lang.IllegalStateException +import java.lang.NullPointerException +import java.lang.NumberFormatException +import java.lang.UnsupportedOperationException +import kotlin.String +import kotlin.jvm.JvmStatic +import kotlin.jvm.Throws + +/** + * @param from This is a definition of a path + * @param to This is a definition of a path + */ +public data class OneOfPrimitiveRef( + public val from: Path, + public val to: Path? = null, +) { + public fun toJson(): JsonElement { + val json = JsonObject() + json.add("from", from.toJson()) + to?.let { toNonNull -> + json.add("to", toNonNull.toJson()) + } + return json + } + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: String): OneOfPrimitiveRef { + try { + val jsonObject = JsonParser.parseString(jsonString).asJsonObject + return fromJsonObject(jsonObject) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into type OneOfPrimitiveRef", + e + ) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonObject(jsonObject: JsonObject): OneOfPrimitiveRef { + try { + val from = jsonObject.get("from").let { + Path.fromJsonElement(it) + } + val to = jsonObject.get("to")?.let { + Path.fromJsonElement(it) + } + return OneOfPrimitiveRef(from, to) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into type OneOfPrimitiveRef", + e + ) + } catch (e: NumberFormatException) { + throw JsonParseException( + "Unable to parse json into type OneOfPrimitiveRef", + e + ) + } catch (e: NullPointerException) { + throw JsonParseException( + "Unable to parse json into type OneOfPrimitiveRef", + e + ) + } + } + } + + /** + * This is a definition of a path + */ + public sealed class Path { + public abstract fun toJson(): JsonElement + + /** + * string element + */ + public data class String( + public val item: kotlin.String, + ) : Path() { + override fun toJson(): JsonElement = JsonPrimitive(item) + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): String { + val jsonElement = JsonParser.parseString(jsonString) + try { + return fromJsonPrimitive(jsonElement.asJsonPrimitive) + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type String", e) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonPrimitive(jsonPrimitive: JsonPrimitive): String { + try { + if (jsonPrimitive.isString) { + return String(jsonPrimitive.asString) + } else { + throw JsonParseException("Can't convert jsonPrimitive to String") + } + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type String", e) + } catch (e: UnsupportedOperationException) { + throw JsonParseException("Unable to parse json into type String", e) + } + } + } + } + + /** + * integer element + */ + public data class Long( + public val item: kotlin.Long, + ) : Path() { + override fun toJson(): JsonElement = JsonPrimitive(item) + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): Long { + val jsonElement = JsonParser.parseString(jsonString) + try { + return fromJsonPrimitive(jsonElement.asJsonPrimitive) + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type Long", e) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonPrimitive(jsonPrimitive: JsonPrimitive): Long { + try { + if (jsonPrimitive.isNumber) { + return Long(jsonPrimitive.asLong) + } else { + throw JsonParseException("Can't convert jsonPrimitive to Long") + } + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type Long", e) + } catch (e: NumberFormatException) { + throw JsonParseException("Unable to parse json into type Long", e) + } catch (e: UnsupportedOperationException) { + throw JsonParseException("Unable to parse json into type Long", e) + } + } + } + } + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): Path { + try { + val jsonElement = JsonParser.parseString(jsonString) + return fromJsonElement(jsonElement) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into one of type Path", + e + ) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonElement(jsonElement: JsonElement): Path { + val errors = mutableListOf() + val asString = try { + if (jsonElement is JsonPrimitive) { + String.fromJsonPrimitive(jsonElement) + } else { + throw JsonParseException("Unable to parse json into type " + + "kotlin.String") + } + } catch (e: JsonParseException) { + errors.add(e) + null + } + val asLong = try { + if (jsonElement is JsonPrimitive) { + Long.fromJsonPrimitive(jsonElement) + } else { + throw JsonParseException("Unable to parse json into type " + + "kotlin.Long") + } + } catch (e: JsonParseException) { + errors.add(e) + null + } + val result = arrayOf( + asString, + asLong, + ).firstOrNull { it != null } + if (result == null) { + val message = "Unable to parse json into one of type \n" + "Path\n" + + errors.joinToString("\n") { it.message.toString() } + throw JsonParseException(message) + } + return result + } + } + } +} diff --git a/buildSrc/src/test/resources/input/cross_file_one_of_primitive.json b/buildSrc/src/test/resources/input/cross_file_one_of_primitive.json new file mode 100644 index 0000000000..7744920ad8 --- /dev/null +++ b/buildSrc/src/test/resources/input/cross_file_one_of_primitive.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Path", + "description": "This is a definition of a path", + "oneOf": [ + { + "type": "string", + "description": "string element" + }, + { + "type": "integer", + "description": "integer element" + } + ] +} diff --git a/buildSrc/src/test/resources/input/cross_file_one_of_primitive_a.json b/buildSrc/src/test/resources/input/cross_file_one_of_primitive_a.json new file mode 100644 index 0000000000..d1765a0547 --- /dev/null +++ b/buildSrc/src/test/resources/input/cross_file_one_of_primitive_a.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CrossFileOneOfPrimitiveA", + "type": "object", + "required": ["path"], + "properties": { + "path": { "$ref": "cross_file_one_of_primitive.json" } + } +} diff --git a/buildSrc/src/test/resources/input/cross_file_one_of_primitive_b.json b/buildSrc/src/test/resources/input/cross_file_one_of_primitive_b.json new file mode 100644 index 0000000000..13f6141fe8 --- /dev/null +++ b/buildSrc/src/test/resources/input/cross_file_one_of_primitive_b.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CrossFileOneOfPrimitiveB", + "type": "object", + "required": ["path"], + "properties": { + "path": { "$ref": "cross_file_one_of_primitive.json" } + } +} diff --git a/buildSrc/src/test/resources/input/one_of_primitive_ref.json b/buildSrc/src/test/resources/input/one_of_primitive_ref.json new file mode 100644 index 0000000000..839a44364e --- /dev/null +++ b/buildSrc/src/test/resources/input/one_of_primitive_ref.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OneOfPrimitiveRef", + "type": "object", + "properties": { + "from": { "$ref": "#/definitions/path" }, + "to": { "$ref": "#/definitions/path" } + }, + "required": ["from"], + "definitions": { + "path": { + "title": "Path", + "description": "This is a definition of a path", + "oneOf": [ + { + "type": "string", + "description": "string element" + }, + { + "type": "integer", + "description": "integer element" + } + ] + } + } +} From 147653a5978226979a494d35c8127d93254529bb Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Thu, 18 Jun 2026 16:09:31 +0100 Subject: [PATCH 15/32] RUM-16325: Updating schema Regenerate RUM models and API surface from the updated JSON schemas: new graphql/stream/trace/transition/vital-duration/view_update schemas and updated action/error/resource/view definitions. --- features/dd-sdk-android-rum/api/dd-sdk-android-rum.api | 2 +- .../src/main/json/rum/timeseries-memory-schema.json | 4 ++-- .../timeseries/serializer/MemoryEventSerializer.kt | 6 +++--- .../timeseries/serializer/MemoryEventSerializerTest.kt | 6 +++--- .../fixtures/timeseries/expected_memory_batch1.json | 10 +++++----- .../fixtures/timeseries/expected_memory_batch2.json | 10 +++++----- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index cfc1e35ad1..61fb3e0dab 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -5204,7 +5204,7 @@ public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; - public final fun getMemoryMax ()Ljava/lang/Number; + public final fun getMemoryFootprint ()Ljava/lang/Number; public final fun getMemoryPercent ()Ljava/lang/Number; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; diff --git a/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json index 7d79da7026..fb240441f2 100644 --- a/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json +++ b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json @@ -140,9 +140,9 @@ "data_point": { "type": "object", "description": "Memory measurements for this sample", - "required": ["memory_max", "memory_percent"], + "required": ["memory_footprint", "memory_percent"], "properties": { - "memory_max": { + "memory_footprint": { "type": "number", "description": "Physical memory footprint of the process in bytes", "readOnly": true diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt index fc8ca5463b..5a9a17ba03 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt @@ -81,8 +81,8 @@ internal class MemoryEventSerializer( val scale = 10.0.pow(precision.toDouble()).toLong() val ts = data.mapToDeltaCompressed { it.timestamp } - val memoryMaxArray = data.mapToDeltaCompressed { - roundToLongSafely(it.dataPoint.memoryMax.toDouble(), scale = scale) + val memoryFootprintArray = data.mapToDeltaCompressed { + roundToLongSafely(it.dataPoint.memoryFootprint.toDouble(), scale = scale) } val memoryPercentArray = data.mapToDeltaCompressed { @@ -93,7 +93,7 @@ internal class MemoryEventSerializer( addProperty("precision", precision) addProperty("resolution", RESOLUTION_NS) add("ts", ts) - add("memory_max", memoryMaxArray) + add("memory_footprint", memoryFootprintArray) add("memory_percent", memoryPercentArray) } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt index f932abfb11..de4648725b 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt @@ -143,7 +143,7 @@ internal class MemoryEventSerializerTest { val parsed = TimeseriesMemoryEvent.fromJsonObject(json) assertThat(parsed.timeseries.data).hasSize(2) val first = parsed.timeseries.data.first().dataPoint - assertThat(first.memoryMax.toDouble()).isCloseTo(fakeMemory, Offset.offset(MEMORY_OFFSET)) + assertThat(first.memoryFootprint.toDouble()).isCloseTo(fakeMemory, Offset.offset(MEMORY_OFFSET)) assertThat(first.memoryPercent.toDouble()) .isCloseTo(fakeMemory / fakeTotalRamBytes * PERCENT_FACTOR, Offset.offset(MEMORY_OFFSET)) } @@ -196,7 +196,7 @@ internal class MemoryEventSerializerTest { assertThat(tsArray[1].asLong).isEqualTo(fakeTimestampStep) assertThat(tsArray[2].asLong).isEqualTo(fakeTimestampStep) - val maxArr = data.get(KEY_MEMORY_MAX).asJsonArray + val maxArr = data.get(KEY_MEMORY_FOOTPRINT).asJsonArray val pctArr = data.get(KEY_MEMORY_PERCENT).asJsonArray val scaledMax = listOf(fakeMem1, fakeMem2, fakeMem3).map { (it * fakeScale).roundToLong() } val scaledPct = listOf(fakeMem1, fakeMem2, fakeMem3) @@ -243,7 +243,7 @@ internal class MemoryEventSerializerTest { private const val KEY_PRECISION: String = "precision" private const val KEY_RESOLUTION: String = "resolution" private const val KEY_TS: String = "ts" - private const val KEY_MEMORY_MAX: String = "memory_max" + private const val KEY_MEMORY_FOOTPRINT: String = "memory_footprint" private const val KEY_MEMORY_PERCENT: String = "memory_percent" // JSON values diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json index 2801516bf6..f84fa93243 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json @@ -13,11 +13,11 @@ "end": 1700000005000000000, "count": 5, "data": [ - {"timestamp": 1700000001000000000, "data_point": {"memory_max": 31233300, "memory_percent": 3.12333}}, - {"timestamp": 1700000002000000000, "data_point": {"memory_max": 31245500, "memory_percent": 3.12455}}, - {"timestamp": 1700000003000000000, "data_point": {"memory_max": 31300800, "memory_percent": 3.13008}}, - {"timestamp": 1700000004000000000, "data_point": {"memory_max": 31289100, "memory_percent": 3.12891}}, - {"timestamp": 1700000005000000000, "data_point": {"memory_max": 31350000, "memory_percent": 3.135}} + {"timestamp": 1700000001000000000, "data_point": {"memory_footprint": 31233300, "memory_percent": 3.12333}}, + {"timestamp": 1700000002000000000, "data_point": {"memory_footprint": 31245500, "memory_percent": 3.12455}}, + {"timestamp": 1700000003000000000, "data_point": {"memory_footprint": 31300800, "memory_percent": 3.13008}}, + {"timestamp": 1700000004000000000, "data_point": {"memory_footprint": 31289100, "memory_percent": 3.12891}}, + {"timestamp": 1700000005000000000, "data_point": {"memory_footprint": 31350000, "memory_percent": 3.135}} ] } } diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json index 5428f37412..24a2bce6a8 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json @@ -13,11 +13,11 @@ "end": 1700000010000000000, "count": 5, "data": [ - {"timestamp": 1700000006000000000, "data_point": {"memory_max": 31420200, "memory_percent": 3.14202}}, - {"timestamp": 1700000007000000000, "data_point": {"memory_max": 31510400, "memory_percent": 3.15104}}, - {"timestamp": 1700000008000000000, "data_point": {"memory_max": 31498700, "memory_percent": 3.14987}}, - {"timestamp": 1700000009000000000, "data_point": {"memory_max": 31550300, "memory_percent": 3.15503}}, - {"timestamp": 1700000010000000000, "data_point": {"memory_max": 31600100, "memory_percent": 3.16001}} + {"timestamp": 1700000006000000000, "data_point": {"memory_footprint": 31420200, "memory_percent": 3.14202}}, + {"timestamp": 1700000007000000000, "data_point": {"memory_footprint": 31510400, "memory_percent": 3.15104}}, + {"timestamp": 1700000008000000000, "data_point": {"memory_footprint": 31498700, "memory_percent": 3.14987}}, + {"timestamp": 1700000009000000000, "data_point": {"memory_footprint": 31550300, "memory_percent": 3.15503}}, + {"timestamp": 1700000010000000000, "data_point": {"memory_footprint": 31600100, "memory_percent": 3.16001}} ] } } From 34c8284275d8af6c2bae003e89d32431b53583a2 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Fri, 19 Jun 2026 14:28:55 +0100 Subject: [PATCH 16/32] RUM-16325: Post-review fixes --- .../internal/DefaultInsightsCollectorTest.kt | 1 - features/dd-sdk-android-rum/api/apiSurface | 5 +- .../api/dd-sdk-android-rum.api | 5 +- .../datadog/android/rum/RumConfiguration.kt | 4 +- .../android/rum/internal/RumFeature.kt | 17 +- .../internal/domain/scope/RumSessionScope.kt | 18 +- .../rum/internal/monitor/DatadogRumMonitor.kt | 7 +- .../serializer/CpuEventSerializer.kt | 4 +- .../serializer/MemoryEventSerializer.kt | 4 +- .../rum/timeseries/TimeseriesConfiguration.kt | 12 +- .../android/rum/internal/RumFeatureTest.kt | 5 +- .../domain/scope/RumSessionScopeTest.kt | 198 ++++++++++++++++-- .../timeseries/csv/TimeseriesEndToEndTest.kt | 20 +- .../timeseries/TimeseriesConfigurationTest.kt | 2 +- .../android/sample/SampleApplication.kt | 2 +- 15 files changed, 252 insertions(+), 52 deletions(-) diff --git a/features/dd-sdk-android-rum-debug-widget/src/test/kotlin/com/datadog/android/insights/internal/DefaultInsightsCollectorTest.kt b/features/dd-sdk-android-rum-debug-widget/src/test/kotlin/com/datadog/android/insights/internal/DefaultInsightsCollectorTest.kt index 3eada26eb3..5ee6c057b9 100644 --- a/features/dd-sdk-android-rum-debug-widget/src/test/kotlin/com/datadog/android/insights/internal/DefaultInsightsCollectorTest.kt +++ b/features/dd-sdk-android-rum-debug-widget/src/test/kotlin/com/datadog/android/insights/internal/DefaultInsightsCollectorTest.kt @@ -33,7 +33,6 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.quality.Strictness -@Suppress("OPT_IN_USAGE") @Extensions( ExtendWith(MockitoExtension::class), ExtendWith(ForgeExtension::class) diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index 3531d55f1a..04c8852e9e 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -309,10 +309,11 @@ class com.datadog.android.rum.timeseries.TimeseriesConfiguration class Builder fun setBufferSize(Int): Builder fun setIntervalMs(Long): Builder - fun setCollectInBackground(Boolean): Builder - fun setUseDeltaCompression(Boolean): Builder + fun collectInBackground(Boolean): Builder + fun useDeltaCompression(Boolean): Builder fun build(): TimeseriesConfiguration companion object + val DEFAULT: TimeseriesConfiguration open class com.datadog.android.rum.tracking.AcceptAllActivities : ComponentPredicate override fun accept(android.app.Activity): Boolean override fun getViewName(android.app.Activity): String? diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index 61fb3e0dab..1db79ed8f8 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -8645,13 +8645,14 @@ public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration { public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder { public fun ()V public final fun build ()Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration; + public final fun collectInBackground (Z)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; public final fun setBufferSize (I)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; - public final fun setCollectInBackground (Z)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; public final fun setIntervalMs (J)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; - public final fun setUseDeltaCompression (Z)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; + public final fun useDeltaCompression (Z)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; } public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Companion { + public final fun getDEFAULT ()Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration; } public class com/datadog/android/rum/tracking/AcceptAllActivities : com/datadog/android/rum/tracking/ComponentPredicate { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt index 9428f4969b..0146aa8773 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt @@ -415,9 +415,9 @@ data class RumConfiguration internal constructor( * Enables memory and CPU timeseries collection. * * When enabled, the SDK samples device memory (RSS) and CPU usage at - * [TimeseriesConfiguration.intervalMs] (default 1 s) and emits a batched + * [TimeseriesConfiguration.intervalMs] (default 1 second) and emits a batched * timeseries event every [TimeseriesConfiguration.bufferSize] samples (default 30). - * Collection runs on a single background thread named `datadog-timeseries`. + * Sampling runs on the shared RUM vitals background executor. * * To disable, pass null. * diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index 33b75eba00..a1b2f4db0d 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -294,7 +294,7 @@ internal class RumFeature( configuration.timeseriesConfiguration?.let { configuration -> timeseriesFactory = createTimeseriesCollectingFactory( configuration, - appContext.readTotalRamBytes(sdkCore.internalLogger) + appContext.readTotalRamBytes(sdkCore.internalLogger) ?: 0L ) } @@ -488,7 +488,8 @@ internal class RumFeature( sdkCore.internalLogger.log( InternalLogger.Level.WARN, InternalLogger.Target.USER, - { MEMORY_TIMESERIES_DISABLED_MESSAGE } + { MEMORY_TIMESERIES_DISABLED_MESSAGE }, + onlyOnce = true ) null } @@ -941,24 +942,22 @@ internal class RumFeature( internal const val FAILED_TO_ENABLE_JANK_STATS_TRACKING_MANUALLY = "Manually enabling JankStats tracking threw an exception." - @Suppress("TooGenericExceptionCaught") - internal fun Context.readTotalRamBytes(internalLogger: InternalLogger): Long = try { + internal fun Context.readTotalRamBytes(internalLogger: InternalLogger): Long? = try { getSystemServiceAs(Context.ACTIVITY_SERVICE) - // UnsafeThirdPartyFunctionCall: MemoryInfo() and getMemoryInfo() can throw - // RuntimeException on broken Android implementations; fall back to 0. ?.run { + // UnsafeThirdPartyFunctionCall: MemoryInfo() and getMemoryInfo() can throw + // RuntimeException on broken Android implementations; fall back to 0. @Suppress("UnsafeThirdPartyFunctionCall") ActivityManager.MemoryInfo().also { getMemoryInfo(it) }.totalMem } - ?: 0L - } catch (e: RuntimeException) { + } catch (@Suppress("TooGenericExceptionCaught") e: RuntimeException) { internalLogger.log( InternalLogger.Level.WARN, InternalLogger.Target.MAINTAINER, { "Failed to read total RAM via ActivityManager" }, e ) - 0L + null } private fun provideUserTrackingStrategy( diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt index d8cf0aff24..71c0526bb8 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt @@ -161,7 +161,7 @@ internal class RumSessionScope( val actualWriter = if (sessionState == State.TRACKED) writer else noOpWriter - val rumContext = activeView?.getRumContext() ?: getRumContext() + val rumContext = getActiveRumContext() when (event) { is RumRawEvent.AppStartTTIDEvent -> { @@ -205,7 +205,7 @@ internal class RumSessionScope( } } - timeseries.onViewTypeUpdate(rumContext.viewType) + timeseries.onViewTypeUpdate(getActiveRumContext().viewType) return if (isSessionComplete()) { null @@ -233,11 +233,15 @@ internal class RumSessionScope( // region Internal + private fun getActiveRumContext(): RumContext { + return activeView?.getRumContext() ?: getRumContext() + } + private fun startTimeseries() { timeseries = timeseriesFactory.create( sessionId = sessionId, applicationId = parentScope.getRumContext().applicationId, - sessionType = rumSessionTypeOverride ?: RumSessionType.USER + sessionType = getRumContext().resolveSessionType(rumSessionTypeOverride) ) timeseries.onSessionStart() } @@ -352,5 +356,13 @@ internal class RumSessionScope( internal const val RUM_SESSION_SAMPLE_RATE_BUS_MESSAGE_KEY = "sessionSampleRate" internal val DEFAULT_SESSION_INACTIVITY_NS = TimeUnit.MINUTES.toNanos(15) internal val DEFAULT_SESSION_MAX_DURATION_NS = TimeUnit.HOURS.toNanos(4) + + internal fun RumContext.resolveSessionType(rumSessionTypeOverride: RumSessionType?): RumSessionType { + return when { + rumSessionTypeOverride != null -> rumSessionTypeOverride + !syntheticsTestId.isNullOrBlank() && !syntheticsResultId.isNullOrBlank() -> RumSessionType.SYNTHETICS + else -> RumSessionType.USER + } + } } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt index 7b525fc11d..20ef673c00 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt @@ -860,10 +860,9 @@ internal class DatadogRumMonitor( * Stops the timeseries collector on the currently active session, if any. * * Must be called from [RumFeature.onStop] **before** [GlobalRumMonitor.unregister], so that - * the sampling [java.util.concurrent.ScheduledExecutorService] is shut down while the monitor - * is still reachable. Without this call, the executor keeps running after SDK teardown and a - * subsequent [com.datadog.android.Datadog.initialize] would create a second - * `datadog-timeseries` thread in the same process. + * the active session can stop sampling and flush buffered timeseries data while the monitor is + * still reachable. The shared RUM vitals executor is owned and shut down separately by + * [RumFeature.onStop]. * * Must be called **before** [RumFeature.dataWriter] is replaced with a * [com.datadog.android.rum.internal.storage.NoOpDataWriter]. [EventWriter.write] captures the diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt index fa8ae47466..26ef0122a8 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt @@ -61,10 +61,10 @@ internal class CpuEventSerializer( end = end, data = data ) - ).toJson() as? JsonObject + ).toJson().asJsonObject // - val timeseriesJson = json?.getAsJsonObject("timeseries") + val timeseriesJson = json.getAsJsonObject("timeseries") if (deltaEncoded != null) { timeseriesJson?.remove("data") timeseriesJson?.add("data", deltaEncoded) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt index 5a9a17ba03..82f39f3a16 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt @@ -63,9 +63,9 @@ internal class MemoryEventSerializer( end = end, data = data ) - ).toJson() as? JsonObject + ).toJson().asJsonObject // - val timeseriesJson = json?.getAsJsonObject("timeseries") + val timeseriesJson = json.getAsJsonObject("timeseries") if (deltaEncoded != null) { timeseriesJson?.remove("data") timeseriesJson?.add("data", deltaEncoded) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt index 0b864abd64..15f66e296f 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt @@ -7,6 +7,9 @@ package com.datadog.android.rum.timeseries import androidx.annotation.IntRange import com.datadog.android.rum.ExperimentalRumApi +import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.DEFAULT_BUFFER_SIZE +import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.DEFAULT_INTERVAL_MS +import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.MIN_INTERVAL_MS /** * Configuration for memory and CPU timeseries collection. @@ -57,7 +60,7 @@ class TimeseriesConfiguration internal constructor( * Sets whether to keep sampling timeseries when the app is in background. * Defaults to `false`. */ - fun setCollectInBackground(collectInBackground: Boolean): Builder = apply { + fun collectInBackground(collectInBackground: Boolean): Builder = apply { this.collectInBackground = collectInBackground } @@ -65,7 +68,7 @@ class TimeseriesConfiguration internal constructor( * Sets whether to encode each sample as a delta from the previous value instead of an * absolute value. Reduces payload size when values change slowly. Defaults to `false`. */ - fun setUseDeltaCompression(useDeltaCompression: Boolean): Builder = apply { + fun useDeltaCompression(useDeltaCompression: Boolean): Builder = apply { this.useDeltaCompression = useDeltaCompression } @@ -79,6 +82,11 @@ class TimeseriesConfiguration internal constructor( } companion object { + + /** Default [TimeseriesConfiguration] built with all default settings. */ + @ExperimentalRumApi + val DEFAULT: TimeseriesConfiguration = Builder().build() + /** Default number of samples to accumulate before emitting a timeseries event. */ internal const val DEFAULT_BUFFER_SIZE: Int = 30 diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt index cf49e83270..25384bf354 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt @@ -885,7 +885,8 @@ internal class RumFeatureTest { mockInternalLogger.verifyLog( InternalLogger.Level.WARN, InternalLogger.Target.USER, - MEMORY_TIMESERIES_DISABLED_MESSAGE + MEMORY_TIMESERIES_DISABLED_MESSAGE, + onlyOnce = true ) } @@ -915,9 +916,9 @@ internal class RumFeatureTest { } @Test + @OptIn(ExperimentalRumApi::class) fun `M initialize vital executor W initialize { frequency = NEVER and timeseries enabled }`() { // Given - @Suppress("OPT_IN_USAGE") fakeConfiguration = fakeConfiguration.copy( vitalsMonitorUpdateFrequency = VitalsUpdateFrequency.NEVER, slowFramesConfiguration = null, diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt index c8ebf94fe0..c7c73adc13 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt @@ -52,6 +52,7 @@ import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.FloatForgery import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -376,20 +377,25 @@ internal class RumSessionScopeTest { assertThat(context.isSessionActive).isFalse } - fun `M return scope from handleEvent W stopped { with active child scopes }`() { + @Test + fun `M return session scope from handleEvent W stopped { with active child scopes }`() { // Given whenever( mockChildScope.handleEvent( any(), - fakeDatadogContext, - mockEventWriteScope, - mockWriter + eq(fakeDatadogContext), + eq(mockEventWriteScope), + eq(mockWriter) ) ) doReturn mockChildScope // When - val result = - testedScope.handleEvent(RumRawEvent.StopSession(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val result = testedScope.handleEvent( + RumRawEvent.StopSession(), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then assertThat(result).isSameAs(testedScope) @@ -1761,7 +1767,7 @@ internal class RumSessionScopeTest { @Test fun `M start timeseries via factory W renewSession { keepSession = true }`(forge: Forge) { // Given - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + fakeParentContext = fakeParentContext.copy(syntheticsTestId = null, syntheticsResultId = null) initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) // When @@ -1781,11 +1787,158 @@ internal class RumSessionScopeTest { verify(mockTimeseries).onSessionStart() } + @Test + fun `M pass USER session type to factory W renewSession { no synthetics, no override }`(forge: Forge) { + // Given + fakeParentContext = fakeParentContext.copy(syntheticsTestId = null, syntheticsResultId = null) + fakeRumSessionType = null + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + + // When + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then + verify(mockTimeseriesFactory).create( + fakeParentContext.applicationId, + testedScope.sessionId, + RumSessionType.USER + ) + } + + @Test + fun `M pass SYNTHETICS session type to factory W renewSession { synthetics IDs in context, no override }`( + forge: Forge, + @StringForgery(regex = "[a-z]{8}") fakeTestId: String, + @StringForgery(regex = "[a-z]{8}") fakeResultId: String + ) { + // Given + fakeParentContext = fakeParentContext.copy(syntheticsTestId = fakeTestId, syntheticsResultId = fakeResultId) + fakeRumSessionType = null + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + + // When + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then + verify(mockTimeseriesFactory).create( + fakeParentContext.applicationId, + testedScope.sessionId, + RumSessionType.SYNTHETICS + ) + } + + @Test + fun `M pass override session type to factory W renewSession { synthetics IDs in context, override set }`( + forge: Forge, + @StringForgery(regex = "[a-z]{8}") fakeTestId: String, + @StringForgery(regex = "[a-z]{8}") fakeResultId: String + ) { + // Given + fakeParentContext = fakeParentContext.copy(syntheticsTestId = fakeTestId, syntheticsResultId = fakeResultId) + val expectedSessionType = forge.aValueFrom(RumSessionType::class.java) + fakeRumSessionType = expectedSessionType + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + + // When + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then + verify(mockTimeseriesFactory).create( + fakeParentContext.applicationId, + testedScope.sessionId, + expectedSessionType + ) + } + + @Test + fun `M pass override session type to factory W renewSession { no synthetics, override set }`( + forge: Forge + ) { + // Given + fakeParentContext = fakeParentContext.copy(syntheticsTestId = null, syntheticsResultId = null) + val expectedSessionType = forge.aValueFrom(RumSessionType::class.java) + fakeRumSessionType = expectedSessionType + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + + // When + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then + verify(mockTimeseriesFactory).create(any(), any(), eq(expectedSessionType)) + } + + @Test + fun `M pass USER session type to factory W renewSession { testId null, no override }`( + forge: Forge, + @StringForgery(regex = "[a-z]{8}") fakeResultId: String + ) { + // Given + fakeParentContext = fakeParentContext.copy(syntheticsTestId = null, syntheticsResultId = fakeResultId) + fakeRumSessionType = null + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + + // When + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then + verify(mockTimeseriesFactory).create(any(), any(), eq(RumSessionType.USER)) + } + + @Test + fun `M pass USER session type to factory W renewSession { resultId null, no override }`( + forge: Forge, + @StringForgery(regex = "[a-z]{8}") fakeTestId: String + ) { + // Given + fakeParentContext = fakeParentContext.copy(syntheticsTestId = fakeTestId, syntheticsResultId = null) + fakeRumSessionType = null + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + + // When + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then + verify(mockTimeseriesFactory).create(any(), any(), eq(RumSessionType.USER)) + } + + @Test + fun `M pass USER session type to factory W renewSession { testId blank, no override }`( + forge: Forge, + @StringForgery(regex = "[a-z]{8}") fakeResultId: String + ) { + // Given + fakeParentContext = fakeParentContext.copy(syntheticsTestId = " ", syntheticsResultId = fakeResultId) + fakeRumSessionType = null + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + + // When + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then + verify(mockTimeseriesFactory).create(any(), any(), eq(RumSessionType.USER)) + } + + @Test + fun `M pass USER session type to factory W renewSession { resultId blank, no override }`( + forge: Forge, + @StringForgery(regex = "[a-z]{8}") fakeTestId: String + ) { + // Given + fakeParentContext = fakeParentContext.copy(syntheticsTestId = fakeTestId, syntheticsResultId = " ") + fakeRumSessionType = null + initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + + // When + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then + verify(mockTimeseriesFactory).create(any(), any(), eq(RumSessionType.USER)) + } + @Test fun `M not start timeseries W renewSession { keepSession = false }`(forge: Forge) { // Given whenever(mockSessionSampler.sample(any())).thenReturn(false) - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) // When @@ -1804,7 +1957,6 @@ internal class RumSessionScopeTest { @Test fun `M stop previous timeseries W renewSession { another tracked session }`(forge: Forge) { // Given - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) testedScope.handleEvent( forge.startViewEvent(), @@ -1831,7 +1983,6 @@ internal class RumSessionScopeTest { @Test fun `M stop timeseries W handleEvent { StopSession }`(forge: Forge) { // Given - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) testedScope.handleEvent( forge.startViewEvent(), @@ -1870,7 +2021,6 @@ internal class RumSessionScopeTest { @Test fun `M notify timeseries of view type W handleEvent`(forge: Forge) { // Given - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) testedScope.handleEvent( forge.startViewEvent(), @@ -1891,10 +2041,30 @@ internal class RumSessionScopeTest { verify(mockTimeseries, times(2)).onViewTypeUpdate(any()) } + @Test + fun `M pass foreground view type W onViewTypeUpdate { first StartView creates view }`(forge: Forge) { + // Given + initializeTestedScope( + withMockChildScope = false, + timeseriesFactory = mockTimeseriesFactory + ) + + // When + testedScope.handleEvent( + forge.startViewEvent(), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // Then + verify(mockTimeseries).onViewTypeUpdate(RumViewType.FOREGROUND) + verify(mockTimeseries, never()).onViewTypeUpdate(RumViewType.NONE) + } + @Test fun `M pass NONE W onViewTypeUpdate { no active view }`(forge: Forge) { // Given — activeView is null by default (mockChildScope.activeView not stubbed) - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) @@ -1912,7 +2082,6 @@ internal class RumSessionScopeTest { val fakeRumContext = forge.getForgery().copy(viewType = RumViewType.FOREGROUND) whenever(mockChildScope.activeView) doReturn mockViewScope whenever(mockViewScope.getRumContext()) doReturn fakeRumContext - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) @@ -1930,7 +2099,6 @@ internal class RumSessionScopeTest { val fakeRumContext = forge.getForgery().copy(viewType = RumViewType.FOREGROUND) whenever(mockChildScope.activeView) doReturn mockViewScope whenever(mockViewScope.getRumContext()) doReturn fakeRumContext - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) @@ -1947,7 +2115,6 @@ internal class RumSessionScopeTest { @Test fun `M stop previous timeseries W renewSession { ResetSession renews tracked session }`(forge: Forge) { // Given - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) val firstTimeseries = mockTimeseries @@ -1966,7 +2133,6 @@ internal class RumSessionScopeTest { @Test fun `M stop previous timeseries W renewSession { expired session then new interaction }`(forge: Forge) { // Given - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries initializeTestedScope(backgroundTrackingEnabled = false, timeseriesFactory = mockTimeseriesFactory) testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) val firstTimeseries = mockTimeseries diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt index 58f01d2087..099916c46b 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt @@ -18,6 +18,7 @@ import com.google.gson.JsonPrimitive import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.fail import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -29,6 +30,7 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.math.BigDecimal +import java.util.UUID @Extensions( ExtendWith(MockitoExtension::class), @@ -106,7 +108,7 @@ internal class TimeseriesEndToEndTest { // Each emitted event must carry a non-blank UUID; randomness is not asserted. for (event in emitted) { val id = event.getAsJsonObject("timeseries").get("id").asString - assertThat(java.util.UUID.fromString(id)).isNotNull + assertThat(UUID.fromString(id)).isNotNull } } @@ -171,9 +173,21 @@ internal class TimeseriesEndToEndTest { private fun assertPrimitiveEquals(expected: JsonPrimitive, actual: JsonPrimitive, path: String) { when { - expected.isString || actual.isString -> + expected.isString != actual.isString -> + fail( + "JSON type mismatch at $path: expected ${if (expected.isString) "string" else "number/boolean"}," + + " got ${if (actual.isString) "string" else "number/boolean"}" + + " (values: ${expected.asString} vs ${actual.asString})" + ) + expected.isBoolean != actual.isBoolean -> + fail( + "JSON type mismatch at $path: expected ${if (expected.isBoolean) "boolean" else "non-boolean"}," + + " got ${if (actual.isBoolean) "boolean" else "non-boolean"}" + + " (values: ${expected.asString} vs ${actual.asString})" + ) + expected.isString -> assertThat(actual.asString).describedAs("String at %s", path).isEqualTo(expected.asString) - expected.isBoolean || actual.isBoolean -> + expected.isBoolean -> assertThat(actual.asBoolean).describedAs("Boolean at %s", path).isEqualTo(expected.asBoolean) else -> assertNumericPrimitivesEqual(expected, actual, path) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt index 4ec7458ee2..3365add0a7 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt @@ -76,7 +76,7 @@ internal class TimeseriesConfigurationTest { @Test fun `M store collectInBackground W setCollectInBackground true`() { // When - val config = TimeseriesConfiguration.Builder().setCollectInBackground(true).build() + val config = TimeseriesConfiguration.Builder().collectInBackground(true).build() // Then assertThat(config.collectInBackground).isTrue() diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt index ca6f166459..e988c659bf 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt @@ -428,7 +428,7 @@ class SampleApplication : Application() { .trackAnonymousUser(true) .enableComposeActionTracking() .collectAccessibility(true) - .setTimeseriesConfiguration(TimeseriesConfiguration.Builder().build()) + .setTimeseriesConfiguration(TimeseriesConfiguration.DEFAULT) .build() } From cc68105b40a31a6b3389853eb69fe7ae469739c5 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Tue, 23 Jun 2026 23:14:19 +0100 Subject: [PATCH 17/32] RUM-16325: Widget support --- .../internal/DefaultInsightsCollector.kt | 4 +++ .../insights/internal/domain/TimelineEvent.kt | 19 +++++++---- .../overlay/DefaultInsightsOverlay.kt | 2 ++ .../insights/internal/widgets/TimelineView.kt | 13 ++++---- .../src/main/res/values-night/colors.xml | 1 + .../src/main/res/values/colors.xml | 3 +- .../internal/DefaultInsightsCollectorTest.kt | 23 +++++++++++-- features/dd-sdk-android-rum/api/apiSurface | 1 + .../api/dd-sdk-android-rum.api | 1 + .../android/rum/internal/RumFeature.kt | 12 ++++--- .../insights/InsightsCollector.kt | 6 ++++ .../rum/internal/timeseries/Pipeline.kt | 14 +++++++- .../android/rum/internal/RumFeatureTest.kt | 6 +++- .../rum/internal/timeseries/PipelineTest.kt | 33 +++++++++++++++++-- 14 files changed, 113 insertions(+), 25 deletions(-) diff --git a/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/DefaultInsightsCollector.kt b/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/DefaultInsightsCollector.kt index ac8e3c3db8..b89d251491 100644 --- a/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/DefaultInsightsCollector.kt +++ b/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/DefaultInsightsCollector.kt @@ -108,6 +108,10 @@ internal class DefaultInsightsCollector internal constructor( TimelineEvent.Resource(durationNs) ) + override fun onTimeseries(name: String) = registerEvent( + TimelineEvent.Timeseries(name) + ) + override fun addUpdateListener(listener: InsightsUpdatesListener) { updatesListeners += listener } diff --git a/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/domain/TimelineEvent.kt b/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/domain/TimelineEvent.kt index 747c009725..5416325396 100644 --- a/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/domain/TimelineEvent.kt +++ b/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/domain/TimelineEvent.kt @@ -7,11 +7,18 @@ package com.datadog.android.insights.internal.domain internal sealed class TimelineEvent( - val durationNs: Long + val text: String ) { - object Action : TimelineEvent(0L) - object Tick : TimelineEvent(0L) - class SlowFrame(durationNs: Long) : TimelineEvent(durationNs) - class Resource(durationNs: Long) : TimelineEvent(durationNs) - class LongTask(durationNs: Long) : TimelineEvent(durationNs) + object Action : TimelineEvent("") + object Tick : TimelineEvent("") + data class Timeseries(val name: String) : TimelineEvent(name) + class SlowFrame(durationNs: Long) : TimelineEvent(durationNs.toMsText()) + class Resource(durationNs: Long) : TimelineEvent(durationNs.toMsText()) + class LongTask(durationNs: Long) : TimelineEvent(durationNs.toMsText()) + + private companion object { + private const val NANOS_PER_MILLI = 1_000_000L + + private fun Long.toMsText(): String = (this / NANOS_PER_MILLI).toString() + } } diff --git a/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/overlay/DefaultInsightsOverlay.kt b/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/overlay/DefaultInsightsOverlay.kt index 1ad8902f4d..ac73e8cdda 100644 --- a/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/overlay/DefaultInsightsOverlay.kt +++ b/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/overlay/DefaultInsightsOverlay.kt @@ -76,6 +76,7 @@ internal class DefaultInsightsOverlay( .append(SEP) .appendColored(ACTION, color(R.color.timeline_action)).append(SEP) .appendColored(RESOURCE, color(R.color.timeline_resource)).append(SEP) + .appendColored(TIMESERIES, color(R.color.timeline_timeseries)).append(SEP) .appendColored(SLOW_FRAME, color(R.color.timeline_slow_frame)).append(SEP) .appendColored(FROZEN_FRAME, color(R.color.timeline_freeze_frame)).append(SEP) } @@ -223,6 +224,7 @@ internal class DefaultInsightsOverlay( private const val SEP = " | " private const val ACTION = "Action" private const val RESOURCE = "Resource" + private const val TIMESERIES = "Timeseries" private const val SLOW_FRAME = "Slow" private const val FROZEN_FRAME = "Frozen" } diff --git a/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/widgets/TimelineView.kt b/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/widgets/TimelineView.kt index e02937eafe..24599731d1 100644 --- a/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/widgets/TimelineView.kt +++ b/features/dd-sdk-android-rum-debug-widget/src/main/kotlin/com/datadog/android/insights/internal/widgets/TimelineView.kt @@ -16,7 +16,6 @@ import android.view.ViewOutlineProvider import androidx.core.graphics.withRotation import com.datadog.android.insights.internal.domain.TimelineEvent import com.datadog.android.insights.internal.extensions.color -import com.datadog.android.insights.internal.extensions.ms import com.datadog.android.insights.internal.extensions.px import com.datadog.android.rumdebugwidget.R @@ -48,6 +47,9 @@ internal class TimelineView @JvmOverloads constructor( private val resourceFramesPaint: Paint get() = paint.apply { color = color(R.color.timeline_resource) } + private val timeseriesFramesPaint: Paint + get() = paint.apply { color = color(R.color.timeline_timeseries) } + private val slowFramesPaint: Paint get() = paint.apply { color = color(R.color.timeline_slow_frame) } @@ -90,17 +92,16 @@ internal class TimelineView @JvmOverloads constructor( is TimelineEvent.Tick -> tickPaint is TimelineEvent.LongTask -> longTaskPaint is TimelineEvent.Action -> actionsFramesPaint + is TimelineEvent.Timeseries -> timeseriesFramesPaint is TimelineEvent.SlowFrame -> slowFramesPaint is TimelineEvent.Resource -> resourceFramesPaint } canvas.drawRect(xOffset, 0f, xOffset + barSize, height.toFloat(), paint) - if (item.durationNs.ms > 0 || (item !is TimelineEvent.Tick && item !is TimelineEvent.Action)) { + if (item.text.isNotEmpty()) { canvas.withRotation(degrees = 90f, pivotX = xOffset, pivotY = 0f) { - val text = item.durationNs.ms.toString() - @Suppress("UnsafeThirdPartyFunctionCall") // measureText() is called on a non-null string - val textWidth = durationPaint.measureText(text) - drawText(text, xOffset + height.toFloat() - textWidth, 0f, durationPaint) + val textWidth = durationPaint.measureText(item.text) + drawText(item.text, xOffset + height.toFloat() - textWidth, 0f, durationPaint) } } diff --git a/features/dd-sdk-android-rum-debug-widget/src/main/res/values-night/colors.xml b/features/dd-sdk-android-rum-debug-widget/src/main/res/values-night/colors.xml index fa1e14e83a..453f727d66 100644 --- a/features/dd-sdk-android-rum-debug-widget/src/main/res/values-night/colors.xml +++ b/features/dd-sdk-android-rum-debug-widget/src/main/res/values-night/colors.xml @@ -13,4 +13,5 @@ #FFFF00 #FF0000 #00FF00 + #66CCFF diff --git a/features/dd-sdk-android-rum-debug-widget/src/main/res/values/colors.xml b/features/dd-sdk-android-rum-debug-widget/src/main/res/values/colors.xml index eeef01372e..81606c0630 100644 --- a/features/dd-sdk-android-rum-debug-widget/src/main/res/values/colors.xml +++ b/features/dd-sdk-android-rum-debug-widget/src/main/res/values/colors.xml @@ -14,4 +14,5 @@ #B8860B #CC0000 #228B22 - \ No newline at end of file + #0077CC + diff --git a/features/dd-sdk-android-rum-debug-widget/src/test/kotlin/com/datadog/android/insights/internal/DefaultInsightsCollectorTest.kt b/features/dd-sdk-android-rum-debug-widget/src/test/kotlin/com/datadog/android/insights/internal/DefaultInsightsCollectorTest.kt index 5ee6c057b9..8a84ed2727 100644 --- a/features/dd-sdk-android-rum-debug-widget/src/test/kotlin/com/datadog/android/insights/internal/DefaultInsightsCollectorTest.kt +++ b/features/dd-sdk-android-rum-debug-widget/src/test/kotlin/com/datadog/android/insights/internal/DefaultInsightsCollectorTest.kt @@ -109,7 +109,7 @@ internal class DefaultInsightsCollectorTest { verify(mockInsightsUpdatesListener).onDataUpdated() val e = testedInsightsCollector.eventsState.single() assertThat(e).isInstanceOf(TimelineEvent.SlowFrame::class.java) - assertThat(e.durationNs).isEqualTo(fakeDurationNs) + assertThat(e.text).isEqualTo((fakeDurationNs / NANOS_PER_MILLI).toString()) } @Test @@ -121,7 +121,7 @@ internal class DefaultInsightsCollectorTest { verify(mockInsightsUpdatesListener).onDataUpdated() val e = testedInsightsCollector.eventsState.single() assertThat(e is TimelineEvent.LongTask).isTrue - assertThat(e.durationNs).isEqualTo(fakeDurationNs) + assertThat(e.text).isEqualTo((fakeDurationNs / NANOS_PER_MILLI).toString()) } @Test @@ -133,7 +133,20 @@ internal class DefaultInsightsCollectorTest { verify(mockInsightsUpdatesListener).onDataUpdated() val e = testedInsightsCollector.eventsState.single() assertThat(e is TimelineEvent.Resource).isTrue - assertThat(e.durationNs).isEqualTo(fakeDurationNs) + assertThat(e.text).isEqualTo((fakeDurationNs / NANOS_PER_MILLI).toString()) + } + + @Test + fun `M append Timeseries event W onTimeseries()`() { + // Given + val fakeName = "view.memory" + + // When + testedInsightsCollector.onTimeseries(fakeName) + + // Then + verify(mockInsightsUpdatesListener).onDataUpdated() + assertThat(testedInsightsCollector.eventsState).containsExactly(TimelineEvent.Timeseries(fakeName)) } @Test @@ -257,4 +270,8 @@ internal class DefaultInsightsCollectorTest { // Then assertThat(testedInsightsCollector.gcCallsPerSecond).isNaN } + + private companion object { + private const val NANOS_PER_MILLI = 1_000_000L + } } diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index 04c8852e9e..ef65ccb3b8 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -224,6 +224,7 @@ interface com.datadog.android.rum.internal.instrumentation.insights.InsightsColl fun onCpuVital(Double?) fun onMemoryVital(Double?) fun onSlowFrameRate(Double?) + fun onTimeseries(String) interface com.datadog.android.rum.internal.instrumentation.insights.InsightsUpdatesListener fun onDataUpdated() interface com.datadog.android.rum.internal.monitor.AdvancedNetworkRumMonitor : com.datadog.android.rum.RumMonitor diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index 1db79ed8f8..a9f64dd216 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -387,6 +387,7 @@ public abstract interface class com/datadog/android/rum/internal/instrumentation public abstract fun onNewView (Ljava/lang/String;)V public abstract fun onSlowFrame (J)V public abstract fun onSlowFrameRate (Ljava/lang/Double;)V + public abstract fun onTimeseries (Ljava/lang/String;)V public abstract fun removeUpdateListener (Lcom/datadog/android/rum/internal/instrumentation/insights/InsightsUpdatesListener;)V public abstract fun setMaxSize (I)V public abstract fun setUpdateIntervalMs (J)V diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index a1b2f4db0d..7c31e8ce61 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -294,7 +294,8 @@ internal class RumFeature( configuration.timeseriesConfiguration?.let { configuration -> timeseriesFactory = createTimeseriesCollectingFactory( configuration, - appContext.readTotalRamBytes(sdkCore.internalLogger) ?: 0L + appContext.readTotalRamBytes(sdkCore.internalLogger) ?: 0L, + insightsCollector ) } @@ -441,7 +442,8 @@ internal class RumFeature( internal fun createTimeseriesCollectingFactory( configuration: TimeseriesConfiguration, - totalRamBytes: Long + totalRamBytes: Long, + insightsCollector: InsightsCollector ): Timeseries.Factory = RumSessionScopeTimeseriesFactory( internalLogger = sdkCore.internalLogger, collectInBackground = configuration.collectInBackground, @@ -463,7 +465,8 @@ internal class RumFeature( timeProvider = sdkCore.timeProvider, useDeltaCompression = configuration.useDeltaCompression ), - dataWriter = dataWriter + dataWriter = dataWriter, + insightsCollector = insightsCollector ), if (totalRamBytes > 0L) { Pipeline( @@ -482,7 +485,8 @@ internal class RumFeature( timeProvider = sdkCore.timeProvider, useDeltaCompression = configuration.useDeltaCompression ), - dataWriter = dataWriter + dataWriter = dataWriter, + insightsCollector = insightsCollector ) } else { sdkCore.internalLogger.log( diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/instrumentation/insights/InsightsCollector.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/instrumentation/insights/InsightsCollector.kt index 0f02407a6b..1e37ecb409 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/instrumentation/insights/InsightsCollector.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/instrumentation/insights/InsightsCollector.kt @@ -14,6 +14,7 @@ import com.datadog.tools.annotation.NoOpImplementation */ @InternalApi @NoOpImplementation +@Suppress("TooManyFunctions") interface InsightsCollector { /** @@ -94,6 +95,11 @@ interface InsightsCollector { * @param rate the slow frame rate value. */ fun onSlowFrameRate(rate: Double?) + + /** + * Notifies the collector that a timeseries batch has been sent. + */ + fun onTimeseries(name: String) } /** diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt index 727211b6f5..ae6f9a3da8 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt @@ -11,6 +11,8 @@ import com.datadog.android.api.feature.Feature import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.api.storage.DataWriter import com.datadog.android.api.storage.EventType +import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector +import com.datadog.android.rum.internal.instrumentation.insights.NoOpInsightsCollector import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer import com.google.gson.JsonObject @@ -20,7 +22,8 @@ internal class Pipeline( private val reader: DataPointsReader, private val buffer: Buffer, private val serializer: JsonSerializer, - private val dataWriter: DataWriter + private val dataWriter: DataWriter, + private val insightsCollector: InsightsCollector = NoOpInsightsCollector() ) { val intervalMs: Long get() = reader.intervalMs @@ -39,5 +42,14 @@ internal class Pipeline( private fun write(json: JsonObject) = sdkCore.getFeature(Feature.RUM_FEATURE_NAME) ?.withWriteContext { _, writeScope -> writeScope { dataWriter.write(it, json, EventType.DEFAULT) } + insightsCollector.onTimeseries(json.timeseriesName()) } + + private fun JsonObject.timeseriesName(): String = + getAsJsonObject(KEY_TIMESERIES)?.get(KEY_NAME)?.asString.orEmpty() + + private companion object { + private const val KEY_TIMESERIES = "timeseries" + private const val KEY_NAME = "name" + } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt index 25384bf354..31201e2462 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt @@ -1807,7 +1807,11 @@ internal class RumFeatureTest { @OptIn(ExperimentalRumApi::class) private fun createTimeseries(totalRamBytes: Long, sessionId: String): Timeseries = testedFeature - .createTimeseriesCollectingFactory(TimeseriesConfiguration.Builder().build(), totalRamBytes) + .createTimeseriesCollectingFactory( + TimeseriesConfiguration.Builder().build(), + totalRamBytes, + testedFeature.configuration.insightsCollector + ) .create(fakeApplicationId.toString(), sessionId, RumSessionType.USER) private fun Forge.anApplicationExitInfoList( diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt index 6e5b984cd9..fe49a86476 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt @@ -14,6 +14,7 @@ import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.api.storage.DataWriter import com.datadog.android.api.storage.EventBatchWriter import com.datadog.android.api.storage.EventType +import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer import com.datadog.android.rum.utils.forge.Configurator @@ -70,6 +71,9 @@ internal class PipelineTest { @Mock lateinit var mockEventBatchWriter: EventBatchWriter + @Mock + lateinit var mockInsightsCollector: InsightsCollector + @IntForgery(min = 2, max = 10) var fakeBufferSize: Int = 0 @@ -88,7 +92,14 @@ internal class PipelineTest { } buffer = Buffer(fakeBufferSize) - testedPipeline = Pipeline(mockSdkCore, mockReader, buffer, mockSerializer, mockDataWriter) + testedPipeline = Pipeline( + sdkCore = mockSdkCore, + reader = mockReader, + buffer = buffer, + serializer = mockSerializer, + dataWriter = mockDataWriter, + insightsCollector = mockInsightsCollector + ) } @Test @@ -135,7 +146,8 @@ internal class PipelineTest { fun `M flush W execute() { buffer fills to capacity }`(forge: Forge) { // Given val fakePoint = forge.getForgery>() - val fakeJson = JsonObject().apply { addProperty("k", "v") } + val fakeTimeseriesName = "view.cpu" + val fakeJson = fakeTimeseriesJson(fakeTimeseriesName) whenever(mockReader.read()) doReturn fakePoint whenever(mockSerializer.serialize(any())) doReturn fakeJson repeat(fakeBufferSize - 1) { testedPipeline.execute() } @@ -145,6 +157,7 @@ internal class PipelineTest { // Then verify(mockDataWriter).write(mockEventBatchWriter, fakeJson, EventType.DEFAULT) + verify(mockInsightsCollector).onTimeseries(fakeTimeseriesName) assertThat(buffer.drain()).isEmpty() } @@ -158,6 +171,7 @@ internal class PipelineTest { // Then verifyNoInteractions(mockDataWriter) + verifyNoInteractions(mockInsightsCollector) } @Test @@ -172,6 +186,7 @@ internal class PipelineTest { // Then verifyNoInteractions(mockDataWriter) + verifyNoInteractions(mockInsightsCollector) } // endregion @@ -182,7 +197,8 @@ internal class PipelineTest { fun `M write partial buffer W flush() { buffer has data }`(forge: Forge) { // Given val fakePoint = forge.getForgery>() - val fakeJson = JsonObject().apply { addProperty("k", "v") } + val fakeTimeseriesName = "view.memory" + val fakeJson = fakeTimeseriesJson(fakeTimeseriesName) whenever(mockReader.read()) doReturn fakePoint whenever(mockSerializer.serialize(any())) doReturn fakeJson val sampleCount = fakeBufferSize - 1 @@ -196,6 +212,7 @@ internal class PipelineTest { verify(mockSerializer).serialize(captor.capture()) assertThat(captor.firstValue).hasSize(sampleCount).allMatch { it == fakePoint } verify(mockDataWriter).write(mockEventBatchWriter, fakeJson, EventType.DEFAULT) + verify(mockInsightsCollector).onTimeseries(fakeTimeseriesName) assertThat(buffer.drain()).isEmpty() } @@ -206,7 +223,17 @@ internal class PipelineTest { // Then verifyNoInteractions(mockDataWriter) + verifyNoInteractions(mockInsightsCollector) } // endregion + + private fun fakeTimeseriesJson(name: String): JsonObject = JsonObject().apply { + add( + "timeseries", + JsonObject().apply { + addProperty("name", name) + } + ) + } } From 11a1211a800d5234f7d4a34dcfa1a7d273f0561b Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Fri, 3 Jul 2026 13:48:58 +0100 Subject: [PATCH 18/32] RUM-16325: Dogfooding fixes --- features/dd-sdk-android-rum/api/apiSurface | 1 + .../api/dd-sdk-android-rum.api | 1 + .../android/rum/internal/RumFeature.kt | 9 +- .../internal/domain/scope/RumSessionScope.kt | 1 + .../rum/internal/timeseries/Pipeline.kt | 52 ++++-- .../timeseries/RumSessionScopeTimeseries.kt | 22 ++- .../rum/internal/timeseries/Timeseries.kt | 2 +- .../serializer/CpuEventSerializer.kt | 35 ++-- .../timeseries/serializer/JsonSerializer.kt | 23 ++- .../serializer/MemoryEventSerializer.kt | 34 ++-- .../rum/timeseries/TimeseriesConfiguration.kt | 15 +- .../rum/assertj/TimeseriesCpuEventAssert.kt | 42 ++++- .../domain/scope/RumSessionScopeTest.kt | 20 +++ .../rum/internal/timeseries/PipelineTest.kt | 153 +++++++++++++++++- .../RumSessionScopeTimeseriesFactoryTest.kt | 13 +- .../RumSessionScopeTimeseriesTest.kt | 122 +++++++++++--- .../internal/timeseries/csv/CsvTimeseries.kt | 81 +++++----- .../timeseries/csv/TimeseriesEndToEndTest.kt | 59 ++++++- .../serializer/CpuEventSerializerTest.kt | 68 ++++++-- .../serializer/MemoryEventSerializerTest.kt | 98 +++++++---- .../timeseries/TimeseriesConfigurationTest.kt | 49 ++++++ 21 files changed, 721 insertions(+), 179 deletions(-) diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index ef65ccb3b8..1dec433c85 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -312,6 +312,7 @@ class com.datadog.android.rum.timeseries.TimeseriesConfiguration fun setIntervalMs(Long): Builder fun collectInBackground(Boolean): Builder fun useDeltaCompression(Boolean): Builder + fun addAttribute(String, String): Builder fun build(): TimeseriesConfiguration companion object val DEFAULT: TimeseriesConfiguration diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index a9f64dd216..852c6b87df 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -8645,6 +8645,7 @@ public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration { public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder { public fun ()V + public final fun addAttribute (Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; public final fun build ()Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration; public final fun collectInBackground (Z)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; public final fun setBufferSize (I)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index 7c31e8ce61..365646442d 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -440,6 +440,7 @@ internal class RumFeature( ) } + @Suppress("LongMethod") internal fun createTimeseriesCollectingFactory( configuration: TimeseriesConfiguration, totalRamBytes: Long, @@ -463,9 +464,11 @@ internal class RumFeature( applicationId = applicationId, sessionType = sessionType, timeProvider = sdkCore.timeProvider, - useDeltaCompression = configuration.useDeltaCompression + useDeltaCompression = configuration.useDeltaCompression, + additionalAttributes = configuration.additionalAttributes ), dataWriter = dataWriter, + internalLogger = sdkCore.internalLogger, insightsCollector = insightsCollector ), if (totalRamBytes > 0L) { @@ -483,9 +486,11 @@ internal class RumFeature( sessionType = sessionType, totalRamBytes = totalRamBytes, timeProvider = sdkCore.timeProvider, - useDeltaCompression = configuration.useDeltaCompression + useDeltaCompression = configuration.useDeltaCompression, + additionalAttributes = configuration.additionalAttributes ), dataWriter = dataWriter, + internalLogger = sdkCore.internalLogger, insightsCollector = insightsCollector ) } else { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt index 71c0526bb8..4c8fe96bbd 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt @@ -298,6 +298,7 @@ internal class RumSessionScope( renewSession(event.eventTime, StartReason.BACKGROUND_LAUNCH) lastUserInteractionNs.set(nanoTime) } else { + stopTimeseries() sessionState = State.EXPIRED } } else if (isTimedOut) { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt index ae6f9a3da8..3edeafd402 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt @@ -7,6 +7,9 @@ package com.datadog.android.rum.internal.timeseries import androidx.annotation.WorkerThread +import com.datadog.android.api.InternalLogger +import com.datadog.android.api.context.DatadogContext +import com.datadog.android.api.feature.EventWriteScope import com.datadog.android.api.feature.Feature import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.api.storage.DataWriter @@ -15,14 +18,17 @@ import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollect import com.datadog.android.rum.internal.instrumentation.insights.NoOpInsightsCollector import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer +import com.datadog.android.rum.internal.timeseries.serializer.TimeseriesAttributes import com.google.gson.JsonObject +@Suppress("LongParameterList") internal class Pipeline( private val sdkCore: FeatureSdkCore, private val reader: DataPointsReader, private val buffer: Buffer, private val serializer: JsonSerializer, private val dataWriter: DataWriter, + private val internalLogger: InternalLogger, private val insightsCollector: InsightsCollector = NoOpInsightsCollector() ) { val intervalMs: Long get() = reader.intervalMs @@ -34,22 +40,42 @@ internal class Pipeline( } @WorkerThread - fun flush() = buffer.drain() - .takeIf { it.isNotEmpty() } - ?.let(serializer::serialize) - ?.let(::write) + fun flush() { + val dataPoints = buffer.drain().ifEmpty { return } + sdkCore.getFeature(Feature.RUM_FEATURE_NAME) + ?.withWriteContext { datadogContext: DatadogContext, writeScope: EventWriteScope -> + val json = safeCall(ERROR_SERIALIZATION_FAILED) { + serializer.serialize(datadogContext, dataPoints) + } ?: return@withWriteContext - private fun write(json: JsonObject) = sdkCore.getFeature(Feature.RUM_FEATURE_NAME) - ?.withWriteContext { _, writeScope -> - writeScope { dataWriter.write(it, json, EventType.DEFAULT) } - insightsCollector.onTimeseries(json.timeseriesName()) - } + writeScope { batchWriter -> + safeCall(ERROR_FLUSH_FAILED) { + if (dataWriter.write(batchWriter, json, EventType.DEFAULT)) { + insightsCollector.onTimeseries(json.timeseriesName) + } + } + } + } + } - private fun JsonObject.timeseriesName(): String = - getAsJsonObject(KEY_TIMESERIES)?.get(KEY_NAME)?.asString.orEmpty() + private inline fun safeCall(message: String, block: () -> R): R? = try { + block() + } catch (@Suppress("TooGenericExceptionCaught") t: Throwable) { + internalLogger.log( + level = InternalLogger.Level.ERROR, + targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), + messageBuilder = { message }, + throwable = t + ) + null + } private companion object { - private const val KEY_TIMESERIES = "timeseries" - private const val KEY_NAME = "name" + private const val ERROR_FLUSH_FAILED = "Timeseries flush failed" + private const val ERROR_SERIALIZATION_FAILED = "Timeseries serialization failed" + private val JsonObject.timeseriesName: String + get() = getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) + ?.get(TimeseriesAttributes.KEY_NAME) + ?.asString.orEmpty() } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt index 6055f3944c..1fc4a36d92 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt @@ -78,13 +78,14 @@ internal class RumSessionScopeTimeseries( } @WorkerThread - override fun onViewTypeUpdate(viewType: RumViewType) { - val isEnterForeground = currentViewType != RumViewType.FOREGROUND && viewType == RumViewType.FOREGROUND - currentViewType = viewType + override fun onViewTypeUpdate(newViewType: RumViewType) { + if (newViewType == currentViewType) return + val isEnterForeground = !currentViewType.isForeground && newViewType.isForeground + currentViewType = newViewType if (!collectInBackground) { if (isEnterForeground && state.compareAndSet(State.SUSPENDED, State.RUNNING)) { startSampling() - } else if (viewType != RumViewType.FOREGROUND) { + } else if (!newViewType.isForeground) { state.compareAndSet(State.RUNNING, State.SUSPENDED) } } @@ -124,8 +125,15 @@ internal class RumSessionScopeTimeseries( state.get() == State.RUNNING && currentGeneration.get() == generation internal companion object { - internal const val OPERATION_NAME = "Timeseries sampling" - internal const val ERROR_SAMPLING_FAILED = "Timeseries sampling iteration failed; rescheduling next sample." - internal const val ERROR_FLUSH_FAILED = "Timeseries flush on session stop failed." + const val OPERATION_NAME = "Timeseries sampling" + const val ERROR_SAMPLING_FAILED = "Timeseries sampling iteration failed; rescheduling next sample." + const val ERROR_FLUSH_FAILED = "Timeseries flush on session stop failed." + val RumViewType?.isForeground: Boolean + get() = when (this) { + RumViewType.FOREGROUND, RumViewType.APPLICATION_LAUNCH -> true + RumViewType.BACKGROUND -> false + RumViewType.NONE -> false + null -> false + } } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt index 655482944c..f08f5144fe 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt @@ -14,7 +14,7 @@ import com.datadog.tools.annotation.NoOpImplementation internal interface Timeseries { fun onSessionStart() fun onSessionStop() - fun onViewTypeUpdate(viewType: RumViewType) + fun onViewTypeUpdate(newViewType: RumViewType) @NoOpImplementation interface Factory { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt index 26ef0122a8..e55e81b159 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt @@ -6,6 +6,7 @@ package com.datadog.android.rum.internal.timeseries.serializer +import com.datadog.android.api.context.DatadogContext import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.internal.timeseries.DataPoint @@ -18,16 +19,18 @@ import com.google.gson.JsonObject import java.util.UUID import kotlin.math.pow +@Suppress("LongParameterList") internal class CpuEventSerializer( private val sessionId: String, private val applicationId: String, private val sessionType: RumSessionType, private val timeProvider: TimeProvider, + private val additionalAttributes: Map, private val useDeltaCompression: Boolean = false, private val precision: Int = DeltaCompression.PRECISION ) : JsonSerializer { - override fun serialize(dataPoints: List>): JsonObject? { + override fun serialize(datadogContext: DatadogContext, dataPoints: List>): JsonObject? { if (dataPoints.isEmpty()) return null val data = dataPoints.map { sample -> TimeseriesCpuEvent.Data( @@ -52,8 +55,8 @@ internal class CpuEventSerializer( ), source = TimeseriesCpuEvent.Source.ANDROID, date = timeProvider.getDeviceTimestampMillis(), - service = null, - version = null, + version = datadogContext.version, + service = datadogContext.service, timeseries = TimeseriesCpuEvent.Timeseries( id = UUID.randomUUID().toString(), schema = schema, @@ -64,12 +67,18 @@ internal class CpuEventSerializer( ).toJson().asJsonObject // - val timeseriesJson = json.getAsJsonObject("timeseries") + val timeseriesJson = json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) if (deltaEncoded != null) { - timeseriesJson?.remove("data") - timeseriesJson?.add("data", deltaEncoded) + timeseriesJson?.remove(TimeseriesAttributes.KEY_DATA) + timeseriesJson?.add(TimeseriesAttributes.KEY_DATA, deltaEncoded) } - timeseriesJson?.addProperty("count", data.size) + if (additionalAttributes.isNotEmpty()) { + timeseriesJson?.add( + TimeseriesAttributes.KEY_TAGS, + additionalAttributes.toJson() + ) + } + timeseriesJson?.addProperty(TimeseriesAttributes.KEY_COUNT, data.size) // return json } @@ -84,14 +93,10 @@ internal class CpuEventSerializer( } return JsonObject().apply { - addProperty("precision", precision) - addProperty("resolution", RESOLUTION_NS) - add("ts", ts) - add("value", cpuUsageArray) + addProperty(TimeseriesAttributes.KEY_PRECISION, precision) + addProperty(TimeseriesAttributes.KEY_RESOLUTION, TimeseriesAttributes.NS) + add(TimeseriesAttributes.KEY_TS, ts) + add(TimeseriesAttributes.KEY_VALUE, cpuUsageArray) } } - - companion object { - private const val RESOLUTION_NS = "ns" - } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt index 6bf484b9e3..b677fe612c 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt @@ -6,9 +6,30 @@ package com.datadog.android.rum.internal.timeseries.serializer +import com.datadog.android.api.context.DatadogContext import com.datadog.android.rum.internal.timeseries.DataPoint import com.google.gson.JsonObject internal fun interface JsonSerializer { - fun serialize(dataPoints: List>): JsonObject? + fun serialize(datadogContext: DatadogContext, dataPoints: List>): JsonObject? +} + +internal object TimeseriesAttributes { + const val KEY_TIMESERIES = "timeseries" + const val KEY_SCHEMA = "schema" + const val KEY_DATA = "data" + const val KEY_COUNT = "count" + const val KEY_TAGS = "tags" + const val KEY_TS = "ts" + const val NS = "ns" + const val KEY_PRECISION = "precision" + const val KEY_RESOLUTION = "resolution" + const val KEY_VALUE = "value" + const val KEY_NAME = "name" + const val KEY_MEMORY_FOOTPRINT = "memory_footprint" + const val KEY_MEMORY_PERCENT = "memory_percent" +} + +internal fun Map.toJson() = JsonObject().also { json -> + entries.forEach { json.addProperty(it.key, it.value) } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt index 82f39f3a16..fbfcea58b3 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt @@ -6,6 +6,7 @@ package com.datadog.android.rum.internal.timeseries.serializer +import com.datadog.android.api.context.DatadogContext import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.internal.timeseries.DataPoint @@ -18,17 +19,19 @@ import com.google.gson.JsonObject import java.util.UUID import kotlin.math.pow +@Suppress("LongParameterList") internal class MemoryEventSerializer( private val sessionId: String, private val applicationId: String, private val sessionType: RumSessionType, private val totalRamBytes: Long, private val timeProvider: TimeProvider, + private val additionalAttributes: Map, private val useDeltaCompression: Boolean = false, private val precision: Int = DeltaCompression.PRECISION ) : JsonSerializer { - override fun serialize(dataPoints: List>): JsonObject? { + override fun serialize(datadogContext: DatadogContext, dataPoints: List>): JsonObject? { if (totalRamBytes <= 0L || dataPoints.isEmpty()) return null val data = dataPoints.map { sample -> val memoryPercent = sample.value / totalRamBytes * PERCENT_FACTOR @@ -54,8 +57,8 @@ internal class MemoryEventSerializer( ), source = TimeseriesMemoryEvent.Source.ANDROID, date = timeProvider.getDeviceTimestampMillis(), - service = null, - version = null, + version = datadogContext.version, + service = datadogContext.service, timeseries = TimeseriesMemoryEvent.Timeseries( id = UUID.randomUUID().toString(), schema = schema, @@ -65,12 +68,18 @@ internal class MemoryEventSerializer( ) ).toJson().asJsonObject // - val timeseriesJson = json.getAsJsonObject("timeseries") + val timeseriesJson = json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) if (deltaEncoded != null) { - timeseriesJson?.remove("data") - timeseriesJson?.add("data", deltaEncoded) + timeseriesJson?.remove(TimeseriesAttributes.KEY_DATA) + timeseriesJson?.add(TimeseriesAttributes.KEY_DATA, deltaEncoded) + } + timeseriesJson?.addProperty(TimeseriesAttributes.KEY_COUNT, data.size) + if (additionalAttributes.isNotEmpty()) { + timeseriesJson?.add( + TimeseriesAttributes.KEY_TAGS, + additionalAttributes.toJson() + ) } - timeseriesJson?.addProperty("count", data.size) // return json } @@ -90,16 +99,15 @@ internal class MemoryEventSerializer( } return JsonObject().apply { - addProperty("precision", precision) - addProperty("resolution", RESOLUTION_NS) - add("ts", ts) - add("memory_footprint", memoryFootprintArray) - add("memory_percent", memoryPercentArray) + addProperty(TimeseriesAttributes.KEY_PRECISION, precision) + addProperty(TimeseriesAttributes.KEY_RESOLUTION, TimeseriesAttributes.NS) + add(TimeseriesAttributes.KEY_TS, ts) + add(TimeseriesAttributes.KEY_MEMORY_FOOTPRINT, memoryFootprintArray) + add(TimeseriesAttributes.KEY_MEMORY_PERCENT, memoryPercentArray) } } companion object { private const val PERCENT_FACTOR = 100.0 - private const val RESOLUTION_NS = "ns" } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt index 15f66e296f..fe2b218359 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt @@ -20,7 +20,8 @@ class TimeseriesConfiguration internal constructor( internal val bufferSize: Int, internal val intervalMs: Long, internal val collectInBackground: Boolean, - internal val useDeltaCompression: Boolean + internal val useDeltaCompression: Boolean, + internal val additionalAttributes: Map ) { /** @@ -33,6 +34,7 @@ class TimeseriesConfiguration internal constructor( private var intervalMs: Long = DEFAULT_INTERVAL_MS private var collectInBackground: Boolean = false private var useDeltaCompression: Boolean = false + private val additionalAttributes: MutableMap = mutableMapOf() /** * Sets the number of samples accumulated per pipeline before sending a batch event. @@ -72,12 +74,21 @@ class TimeseriesConfiguration internal constructor( this.useDeltaCompression = useDeltaCompression } + /** + * Adds an attribute to all timeseries events created with this configuration. + * The attribute is emitted as a key/value entry in the `timeseries.tags` JSON object. + */ + fun addAttribute(key: String, value: String): Builder = apply { + additionalAttributes[key] = value + } + /** Builds a [TimeseriesConfiguration] from the current builder state. */ fun build(): TimeseriesConfiguration = TimeseriesConfiguration( bufferSize = bufferSize, intervalMs = intervalMs, collectInBackground = collectInBackground, - useDeltaCompression = useDeltaCompression + useDeltaCompression = useDeltaCompression, + additionalAttributes = additionalAttributes.toMap() ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt index 6eddc5ce28..3fb9a4d38f 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt @@ -6,6 +6,7 @@ package com.datadog.android.rum.assertj +import com.datadog.android.rum.internal.timeseries.serializer.TimeseriesAttributes import com.datadog.android.rum.model.TimeseriesCpuEvent import com.google.gson.JsonArray import com.google.gson.JsonObject @@ -43,6 +44,18 @@ internal class TimeseriesCpuEventAssert private constructor( .isEqualTo(expected) } + fun hasService(expected: String): TimeseriesCpuEventAssert = apply { + assertThat(actual.service) + .overridingErrorMessage { "Expected event to have service $expected but was ${actual.service}" } + .isEqualTo(expected) + } + + fun hasVersion(expected: String): TimeseriesCpuEventAssert = apply { + assertThat(actual.version) + .overridingErrorMessage { "Expected event to have version $expected but was ${actual.version}" } + .isEqualTo(expected) + } + fun hasDate(expected: Long): TimeseriesCpuEventAssert = apply { assertThat(actual.date) .overridingErrorMessage { "Expected event to have date $expected but was ${actual.date}" } @@ -102,21 +115,33 @@ internal class TimeseriesCpuEventAssert private constructor( // Use assertThatDelta(JsonObject) to enter this path. fun hasDeltaPrecision(expected: Int): TimeseriesCpuEventAssert = apply { - val precision = json.timeseries.getAsJsonObject("data").get("precision").asInt + val precision = json.timeseries + .getAsJsonObject(TimeseriesAttributes.KEY_DATA) + .get(TimeseriesAttributes.KEY_PRECISION) + .asInt + assertThat(precision) .overridingErrorMessage { "Expected delta data to have precision $expected but was $precision" } .isEqualTo(expected) } fun hasDeltaResolution(expected: String): TimeseriesCpuEventAssert = apply { - val resolution = json.timeseries.getAsJsonObject("data").get("resolution").asString + val resolution = json.timeseries + .getAsJsonObject(TimeseriesAttributes.KEY_DATA) + .get(TimeseriesAttributes.KEY_RESOLUTION) + .asString + assertThat(resolution) .overridingErrorMessage { "Expected delta data to have resolution $expected but was $resolution" } .isEqualTo(expected) } fun hasDeltaTsValues(vararg expected: Long): TimeseriesCpuEventAssert = apply { - val tsArray = json.timeseries.getAsJsonObject("data").get("ts").asJsonArray + val tsArray = json.timeseries + .getAsJsonObject(TimeseriesAttributes.KEY_DATA) + .get(TimeseriesAttributes.KEY_TS) + .asJsonArray + expected.forEachIndexed { i, v -> assertThat(tsArray[i].asLong) .overridingErrorMessage { "Expected delta ts[$i] to be $v but was ${tsArray[i].asLong}" } @@ -125,7 +150,11 @@ internal class TimeseriesCpuEventAssert private constructor( } fun hasDeltaValueAt(index: Int, expected: Long): TimeseriesCpuEventAssert = apply { - val valueArray = json.timeseries.getAsJsonObject("data").get("value").asJsonArray + val valueArray = json.timeseries + .getAsJsonObject(TimeseriesAttributes.KEY_DATA) + .get(TimeseriesAttributes.KEY_VALUE) + .asJsonArray + val actual = valueArray[index].asLong assertThat(actual) .overridingErrorMessage { "Expected delta value[$index] to be $expected but was $actual" } @@ -135,7 +164,7 @@ internal class TimeseriesCpuEventAssert private constructor( // endregion companion object { - private val JsonObject.timeseries: JsonObject get() = getAsJsonObject("timeseries") + private val JsonObject.timeseries: JsonObject get() = getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) internal fun assertThat(event: TimeseriesCpuEvent): TimeseriesCpuEventAssert = TimeseriesCpuEventAssert(event, event.toJson() as JsonObject) @@ -150,7 +179,8 @@ internal class TimeseriesCpuEventAssert private constructor( */ internal fun assertThatDelta(json: JsonObject): TimeseriesCpuEventAssert { val patchedJson = json.deepCopy().also { - it.getAsJsonObject("timeseries").add("data", JsonArray()) + it.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) + .add(TimeseriesAttributes.KEY_DATA, JsonArray()) } return TimeseriesCpuEventAssert(TimeseriesCpuEvent.fromJsonObject(patchedJson), json) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt index c7c73adc13..056352e240 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt @@ -1980,6 +1980,26 @@ internal class RumSessionScopeTest { verify(mockTimeseries, times(2)).onSessionStart() } + @Test + fun `M stop timeseries W handleEvent { session expires without renewal }`(forge: Forge) { + // Given + initializeTestedScope(backgroundTrackingEnabled = false, timeseriesFactory = mockTimeseriesFactory) + testedScope.handleEvent( + forge.startViewEvent(), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) + + // When + advanceTimeByMs(TEST_INACTIVITY_MS) + testedScope.handleEvent(mock(), fakeDatadogContext, mockEventWriteScope, mockWriter) + + // Then + assertThat(testedScope.sessionState).isEqualTo(RumSessionScope.State.EXPIRED) + verify(mockTimeseries).onSessionStop() + } + @Test fun `M stop timeseries W handleEvent { StopSession }`(forge: Forge) { // Given diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt index fe49a86476..3dfee43c74 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt @@ -6,6 +6,7 @@ package com.datadog.android.rum.internal.timeseries +import com.datadog.android.api.InternalLogger import com.datadog.android.api.context.DatadogContext import com.datadog.android.api.feature.EventWriteScope import com.datadog.android.api.feature.Feature @@ -17,7 +18,10 @@ import com.datadog.android.api.storage.EventType import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer +import com.datadog.android.rum.internal.timeseries.serializer.TimeseriesAttributes import com.datadog.android.rum.utils.forge.Configurator +import com.datadog.android.utils.verifyLog +import com.datadog.tools.unit.forge.aThrowable import com.google.gson.JsonObject import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.IntForgery @@ -36,7 +40,8 @@ import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.eq import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever @@ -74,6 +79,12 @@ internal class PipelineTest { @Mock lateinit var mockInsightsCollector: InsightsCollector + @Mock + lateinit var mockDatadogContext: DatadogContext + + @Mock + lateinit var mockInternalLogger: InternalLogger + @IntForgery(min = 2, max = 10) var fakeBufferSize: Int = 0 @@ -85,11 +96,12 @@ internal class PipelineTest { whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope whenever(mockRumFeatureScope.withWriteContext(any(), any())) doAnswer { it.getArgument<(DatadogContext, EventWriteScope) -> Unit>(it.arguments.lastIndex) - .invoke(mock(), mockEventWriteScope) + .invoke(mockDatadogContext, mockEventWriteScope) } whenever(mockEventWriteScope.invoke(any())) doAnswer { it.getArgument<(EventBatchWriter) -> Unit>(0).invoke(mockEventBatchWriter) } + whenever(mockDataWriter.write(any(), any(), any())) doReturn true buffer = Buffer(fakeBufferSize) testedPipeline = Pipeline( @@ -98,6 +110,7 @@ internal class PipelineTest { buffer = buffer, serializer = mockSerializer, dataWriter = mockDataWriter, + internalLogger = mockInternalLogger, insightsCollector = mockInsightsCollector ) } @@ -149,7 +162,7 @@ internal class PipelineTest { val fakeTimeseriesName = "view.cpu" val fakeJson = fakeTimeseriesJson(fakeTimeseriesName) whenever(mockReader.read()) doReturn fakePoint - whenever(mockSerializer.serialize(any())) doReturn fakeJson + whenever(mockSerializer.serialize(any(), any())) doReturn fakeJson repeat(fakeBufferSize - 1) { testedPipeline.execute() } // When @@ -178,7 +191,7 @@ internal class PipelineTest { fun `M not flush W execute() { serializer returns null }`(forge: Forge) { // Given whenever(mockReader.read()) doReturn forge.getForgery>() - whenever(mockSerializer.serialize(any())) doReturn null + whenever(mockSerializer.serialize(any(), any())) doReturn null repeat(fakeBufferSize - 1) { testedPipeline.execute() } // When @@ -200,7 +213,7 @@ internal class PipelineTest { val fakeTimeseriesName = "view.memory" val fakeJson = fakeTimeseriesJson(fakeTimeseriesName) whenever(mockReader.read()) doReturn fakePoint - whenever(mockSerializer.serialize(any())) doReturn fakeJson + whenever(mockSerializer.serialize(any(), any())) doReturn fakeJson val sampleCount = fakeBufferSize - 1 repeat(sampleCount) { testedPipeline.execute() } @@ -209,13 +222,73 @@ internal class PipelineTest { // Then val captor = argumentCaptor>>() - verify(mockSerializer).serialize(captor.capture()) + verify(mockSerializer).serialize(any(), captor.capture()) assertThat(captor.firstValue).hasSize(sampleCount).allMatch { it == fakePoint } verify(mockDataWriter).write(mockEventBatchWriter, fakeJson, EventType.DEFAULT) verify(mockInsightsCollector).onTimeseries(fakeTimeseriesName) assertThat(buffer.drain()).isEmpty() } + @Test + fun `M drain buffer before write context callback W flush()`(forge: Forge) { + // Given + val fakePoint = forge.getForgery>() + whenever(mockRumFeatureScope.withWriteContext(any(), any())) doAnswer { Unit } + whenever(mockSerializer.serialize(any(), any())) doReturn fakeTimeseriesJson("view.cpu") + whenever(mockReader.read()) doReturn fakePoint + testedPipeline.execute() + + // When + testedPipeline.flush() + + // Then + assertThat(buffer.drain()).isEmpty() + verifyNoInteractions(mockSerializer) + verifyNoInteractions(mockDataWriter) + verifyNoInteractions(mockInsightsCollector) + } + + @Test + fun `M pass only pre-flush points to serializer W execute() { point added before write context callback invoked }`( + forge: Forge + ) { + // Given + val fakePoint = forge.getForgery>() + val fakeNextPoint = forge.getForgery>() + val callbackCaptor = argumentCaptor<(DatadogContext, EventWriteScope) -> Unit>() + whenever(mockRumFeatureScope.withWriteContext(any(), any())) doAnswer { Unit } + whenever(mockSerializer.serialize(any(), any())) doReturn fakeTimeseriesJson("view.cpu") + whenever(mockReader.read()) doReturn fakePoint + testedPipeline.execute() + testedPipeline.flush() + whenever(mockReader.read()) doReturn fakeNextPoint + + // When + testedPipeline.execute() + verify(mockRumFeatureScope).withWriteContext(any(), callbackCaptor.capture()) + callbackCaptor.firstValue.invoke(mockDatadogContext, mockEventWriteScope) + + // Then + val dataPointsCaptor = argumentCaptor>>() + verify(mockSerializer).serialize(eq(mockDatadogContext), dataPointsCaptor.capture()) + assertThat(dataPointsCaptor.firstValue).containsExactly(fakePoint) + assertThat(buffer.drain()).containsExactly(fakeNextPoint) + } + + @Test + fun `M pass datadogContext from withWriteContext W flush()`(forge: Forge) { + // Given + whenever(mockReader.read()) doReturn forge.getForgery>() + whenever(mockSerializer.serialize(any(), any())) doReturn fakeTimeseriesJson("view.cpu") + testedPipeline.execute() + + // When + testedPipeline.flush() + + // Then + verify(mockSerializer).serialize(eq(mockDatadogContext), any()) + } + @Test fun `M not write W flush() { buffer empty }`() { // When @@ -226,11 +299,77 @@ internal class PipelineTest { verifyNoInteractions(mockInsightsCollector) } + @Test + fun `M log error W flush() { serializer throws }`(forge: Forge) { + // Given + val fakeThrowable = forge.aThrowable() + whenever(mockReader.read()) doReturn forge.getForgery>() + whenever(mockSerializer.serialize(any(), any())) doThrow fakeThrowable + testedPipeline.execute() + + // When + testedPipeline.flush() + + // Then + mockInternalLogger.verifyLog( + level = InternalLogger.Level.ERROR, + targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), + message = "Timeseries serialization failed", + throwable = fakeThrowable + ) + verifyNoInteractions(mockDataWriter) + verifyNoInteractions(mockInsightsCollector) + } + + @Test + fun `M log error W flush() { dataWriter throws }`(forge: Forge) { + // Given + val fakeThrowable = forge.aThrowable() + val fakeJson = fakeTimeseriesJson("view.cpu") + val writeBlockCaptor = argumentCaptor<(EventBatchWriter) -> Unit>() + whenever(mockReader.read()) doReturn forge.getForgery>() + whenever(mockSerializer.serialize(any(), any())) doReturn fakeJson + whenever(mockDataWriter.write(any(), any(), any())) doThrow fakeThrowable + whenever(mockEventWriteScope.invoke(any())) doAnswer { Unit } + testedPipeline.execute() + + // When + testedPipeline.flush() + verify(mockEventWriteScope).invoke(writeBlockCaptor.capture()) + writeBlockCaptor.firstValue.invoke(mockEventBatchWriter) + + // Then + mockInternalLogger.verifyLog( + level = InternalLogger.Level.ERROR, + targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), + message = "Timeseries flush failed", + throwable = fakeThrowable + ) + verifyNoInteractions(mockInsightsCollector) + } + + @Test + fun `M not notify insights W flush() { dataWriter returns false }`(forge: Forge) { + // Given + val fakeJson = fakeTimeseriesJson("view.cpu") + whenever(mockReader.read()) doReturn forge.getForgery>() + whenever(mockSerializer.serialize(any(), any())) doReturn fakeJson + whenever(mockDataWriter.write(any(), any(), any())) doReturn false + testedPipeline.execute() + + // When + testedPipeline.flush() + + // Then + verify(mockDataWriter).write(mockEventBatchWriter, fakeJson, EventType.DEFAULT) + verifyNoInteractions(mockInsightsCollector) + } + // endregion private fun fakeTimeseriesJson(name: String): JsonObject = JsonObject().apply { add( - "timeseries", + TimeseriesAttributes.KEY_TIMESERIES, JsonObject().apply { addProperty("name", name) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt index 9301b3cbd0..16c418b19a 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt @@ -18,6 +18,7 @@ import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions import org.mockito.Mock @@ -92,17 +93,15 @@ internal class RumSessionScopeTimeseriesFactoryTest { pipelinesProvider = emptyProvider ) - // When - val timeseries = testedFactory.create(fakeSessionId, fakeApplicationId, RumSessionType.USER) - - // Then — collectInBackground is private; tick behavior is covered by RumSessionScopeTimeseriesTest. - // Here we just assert the instance was built without throwing. - assertThat(timeseries).isNotNull + // When / Then + assertDoesNotThrow { + testedFactory.create(fakeSessionId, fakeApplicationId, RumSessionType.USER) + } } private fun mockPipeline(): Pipeline<*> { val reader = mock>() val serializer = mock>() - return Pipeline(mockSdkCore, reader, Buffer(1), serializer, mockWriter) + return Pipeline(mockSdkCore, reader, Buffer(1), serializer, mockWriter, mockInternalLogger) } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesTest.kt index 41a004ea3a..4429538af9 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesTest.kt @@ -124,8 +124,8 @@ internal class RumSessionScopeTimeseriesTest { bufferA = Buffer(fakeBufferSize) bufferB = Buffer(fakeBufferSize) - pipelineA = Pipeline(mockSdkCore, mockReaderA, bufferA, mockSerializerA, mockDataWriter) - pipelineB = Pipeline(mockSdkCore, mockReaderB, bufferB, mockSerializerB, mockDataWriter) + pipelineA = Pipeline(mockSdkCore, mockReaderA, bufferA, mockSerializerA, mockDataWriter, mockInternalLogger) + pipelineB = Pipeline(mockSdkCore, mockReaderB, bufferB, mockSerializerB, mockDataWriter, mockInternalLogger) testedTimeseries = RumSessionScopeTimeseries( pipelines = listOf(pipelineA, pipelineB), @@ -188,8 +188,8 @@ internal class RumSessionScopeTimeseriesTest { val fakeJson = JsonObject().apply { addProperty("k", "v") } whenever(mockReaderA.read()) doReturn forge.getForgery>() whenever(mockReaderB.read()) doReturn forge.getForgery>() - whenever(mockSerializerA.serialize(any())) doThrow fakeError - whenever(mockSerializerB.serialize(any())) doReturn fakeJson + whenever(mockSerializerA.serialize(any(), any())) doThrow fakeError + whenever(mockSerializerB.serialize(any(), any())) doReturn fakeJson testedTimeseries.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) val runnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) @@ -203,7 +203,7 @@ internal class RumSessionScopeTimeseriesTest { mockInternalLogger.verifyLog( InternalLogger.Level.ERROR, targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), - RumSessionScopeTimeseries.ERROR_FLUSH_FAILED, + "Timeseries serialization failed", fakeError ) verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) @@ -214,7 +214,7 @@ internal class RumSessionScopeTimeseriesTest { // Given val fakeJson = JsonObject().apply { addProperty("k", "v") } whenever(mockReaderA.read()) doReturn forge.getForgery>() - whenever(mockSerializerA.serialize(any())) doReturn fakeJson + whenever(mockSerializerA.serialize(any(), any())) doReturn fakeJson testedTimeseries.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) runnableA.run() @@ -223,7 +223,7 @@ internal class RumSessionScopeTimeseriesTest { testedTimeseries.onSessionStop() // Then - verify(mockSerializerA).serialize(any()) + verify(mockSerializerA).serialize(any(), any()) verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) } @@ -234,7 +234,7 @@ internal class RumSessionScopeTimeseriesTest { val dataPoints = List(fakeBufferSize) { element } val fakeJson = JsonObject().apply { addProperty("a", "1") } whenever(mockReaderA.read()) doReturn element - whenever(mockSerializerA.serialize(dataPoints)) doReturn fakeJson + whenever(mockSerializerA.serialize(any(), eq(dataPoints))) doReturn fakeJson testedTimeseries.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) @@ -243,7 +243,7 @@ internal class RumSessionScopeTimeseriesTest { repeat(fakeBufferSize) { runnableA.run() } // Then - verify(mockSerializerA).serialize(any()) + verify(mockSerializerA).serialize(any(), any()) verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) } @@ -273,19 +273,7 @@ internal class RumSessionScopeTimeseriesTest { runnableA.run() // Then - verify(mockSerializerA, never()).serialize(any()) - verify(mockDataWriter, never()).write(any(), any(), any()) - } - - @Test - fun `M discard pre-existing data W onSessionStart() { buffer not empty }`(forge: Forge) { - // Given - start() drains all pipelines and ignores any serialized result - bufferA.add(forge.getForgery>()) - - // When - testedTimeseries.onSessionStart() - - // Then - even though serializer may be invoked, no event reaches the writer + verify(mockSerializerA, never()).serialize(any(), any()) verify(mockDataWriter, never()).write(any(), any(), any()) } @@ -293,7 +281,7 @@ internal class RumSessionScopeTimeseriesTest { fun `M skip writer W sample tick { serializer returns null }`(forge: Forge) { // Given - fill the buffer so Pipeline.drain() reaches the serializer; serializer returns null whenever(mockReaderA.read()) doReturn forge.getForgery>() - whenever(mockSerializerA.serialize(any())) doReturn null + whenever(mockSerializerA.serialize(any(), any())) doReturn null testedTimeseries.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) @@ -301,7 +289,7 @@ internal class RumSessionScopeTimeseriesTest { repeat(fakeBufferSize) { runnableA.run() } // Then - verify(mockSerializerA).serialize(any()) + verify(mockSerializerA).serialize(any(), any()) verify(mockDataWriter, never()).write(any(), any(), any()) } @@ -396,6 +384,92 @@ internal class RumSessionScopeTimeseriesTest { ) } + @Test + fun `M log error and reschedule W sample tick { buffer drain throws }`(forge: Forge) { + // Given — drain() throws outside Pipeline's own try/catch, so it must be + // caught by RumSessionScopeTimeseries' sampling try/catch instead. + val fakeError = RuntimeException("drain failure") + val mockBuffer = mock>() + whenever(mockBuffer.isFull()) doReturn true + whenever(mockBuffer.drain()) doThrow fakeError + whenever(mockReaderA.read()) doReturn forge.getForgery>() + val pipeline = + Pipeline(mockSdkCore, mockReaderA, mockBuffer, mockSerializerA, mockDataWriter, mockInternalLogger) + val timeseries = RumSessionScopeTimeseries( + pipelines = listOf(pipeline), + internalLogger = mockInternalLogger, + collectInBackground = false, + scheduledExecutorService = mockExecutor + ) + timeseries.onViewTypeUpdate(RumViewType.FOREGROUND) + timeseries.onSessionStart() + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + + // When + runnableA.run() + + // Then + mockInternalLogger.verifyLog( + InternalLogger.Level.ERROR, + targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), + RumSessionScopeTimeseries.ERROR_SAMPLING_FAILED, + fakeError + ) + verify(mockExecutor, times(2)) + .schedule(any(), eq(fakeIntervalAMs), eq(TimeUnit.MILLISECONDS)) + } + + @Test + fun `M log error and reschedule W sample tick { serializer throws }`(forge: Forge) { + // Given — serializer throws inside Pipeline's own try/catch: Pipeline logs it itself + // and the tick completes normally, so the sampling chain reschedules as usual. + val fakeError = RuntimeException("serializer failure") + whenever(mockReaderA.read()) doReturn forge.getForgery>() + whenever(mockSerializerA.serialize(any(), any())) doThrow fakeError + testedTimeseries.onSessionStart() + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + + // When + repeat(fakeBufferSize) { runnableA.run() } + + // Then + // times(1) default on verifyLog also confirms this is the only ERROR log with that + // throwable — i.e. RumSessionScopeTimeseries' own sampling catch never fired here. + mockInternalLogger.verifyLog( + InternalLogger.Level.ERROR, + targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), + "Timeseries serialization failed", + fakeError + ) + verify(mockDataWriter, never()).write(any(), any(), any()) + verify(mockExecutor, times(fakeBufferSize + 1)) + .schedule(any(), eq(fakeIntervalAMs), eq(TimeUnit.MILLISECONDS)) + } + + @Test + fun `M log error and reschedule W sample tick { write context resolution throws }`(forge: Forge) { + // Given — withWriteContext() itself throws (context/scope resolution failure), outside + // Pipeline's own try/catch, so it propagates up to the sampling try/catch. + val fakeError = RuntimeException("write context resolution failure") + whenever(mockReaderA.read()) doReturn forge.getForgery>() + whenever(mockRumFeatureScope.withWriteContext(any(), any())) doThrow fakeError + testedTimeseries.onSessionStart() + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + + // When + repeat(fakeBufferSize) { runnableA.run() } + + // Then + mockInternalLogger.verifyLog( + InternalLogger.Level.ERROR, + targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), + RumSessionScopeTimeseries.ERROR_SAMPLING_FAILED, + fakeError + ) + verify(mockExecutor, times(fakeBufferSize + 1)) + .schedule(any(), eq(fakeIntervalAMs), eq(TimeUnit.MILLISECONDS)) + } + @Test fun `M not reschedule W stopped { sample tick skips try block }`() { // Given diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt index 987fb01ba6..0dcb9dff9d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt @@ -6,6 +6,7 @@ package com.datadog.android.rum.internal.timeseries.csv +import com.datadog.android.api.context.DatadogContext import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.internal.domain.scope.RumViewType @@ -21,8 +22,10 @@ import com.google.gson.JsonObject * but pulls samples from CSV-backed readers instead of `VitalReaderWrapper` and drives the * pipelines synchronously (no executor) so callers can assert on every emitted JSON. */ -internal class CsvTimeseries(private val pipelines: List, JsonSerializer>>) : - Timeseries { +internal class CsvTimeseries( + private val pipelines: List, JsonSerializer>>, + private val datadogContext: DatadogContext +) : Timeseries { private val emitted = mutableListOf() @@ -37,17 +40,20 @@ internal class CsvTimeseries(private val pipelines: List, serializer: JsonSerializer) = buffer.drain() + .takeIf { it.isNotEmpty() } + ?.let { serializer.serialize(datadogContext, it) } + ?.let(emitted::add) + override fun onSessionStop() = Unit - override fun onViewTypeUpdate(viewType: RumViewType) = Unit + override fun onViewTypeUpdate(newViewType: RumViewType) = Unit companion object { @@ -56,6 +62,7 @@ internal class CsvTimeseries(private val pipelines: List = emptyMap() + ) = CsvTimeseries( + datadogContext = datadogContext, + pipelines = listOf( + Triple( + CSVReader(csvContent, METRIC_MEMORY_USAGE, timeProvider), + Buffer(bufferSize), + MemoryEventSerializer( + sessionId = sessionId, + applicationId = applicationId, + sessionType = sessionType, + totalRamBytes = totalRamBytes, + timeProvider = timeProvider, + additionalAttributes = additionalAttributes, + useDeltaCompression = useDeltaCompression + ) + ), + Triple( + CSVReader(csvContent, METRIC_CPU_USAGE, timeProvider), + Buffer(bufferSize), + CpuEventSerializer( + sessionId = sessionId, + applicationId = applicationId, + sessionType = sessionType, + timeProvider = timeProvider, + additionalAttributes = additionalAttributes, + useDeltaCompression = useDeltaCompression ) ) ) - } + ) const val METRIC_MEMORY_USAGE: String = "memory_usage" const val METRIC_CPU_USAGE: String = "cpu_usage" diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt index 099916c46b..4b1f005d8f 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt @@ -6,8 +6,10 @@ package com.datadog.android.rum.internal.timeseries.csv +import com.datadog.android.api.context.DatadogContext import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.timeseries.serializer.TimeseriesAttributes import com.datadog.android.rum.utils.forge.Configurator import com.google.gson.JsonArray import com.google.gson.JsonElement @@ -15,6 +17,7 @@ import com.google.gson.JsonNull import com.google.gson.JsonObject import com.google.gson.JsonParser import com.google.gson.JsonPrimitive +import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -43,6 +46,9 @@ internal class TimeseriesEndToEndTest { @Mock lateinit var mockTimeProvider: TimeProvider + @Mock + lateinit var mockDatadogContext: DatadogContext + private lateinit var csvContent: String private lateinit var emitted: List @@ -58,7 +64,8 @@ internal class TimeseriesEndToEndTest { sessionType = RumSessionType.USER, totalRamBytes = TOTAL_RAM_BYTES, bufferSize = BATCH_SIZE, - timeProvider = mockTimeProvider + timeProvider = mockTimeProvider, + datadogContext = mockDatadogContext ) testedTimeseries.onSessionStart() testedTimeseries.onSessionStop() @@ -107,7 +114,7 @@ internal class TimeseriesEndToEndTest { fun `M emit valid timeseries id W any batch`() { // Each emitted event must carry a non-blank UUID; randomness is not asserted. for (event in emitted) { - val id = event.getAsJsonObject("timeseries").get("id").asString + val id = event.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES).get("id").asString assertThat(UUID.fromString(id)).isNotNull } } @@ -115,16 +122,56 @@ internal class TimeseriesEndToEndTest { @Test fun `M produce monotonic start lt or eq to end W any batch`() { for (event in emitted) { - val ts = event.getAsJsonObject("timeseries") + val ts = event.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) assertThat(ts.get("start").asLong).isLessThanOrEqualTo(ts.get("end").asLong) } } - private fun metricNameOf(event: JsonObject): String = event.getAsJsonObject("timeseries").get("name").asString + @Test + fun `M add tags to every emitted event W additionalAttributes is not empty`( + forge: Forge + ) { + // Given + val fakeAdditionalAttributes = forge.aMap { anAlphabeticalString() to anAlphabeticalString() } + val testedTimeseries = CsvTimeseries.create( + csvContent = csvContent, + sessionId = SESSION_ID, + applicationId = APPLICATION_ID, + sessionType = RumSessionType.USER, + totalRamBytes = TOTAL_RAM_BYTES, + bufferSize = BATCH_SIZE, + timeProvider = mockTimeProvider, + datadogContext = mockDatadogContext, + additionalAttributes = fakeAdditionalAttributes + ) + + // When + testedTimeseries.onSessionStart() + testedTimeseries.onSessionStop() + + // Then + val events = testedTimeseries.captured + assertThat(events).isNotEmpty + for (event in events) { + val tags = event.getAsJsonObject( + TimeseriesAttributes.KEY_TIMESERIES + ).getAsJsonObject(TimeseriesAttributes.KEY_TAGS) + assertThat(tags).describedAs("tags on %s", event).isNotNull + for ((key, value) in fakeAdditionalAttributes) { + assertThat(tags.get(key)?.asString).isEqualTo(value) + } + } + } + + private fun metricNameOf(event: JsonObject): String = + event.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) + .get(TimeseriesAttributes.KEY_NAME) + .asString private fun maskTimeseriesId(actual: JsonObject): JsonObject { val copy = actual.deepCopy() - copy.getAsJsonObject("timeseries").addProperty("id", MASKED_UUID) + copy.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) + .addProperty(KEY_ID, MASKED_UUID) return copy } @@ -236,5 +283,7 @@ internal class TimeseriesEndToEndTest { private const val NAME_CPU: String = "cpu" private const val MASKED_UUID: String = "00000000-0000-0000-0000-000000000000" private const val FP_TOLERANCE: Double = 1e-9 + + private const val KEY_ID: String = "id" } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt index 0e3cd6db7e..de4244fca2 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt @@ -6,6 +6,7 @@ package com.datadog.android.rum.internal.timeseries.serializer +import com.datadog.android.api.context.DatadogContext import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.assertj.TimeseriesCpuEventAssert.Companion.assertThat @@ -15,6 +16,7 @@ import com.datadog.android.rum.model.TimeseriesCpuEvent import com.datadog.android.rum.utils.forge.Configurator import com.google.gson.JsonObject import fr.xgouchet.elmyr.annotation.DoubleForgery +import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery @@ -53,6 +55,9 @@ internal class CpuEventSerializerTest { @IntForgery(min = 2, max = 9) var fakePrecision: Int = 0 + @Forgery + lateinit var fakeDatadogContext: DatadogContext + val fakeScale: Long get() = 10.0.pow(fakePrecision.toDouble()).toLong() val mockTimeProvider: TimeProvider = mock { @@ -62,12 +67,14 @@ internal class CpuEventSerializerTest { private fun testedSerializer( useDeltaCompression: Boolean = false, sessionType: RumSessionType = RumSessionType.USER, - precision: Int = fakePrecision + precision: Int = fakePrecision, + additionalAttributes: Map = emptyMap() ) = CpuEventSerializer( sessionId = fakeSessionId, applicationId = fakeApplicationId, sessionType = sessionType, timeProvider = mockTimeProvider, + additionalAttributes = additionalAttributes, useDeltaCompression = useDeltaCompression, precision = precision ) @@ -75,7 +82,7 @@ internal class CpuEventSerializerTest { @Test fun `M return null W serialize() { empty input }`() { // When - val result = testedSerializer().serialize(emptyList()) + val result = testedSerializer().serialize(fakeDatadogContext, emptyList()) // Then assertThat(result).isNull() @@ -93,7 +100,7 @@ internal class CpuEventSerializerTest { ) // When - val json = testedSerializer().serialize(samples).failIfNull() + val json = testedSerializer().serialize(fakeDatadogContext, samples).failIfNull() // Then assertThat(json) @@ -105,6 +112,8 @@ internal class CpuEventSerializerTest { .hasApplicationId(fakeApplicationId) .hasSessionType(TimeseriesCpuEvent.Type.USER) .hasTimeseriesSchema(TimeseriesCpuEvent.Schema.OBJECT) + .hasService(fakeDatadogContext.service) + .hasVersion(fakeDatadogContext.version) } @Test @@ -117,13 +126,55 @@ internal class CpuEventSerializerTest { // When val json = testedSerializer(sessionType = RumSessionType.SYNTHETICS) - .serialize(samples) + .serialize(fakeDatadogContext, samples) .failIfNull() // Then assertThat(json).hasSessionType(TimeseriesCpuEvent.Type.SYNTHETICS) } + @Test + fun `M add tags W serialize() { additionalAttributes non-empty }`( + @DoubleForgery(min = 0.0, max = 100.0) fakeCpuValue: Double, + @LongForgery(min = 1L) fakeTs: Long, + @StringForgery fakeTagKey: String, + @StringForgery fakeTagValue: String + ) { + // Given + val samples = listOf(DataPoint(fakeTs, fakeCpuValue)) + + // When + val json = testedSerializer(additionalAttributes = mapOf(fakeTagKey to fakeTagValue)) + .serialize(fakeDatadogContext, samples) + .failIfNull() + + // Then + val tags = json + .getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) + .getAsJsonObject(TimeseriesAttributes.KEY_TAGS) + assertThat(tags.get(fakeTagKey).asString).isEqualTo(fakeTagValue) + } + + @Test + fun `M not add tags W serialize() { additionalAttributes empty }`( + @DoubleForgery(min = 0.0, max = 100.0) fakeCpuValue: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // Given + val samples = listOf(DataPoint(fakeTs, fakeCpuValue)) + + // When + val json = testedSerializer(additionalAttributes = emptyMap()) + .serialize(fakeDatadogContext, samples) + .failIfNull() + + // Then + assertThat( + json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) + .has(TimeseriesAttributes.KEY_TAGS) + ).isFalse() + } + @Test fun `M produce delta-scalar schema W serialize() { compression on, multi-sample }`( @DoubleForgery(min = 0.001, max = 100.0) fakeFirstCpu: Double, @@ -143,7 +194,7 @@ internal class CpuEventSerializerTest { // When val json = testedSerializer(useDeltaCompression = true) - .serialize(samples) + .serialize(fakeDatadogContext, samples) .failIfNull() // Then @@ -151,7 +202,7 @@ internal class CpuEventSerializerTest { assertThatDelta(json) .hasTimeseriesSchema(TimeseriesCpuEvent.Schema.DELTA_SCALAR) .hasDeltaPrecision(fakePrecision) - .hasDeltaResolution(RESOLUTION_NS) + .hasDeltaResolution(TimeseriesAttributes.NS) .hasDeltaTsValues(fakeTs1, fakeTimestampStep, fakeTimestampStep) .hasDeltaValueAt(0, scaled[0]) .hasDeltaValueAt(1, scaled[1] - scaled[0]) @@ -165,7 +216,7 @@ internal class CpuEventSerializerTest { ) { // When val json = testedSerializer(useDeltaCompression = true) - .serialize(listOf(DataPoint(fakeTs, fakeCpu))) + .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeCpu))) .failIfNull() // Then @@ -186,7 +237,7 @@ internal class CpuEventSerializerTest { ) // When - val json = testedSerializer().serialize(samples).failIfNull() + val json = testedSerializer().serialize(fakeDatadogContext, samples).failIfNull() // Then assertThat(json) @@ -195,7 +246,6 @@ internal class CpuEventSerializerTest { private companion object { private const val CPU_OFFSET: Double = 0.0001 - private const val RESOLUTION_NS: String = "ns" private const val NON_NULL_JSON_ERROR: String = "expected non-null json" private fun JsonObject?.failIfNull(): JsonObject { if (this == null) error(NON_NULL_JSON_ERROR) else return this diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt index de4648725b..7d7b294a5d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt @@ -6,12 +6,14 @@ package com.datadog.android.rum.internal.timeseries.serializer +import com.datadog.android.api.context.DatadogContext import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.internal.timeseries.DataPoint import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.utils.forge.Configurator import fr.xgouchet.elmyr.annotation.DoubleForgery +import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery @@ -60,6 +62,9 @@ internal class MemoryEventSerializerTest { @IntForgery(min = 2, max = 9) var fakePrecision: Int = 0 + @Forgery + lateinit var fakeDatadogContext: DatadogContext + val fakeScale: Long get() = 10.0.pow(fakePrecision.toDouble()).toLong() @BeforeEach @@ -71,13 +76,15 @@ internal class MemoryEventSerializerTest { useDeltaCompression: Boolean = false, sessionType: RumSessionType = RumSessionType.USER, totalRamBytes: Long = fakeTotalRamBytes, - precision: Int = fakePrecision + precision: Int = fakePrecision, + additionalAttributes: Map = emptyMap() ) = MemoryEventSerializer( sessionId = fakeSessionId, applicationId = fakeApplicationId, sessionType = sessionType, totalRamBytes = totalRamBytes, timeProvider = mockTimeProvider, + additionalAttributes = additionalAttributes, useDeltaCompression = useDeltaCompression, precision = precision ) @@ -85,7 +92,7 @@ internal class MemoryEventSerializerTest { @Test fun `M return null W serialize() { empty input }`() { // When - val result = testedSerializer().serialize(emptyList()) + val result = testedSerializer().serialize(fakeDatadogContext, emptyList()) // Then assertThat(result).isNull() @@ -98,7 +105,7 @@ internal class MemoryEventSerializerTest { ) { // When val result = testedSerializer(totalRamBytes = 0L) - .serialize(listOf(DataPoint(fakeTs, fakeMemory))) + .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeMemory))) // Then assertThat(result).isNull() @@ -112,7 +119,7 @@ internal class MemoryEventSerializerTest { ) { // When val result = testedSerializer(totalRamBytes = fakeNegativeRam) - .serialize(listOf(DataPoint(fakeTs, fakeMemory))) + .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeMemory))) // Then assertThat(result).isNull() @@ -130,15 +137,17 @@ internal class MemoryEventSerializerTest { ) // When - val result = testedSerializer().serialize(samples) + val result = testedSerializer().serialize(fakeDatadogContext, samples) // Then val json = checkNotNull(result) - val timeseries = json.getAsJsonObject(KEY_TIMESERIES) - assertThat(timeseries.get(KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_OBJECT) + val timeseries = json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) + assertThat(timeseries.get(TimeseriesAttributes.KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_OBJECT) assertThat(timeseries.get(KEY_START).asLong).isEqualTo(fakeTs) assertThat(timeseries.get(KEY_END).asLong).isEqualTo(fakeTs + 1L) assertDoesNotThrow { UUID.fromString(timeseries.get(KEY_ID).asString) } + assertThat(json.get(KEY_SERVICE).asString).isEqualTo(fakeDatadogContext.service) + assertThat(json.get(KEY_VERSION).asString).isEqualTo(fakeDatadogContext.version) val parsed = TimeseriesMemoryEvent.fromJsonObject(json) assertThat(parsed.timeseries.data).hasSize(2) @@ -155,13 +164,49 @@ internal class MemoryEventSerializerTest { ) { // When val result = testedSerializer(sessionType = RumSessionType.SYNTHETICS) - .serialize(listOf(DataPoint(fakeTs, fakeMemory), DataPoint(fakeTs + 1L, fakeMemory))) + .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeMemory), DataPoint(fakeTs + 1L, fakeMemory))) // Then val json = checkNotNull(result) assertThat(json.getAsJsonObject(KEY_SESSION).get(KEY_TYPE).asString).isEqualTo(VALUE_TYPE_SYNTHETICS) } + @Test + fun `M add tags W serialize() { additionalAttributes non-empty }`( + @DoubleForgery(min = 1.0) fakeMemory: Double, + @LongForgery(min = 1L) fakeTs: Long, + @StringForgery fakeTagKey: String, + @StringForgery fakeTagValue: String + ) { + // When + val result = testedSerializer(additionalAttributes = mapOf(fakeTagKey to fakeTagValue)) + .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeMemory))) + + // Then + val json = checkNotNull(result) + val tags = json + .getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) + .getAsJsonObject(TimeseriesAttributes.KEY_TAGS) + assertThat(tags.get(fakeTagKey).asString).isEqualTo(fakeTagValue) + } + + @Test + fun `M not add tags W serialize() { additionalAttributes empty }`( + @DoubleForgery(min = 1.0) fakeMemory: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // When + val result = testedSerializer(additionalAttributes = emptyMap()) + .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeMemory))) + + // Then + val json = checkNotNull(result) + assertThat( + json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) + .has(TimeseriesAttributes.KEY_TAGS) + ).isFalse() + } + @Test fun `M produce delta-object schema W serialize() { compression on, multi-sample }`( @DoubleForgery(min = 1.0, max = 1_000_000.0) fakeMem1: Double, @@ -181,23 +226,23 @@ internal class MemoryEventSerializerTest { ) // When - val result = testedSerializer(useDeltaCompression = true).serialize(samples) + val result = testedSerializer(useDeltaCompression = true).serialize(fakeDatadogContext, samples) // Then val json = checkNotNull(result) - val timeseries = json.getAsJsonObject(KEY_TIMESERIES) - assertThat(timeseries.get(KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_DELTA_OBJECT) - val data = timeseries.get(KEY_DATA).asJsonObject - assertThat(data.get(KEY_PRECISION).asInt).isEqualTo(fakePrecision) - assertThat(data.get(KEY_RESOLUTION).asString).isEqualTo(VALUE_RESOLUTION_NS) + val timeseries = json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) + assertThat(timeseries.get(TimeseriesAttributes.KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_DELTA_OBJECT) + val data = timeseries.get(TimeseriesAttributes.KEY_DATA).asJsonObject + assertThat(data.get(TimeseriesAttributes.KEY_PRECISION).asInt).isEqualTo(fakePrecision) + assertThat(data.get(TimeseriesAttributes.KEY_RESOLUTION).asString).isEqualTo(TimeseriesAttributes.NS) - val tsArray = data.get(KEY_TS).asJsonArray + val tsArray = data.get(TimeseriesAttributes.KEY_TS).asJsonArray assertThat(tsArray[0].asLong).isEqualTo(fakeTs1) assertThat(tsArray[1].asLong).isEqualTo(fakeTimestampStep) assertThat(tsArray[2].asLong).isEqualTo(fakeTimestampStep) - val maxArr = data.get(KEY_MEMORY_FOOTPRINT).asJsonArray - val pctArr = data.get(KEY_MEMORY_PERCENT).asJsonArray + val maxArr = data.get(TimeseriesAttributes.KEY_MEMORY_FOOTPRINT).asJsonArray + val pctArr = data.get(TimeseriesAttributes.KEY_MEMORY_PERCENT).asJsonArray val scaledMax = listOf(fakeMem1, fakeMem2, fakeMem3).map { (it * fakeScale).roundToLong() } val scaledPct = listOf(fakeMem1, fakeMem2, fakeMem3) .map { (it / fakeTotalRamBytes * PERCENT_FACTOR * fakeScale).roundToLong() } @@ -218,13 +263,13 @@ internal class MemoryEventSerializerTest { ) { // When val result = testedSerializer(useDeltaCompression = true) - .serialize(listOf(DataPoint(fakeTs, fakeMemory))) + .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeMemory))) // Then val json = checkNotNull(result) - val timeseries = json.getAsJsonObject(KEY_TIMESERIES) - assertThat(timeseries.get(KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_OBJECT) - assertThat(timeseries.get(KEY_DATA).isJsonArray).isTrue() + val timeseries = json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) + assertThat(timeseries.get(TimeseriesAttributes.KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_OBJECT) + assertThat(timeseries.get(TimeseriesAttributes.KEY_DATA).isJsonArray).isTrue() } private companion object { @@ -232,24 +277,17 @@ internal class MemoryEventSerializerTest { private const val MEMORY_OFFSET: Double = 0.0001 // JSON keys - private const val KEY_TIMESERIES: String = "timeseries" - private const val KEY_SCHEMA: String = "schema" - private const val KEY_DATA: String = "data" private const val KEY_SESSION: String = "session" private const val KEY_TYPE: String = "type" private const val KEY_ID: String = "id" private const val KEY_START: String = "start" private const val KEY_END: String = "end" - private const val KEY_PRECISION: String = "precision" - private const val KEY_RESOLUTION: String = "resolution" - private const val KEY_TS: String = "ts" - private const val KEY_MEMORY_FOOTPRINT: String = "memory_footprint" - private const val KEY_MEMORY_PERCENT: String = "memory_percent" + private const val KEY_SERVICE: String = "service" + private const val KEY_VERSION: String = "version" // JSON values private const val VALUE_SCHEMA_OBJECT: String = "object" private const val VALUE_SCHEMA_DELTA_OBJECT: String = "delta-object" private const val VALUE_TYPE_SYNTHETICS: String = "synthetics" - private const val VALUE_RESOLUTION_NS: String = "ns" } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt index 3365add0a7..f64cae8b6f 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt @@ -81,4 +81,53 @@ internal class TimeseriesConfigurationTest { // Then assertThat(config.collectInBackground).isTrue() } + + @Test + fun `M default additionalAttributes to empty W built with no args`() { + // When + val config = TimeseriesConfiguration.Builder().build() + + // Then + assertThat(config.additionalAttributes).isEmpty() + } + + @Test + fun `M store attribute W addAttribute`() { + // When + val config = TimeseriesConfiguration.Builder() + .addAttribute("key1", "value1") + .addAttribute("key2", "value2") + .build() + + // Then + assertThat(config.additionalAttributes).containsExactlyInAnyOrderEntriesOf( + mapOf("key1" to "value1", "key2" to "value2") + ) + } + + @Test + fun `M overwrite value W addAttribute called twice with same key`() { + // When + val config = TimeseriesConfiguration.Builder() + .addAttribute("key", "first") + .addAttribute("key", "second") + .build() + + // Then + assertThat(config.additionalAttributes).containsExactlyEntriesOf(mapOf("key" to "second")) + } + + @Test + fun `M keep built attributes unchanged W addAttribute after build`() { + // Given + val builder = TimeseriesConfiguration.Builder() + .addAttribute("key1", "value1") + val config = builder.build() + + // When + builder.addAttribute("key2", "value2") + + // Then + assertThat(config.additionalAttributes).containsExactlyEntriesOf(mapOf("key1" to "value1")) + } } From 9830aa1cdfd4ece61fbff2d2463e4746180b12b9 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Tue, 14 Jul 2026 13:43:29 +0100 Subject: [PATCH 19/32] RUM-16325: Updating to recent schema --- features/dd-sdk-android-rum/api/apiSurface | 43 ++--- .../api/dd-sdk-android-rum.api | 173 +++++++----------- .../main/json/rum/timeseries-cpu-schema.json | 49 ++--- .../json/rum/timeseries-memory-schema.json | 57 +++--- .../android/rum/internal/RumFeature.kt | 8 +- .../internal/timeseries/DeltaCompression.kt | 27 --- .../serializer/CpuEventSerializer.kt | 73 +------- .../timeseries/serializer/JsonSerializer.kt | 14 -- .../serializer/MemoryEventSerializer.kt | 89 ++------- .../rum/timeseries/TimeseriesConfiguration.kt | 32 +--- .../rum/assertj/TimeseriesCpuEventAssert.kt | 86 +-------- .../internal/timeseries/csv/CsvTimeseries.kt | 12 +- .../timeseries/csv/TimeseriesEndToEndTest.kt | 39 +--- .../serializer/CpuEventSerializerTest.kt | 113 +----------- .../serializer/MemoryEventSerializerTest.kt | 130 +------------ .../timeseries/TimeseriesConfigurationTest.kt | 49 ----- .../timeseries/expected_cpu_batch1.json | 16 +- .../timeseries/expected_cpu_batch2.json | 16 +- .../timeseries/expected_memory_batch1.json | 17 +- .../timeseries/expected_memory_batch2.json | 17 +- 20 files changed, 227 insertions(+), 833 deletions(-) delete mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DeltaCompression.kt diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index 1dec433c85..38844312cc 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -310,9 +310,6 @@ class com.datadog.android.rum.timeseries.TimeseriesConfiguration class Builder fun setBufferSize(Int): Builder fun setIntervalMs(Long): Builder - fun collectInBackground(Boolean): Builder - fun useDeltaCompression(Boolean): Builder - fun addAttribute(String, String): Builder fun build(): TimeseriesConfiguration companion object val DEFAULT: TimeseriesConfiguration @@ -1767,24 +1764,25 @@ data class com.datadog.android.rum.model.TimeseriesCpuEvent fun fromJson(kotlin.String): Session fun fromJsonObject(com.google.gson.JsonObject): Session data class Timeseries - constructor(kotlin.String, Schema, kotlin.Long, kotlin.Long, kotlin.collections.List) + constructor(kotlin.String, kotlin.Long, kotlin.Long, Data) val name: kotlin.String + val schema: kotlin.String fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Timeseries fun fromJsonObject(com.google.gson.JsonObject): Timeseries data class Data - constructor(kotlin.Long, DataPoint) + constructor(kotlin.collections.List, Values) fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Data fun fromJsonObject(com.google.gson.JsonObject): Data - data class DataPoint - constructor(kotlin.Number) + data class Values + constructor(kotlin.collections.List) fun toJson(): com.google.gson.JsonElement companion object - fun fromJson(kotlin.String): DataPoint - fun fromJsonObject(com.google.gson.JsonObject): DataPoint + fun fromJson(kotlin.String): Values + fun fromJsonObject(com.google.gson.JsonObject): Values enum Source constructor(kotlin.String) - ANDROID @@ -1808,13 +1806,6 @@ data class com.datadog.android.rum.model.TimeseriesCpuEvent fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Type - enum Schema - constructor(kotlin.String) - - OBJECT - - DELTA_SCALAR - fun toJson(): com.google.gson.JsonElement - companion object - fun fromJson(kotlin.String): Schema data class com.datadog.android.rum.model.TimeseriesMemoryEvent constructor(Dd, Application, Session, Source, kotlin.Long, kotlin.String? = null, kotlin.String? = null, Timeseries) val type: kotlin.String @@ -1841,24 +1832,25 @@ data class com.datadog.android.rum.model.TimeseriesMemoryEvent fun fromJson(kotlin.String): Session fun fromJsonObject(com.google.gson.JsonObject): Session data class Timeseries - constructor(kotlin.String, Schema, kotlin.Long, kotlin.Long, kotlin.collections.List) + constructor(kotlin.String, kotlin.Long, kotlin.Long, Data) val name: kotlin.String + val schema: kotlin.String fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Timeseries fun fromJsonObject(com.google.gson.JsonObject): Timeseries data class Data - constructor(kotlin.Long, DataPoint) + constructor(kotlin.collections.List, Values) fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Data fun fromJsonObject(com.google.gson.JsonObject): Data - data class DataPoint - constructor(kotlin.Number, kotlin.Number) + data class Values + constructor(kotlin.collections.List, kotlin.collections.List) fun toJson(): com.google.gson.JsonElement companion object - fun fromJson(kotlin.String): DataPoint - fun fromJsonObject(com.google.gson.JsonObject): DataPoint + fun fromJson(kotlin.String): Values + fun fromJsonObject(com.google.gson.JsonObject): Values enum Source constructor(kotlin.String) - ANDROID @@ -1882,13 +1874,6 @@ data class com.datadog.android.rum.model.TimeseriesMemoryEvent fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Type - enum Schema - constructor(kotlin.String) - - OBJECT - - DELTA_OBJECT - fun toJson(): com.google.gson.JsonElement - companion object - fun fromJson(kotlin.String): Schema data class com.datadog.android.rum.model.ViewEvent constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, ViewEventSession, ViewEventSource? = null, ViewEventView, Usr? = null, Account? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Container? = null, Context? = null, Privacy? = null) val type: kotlin.String diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index 852c6b87df..4462fcba55 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -4961,16 +4961,16 @@ public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Companion { public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Data { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data$Companion; - public fun (JLcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint;)V - public final fun component1 ()J - public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; - public final fun copy (JLcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data;JLcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; + public fun (Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values; + public final fun copy (Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data;Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; - public final fun getDataPoint ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; - public final fun getTimestamp ()J + public final fun getTimestamps ()Ljava/util/List; + public final fun getValues ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; @@ -4981,26 +4981,6 @@ public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Data$Compani public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; } -public final class com/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint { - public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint$Companion; - public fun (Ljava/lang/Number;)V - public final fun component1 ()Ljava/lang/Number; - public final fun copy (Ljava/lang/Number;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; - public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; - public final fun getCpuUsage ()Ljava/lang/Number; - public fun hashCode ()I - public final fun toJson ()Lcom/google/gson/JsonElement; - public fun toString ()Ljava/lang/String; -} - -public final class com/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DataPoint; -} - public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Dd { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd$Companion; public fun ()V @@ -5015,20 +4995,6 @@ public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Dd$Companion public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; } -public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Schema : java/lang/Enum { - public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema$Companion; - public static final field DELTA_SCALAR Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; - public static final field OBJECT Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; - public final fun toJson ()Lcom/google/gson/JsonElement; - public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; - public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; -} - -public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Schema$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; -} - public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Session { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session$Companion; public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type;)V @@ -5075,22 +5041,21 @@ public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Source$Compa public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries$Companion; - public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema;JJLjava/util/List;)V + public fun (Ljava/lang/String;JJLcom/datadog/android/rum/model/TimeseriesCpuEvent$Data;)V public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; + public final fun component2 ()J public final fun component3 ()J - public final fun component4 ()J - public final fun component5 ()Ljava/util/List; - public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema;JJLjava/util/List;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema;JJLjava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; + public final fun component4 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; + public final fun copy (Ljava/lang/String;JJLcom/datadog/android/rum/model/TimeseriesCpuEvent$Data;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;Ljava/lang/String;JJLcom/datadog/android/rum/model/TimeseriesCpuEvent$Data;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; - public final fun getData ()Ljava/util/List; + public final fun getData ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; public final fun getEnd ()J public final fun getId ()Ljava/lang/String; public final fun getName ()Ljava/lang/String; - public final fun getSchema ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Schema; + public final fun getSchema ()Ljava/lang/String; public final fun getStart ()J public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; @@ -5117,6 +5082,26 @@ public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Type$Compani public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; } +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Values { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values$Companion; + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values;Ljava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values; + public final fun getCpuUsage ()Ljava/util/List; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Values$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values; +} + public final class com/datadog/android/rum/model/TimeseriesMemoryEvent { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Companion; public fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;)V @@ -5175,16 +5160,16 @@ public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Companion public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Data { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data$Companion; - public fun (JLcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint;)V - public final fun component1 ()J - public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; - public final fun copy (JLcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data;JLcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; + public fun (Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values; + public final fun copy (Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data;Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; - public final fun getDataPoint ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; - public final fun getTimestamp ()J + public final fun getTimestamps ()Ljava/util/List; + public final fun getValues ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; @@ -5195,28 +5180,6 @@ public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Data$Comp public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; } -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint { - public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint$Companion; - public fun (Ljava/lang/Number;Ljava/lang/Number;)V - public final fun component1 ()Ljava/lang/Number; - public final fun component2 ()Ljava/lang/Number; - public final fun copy (Ljava/lang/Number;Ljava/lang/Number;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint;Ljava/lang/Number;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; - public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; - public final fun getMemoryFootprint ()Ljava/lang/Number; - public final fun getMemoryPercent ()Ljava/lang/Number; - public fun hashCode ()I - public final fun toJson ()Lcom/google/gson/JsonElement; - public fun toString ()Ljava/lang/String; -} - -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DataPoint; -} - public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Dd { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd$Companion; public fun ()V @@ -5231,20 +5194,6 @@ public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Dd$Compan public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; } -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Schema : java/lang/Enum { - public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema$Companion; - public static final field DELTA_OBJECT Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; - public static final field OBJECT Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; - public final fun toJson ()Lcom/google/gson/JsonElement; - public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; - public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; -} - -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Schema$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; -} - public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Session { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session$Companion; public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type;)V @@ -5291,22 +5240,21 @@ public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Source$Co public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries$Companion; - public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema;JJLjava/util/List;)V + public fun (Ljava/lang/String;JJLcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data;)V public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; + public final fun component2 ()J public final fun component3 ()J - public final fun component4 ()J - public final fun component5 ()Ljava/util/List; - public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema;JJLjava/util/List;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema;JJLjava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; + public final fun component4 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; + public final fun copy (Ljava/lang/String;JJLcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;Ljava/lang/String;JJLcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; - public final fun getData ()Ljava/util/List; + public final fun getData ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; public final fun getEnd ()J public final fun getId ()Ljava/lang/String; public final fun getName ()Ljava/lang/String; - public final fun getSchema ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Schema; + public final fun getSchema ()Ljava/lang/String; public final fun getStart ()J public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; @@ -5333,6 +5281,28 @@ public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Type$Comp public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; } +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Values { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values$Companion; + public fun (Ljava/util/List;Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/util/List;Ljava/util/List;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values; + public final fun getMemoryFootprint ()Ljava/util/List; + public final fun getMemoryPercent ()Ljava/util/List; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Values$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values; +} + public final class com/datadog/android/rum/model/ViewEvent { public static final field Companion Lcom/datadog/android/rum/model/ViewEvent$Companion; public fun (JLcom/datadog/android/rum/model/ViewEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource;Lcom/datadog/android/rum/model/ViewEvent$ViewEventView;Lcom/datadog/android/rum/model/ViewEvent$Usr;Lcom/datadog/android/rum/model/ViewEvent$Account;Lcom/datadog/android/rum/model/ViewEvent$Connectivity;Lcom/datadog/android/rum/model/ViewEvent$Display;Lcom/datadog/android/rum/model/ViewEvent$Synthetics;Lcom/datadog/android/rum/model/ViewEvent$CiTest;Lcom/datadog/android/rum/model/ViewEvent$Os;Lcom/datadog/android/rum/model/ViewEvent$Device;Lcom/datadog/android/rum/model/ViewEvent$Dd;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Container;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Privacy;)V @@ -8645,12 +8615,9 @@ public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration { public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder { public fun ()V - public final fun addAttribute (Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; public final fun build ()Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration; - public final fun collectInBackground (Z)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; public final fun setBufferSize (I)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; public final fun setIntervalMs (J)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; - public final fun useDeltaCompression (Z)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; } public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Companion { diff --git a/features/dd-sdk-android-rum/src/main/json/rum/timeseries-cpu-schema.json b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-cpu-schema.json index 7d08a7dfbd..bc7c72f850 100644 --- a/features/dd-sdk-android-rum/src/main/json/rum/timeseries-cpu-schema.json +++ b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-cpu-schema.json @@ -111,7 +111,7 @@ "schema": { "type": "string", "description": "Wire-shape discriminator for the data field", - "enum": ["object", "delta-scalar"], + "const": "object-v2", "readOnly": true }, "start": { @@ -125,33 +125,36 @@ "readOnly": true }, "data": { - "type": "array", - "description": "Array of CPU data points", - "items": { - "type": "object", - "description": "A single CPU data point", - "required": ["timestamp", "data_point"], - "properties": { - "timestamp": { + "type": "object", + "description": "Flattened CPU data points", + "required": ["timestamps", "values"], + "properties": { + "timestamps": { + "type": "array", + "description": "Sample timestamps in nanoseconds from epoch", + "items": { "type": "integer", - "description": "Sample timestamp in nanoseconds from epoch", "readOnly": true }, - "data_point": { - "type": "object", - "description": "CPU measurements for this sample", - "required": ["cpu_usage"], - "properties": { - "cpu_usage": { + "readOnly": true + }, + "values": { + "type": "object", + "description": "CPU measurements, aligned index-for-index with timestamps", + "required": ["cpu_usage"], + "properties": { + "cpu_usage": { + "type": "array", + "description": "CPU usage as a percentage (0.0 to 100.0)", + "items": { "type": "number", - "description": "CPU usage as a percentage (0.0 to 100.0)", "readOnly": true - } - }, - "readOnly": true - } - }, - "readOnly": true + }, + "readOnly": true + } + }, + "readOnly": true + } }, "readOnly": true } diff --git a/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json index fb240441f2..9e642e1a8d 100644 --- a/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json +++ b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json @@ -111,7 +111,7 @@ "schema": { "type": "string", "description": "Wire-shape discriminator for the data field", - "enum": ["object", "delta-object"], + "const": "object-v2", "readOnly": true }, "start": { @@ -125,38 +125,45 @@ "readOnly": true }, "data": { - "type": "array", - "description": "Array of memory data points", - "items": { - "type": "object", - "description": "A single memory data point", - "required": ["timestamp", "data_point"], - "properties": { - "timestamp": { + "type": "object", + "description": "Flattened memory data points", + "required": ["timestamps", "values"], + "properties": { + "timestamps": { + "type": "array", + "description": "Sample timestamps in nanoseconds from epoch", + "items": { "type": "integer", - "description": "Sample timestamp in nanoseconds from epoch", "readOnly": true }, - "data_point": { - "type": "object", - "description": "Memory measurements for this sample", - "required": ["memory_footprint", "memory_percent"], - "properties": { - "memory_footprint": { + "readOnly": true + }, + "values": { + "type": "object", + "description": "Memory measurements, aligned index-for-index with timestamps", + "required": ["memory_footprint", "memory_percent"], + "properties": { + "memory_footprint": { + "type": "array", + "description": "Physical memory footprint of the process in kilobytes", + "items": { "type": "number", - "description": "Physical memory footprint of the process in bytes", "readOnly": true }, - "memory_percent": { + "readOnly": true + }, + "memory_percent": { + "type": "array", + "description": "Memory footprint as a percentage of total device RAM", + "items": { "type": "number", - "description": "Memory footprint as a percentage of total device RAM", "readOnly": true - } - }, - "readOnly": true - } - }, - "readOnly": true + }, + "readOnly": true + } + }, + "readOnly": true + } }, "readOnly": true } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index 365646442d..783cb8eff7 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -463,9 +463,7 @@ internal class RumFeature( sessionId = sessionId, applicationId = applicationId, sessionType = sessionType, - timeProvider = sdkCore.timeProvider, - useDeltaCompression = configuration.useDeltaCompression, - additionalAttributes = configuration.additionalAttributes + timeProvider = sdkCore.timeProvider ), dataWriter = dataWriter, internalLogger = sdkCore.internalLogger, @@ -485,9 +483,7 @@ internal class RumFeature( applicationId = applicationId, sessionType = sessionType, totalRamBytes = totalRamBytes, - timeProvider = sdkCore.timeProvider, - useDeltaCompression = configuration.useDeltaCompression, - additionalAttributes = configuration.additionalAttributes + timeProvider = sdkCore.timeProvider ), dataWriter = dataWriter, internalLogger = sdkCore.internalLogger, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DeltaCompression.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DeltaCompression.kt deleted file mode 100644 index 2cac530389..0000000000 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DeltaCompression.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ -package com.datadog.android.rum.internal.timeseries - -import com.google.gson.JsonArray -import kotlin.math.roundToLong - -internal object DeltaCompression { - - fun roundToLongSafely(value: Double, scale: Long, replaceNaNWith: Long = 0L): Long = try { - (value * scale).roundToLong() - } catch (_: IllegalArgumentException) { - replaceNaNWith - } - - fun List.mapToDeltaCompressed(extractor: (T) -> Long) = JsonArray(size).also { - for (i in indices) { - @Suppress("UnsafeThirdPartyFunctionCall") // NoSuchElementException could not be thrown here - it.add(if (i == 0) extractor(get(i)) else extractor(get(i)) - extractor(get(i - 1))) - } - } - - const val PRECISION = 4 -} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt index e55e81b159..64d7fdf040 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt @@ -10,93 +10,38 @@ import com.datadog.android.api.context.DatadogContext import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.internal.timeseries.DataPoint -import com.datadog.android.rum.internal.timeseries.DeltaCompression -import com.datadog.android.rum.internal.timeseries.DeltaCompression.mapToDeltaCompressed -import com.datadog.android.rum.internal.timeseries.DeltaCompression.roundToLongSafely import com.datadog.android.rum.internal.toTimeseriesCpuSessionType import com.datadog.android.rum.model.TimeseriesCpuEvent import com.google.gson.JsonObject import java.util.UUID -import kotlin.math.pow @Suppress("LongParameterList") internal class CpuEventSerializer( private val sessionId: String, private val applicationId: String, private val sessionType: RumSessionType, - private val timeProvider: TimeProvider, - private val additionalAttributes: Map, - private val useDeltaCompression: Boolean = false, - private val precision: Int = DeltaCompression.PRECISION + private val timeProvider: TimeProvider ) : JsonSerializer { override fun serialize(datadogContext: DatadogContext, dataPoints: List>): JsonObject? { if (dataPoints.isEmpty()) return null - val data = dataPoints.map { sample -> - TimeseriesCpuEvent.Data( - timestamp = sample.timestampNs, - dataPoint = TimeseriesCpuEvent.DataPoint(sample.value) - ) - } - val start = data.firstOrNull()?.timestamp ?: 0L - val end = data.lastOrNull()?.timestamp ?: 0L - val deltaEncoded = if (useDeltaCompression) encodeDelta(data) else null - val schema = if (deltaEncoded != null) { - TimeseriesCpuEvent.Schema.DELTA_SCALAR - } else { - TimeseriesCpuEvent.Schema.OBJECT - } - val json = TimeseriesCpuEvent( + return TimeseriesCpuEvent( dd = TimeseriesCpuEvent.Dd(), application = TimeseriesCpuEvent.Application(id = applicationId), - session = TimeseriesCpuEvent.Session( - id = sessionId, - type = sessionType.toTimeseriesCpuSessionType() - ), + session = TimeseriesCpuEvent.Session(id = sessionId, type = sessionType.toTimeseriesCpuSessionType()), source = TimeseriesCpuEvent.Source.ANDROID, date = timeProvider.getDeviceTimestampMillis(), version = datadogContext.version, service = datadogContext.service, timeseries = TimeseriesCpuEvent.Timeseries( id = UUID.randomUUID().toString(), - schema = schema, - start = start, - end = end, - data = data + start = dataPoints.firstOrNull()?.timestampNs ?: 0L, + end = dataPoints.lastOrNull()?.timestampNs ?: 0L, + data = TimeseriesCpuEvent.Data( + timestamps = dataPoints.map { it.timestampNs }, + values = TimeseriesCpuEvent.Values(dataPoints.map { it.value }) + ) ) ).toJson().asJsonObject - - // - val timeseriesJson = json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - if (deltaEncoded != null) { - timeseriesJson?.remove(TimeseriesAttributes.KEY_DATA) - timeseriesJson?.add(TimeseriesAttributes.KEY_DATA, deltaEncoded) - } - if (additionalAttributes.isNotEmpty()) { - timeseriesJson?.add( - TimeseriesAttributes.KEY_TAGS, - additionalAttributes.toJson() - ) - } - timeseriesJson?.addProperty(TimeseriesAttributes.KEY_COUNT, data.size) - // - return json - } - - private fun encodeDelta(data: List): JsonObject? { - if (data.size <= 1) return null - - val scale = 10.0.pow(precision.toDouble()).toLong() - val ts = data.mapToDeltaCompressed { it.timestamp } - val cpuUsageArray = data.mapToDeltaCompressed { - roundToLongSafely(it.dataPoint.cpuUsage.toDouble(), scale = scale) - } - - return JsonObject().apply { - addProperty(TimeseriesAttributes.KEY_PRECISION, precision) - addProperty(TimeseriesAttributes.KEY_RESOLUTION, TimeseriesAttributes.NS) - add(TimeseriesAttributes.KEY_TS, ts) - add(TimeseriesAttributes.KEY_VALUE, cpuUsageArray) - } } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt index b677fe612c..c796d28f4c 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt @@ -17,19 +17,5 @@ internal fun interface JsonSerializer { internal object TimeseriesAttributes { const val KEY_TIMESERIES = "timeseries" const val KEY_SCHEMA = "schema" - const val KEY_DATA = "data" - const val KEY_COUNT = "count" - const val KEY_TAGS = "tags" - const val KEY_TS = "ts" - const val NS = "ns" - const val KEY_PRECISION = "precision" - const val KEY_RESOLUTION = "resolution" - const val KEY_VALUE = "value" const val KEY_NAME = "name" - const val KEY_MEMORY_FOOTPRINT = "memory_footprint" - const val KEY_MEMORY_PERCENT = "memory_percent" -} - -internal fun Map.toJson() = JsonObject().also { json -> - entries.forEach { json.addProperty(it.key, it.value) } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt index fbfcea58b3..7d9d1c4708 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt @@ -10,14 +10,10 @@ import com.datadog.android.api.context.DatadogContext import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.internal.timeseries.DataPoint -import com.datadog.android.rum.internal.timeseries.DeltaCompression -import com.datadog.android.rum.internal.timeseries.DeltaCompression.mapToDeltaCompressed -import com.datadog.android.rum.internal.timeseries.DeltaCompression.roundToLongSafely import com.datadog.android.rum.internal.toTimeseriesMemorySessionType import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.google.gson.JsonObject import java.util.UUID -import kotlin.math.pow @Suppress("LongParameterList") internal class MemoryEventSerializer( @@ -25,89 +21,40 @@ internal class MemoryEventSerializer( private val applicationId: String, private val sessionType: RumSessionType, private val totalRamBytes: Long, - private val timeProvider: TimeProvider, - private val additionalAttributes: Map, - private val useDeltaCompression: Boolean = false, - private val precision: Int = DeltaCompression.PRECISION + private val timeProvider: TimeProvider ) : JsonSerializer { override fun serialize(datadogContext: DatadogContext, dataPoints: List>): JsonObject? { if (totalRamBytes <= 0L || dataPoints.isEmpty()) return null - val data = dataPoints.map { sample -> - val memoryPercent = sample.value / totalRamBytes * PERCENT_FACTOR - TimeseriesMemoryEvent.Data( - timestamp = sample.timestampNs, - dataPoint = TimeseriesMemoryEvent.DataPoint(sample.value, memoryPercent) - ) - } - val start = data.firstOrNull()?.timestamp ?: 0L - val end = data.lastOrNull()?.timestamp ?: 0L - val deltaEncoded = if (useDeltaCompression) encodeDelta(data) else null - val schema = if (deltaEncoded != null) { - TimeseriesMemoryEvent.Schema.DELTA_OBJECT - } else { - TimeseriesMemoryEvent.Schema.OBJECT - } - val json = TimeseriesMemoryEvent( + return TimeseriesMemoryEvent( dd = TimeseriesMemoryEvent.Dd(), application = TimeseriesMemoryEvent.Application(id = applicationId), - session = TimeseriesMemoryEvent.Session( - id = sessionId, - type = sessionType.toTimeseriesMemorySessionType() - ), + session = TimeseriesMemoryEvent.Session(id = sessionId, type = sessionType.toTimeseriesMemorySessionType()), source = TimeseriesMemoryEvent.Source.ANDROID, date = timeProvider.getDeviceTimestampMillis(), version = datadogContext.version, service = datadogContext.service, timeseries = TimeseriesMemoryEvent.Timeseries( id = UUID.randomUUID().toString(), - schema = schema, - start = start, - end = end, - data = data + start = dataPoints.firstOrNull()?.timestampNs ?: 0L, + end = dataPoints.lastOrNull()?.timestampNs ?: 0L, + data = TimeseriesMemoryEvent.Data( + timestamps = dataPoints.map { it.timestampNs }, + values = TimeseriesMemoryEvent.Values( + memoryPercent = dataPoints.map { it.value / totalRamBytes * PERCENT_FACTOR }, + memoryFootprint = dataPoints.map { it.value / BYTES_IN_KB } + ) + ) ) ).toJson().asJsonObject - // - val timeseriesJson = json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - if (deltaEncoded != null) { - timeseriesJson?.remove(TimeseriesAttributes.KEY_DATA) - timeseriesJson?.add(TimeseriesAttributes.KEY_DATA, deltaEncoded) - } - timeseriesJson?.addProperty(TimeseriesAttributes.KEY_COUNT, data.size) - if (additionalAttributes.isNotEmpty()) { - timeseriesJson?.add( - TimeseriesAttributes.KEY_TAGS, - additionalAttributes.toJson() - ) - } - // - return json } - private fun encodeDelta(data: List): JsonObject? { - if (data.size <= 1) return null - - val scale = 10.0.pow(precision.toDouble()).toLong() - val ts = data.mapToDeltaCompressed { it.timestamp } - - val memoryFootprintArray = data.mapToDeltaCompressed { - roundToLongSafely(it.dataPoint.memoryFootprint.toDouble(), scale = scale) - } - - val memoryPercentArray = data.mapToDeltaCompressed { - roundToLongSafely(it.dataPoint.memoryPercent.toDouble(), scale = scale) - } - - return JsonObject().apply { - addProperty(TimeseriesAttributes.KEY_PRECISION, precision) - addProperty(TimeseriesAttributes.KEY_RESOLUTION, TimeseriesAttributes.NS) - add(TimeseriesAttributes.KEY_TS, ts) - add(TimeseriesAttributes.KEY_MEMORY_FOOTPRINT, memoryFootprintArray) - add(TimeseriesAttributes.KEY_MEMORY_PERCENT, memoryPercentArray) - } - } + private companion object { + const val PERCENT_FACTOR = 100.0 - companion object { - private const val PERCENT_FACTOR = 100.0 + // Must match the factor used in MemoryVitalReader when converting VmRSS kB to bytes. + // Datadog memory vitals use decimal (SI) units, so 1000 (not 1024) keeps the emitted + // memory_footprint consistent with the collected value and with memory_percent. + const val BYTES_IN_KB = 1000 } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt index fe2b218359..2b9e762483 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt @@ -19,9 +19,7 @@ import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.MIN_ class TimeseriesConfiguration internal constructor( internal val bufferSize: Int, internal val intervalMs: Long, - internal val collectInBackground: Boolean, - internal val useDeltaCompression: Boolean, - internal val additionalAttributes: Map + internal val collectInBackground: Boolean ) { /** @@ -33,13 +31,11 @@ class TimeseriesConfiguration internal constructor( private var bufferSize: Int = DEFAULT_BUFFER_SIZE private var intervalMs: Long = DEFAULT_INTERVAL_MS private var collectInBackground: Boolean = false - private var useDeltaCompression: Boolean = false - private val additionalAttributes: MutableMap = mutableMapOf() /** * Sets the number of samples accumulated per pipeline before sending a batch event. * Must be > 0; out-of-range values fall back to [DEFAULT_BUFFER_SIZE]. - * Defaults to [DEFAULT_BUFFER_SIZE] (≈30 seconds at the default 1 s interval). + * Defaults to [DEFAULT_BUFFER_SIZE] (≈120 seconds at the default 1 s interval). */ fun setBufferSize( @IntRange(from = 1, to = Int.MAX_VALUE.toLong()) bufferSize: Int @@ -62,33 +58,15 @@ class TimeseriesConfiguration internal constructor( * Sets whether to keep sampling timeseries when the app is in background. * Defaults to `false`. */ - fun collectInBackground(collectInBackground: Boolean): Builder = apply { + internal fun collectInBackground(collectInBackground: Boolean): Builder = apply { this.collectInBackground = collectInBackground } - /** - * Sets whether to encode each sample as a delta from the previous value instead of an - * absolute value. Reduces payload size when values change slowly. Defaults to `false`. - */ - fun useDeltaCompression(useDeltaCompression: Boolean): Builder = apply { - this.useDeltaCompression = useDeltaCompression - } - - /** - * Adds an attribute to all timeseries events created with this configuration. - * The attribute is emitted as a key/value entry in the `timeseries.tags` JSON object. - */ - fun addAttribute(key: String, value: String): Builder = apply { - additionalAttributes[key] = value - } - /** Builds a [TimeseriesConfiguration] from the current builder state. */ fun build(): TimeseriesConfiguration = TimeseriesConfiguration( bufferSize = bufferSize, intervalMs = intervalMs, - collectInBackground = collectInBackground, - useDeltaCompression = useDeltaCompression, - additionalAttributes = additionalAttributes.toMap() + collectInBackground = collectInBackground ) } @@ -99,7 +77,7 @@ class TimeseriesConfiguration internal constructor( val DEFAULT: TimeseriesConfiguration = Builder().build() /** Default number of samples to accumulate before emitting a timeseries event. */ - internal const val DEFAULT_BUFFER_SIZE: Int = 30 + internal const val DEFAULT_BUFFER_SIZE: Int = 120 /** Default sampling interval in milliseconds. */ internal const val DEFAULT_INTERVAL_MS: Long = 1000L diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt index 3fb9a4d38f..d03fb25d51 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt @@ -6,9 +6,7 @@ package com.datadog.android.rum.assertj -import com.datadog.android.rum.internal.timeseries.serializer.TimeseriesAttributes import com.datadog.android.rum.model.TimeseriesCpuEvent -import com.google.gson.JsonArray import com.google.gson.JsonObject import org.assertj.core.api.AbstractObjectAssert import org.assertj.core.api.Assertions.assertThat @@ -17,8 +15,7 @@ import org.junit.jupiter.api.assertDoesNotThrow import java.util.UUID internal class TimeseriesCpuEventAssert private constructor( - actual: TimeseriesCpuEvent, - private val json: JsonObject + actual: TimeseriesCpuEvent ) : AbstractObjectAssert( actual, TimeseriesCpuEventAssert::class.java @@ -62,7 +59,7 @@ internal class TimeseriesCpuEventAssert private constructor( .isEqualTo(expected) } - fun hasTimeseriesSchema(expected: TimeseriesCpuEvent.Schema): TimeseriesCpuEventAssert = apply { + fun hasTimeseriesSchema(expected: String): TimeseriesCpuEventAssert = apply { assertThat(actual.timeseries.schema) .overridingErrorMessage { "Expected event to have timeseries.schema $expected but was ${actual.timeseries.schema}" @@ -93,96 +90,27 @@ internal class TimeseriesCpuEventAssert private constructor( } fun hasTimeseriesDataCount(expected: Int): TimeseriesCpuEventAssert = apply { - assertThat(actual.timeseries.data) + assertThat(actual.timeseries.data.timestamps) .overridingErrorMessage { - "Expected event to have timeseries.data of size $expected but was ${actual.timeseries.data.size}" + "Expected event to have timeseries.data of size $expected " + + "but was ${actual.timeseries.data.timestamps.size}" } .hasSize(expected) } fun hasCpuUsage(expected: Double, offset: Offset, position: Int = 0): TimeseriesCpuEventAssert = apply { - val cpuUsage = actual.timeseries.data.getOrNull(position)?.dataPoint?.cpuUsage?.toDouble() + val cpuUsage = actual.timeseries.data.values.cpuUsage.getOrNull(position)?.toDouble() assertThat(cpuUsage) .overridingErrorMessage { "Expected first data point cpu_usage to be close to $expected but was $cpuUsage" } .isNotNull() .isCloseTo(expected, offset) } - // region Delta Compression - - // Delta compression is not GA — these assertions cover the delta-scalar wire - // format used during dogfooding only. This section will be revisited or removed before GA. - // Use assertThatDelta(JsonObject) to enter this path. - - fun hasDeltaPrecision(expected: Int): TimeseriesCpuEventAssert = apply { - val precision = json.timeseries - .getAsJsonObject(TimeseriesAttributes.KEY_DATA) - .get(TimeseriesAttributes.KEY_PRECISION) - .asInt - - assertThat(precision) - .overridingErrorMessage { "Expected delta data to have precision $expected but was $precision" } - .isEqualTo(expected) - } - - fun hasDeltaResolution(expected: String): TimeseriesCpuEventAssert = apply { - val resolution = json.timeseries - .getAsJsonObject(TimeseriesAttributes.KEY_DATA) - .get(TimeseriesAttributes.KEY_RESOLUTION) - .asString - - assertThat(resolution) - .overridingErrorMessage { "Expected delta data to have resolution $expected but was $resolution" } - .isEqualTo(expected) - } - - fun hasDeltaTsValues(vararg expected: Long): TimeseriesCpuEventAssert = apply { - val tsArray = json.timeseries - .getAsJsonObject(TimeseriesAttributes.KEY_DATA) - .get(TimeseriesAttributes.KEY_TS) - .asJsonArray - - expected.forEachIndexed { i, v -> - assertThat(tsArray[i].asLong) - .overridingErrorMessage { "Expected delta ts[$i] to be $v but was ${tsArray[i].asLong}" } - .isEqualTo(v) - } - } - - fun hasDeltaValueAt(index: Int, expected: Long): TimeseriesCpuEventAssert = apply { - val valueArray = json.timeseries - .getAsJsonObject(TimeseriesAttributes.KEY_DATA) - .get(TimeseriesAttributes.KEY_VALUE) - .asJsonArray - - val actual = valueArray[index].asLong - assertThat(actual) - .overridingErrorMessage { "Expected delta value[$index] to be $expected but was $actual" } - .isEqualTo(expected) - } - - // endregion - companion object { - private val JsonObject.timeseries: JsonObject get() = getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - internal fun assertThat(event: TimeseriesCpuEvent): TimeseriesCpuEventAssert = - TimeseriesCpuEventAssert(event, event.toJson() as JsonObject) + TimeseriesCpuEventAssert(event) internal fun assertThat(json: JsonObject): TimeseriesCpuEventAssert = assertThat(TimeseriesCpuEvent.fromJsonObject(json)) - - /** - * Delta encoding replaces timeseries.data with a JsonObject in-place, - * making fromJsonObject fail. A patched copy (empty data array) is used to construct - * the typed model; the original json is retained for delta assertions. - */ - internal fun assertThatDelta(json: JsonObject): TimeseriesCpuEventAssert { - val patchedJson = json.deepCopy().also { - it.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - .add(TimeseriesAttributes.KEY_DATA, JsonArray()) - } - return TimeseriesCpuEventAssert(TimeseriesCpuEvent.fromJsonObject(patchedJson), json) - } } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt index 0dcb9dff9d..7444584bd0 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt @@ -71,9 +71,7 @@ internal class CsvTimeseries( totalRamBytes: Long, bufferSize: Int, timeProvider: TimeProvider, - datadogContext: DatadogContext, - useDeltaCompression: Boolean = false, - additionalAttributes: Map = emptyMap() + datadogContext: DatadogContext ) = CsvTimeseries( datadogContext = datadogContext, pipelines = listOf( @@ -85,9 +83,7 @@ internal class CsvTimeseries( applicationId = applicationId, sessionType = sessionType, totalRamBytes = totalRamBytes, - timeProvider = timeProvider, - additionalAttributes = additionalAttributes, - useDeltaCompression = useDeltaCompression + timeProvider = timeProvider ) ), Triple( @@ -97,9 +93,7 @@ internal class CsvTimeseries( sessionId = sessionId, applicationId = applicationId, sessionType = sessionType, - timeProvider = timeProvider, - additionalAttributes = additionalAttributes, - useDeltaCompression = useDeltaCompression + timeProvider = timeProvider ) ) ) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt index 4b1f005d8f..444b41f7ca 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt @@ -17,7 +17,6 @@ import com.google.gson.JsonNull import com.google.gson.JsonObject import com.google.gson.JsonParser import com.google.gson.JsonPrimitive -import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -127,42 +126,6 @@ internal class TimeseriesEndToEndTest { } } - @Test - fun `M add tags to every emitted event W additionalAttributes is not empty`( - forge: Forge - ) { - // Given - val fakeAdditionalAttributes = forge.aMap { anAlphabeticalString() to anAlphabeticalString() } - val testedTimeseries = CsvTimeseries.create( - csvContent = csvContent, - sessionId = SESSION_ID, - applicationId = APPLICATION_ID, - sessionType = RumSessionType.USER, - totalRamBytes = TOTAL_RAM_BYTES, - bufferSize = BATCH_SIZE, - timeProvider = mockTimeProvider, - datadogContext = mockDatadogContext, - additionalAttributes = fakeAdditionalAttributes - ) - - // When - testedTimeseries.onSessionStart() - testedTimeseries.onSessionStop() - - // Then - val events = testedTimeseries.captured - assertThat(events).isNotEmpty - for (event in events) { - val tags = event.getAsJsonObject( - TimeseriesAttributes.KEY_TIMESERIES - ).getAsJsonObject(TimeseriesAttributes.KEY_TAGS) - assertThat(tags).describedAs("tags on %s", event).isNotNull - for ((key, value) in fakeAdditionalAttributes) { - assertThat(tags.get(key)?.asString).isEqualTo(value) - } - } - } - private fun metricNameOf(event: JsonObject): String = event.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) .get(TimeseriesAttributes.KEY_NAME) @@ -269,7 +232,7 @@ internal class TimeseriesEndToEndTest { private fun String.toBigDecimalOrNull(): BigDecimal? = try { BigDecimal(this) - } catch (e: NumberFormatException) { + } catch (_: NumberFormatException) { null } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt index de4244fca2..260998da04 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt @@ -10,14 +10,12 @@ import com.datadog.android.api.context.DatadogContext import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.assertj.TimeseriesCpuEventAssert.Companion.assertThat -import com.datadog.android.rum.assertj.TimeseriesCpuEventAssert.Companion.assertThatDelta import com.datadog.android.rum.internal.timeseries.DataPoint import com.datadog.android.rum.model.TimeseriesCpuEvent import com.datadog.android.rum.utils.forge.Configurator import com.google.gson.JsonObject import fr.xgouchet.elmyr.annotation.DoubleForgery import fr.xgouchet.elmyr.annotation.Forgery -import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -32,8 +30,6 @@ import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.doAnswer import org.mockito.kotlin.mock import org.mockito.quality.Strictness -import kotlin.math.pow -import kotlin.math.roundToLong @Extensions( ExtendWith(MockitoExtension::class), @@ -52,31 +48,20 @@ internal class CpuEventSerializerTest { @StringForgery lateinit var fakeApplicationId: String - @IntForgery(min = 2, max = 9) - var fakePrecision: Int = 0 - @Forgery lateinit var fakeDatadogContext: DatadogContext - val fakeScale: Long get() = 10.0.pow(fakePrecision.toDouble()).toLong() - val mockTimeProvider: TimeProvider = mock { on { getDeviceTimestampMillis() } doAnswer { fakeNowMs } } private fun testedSerializer( - useDeltaCompression: Boolean = false, - sessionType: RumSessionType = RumSessionType.USER, - precision: Int = fakePrecision, - additionalAttributes: Map = emptyMap() + sessionType: RumSessionType = RumSessionType.USER ) = CpuEventSerializer( sessionId = fakeSessionId, applicationId = fakeApplicationId, sessionType = sessionType, - timeProvider = mockTimeProvider, - additionalAttributes = additionalAttributes, - useDeltaCompression = useDeltaCompression, - precision = precision + timeProvider = mockTimeProvider ) @Test @@ -111,7 +96,7 @@ internal class CpuEventSerializerTest { .hasTimeseriesEnd(fakeTs + 1L) .hasApplicationId(fakeApplicationId) .hasSessionType(TimeseriesCpuEvent.Type.USER) - .hasTimeseriesSchema(TimeseriesCpuEvent.Schema.OBJECT) + .hasTimeseriesSchema("object-v2") .hasService(fakeDatadogContext.service) .hasVersion(fakeDatadogContext.version) } @@ -133,98 +118,6 @@ internal class CpuEventSerializerTest { assertThat(json).hasSessionType(TimeseriesCpuEvent.Type.SYNTHETICS) } - @Test - fun `M add tags W serialize() { additionalAttributes non-empty }`( - @DoubleForgery(min = 0.0, max = 100.0) fakeCpuValue: Double, - @LongForgery(min = 1L) fakeTs: Long, - @StringForgery fakeTagKey: String, - @StringForgery fakeTagValue: String - ) { - // Given - val samples = listOf(DataPoint(fakeTs, fakeCpuValue)) - - // When - val json = testedSerializer(additionalAttributes = mapOf(fakeTagKey to fakeTagValue)) - .serialize(fakeDatadogContext, samples) - .failIfNull() - - // Then - val tags = json - .getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - .getAsJsonObject(TimeseriesAttributes.KEY_TAGS) - assertThat(tags.get(fakeTagKey).asString).isEqualTo(fakeTagValue) - } - - @Test - fun `M not add tags W serialize() { additionalAttributes empty }`( - @DoubleForgery(min = 0.0, max = 100.0) fakeCpuValue: Double, - @LongForgery(min = 1L) fakeTs: Long - ) { - // Given - val samples = listOf(DataPoint(fakeTs, fakeCpuValue)) - - // When - val json = testedSerializer(additionalAttributes = emptyMap()) - .serialize(fakeDatadogContext, samples) - .failIfNull() - - // Then - assertThat( - json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - .has(TimeseriesAttributes.KEY_TAGS) - ).isFalse() - } - - @Test - fun `M produce delta-scalar schema W serialize() { compression on, multi-sample }`( - @DoubleForgery(min = 0.001, max = 100.0) fakeFirstCpu: Double, - @DoubleForgery(min = 0.001, max = 100.0) fakeSecondCpu: Double, - @DoubleForgery(min = 0.001, max = 100.0) fakeThirdCpu: Double, - @LongForgery(min = 1L, max = 1_000_000L) fakeTs1: Long, - @LongForgery(min = 1L, max = 1_000L) fakeTimestampStep: Long - ) { - // Given - val fakeTs2 = fakeTs1 + fakeTimestampStep - val fakeTs3 = fakeTs2 + fakeTimestampStep - val samples = listOf( - DataPoint(fakeTs1, fakeFirstCpu), - DataPoint(fakeTs2, fakeSecondCpu), - DataPoint(fakeTs3, fakeThirdCpu) - ) - - // When - val json = testedSerializer(useDeltaCompression = true) - .serialize(fakeDatadogContext, samples) - .failIfNull() - - // Then - val scaled = listOf(fakeFirstCpu, fakeSecondCpu, fakeThirdCpu).map { (it * fakeScale).roundToLong() } - assertThatDelta(json) - .hasTimeseriesSchema(TimeseriesCpuEvent.Schema.DELTA_SCALAR) - .hasDeltaPrecision(fakePrecision) - .hasDeltaResolution(TimeseriesAttributes.NS) - .hasDeltaTsValues(fakeTs1, fakeTimestampStep, fakeTimestampStep) - .hasDeltaValueAt(0, scaled[0]) - .hasDeltaValueAt(1, scaled[1] - scaled[0]) - .hasDeltaValueAt(2, scaled[2] - scaled[1]) - } - - @Test - fun `M fall back to object schema W serialize() { compression on, single sample }`( - @DoubleForgery(min = 0.001, max = 100.0) fakeCpu: Double, - @LongForgery(min = 1L) fakeTs: Long - ) { - // When - val json = testedSerializer(useDeltaCompression = true) - .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeCpu))) - .failIfNull() - - // Then - assertThat(json) - .hasTimeseriesSchema(TimeseriesCpuEvent.Schema.OBJECT) - .hasTimeseriesDataCount(1) - } - @Test fun `M preserve cpu_usage W serialize() { object schema }`( @DoubleForgery(min = 1.0, max = 100.0) fakeCpu: Double, diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt index 7d7b294a5d..a24132ac68 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt @@ -14,7 +14,6 @@ import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.utils.forge.Configurator import fr.xgouchet.elmyr.annotation.DoubleForgery import fr.xgouchet.elmyr.annotation.Forgery -import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -33,8 +32,6 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.util.UUID -import kotlin.math.pow -import kotlin.math.roundToLong @Extensions( ExtendWith(MockitoExtension::class), @@ -59,34 +56,23 @@ internal class MemoryEventSerializerTest { @LongForgery(min = 1L) var fakeNowMs: Long = 0L - @IntForgery(min = 2, max = 9) - var fakePrecision: Int = 0 - @Forgery lateinit var fakeDatadogContext: DatadogContext - val fakeScale: Long get() = 10.0.pow(fakePrecision.toDouble()).toLong() - @BeforeEach fun `set up`() { whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn fakeNowMs } private fun testedSerializer( - useDeltaCompression: Boolean = false, sessionType: RumSessionType = RumSessionType.USER, - totalRamBytes: Long = fakeTotalRamBytes, - precision: Int = fakePrecision, - additionalAttributes: Map = emptyMap() + totalRamBytes: Long = fakeTotalRamBytes ) = MemoryEventSerializer( sessionId = fakeSessionId, applicationId = fakeApplicationId, sessionType = sessionType, totalRamBytes = totalRamBytes, - timeProvider = mockTimeProvider, - additionalAttributes = additionalAttributes, - useDeltaCompression = useDeltaCompression, - precision = precision + timeProvider = mockTimeProvider ) @Test @@ -150,10 +136,10 @@ internal class MemoryEventSerializerTest { assertThat(json.get(KEY_VERSION).asString).isEqualTo(fakeDatadogContext.version) val parsed = TimeseriesMemoryEvent.fromJsonObject(json) - assertThat(parsed.timeseries.data).hasSize(2) - val first = parsed.timeseries.data.first().dataPoint - assertThat(first.memoryFootprint.toDouble()).isCloseTo(fakeMemory, Offset.offset(MEMORY_OFFSET)) - assertThat(first.memoryPercent.toDouble()) + assertThat(parsed.timeseries.data.timestamps).hasSize(2) + assertThat(parsed.timeseries.data.values.memoryFootprint.first().toDouble()) + .isCloseTo(fakeMemory / 1000.0, Offset.offset(MEMORY_OFFSET)) + assertThat(parsed.timeseries.data.values.memoryPercent.first().toDouble()) .isCloseTo(fakeMemory / fakeTotalRamBytes * PERCENT_FACTOR, Offset.offset(MEMORY_OFFSET)) } @@ -171,107 +157,6 @@ internal class MemoryEventSerializerTest { assertThat(json.getAsJsonObject(KEY_SESSION).get(KEY_TYPE).asString).isEqualTo(VALUE_TYPE_SYNTHETICS) } - @Test - fun `M add tags W serialize() { additionalAttributes non-empty }`( - @DoubleForgery(min = 1.0) fakeMemory: Double, - @LongForgery(min = 1L) fakeTs: Long, - @StringForgery fakeTagKey: String, - @StringForgery fakeTagValue: String - ) { - // When - val result = testedSerializer(additionalAttributes = mapOf(fakeTagKey to fakeTagValue)) - .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeMemory))) - - // Then - val json = checkNotNull(result) - val tags = json - .getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - .getAsJsonObject(TimeseriesAttributes.KEY_TAGS) - assertThat(tags.get(fakeTagKey).asString).isEqualTo(fakeTagValue) - } - - @Test - fun `M not add tags W serialize() { additionalAttributes empty }`( - @DoubleForgery(min = 1.0) fakeMemory: Double, - @LongForgery(min = 1L) fakeTs: Long - ) { - // When - val result = testedSerializer(additionalAttributes = emptyMap()) - .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeMemory))) - - // Then - val json = checkNotNull(result) - assertThat( - json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - .has(TimeseriesAttributes.KEY_TAGS) - ).isFalse() - } - - @Test - fun `M produce delta-object schema W serialize() { compression on, multi-sample }`( - @DoubleForgery(min = 1.0, max = 1_000_000.0) fakeMem1: Double, - @DoubleForgery(min = 1.0, max = 1_000_000.0) fakeMem2: Double, - @DoubleForgery(min = 1.0, max = 1_000_000.0) fakeMem3: Double, - @LongForgery(min = 1L, max = 1_000_000L) fakeTs1: Long, - @LongForgery(min = 1L, max = 1_000L) fakeTimestampStep: Long - ) { - // Given - val fakeTs2 = fakeTs1 + fakeTimestampStep - val fakeTs3 = fakeTs2 + fakeTimestampStep - - val samples = listOf( - DataPoint(fakeTs1, fakeMem1), - DataPoint(fakeTs2, fakeMem2), - DataPoint(fakeTs3, fakeMem3) - ) - - // When - val result = testedSerializer(useDeltaCompression = true).serialize(fakeDatadogContext, samples) - - // Then - val json = checkNotNull(result) - val timeseries = json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - assertThat(timeseries.get(TimeseriesAttributes.KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_DELTA_OBJECT) - val data = timeseries.get(TimeseriesAttributes.KEY_DATA).asJsonObject - assertThat(data.get(TimeseriesAttributes.KEY_PRECISION).asInt).isEqualTo(fakePrecision) - assertThat(data.get(TimeseriesAttributes.KEY_RESOLUTION).asString).isEqualTo(TimeseriesAttributes.NS) - - val tsArray = data.get(TimeseriesAttributes.KEY_TS).asJsonArray - assertThat(tsArray[0].asLong).isEqualTo(fakeTs1) - assertThat(tsArray[1].asLong).isEqualTo(fakeTimestampStep) - assertThat(tsArray[2].asLong).isEqualTo(fakeTimestampStep) - - val maxArr = data.get(TimeseriesAttributes.KEY_MEMORY_FOOTPRINT).asJsonArray - val pctArr = data.get(TimeseriesAttributes.KEY_MEMORY_PERCENT).asJsonArray - val scaledMax = listOf(fakeMem1, fakeMem2, fakeMem3).map { (it * fakeScale).roundToLong() } - val scaledPct = listOf(fakeMem1, fakeMem2, fakeMem3) - .map { (it / fakeTotalRamBytes * PERCENT_FACTOR * fakeScale).roundToLong() } - - assertThat(maxArr[0].asLong).isEqualTo(scaledMax[0]) - assertThat(maxArr[1].asLong).isEqualTo(scaledMax[1] - scaledMax[0]) - assertThat(maxArr[2].asLong).isEqualTo(scaledMax[2] - scaledMax[1]) - - assertThat(pctArr[0].asLong).isEqualTo(scaledPct[0]) - assertThat(pctArr[1].asLong).isEqualTo(scaledPct[1] - scaledPct[0]) - assertThat(pctArr[2].asLong).isEqualTo(scaledPct[2] - scaledPct[1]) - } - - @Test - fun `M fall back to object schema W serialize() { compression on, single sample }`( - @DoubleForgery(min = 1.0) fakeMemory: Double, - @LongForgery(min = 1L) fakeTs: Long - ) { - // When - val result = testedSerializer(useDeltaCompression = true) - .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeMemory))) - - // Then - val json = checkNotNull(result) - val timeseries = json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - assertThat(timeseries.get(TimeseriesAttributes.KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_OBJECT) - assertThat(timeseries.get(TimeseriesAttributes.KEY_DATA).isJsonArray).isTrue() - } - private companion object { private const val PERCENT_FACTOR: Double = 100.0 private const val MEMORY_OFFSET: Double = 0.0001 @@ -286,8 +171,7 @@ internal class MemoryEventSerializerTest { private const val KEY_VERSION: String = "version" // JSON values - private const val VALUE_SCHEMA_OBJECT: String = "object" - private const val VALUE_SCHEMA_DELTA_OBJECT: String = "delta-object" + private const val VALUE_SCHEMA_OBJECT: String = "object-v2" private const val VALUE_TYPE_SYNTHETICS: String = "synthetics" } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt index f64cae8b6f..3365add0a7 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt @@ -81,53 +81,4 @@ internal class TimeseriesConfigurationTest { // Then assertThat(config.collectInBackground).isTrue() } - - @Test - fun `M default additionalAttributes to empty W built with no args`() { - // When - val config = TimeseriesConfiguration.Builder().build() - - // Then - assertThat(config.additionalAttributes).isEmpty() - } - - @Test - fun `M store attribute W addAttribute`() { - // When - val config = TimeseriesConfiguration.Builder() - .addAttribute("key1", "value1") - .addAttribute("key2", "value2") - .build() - - // Then - assertThat(config.additionalAttributes).containsExactlyInAnyOrderEntriesOf( - mapOf("key1" to "value1", "key2" to "value2") - ) - } - - @Test - fun `M overwrite value W addAttribute called twice with same key`() { - // When - val config = TimeseriesConfiguration.Builder() - .addAttribute("key", "first") - .addAttribute("key", "second") - .build() - - // Then - assertThat(config.additionalAttributes).containsExactlyEntriesOf(mapOf("key" to "second")) - } - - @Test - fun `M keep built attributes unchanged W addAttribute after build`() { - // Given - val builder = TimeseriesConfiguration.Builder() - .addAttribute("key1", "value1") - val config = builder.build() - - // When - builder.addAttribute("key2", "value2") - - // Then - assertThat(config.additionalAttributes).containsExactlyEntriesOf(mapOf("key1" to "value1")) - } } diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json index 3325d650ed..1d5b4ecc97 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json @@ -8,16 +8,14 @@ "timeseries": { "id": "00000000-0000-0000-0000-000000000000", "name": "cpu", - "schema": "object", + "schema": "object-v2", "start": 1700000001000000000, "end": 1700000005000000000, - "count": 5, - "data": [ - {"timestamp": 1700000001000000000, "data_point": {"cpu_usage": 12.5}}, - {"timestamp": 1700000002000000000, "data_point": {"cpu_usage": 14.2}}, - {"timestamp": 1700000003000000000, "data_point": {"cpu_usage": 11.8}}, - {"timestamp": 1700000004000000000, "data_point": {"cpu_usage": 16.3}}, - {"timestamp": 1700000005000000000, "data_point": {"cpu_usage": 13.7}} - ] + "data": { + "timestamps": [1700000001000000000, 1700000002000000000, 1700000003000000000, 1700000004000000000, 1700000005000000000], + "values": { + "cpu_usage": [12.5, 14.2, 11.8, 16.3, 13.7] + } + } } } diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json index c422d701eb..6415084548 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json @@ -8,16 +8,14 @@ "timeseries": { "id": "00000000-0000-0000-0000-000000000000", "name": "cpu", - "schema": "object", + "schema": "object-v2", "start": 1700000006000000000, "end": 1700000010000000000, - "count": 5, - "data": [ - {"timestamp": 1700000006000000000, "data_point": {"cpu_usage": 18.1}}, - {"timestamp": 1700000007000000000, "data_point": {"cpu_usage": 22.4}}, - {"timestamp": 1700000008000000000, "data_point": {"cpu_usage": 19.6}}, - {"timestamp": 1700000009000000000, "data_point": {"cpu_usage": 15.9}}, - {"timestamp": 1700000010000000000, "data_point": {"cpu_usage": 13.2}} - ] + "data": { + "timestamps": [1700000006000000000, 1700000007000000000, 1700000008000000000, 1700000009000000000, 1700000010000000000], + "values": { + "cpu_usage": [18.1, 22.4, 19.6, 15.9, 13.2] + } + } } } diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json index f84fa93243..4723b0d1cb 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json @@ -8,16 +8,15 @@ "timeseries": { "id": "00000000-0000-0000-0000-000000000000", "name": "memory", - "schema": "object", + "schema": "object-v2", "start": 1700000001000000000, "end": 1700000005000000000, - "count": 5, - "data": [ - {"timestamp": 1700000001000000000, "data_point": {"memory_footprint": 31233300, "memory_percent": 3.12333}}, - {"timestamp": 1700000002000000000, "data_point": {"memory_footprint": 31245500, "memory_percent": 3.12455}}, - {"timestamp": 1700000003000000000, "data_point": {"memory_footprint": 31300800, "memory_percent": 3.13008}}, - {"timestamp": 1700000004000000000, "data_point": {"memory_footprint": 31289100, "memory_percent": 3.12891}}, - {"timestamp": 1700000005000000000, "data_point": {"memory_footprint": 31350000, "memory_percent": 3.135}} - ] + "data": { + "timestamps": [1700000001000000000, 1700000002000000000, 1700000003000000000, 1700000004000000000, 1700000005000000000], + "values": { + "memory_footprint": [31233.3, 31245.5, 31300.8, 31289.1, 31350.0], + "memory_percent": [3.12333, 3.12455, 3.13008, 3.12891, 3.135] + } + } } } diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json index 24a2bce6a8..ab5b91113d 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json @@ -8,16 +8,15 @@ "timeseries": { "id": "00000000-0000-0000-0000-000000000000", "name": "memory", - "schema": "object", + "schema": "object-v2", "start": 1700000006000000000, "end": 1700000010000000000, - "count": 5, - "data": [ - {"timestamp": 1700000006000000000, "data_point": {"memory_footprint": 31420200, "memory_percent": 3.14202}}, - {"timestamp": 1700000007000000000, "data_point": {"memory_footprint": 31510400, "memory_percent": 3.15104}}, - {"timestamp": 1700000008000000000, "data_point": {"memory_footprint": 31498700, "memory_percent": 3.14987}}, - {"timestamp": 1700000009000000000, "data_point": {"memory_footprint": 31550300, "memory_percent": 3.15503}}, - {"timestamp": 1700000010000000000, "data_point": {"memory_footprint": 31600100, "memory_percent": 3.16001}} - ] + "data": { + "timestamps": [1700000006000000000, 1700000007000000000, 1700000008000000000, 1700000009000000000, 1700000010000000000], + "values": { + "memory_footprint": [31420.2, 31510.4, 31498.7, 31550.3, 31600.1], + "memory_percent": [3.14202, 3.15104, 3.14987, 3.15503, 3.16001] + } + } } } From 50f5169a6732dea596b2b4c5c94c8672a712e361 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Fri, 31 Jul 2026 16:17:40 +0100 Subject: [PATCH 20/32] RUM-17613: Fix detekt --- .../kotlin/com/datadog/android/rum/Rum.kt | 22 ++++++++++++------- .../domain/scope/RumApplicationScopeTest.kt | 1 - ...RumSessionScopeAttributePropagationTest.kt | 1 - .../domain/scope/RumSessionScopeTest.kt | 14 ++++++++++-- .../internal/monitor/DatadogRumMonitorTest.kt | 1 - 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt index d580cd59e2..56eba4f318 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt @@ -142,14 +142,7 @@ object Rum { sessionSampler = sessionSampler, writer = rumFeature.dataWriter, handler = Handler(Looper.getMainLooper()), - telemetryEventHandler = TelemetryEventHandler( - sdkCore = sdkCore, - eventSampler = RateBasedSampler(rumFeature.telemetrySampleRate), - sessionEndedMetricDispatcher = sessionEndedMetricDispatcher, - configurationExtraSampler = RateBasedSampler( - rumFeature.telemetryConfigurationSampleRate - ) - ), + telemetryEventHandler = createTelemetryEventHandler(sdkCore, rumFeature, sessionEndedMetricDispatcher), firstPartyHostHeaderTypeResolver = sdkCore.firstPartyHostResolver, cpuVitalMonitor = rumFeature.cpuVitalMonitor, memoryVitalMonitor = rumFeature.memoryVitalMonitor, @@ -178,6 +171,19 @@ object Rum { ) } + private fun createTelemetryEventHandler( + sdkCore: InternalSdkCore, + rumFeature: RumFeature, + sessionEndedMetricDispatcher: SessionEndedMetricDispatcher + ) = TelemetryEventHandler( + sdkCore = sdkCore, + eventSampler = RateBasedSampler(rumFeature.telemetrySampleRate), + sessionEndedMetricDispatcher = sessionEndedMetricDispatcher, + configurationExtraSampler = RateBasedSampler( + rumFeature.telemetryConfigurationSampleRate + ) + ) + // endregion // region Constants diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt index 6ce629bcad..4547c169ce 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt @@ -30,7 +30,6 @@ import com.datadog.android.rum.internal.domain.state.ViewUIPerformanceReport import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener -import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter import com.datadog.android.rum.internal.timeseries.Timeseries import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt index 7e5744bc4d..df8b816ce6 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt @@ -26,7 +26,6 @@ import com.datadog.android.rum.internal.domain.display.DisplayInfo import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener -import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter import com.datadog.android.rum.internal.timeseries.Timeseries import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt index a374eaadca..99bae97d64 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt @@ -2232,7 +2232,12 @@ internal class RumSessionScopeTest { whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn secondTimeseries // When - testedScope.handleEvent(RumRawEvent.ResetSession(eventTime = currentFakeTime()), fakeDatadogContext, mockEventWriteScope, mockWriter) + testedScope.handleEvent( + RumRawEvent.ResetSession(eventTime = currentFakeTime()), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then verify(firstTimeseries).onSessionStart() @@ -2263,7 +2268,12 @@ internal class RumSessionScopeTest { @Test fun `M not stop timeseries W handleEvent { StopSession, no tracked session }`() { // When - testedScope.handleEvent(RumRawEvent.StopSession(eventTime = currentFakeTime()), fakeDatadogContext, mockEventWriteScope, mockWriter) + testedScope.handleEvent( + RumRawEvent.StopSession(eventTime = currentFakeTime()), + fakeDatadogContext, + mockEventWriteScope, + mockWriter + ) // Then verifyNoInteractions(mockTimeseries) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt index c99fc5d574..5d32fefa68 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt @@ -56,7 +56,6 @@ import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.monitor.DatadogRumMonitor.Companion.OPERATION_ERROR_INVALID_NAME import com.datadog.android.rum.internal.monitor.DatadogRumMonitor.Companion.OPERATION_ERROR_INVALID_NAME_CHARACTERS import com.datadog.android.rum.internal.monitor.DatadogRumMonitor.Companion.OPERATION_ERROR_INVALID_OPERATION_KEY -import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesFactory import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier From e95d0fdedc3f4e3e3cc6d0fd1e7e3173f80d7b84 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Fri, 14 Aug 2026 12:06:27 +0100 Subject: [PATCH 21/32] RUM-17613: Rename Timeseries abstractions to TimeseriesCollector Rename the internal Timeseries interface to TimeseriesCollector and rename its implementations to DefaultTimeseriesCollector and DefaultTimeseriesCollectorFactory, propagating the new names through RumFeature, the RUM scope tree and the tests. No behaviour change. Ref: RUM-17613 --- detekt_custom_safe_calls_third_party.yml | 6 +- .../kotlin/com/datadog/android/rum/Rum.kt | 2 +- .../android/rum/internal/RumFeature.kt | 16 +- .../domain/scope/RumApplicationScope.kt | 8 +- .../internal/domain/scope/RumSessionScope.kt | 20 +-- .../rum/internal/monitor/DatadogRumMonitor.kt | 8 +- ...eries.kt => DefaultTimeseriesCollector.kt} | 4 +- ...t => DefaultTimeseriesCollectorFactory.kt} | 6 +- .../{Timeseries.kt => TimeseriesCollector.kt} | 4 +- .../android/rum/internal/RumFeatureTest.kt | 12 +- ...pplicationScopeAttributePropagationTest.kt | 10 +- .../domain/scope/RumApplicationScopeTest.kt | 14 +- ...RumSessionScopeAttributePropagationTest.kt | 10 +- .../domain/scope/RumSessionScopeTest.kt | 138 +++++++++--------- .../internal/monitor/DatadogRumMonitorTest.kt | 18 +-- ... DefaultTimeseriesCollectorFactoryTest.kt} | 8 +- ...t.kt => DefaultTimeseriesCollectorTest.kt} | 100 ++++++------- .../csv/{CsvTimeseries.kt => CsvCollector.kt} | 12 +- ....kt => TimeseriesCollectorEndToEndTest.kt} | 10 +- .../VitalReaderWrapperTest.kt} | 5 +- 20 files changed, 208 insertions(+), 203 deletions(-) rename features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/{RumSessionScopeTimeseries.kt => DefaultTimeseriesCollector.kt} (98%) rename features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/{RumSessionScopeTimeseriesFactory.kt => DefaultTimeseriesCollectorFactory.kt} (90%) rename features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/{Timeseries.kt => TimeseriesCollector.kt} (89%) rename features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/{RumSessionScopeTimeseriesFactoryTest.kt => DefaultTimeseriesCollectorFactoryTest.kt} (93%) rename features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/{RumSessionScopeTimeseriesTest.kt => DefaultTimeseriesCollectorTest.kt} (87%) rename features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/{CsvTimeseries.kt => CsvCollector.kt} (91%) rename features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/{TimeseriesEndToEndTest.kt => TimeseriesCollectorEndToEndTest.kt} (97%) rename features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/{VitalReaderDataPointsReaderTest.kt => provider/VitalReaderWrapperTest.kt} (94%) diff --git a/detekt_custom_safe_calls_third_party.yml b/detekt_custom_safe_calls_third_party.yml index b44f9ddebc..9f3686b425 100644 --- a/detekt_custom_safe_calls_third_party.yml +++ b/detekt_custom_safe_calls_third_party.yml @@ -259,7 +259,7 @@ datadog: - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.android.api.SdkCore?, com.datadog.android.api.SdkCore?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.android.flags.internal.repository.DefaultFlagsRepository.FlagsState?, com.datadog.android.flags.internal.repository.DefaultFlagsRepository.FlagsState?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI?, com.datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI?)" - - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.android.rum.internal.timeseries.RumSessionScopeTimeseries.State?, com.datadog.android.rum.internal.timeseries.RumSessionScopeTimeseries.State?)" + - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.android.rum.internal.timeseries.DefaultTimeseriesCollector.State?, com.datadog.android.rum.internal.timeseries.DefaultTimeseriesCollector.State?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.android.trace.api.tracer.DatadogTracer?, com.datadog.android.trace.api.tracer.DatadogTracer?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(kotlin.String?, kotlin.String?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(kotlin.collections.Set?, kotlin.collections.Set?)" @@ -272,14 +272,14 @@ datadog: - "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.flags.model.FlagsClientState?)" - "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.flags.model.ProviderContext?)" - "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.rum.internal.domain.RumContext?)" - - "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.rum.internal.timeseries.RumSessionScopeTimeseries.State?)" + - "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.rum.internal.timeseries.DefaultTimeseriesCollector.State?)" - "java.util.concurrent.atomic.AtomicReference.constructor(kotlin.collections.Map?)" - "java.util.concurrent.atomic.AtomicReference.constructor(kotlin.String?)" - "java.util.concurrent.atomic.AtomicReference.constructor(kotlin.collections.Set?)" - "java.util.concurrent.atomic.AtomicReference.get()" - "java.util.concurrent.atomic.AtomicReference.getAndSet(kotlin.String?)" - "java.util.concurrent.atomic.AtomicReference.getAndSet(java.util.concurrent.Future?)" - - "java.util.concurrent.atomic.AtomicReference.getAndSet(com.datadog.android.rum.internal.timeseries.RumSessionScopeTimeseries.State?)" + - "java.util.concurrent.atomic.AtomicReference.getAndSet(com.datadog.android.rum.internal.timeseries.DefaultTimeseriesCollector.State?)" - "java.util.concurrent.atomic.AtomicReference.set(android.app.Application.ActivityLifecycleCallbacks?)" - "java.util.concurrent.atomic.AtomicReference.set(com.datadog.android.api.FeatureEventReceiver?)" - "java.util.concurrent.atomic.AtomicReference.set(com.datadog.android.api.SdkCore?)" diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt index 56eba4f318..80ffa3b0aa 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt @@ -167,7 +167,7 @@ object Rum { }, insightsCollector = rumFeature.insightsCollector, heatmapIdentifierRegistry = rumFeature.heatmapIdentifierRegistry, - timeseriesFactory = rumFeature.timeseriesFactory + timeseriesCollectorFactory = rumFeature.timeseriesCollectorFactory ) } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index 8c995b90e6..89c48906c6 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -92,10 +92,10 @@ import com.datadog.android.rum.internal.startup.RumStartupScenario import com.datadog.android.rum.internal.startup.RumTTIDInfo import com.datadog.android.rum.internal.thread.NoOpScheduledExecutorService import com.datadog.android.rum.internal.timeseries.Buffer -import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesFactory +import com.datadog.android.rum.internal.timeseries.DefaultTimeseriesCollectorFactory +import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesCollectorFactory import com.datadog.android.rum.internal.timeseries.Pipeline -import com.datadog.android.rum.internal.timeseries.RumSessionScopeTimeseriesFactory -import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.TimeseriesCollector import com.datadog.android.rum.internal.timeseries.provider.CpuDatapointReader import com.datadog.android.rum.internal.timeseries.provider.VitalReaderWrapper import com.datadog.android.rum.internal.timeseries.serializer.CpuEventSerializer @@ -200,7 +200,7 @@ internal class RumFeature( internal val rumContextUpdateReceivers = mutableSetOf() internal var insightsCollector: InsightsCollector = NoOpInsightsCollector() override val heatmapIdentifierRegistry: HeatmapIdentifierRegistry = HeatmapIdentifierRegistry.create() - internal var timeseriesFactory: Timeseries.Factory = NoOpTimeseriesFactory() + internal var timeseriesCollectorFactory: TimeseriesCollector.Factory = NoOpTimeseriesCollectorFactory() private val lateCrashEventHandler by lazy { lateCrashReporterFactory(sdkCore as InternalSdkCore) } internal var rumAppStartupDetector: RumAppStartupDetector? = null @@ -303,7 +303,7 @@ internal class RumFeature( sessionListener = configuration.sessionListener configuration.timeseriesConfiguration?.let { configuration -> - timeseriesFactory = createTimeseriesCollectingFactory( + timeseriesCollectorFactory = createTimeseriesCollectingFactory( configuration, appContext.readTotalRamBytes(sdkCore.internalLogger) ?: 0L, insightsCollector @@ -375,8 +375,8 @@ internal class RumFeature( unregisterTrackingStrategies(appContext) - timeseriesFactory = NoOpTimeseriesFactory() - (GlobalRumMonitor.get(sdkCore) as? DatadogRumMonitor)?.stopActiveTimeseries() + timeseriesCollectorFactory = NoOpTimeseriesCollectorFactory() + (GlobalRumMonitor.get(sdkCore) as? DatadogRumMonitor)?.stopTimeseries() dataWriter = NoOpDataWriter() @@ -457,7 +457,7 @@ internal class RumFeature( configuration: TimeseriesConfiguration, totalRamBytes: Long, insightsCollector: InsightsCollector - ): Timeseries.Factory = RumSessionScopeTimeseriesFactory( + ): TimeseriesCollector.Factory = DefaultTimeseriesCollectorFactory( internalLogger = sdkCore.internalLogger, collectInBackground = configuration.collectInBackground, scheduledExecutorService = vitalExecutorService, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt index daeb20280a..07aba22a7d 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt @@ -30,7 +30,7 @@ import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollect import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.startup.RumSessionScopeStartupManager -import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.TimeseriesCollector import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -59,7 +59,7 @@ internal class RumApplicationScope( private val rumSessionScopeStartupManagerFactory: () -> RumSessionScopeStartupManager, private val insightsCollector: InsightsCollector, private val heatmapIdentifierRegistry: HeatmapIdentifierRegistry?, - private val timeseriesFactory: Timeseries.Factory + private val timeseriesCollectorFactory: TimeseriesCollector.Factory ) : RumScope, RumViewChangedListener { override val parentScope: RumScope? = null @@ -91,7 +91,7 @@ internal class RumApplicationScope( rumSessionScopeStartupManagerFactory = rumSessionScopeStartupManagerFactory, insightsCollector = insightsCollector, heatmapIdentifierRegistry = heatmapIdentifierRegistry, - timeseriesFactory = timeseriesFactory + timeseriesCollectorFactory = timeseriesCollectorFactory ) ) @@ -215,7 +215,7 @@ internal class RumApplicationScope( rumSessionScopeStartupManagerFactory = rumSessionScopeStartupManagerFactory, insightsCollector = insightsCollector, heatmapIdentifierRegistry = heatmapIdentifierRegistry, - timeseriesFactory = timeseriesFactory + timeseriesCollectorFactory = timeseriesCollectorFactory ) childScopes.add(newSession) if (event !is RumRawEvent.StartView) { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt index 8d5396e559..618f28c7db 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt @@ -28,9 +28,9 @@ import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollect import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.startup.RumSessionScopeStartupManager -import com.datadog.android.rum.internal.timeseries.NoOpTimeseries -import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesFactory -import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesCollector +import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesCollectorFactory +import com.datadog.android.rum.internal.timeseries.TimeseriesCollector import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -65,10 +65,10 @@ internal class RumSessionScope( private val rumSessionScopeStartupManagerFactory: () -> RumSessionScopeStartupManager, insightsCollector: InsightsCollector, heatmapIdentifierRegistry: HeatmapIdentifierRegistry?, - private val timeseriesFactory: Timeseries.Factory = NoOpTimeseriesFactory() + private val timeseriesCollectorFactory: TimeseriesCollector.Factory = NoOpTimeseriesCollectorFactory() ) : RumScope { - private var timeseries: Timeseries = NoOpTimeseries() + private var timeseriesCollector: TimeseriesCollector = NoOpTimeseriesCollector() internal var sessionId = RumContext.NULL_UUID internal var sessionState: State = State.NOT_TRACKED @@ -201,7 +201,7 @@ internal class RumSessionScope( } } - timeseries.onViewTypeUpdate(getActiveRumContext().viewType) + timeseriesCollector.onViewTypeUpdate(getActiveRumContext().viewType) return if (isSessionComplete()) { null @@ -234,17 +234,17 @@ internal class RumSessionScope( } private fun startTimeseries() { - timeseries = timeseriesFactory.create( + timeseriesCollector = timeseriesCollectorFactory.create( sessionId = sessionId, applicationId = parentScope.getRumContext().applicationId, sessionType = getRumContext().resolveSessionType(rumSessionTypeOverride) ) - timeseries.onSessionStart() + timeseriesCollector.onSessionStart() } internal fun stopTimeseries() { - timeseries.onSessionStop() - timeseries = NoOpTimeseries() + timeseriesCollector.onSessionStop() + timeseriesCollector = NoOpTimeseriesCollector() } private fun stopSession() { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt index e73a358cca..c2c834a073 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt @@ -66,7 +66,7 @@ import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.startup.RumSessionScopeStartupManager import com.datadog.android.rum.internal.startup.RumStartupScenario import com.datadog.android.rum.internal.startup.RumTTIDInfo -import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.TimeseriesCollector import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -111,7 +111,7 @@ internal class DatadogRumMonitor( private val rumSessionScopeStartupManagerFactory: () -> RumSessionScopeStartupManager, insightsCollector: InsightsCollector, heatmapIdentifierRegistry: HeatmapIdentifierRegistry?, - timeseriesFactory: Timeseries.Factory + timeseriesCollectorFactory: TimeseriesCollector.Factory ) : RumMonitor, AdvancedRumMonitor { @Volatile private var cachedViewUrl: String? = null @@ -138,7 +138,7 @@ internal class DatadogRumMonitor( rumSessionScopeStartupManagerFactory = rumSessionScopeStartupManagerFactory, insightsCollector = insightsCollector, heatmapIdentifierRegistry = heatmapIdentifierRegistry, - timeseriesFactory = timeseriesFactory + timeseriesCollectorFactory = timeseriesCollectorFactory ) internal var debugListener: RumDebugListener? = null @@ -935,7 +935,7 @@ internal class DatadogRumMonitor( * de-initializes and shuts down its context executor before the async write task fires, the * write is silently skipped regardless. */ - internal fun stopActiveTimeseries() { + internal fun stopTimeseries() { synchronized(rootScope) { rootScope.activeSession?.stopTimeseries() } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt similarity index 98% rename from features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt rename to features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt index 1fc4a36d92..506506ac77 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseries.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt @@ -36,12 +36,12 @@ import java.util.concurrent.atomic.AtomicReference * When [startSampling] starts a new generation, any in-flight or queued ticks from the * previous generation self-terminate on their first check. */ -internal class RumSessionScopeTimeseries( +internal class DefaultTimeseriesCollector( private val internalLogger: InternalLogger, internal val pipelines: List>, private val collectInBackground: Boolean, internal val scheduledExecutorService: ScheduledExecutorService -) : Timeseries { +) : TimeseriesCollector { private enum class State { IDLE, RUNNING, SUSPENDED, STOPPED } private val state = AtomicReference(State.IDLE) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactory.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt similarity index 90% rename from features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactory.kt rename to features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt index 9e42560294..bec629e64e 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactory.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt @@ -10,7 +10,7 @@ import com.datadog.android.api.InternalLogger import com.datadog.android.rum.RumSessionType import java.util.concurrent.ScheduledExecutorService -internal class RumSessionScopeTimeseriesFactory( +internal class DefaultTimeseriesCollectorFactory( private val internalLogger: InternalLogger, private val collectInBackground: Boolean, private val scheduledExecutorService: ScheduledExecutorService, @@ -19,10 +19,10 @@ internal class RumSessionScopeTimeseriesFactory( sessionId: String, sessionType: RumSessionType ) -> List> -) : Timeseries.Factory { +) : TimeseriesCollector.Factory { override fun create(applicationId: String, sessionId: String, sessionType: RumSessionType) = - RumSessionScopeTimeseries( + DefaultTimeseriesCollector( internalLogger = internalLogger, collectInBackground = collectInBackground, scheduledExecutorService = scheduledExecutorService, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesCollector.kt similarity index 89% rename from features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt rename to features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesCollector.kt index f08f5144fe..93fc71ec3f 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Timeseries.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesCollector.kt @@ -11,13 +11,13 @@ import com.datadog.android.rum.internal.domain.scope.RumViewType import com.datadog.tools.annotation.NoOpImplementation @NoOpImplementation -internal interface Timeseries { +internal interface TimeseriesCollector { fun onSessionStart() fun onSessionStop() fun onViewTypeUpdate(newViewType: RumViewType) @NoOpImplementation interface Factory { - fun create(applicationId: String, sessionId: String, sessionType: RumSessionType): Timeseries + fun create(applicationId: String, sessionId: String, sessionType: RumSessionType): TimeseriesCollector } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt index 3778de151e..bb3139225d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt @@ -56,8 +56,8 @@ import com.datadog.android.rum.internal.monitor.DatadogRumMonitor import com.datadog.android.rum.internal.monitor.NoOpAdvancedRumMonitor import com.datadog.android.rum.internal.startup.RumAppStartupDetector import com.datadog.android.rum.internal.thread.NoOpScheduledExecutorService -import com.datadog.android.rum.internal.timeseries.RumSessionScopeTimeseries -import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.DefaultTimeseriesCollector +import com.datadog.android.rum.internal.timeseries.TimeseriesCollector import com.datadog.android.rum.internal.tracking.NoOpInteractionPredicate import com.datadog.android.rum.internal.tracking.NoOpUserActionTrackingStrategy import com.datadog.android.rum.internal.tracking.UserActionTrackingStrategy @@ -869,7 +869,7 @@ internal class RumFeatureTest { var writerAtFlushTime: DataWriter? = null doAnswer { writerAtFlushTime = testedFeature.dataWriter - }.whenever(mockDatadogMonitor).stopActiveTimeseries() + }.whenever(mockDatadogMonitor).stopTimeseries() // When testedFeature.onStop() @@ -904,7 +904,7 @@ internal class RumFeatureTest { val timeseries = createTimeseries(totalRamBytes = 0L, sessionId = fakeSessionId) // Then: only the CPU pipeline is created; the memory pipeline is skipped. - check(timeseries is RumSessionScopeTimeseries) + check(timeseries is DefaultTimeseriesCollector) assertThat(timeseries.pipelines).hasSize(1) } @@ -917,7 +917,7 @@ internal class RumFeatureTest { val timeseries = createTimeseries(totalRamBytes = fakeTotalRamBytes, sessionId = fakeSessionId) // Then: both the CPU and memory pipelines are created. - check(timeseries is RumSessionScopeTimeseries) + check(timeseries is DefaultTimeseriesCollector) assertThat(timeseries.pipelines).hasSize(2) } @@ -1918,7 +1918,7 @@ internal class RumFeatureTest { // Builds a timeseries collector for the given total device RAM (in bytes), bypassing the // ActivityManager read so the skip/collect/log behavior can be exercised deterministically. @OptIn(ExperimentalRumApi::class) - private fun createTimeseries(totalRamBytes: Long, sessionId: String): Timeseries = + private fun createTimeseries(totalRamBytes: Long, sessionId: String): TimeseriesCollector = testedFeature .createTimeseriesCollectingFactory( TimeseriesConfiguration.Builder().build(), diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt index f2e1e67b81..b0735e5c7d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt @@ -27,7 +27,7 @@ import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollect import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter -import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.TimeseriesCollector import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -137,10 +137,10 @@ internal class RumApplicationScopeAttributePropagationTest { lateinit var mockSessionSampler: Sampler @Mock - lateinit var mockTimeseriesFactory: Timeseries.Factory + lateinit var mockTimeseriesCollectorFactory: TimeseriesCollector.Factory @Mock - lateinit var mockTimeseries: Timeseries + lateinit var mockTimeseriesCollector: TimeseriesCollector @Forgery lateinit var fakeEventTime: Time @@ -201,7 +201,7 @@ internal class RumApplicationScopeAttributePropagationTest { fakeRumSessionType = forge.aNullable { aValueFrom(RumSessionType::class.java) } whenever(mockSessionSampler.getSampleRate()).thenReturn(fakeSampleRate) whenever(mockSessionSampler.sample(any())).thenReturn(true) - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + whenever(mockTimeseriesCollectorFactory.create(any(), any(), any())) doReturn mockTimeseriesCollector testedScope = RumApplicationScope( applicationId = fakeApplicationId, sdkCore = rumMonitor.mockSdkCore, @@ -224,7 +224,7 @@ internal class RumApplicationScopeAttributePropagationTest { rumSessionScopeStartupManagerFactory = mock(), insightsCollector = mockInsightsCollector, heatmapIdentifierRegistry = null, - timeseriesFactory = mockTimeseriesFactory + timeseriesCollectorFactory = mockTimeseriesCollectorFactory ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt index 4547c169ce..315f84fccb 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt @@ -30,7 +30,7 @@ import com.datadog.android.rum.internal.domain.state.ViewUIPerformanceReport import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener -import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.TimeseriesCollector import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -139,10 +139,10 @@ internal class RumApplicationScopeTest { lateinit var mockSessionSampler: Sampler @Mock - lateinit var mockTimeseriesFactory: Timeseries.Factory + lateinit var mockTimeseriesCollectorFactory: TimeseriesCollector.Factory @Mock - lateinit var mockTimeseries: Timeseries + lateinit var mockTimeseriesCollector: TimeseriesCollector @StringForgery(regex = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}") lateinit var fakeApplicationId: String @@ -185,7 +185,7 @@ internal class RumApplicationScopeTest { whenever(mockSessionSampler.getSampleRate()).thenReturn(fakeSampleRate) whenever(mockSessionSampler.sample(any())).thenReturn(true) - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + whenever(mockTimeseriesCollectorFactory.create(any(), any(), any())) doReturn mockTimeseriesCollector testedScope = RumApplicationScope( applicationId = fakeApplicationId, @@ -209,7 +209,7 @@ internal class RumApplicationScopeTest { rumSessionScopeStartupManagerFactory = mock(), insightsCollector = mockInsightsCollector, heatmapIdentifierRegistry = null, - timeseriesFactory = mockTimeseriesFactory + timeseriesCollectorFactory = mockTimeseriesCollectorFactory ) } @@ -243,7 +243,7 @@ internal class RumApplicationScopeTest { ) // Then - the child session uses the same factory we gave to the application scope - verify(mockTimeseriesFactory).create(eq(fakeApplicationId), any(), any()) + verify(mockTimeseriesCollectorFactory).create(eq(fakeApplicationId), any(), any()) } @Test @@ -282,7 +282,7 @@ internal class RumApplicationScopeTest { ) // Then - factory.create is invoked once per tracked session (initial + new) - verify(mockTimeseriesFactory, times(2)).create(eq(fakeApplicationId), any(), any()) + verify(mockTimeseriesCollectorFactory, times(2)).create(eq(fakeApplicationId), any(), any()) } @Test diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt index df8b816ce6..15c7b7a948 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt @@ -26,7 +26,7 @@ import com.datadog.android.rum.internal.domain.display.DisplayInfo import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener -import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.TimeseriesCollector import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -130,10 +130,10 @@ internal class RumSessionScopeAttributePropagationTest { lateinit var mockSessionSampler: Sampler @Mock - lateinit var mockTimeseriesFactory: Timeseries.Factory + lateinit var mockTimeseriesCollectorFactory: TimeseriesCollector.Factory @Mock - lateinit var mockTimeseries: Timeseries + lateinit var mockTimeseriesCollector: TimeseriesCollector lateinit var fakeParentAttributes: Map @@ -171,7 +171,7 @@ internal class RumSessionScopeAttributePropagationTest { fakeRumSessionType = forge.aNullable { aValueFrom(RumSessionType::class.java) } whenever(mockSessionSampler.getSampleRate()).thenReturn(fakeSampleRate) whenever(mockSessionSampler.sample(any())).thenReturn(true) - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + whenever(mockTimeseriesCollectorFactory.create(any(), any(), any())) doReturn mockTimeseriesCollector testedScope = RumSessionScope( parentScope = mockParentScope, sdkCore = mockSdkCore, @@ -198,7 +198,7 @@ internal class RumSessionScopeAttributePropagationTest { rumSessionScopeStartupManagerFactory = mock(), insightsCollector = mockInsightsCollector, heatmapIdentifierRegistry = null, - timeseriesFactory = mockTimeseriesFactory + timeseriesCollectorFactory = mockTimeseriesCollectorFactory ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt index 99bae97d64..8a9966d37e 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt @@ -37,8 +37,8 @@ import com.datadog.android.rum.internal.startup.RumSessionScopeStartupManager import com.datadog.android.rum.internal.startup.RumStartupScenario import com.datadog.android.rum.internal.startup.RumTTIDInfo import com.datadog.android.rum.internal.startup.testRumStartupScenarios -import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesFactory -import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesCollectorFactory +import com.datadog.android.rum.internal.timeseries.TimeseriesCollector import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -197,10 +197,10 @@ internal class RumSessionScopeTest { private lateinit var mockRumSessionScopeStartupManager: RumSessionScopeStartupManager @Mock - private lateinit var mockTimeseriesFactory: Timeseries.Factory + private lateinit var mockTimeseriesCollectorFactory: TimeseriesCollector.Factory @Mock - private lateinit var mockTimeseries: Timeseries + private lateinit var mockTimeseriesCollector: TimeseriesCollector private lateinit var stubTimeProvider: StubTimeProvider @@ -230,7 +230,7 @@ internal class RumSessionScopeTest { whenever(mockDisplayInfoProvider.getState()) doReturn fakeDisplayInfo whenever(mockSessionSampler.sample(any())).thenReturn(true) whenever(mockSessionSampler.getSampleRate()).thenReturn(100f) - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn mockTimeseries + whenever(mockTimeseriesCollectorFactory.create(any(), any(), any())) doReturn mockTimeseriesCollector fakeParentAttributes = forge.exhaustiveAttributes() whenever(mockParentScope.getCustomAttributes()) doReturn fakeParentAttributes @@ -1858,7 +1858,7 @@ internal class RumSessionScopeTest { fun `M start timeseries via factory W renewSession { keepSession = true }`(forge: Forge) { // Given fakeParentContext = fakeParentContext.copy(syntheticsTestId = null, syntheticsResultId = null) - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) // When testedScope.handleEvent( @@ -1869,12 +1869,12 @@ internal class RumSessionScopeTest { ) // Then - verify(mockTimeseriesFactory).create( + verify(mockTimeseriesCollectorFactory).create( fakeParentContext.applicationId, testedScope.sessionId, fakeRumSessionType ?: RumSessionType.USER ) - verify(mockTimeseries).onSessionStart() + verify(mockTimeseriesCollector).onSessionStart() } @Test @@ -1882,13 +1882,13 @@ internal class RumSessionScopeTest { // Given fakeParentContext = fakeParentContext.copy(syntheticsTestId = null, syntheticsResultId = null) fakeRumSessionType = null - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) // When testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then - verify(mockTimeseriesFactory).create( + verify(mockTimeseriesCollectorFactory).create( fakeParentContext.applicationId, testedScope.sessionId, RumSessionType.USER @@ -1904,13 +1904,13 @@ internal class RumSessionScopeTest { // Given fakeParentContext = fakeParentContext.copy(syntheticsTestId = fakeTestId, syntheticsResultId = fakeResultId) fakeRumSessionType = null - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) // When testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then - verify(mockTimeseriesFactory).create( + verify(mockTimeseriesCollectorFactory).create( fakeParentContext.applicationId, testedScope.sessionId, RumSessionType.SYNTHETICS @@ -1927,13 +1927,13 @@ internal class RumSessionScopeTest { fakeParentContext = fakeParentContext.copy(syntheticsTestId = fakeTestId, syntheticsResultId = fakeResultId) val expectedSessionType = forge.aValueFrom(RumSessionType::class.java) fakeRumSessionType = expectedSessionType - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) // When testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then - verify(mockTimeseriesFactory).create( + verify(mockTimeseriesCollectorFactory).create( fakeParentContext.applicationId, testedScope.sessionId, expectedSessionType @@ -1948,13 +1948,13 @@ internal class RumSessionScopeTest { fakeParentContext = fakeParentContext.copy(syntheticsTestId = null, syntheticsResultId = null) val expectedSessionType = forge.aValueFrom(RumSessionType::class.java) fakeRumSessionType = expectedSessionType - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) // When testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then - verify(mockTimeseriesFactory).create(any(), any(), eq(expectedSessionType)) + verify(mockTimeseriesCollectorFactory).create(any(), any(), eq(expectedSessionType)) } @Test @@ -1965,13 +1965,13 @@ internal class RumSessionScopeTest { // Given fakeParentContext = fakeParentContext.copy(syntheticsTestId = null, syntheticsResultId = fakeResultId) fakeRumSessionType = null - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) // When testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then - verify(mockTimeseriesFactory).create(any(), any(), eq(RumSessionType.USER)) + verify(mockTimeseriesCollectorFactory).create(any(), any(), eq(RumSessionType.USER)) } @Test @@ -1982,13 +1982,13 @@ internal class RumSessionScopeTest { // Given fakeParentContext = fakeParentContext.copy(syntheticsTestId = fakeTestId, syntheticsResultId = null) fakeRumSessionType = null - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) // When testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then - verify(mockTimeseriesFactory).create(any(), any(), eq(RumSessionType.USER)) + verify(mockTimeseriesCollectorFactory).create(any(), any(), eq(RumSessionType.USER)) } @Test @@ -1999,13 +1999,13 @@ internal class RumSessionScopeTest { // Given fakeParentContext = fakeParentContext.copy(syntheticsTestId = " ", syntheticsResultId = fakeResultId) fakeRumSessionType = null - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) // When testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then - verify(mockTimeseriesFactory).create(any(), any(), eq(RumSessionType.USER)) + verify(mockTimeseriesCollectorFactory).create(any(), any(), eq(RumSessionType.USER)) } @Test @@ -2016,20 +2016,20 @@ internal class RumSessionScopeTest { // Given fakeParentContext = fakeParentContext.copy(syntheticsTestId = fakeTestId, syntheticsResultId = " ") fakeRumSessionType = null - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) // When testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then - verify(mockTimeseriesFactory).create(any(), any(), eq(RumSessionType.USER)) + verify(mockTimeseriesCollectorFactory).create(any(), any(), eq(RumSessionType.USER)) } @Test fun `M not start timeseries W renewSession { keepSession = false }`(forge: Forge) { // Given whenever(mockSessionSampler.sample(any())).thenReturn(false) - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) // When testedScope.handleEvent( @@ -2040,14 +2040,14 @@ internal class RumSessionScopeTest { ) // Then - verify(mockTimeseriesFactory, never()).create(any(), any(), any()) - verify(mockTimeseries, never()).onSessionStart() + verify(mockTimeseriesCollectorFactory, never()).create(any(), any(), any()) + verify(mockTimeseriesCollector, never()).onSessionStart() } @Test fun `M stop previous timeseries W renewSession { another tracked session }`(forge: Forge) { // Given - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) testedScope.handleEvent( forge.startViewEvent(), fakeDatadogContext, @@ -2065,15 +2065,18 @@ internal class RumSessionScopeTest { ) // Then - verify(mockTimeseries).onSessionStop() - verify(mockTimeseriesFactory, times(2)).create(any(), any(), any()) - verify(mockTimeseries, times(2)).onSessionStart() + verify(mockTimeseriesCollector).onSessionStop() + verify(mockTimeseriesCollectorFactory, times(2)).create(any(), any(), any()) + verify(mockTimeseriesCollector, times(2)).onSessionStart() } @Test fun `M stop timeseries W handleEvent { session expires without renewal }`(forge: Forge) { // Given - initializeTestedScope(backgroundTrackingEnabled = false, timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope( + backgroundTrackingEnabled = false, + timeseriesCollectorFactory = mockTimeseriesCollectorFactory + ) testedScope.handleEvent( forge.startViewEvent(), fakeDatadogContext, @@ -2087,13 +2090,13 @@ internal class RumSessionScopeTest { // Then assertThat(testedScope.sessionState).isEqualTo(RumSessionScope.State.EXPIRED) - verify(mockTimeseries).onSessionStop() + verify(mockTimeseriesCollector).onSessionStop() } @Test fun `M stop timeseries W handleEvent { StopSession }`(forge: Forge) { // Given - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) testedScope.handleEvent( forge.startViewEvent(), fakeDatadogContext, @@ -2110,7 +2113,7 @@ internal class RumSessionScopeTest { ) // Then - verify(mockTimeseries).onSessionStop() + verify(mockTimeseriesCollector).onSessionStop() } @Test @@ -2125,13 +2128,13 @@ internal class RumSessionScopeTest { // Then assertThat(result).isSameAs(testedScope) - verifyNoInteractions(mockTimeseries, mockTimeseriesFactory) + verifyNoInteractions(mockTimeseriesCollector, mockTimeseriesCollectorFactory) } @Test fun `M notify timeseries of view type W handleEvent`(forge: Forge) { // Given - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) testedScope.handleEvent( forge.startViewEvent(), fakeDatadogContext, @@ -2148,7 +2151,7 @@ internal class RumSessionScopeTest { ) // Then — called once per handleEvent invocation - verify(mockTimeseries, times(2)).onViewTypeUpdate(any()) + verify(mockTimeseriesCollector, times(2)).onViewTypeUpdate(any()) } @Test @@ -2156,7 +2159,7 @@ internal class RumSessionScopeTest { // Given initializeTestedScope( withMockChildScope = false, - timeseriesFactory = mockTimeseriesFactory + timeseriesCollectorFactory = mockTimeseriesCollectorFactory ) // When @@ -2168,21 +2171,21 @@ internal class RumSessionScopeTest { ) // Then - verify(mockTimeseries).onViewTypeUpdate(RumViewType.FOREGROUND) - verify(mockTimeseries, never()).onViewTypeUpdate(RumViewType.NONE) + verify(mockTimeseriesCollector).onViewTypeUpdate(RumViewType.FOREGROUND) + verify(mockTimeseriesCollector, never()).onViewTypeUpdate(RumViewType.NONE) } @Test fun `M pass NONE W onViewTypeUpdate { no active view }`(forge: Forge) { // Given — activeView is null by default (mockChildScope.activeView not stubbed) - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // When testedScope.handleEvent(forge.addErrorEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then - verify(mockTimeseries, times(2)).onViewTypeUpdate(RumViewType.NONE) + verify(mockTimeseriesCollector, times(2)).onViewTypeUpdate(RumViewType.NONE) } @Test @@ -2192,14 +2195,14 @@ internal class RumSessionScopeTest { val fakeRumContext = forge.getForgery().copy(viewType = RumViewType.FOREGROUND) whenever(mockChildScope.activeView) doReturn mockViewScope whenever(mockViewScope.getRumContext()) doReturn fakeRumContext - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // When testedScope.handleEvent(forge.addErrorEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then — at least the last call carries FOREGROUND - verify(mockTimeseries, atLeastOnce()).onViewTypeUpdate(RumViewType.FOREGROUND) + verify(mockTimeseriesCollector, atLeastOnce()).onViewTypeUpdate(RumViewType.FOREGROUND) } @Test @@ -2209,7 +2212,7 @@ internal class RumSessionScopeTest { val fakeRumContext = forge.getForgery().copy(viewType = RumViewType.FOREGROUND) whenever(mockChildScope.activeView) doReturn mockViewScope whenever(mockViewScope.getRumContext()) doReturn fakeRumContext - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // When — expire session and start a new one @@ -2217,19 +2220,19 @@ internal class RumSessionScopeTest { testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then — second timeseries (same factory mock) also receives FOREGROUND right after start() - val inOrder = inOrder(mockTimeseries) - inOrder.verify(mockTimeseries, times(2)).onSessionStart() - inOrder.verify(mockTimeseries).onViewTypeUpdate(RumViewType.FOREGROUND) + val inOrder = inOrder(mockTimeseriesCollector) + inOrder.verify(mockTimeseriesCollector, times(2)).onSessionStart() + inOrder.verify(mockTimeseriesCollector).onViewTypeUpdate(RumViewType.FOREGROUND) } @Test fun `M stop previous timeseries W renewSession { ResetSession renews tracked session }`(forge: Forge) { // Given - initializeTestedScope(timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - val firstTimeseries = mockTimeseries - val secondTimeseries: Timeseries = mock() - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn secondTimeseries + val firstCollector = mockTimeseriesCollector + val secondTimeseriesCollector: TimeseriesCollector = mock() + whenever(mockTimeseriesCollectorFactory.create(any(), any(), any())) doReturn secondTimeseriesCollector // When testedScope.handleEvent( @@ -2240,19 +2243,22 @@ internal class RumSessionScopeTest { ) // Then - verify(firstTimeseries).onSessionStart() - verify(firstTimeseries).onSessionStop() - verify(secondTimeseries).onSessionStart() + verify(firstCollector).onSessionStart() + verify(firstCollector).onSessionStop() + verify(secondTimeseriesCollector).onSessionStart() } @Test fun `M stop previous timeseries W renewSession { expired session then new interaction }`(forge: Forge) { // Given - initializeTestedScope(backgroundTrackingEnabled = false, timeseriesFactory = mockTimeseriesFactory) + initializeTestedScope( + backgroundTrackingEnabled = false, + timeseriesCollectorFactory = mockTimeseriesCollectorFactory + ) testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - val firstTimeseries = mockTimeseries - val secondTimeseries: Timeseries = mock() - whenever(mockTimeseriesFactory.create(any(), any(), any())) doReturn secondTimeseries + val firstCollector = mockTimeseriesCollector + val secondTimeseriesCollector: TimeseriesCollector = mock() + whenever(mockTimeseriesCollectorFactory.create(any(), any(), any())) doReturn secondTimeseriesCollector advanceTimeByMs(TEST_INACTIVITY_MS) testedScope.handleEvent(mock(), fakeDatadogContext, mockEventWriteScope, mockWriter) @@ -2260,9 +2266,9 @@ internal class RumSessionScopeTest { testedScope.handleEvent(forge.startActionEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then - verify(firstTimeseries).onSessionStart() - verify(firstTimeseries).onSessionStop() - verify(secondTimeseries).onSessionStart() + verify(firstCollector).onSessionStart() + verify(firstCollector).onSessionStop() + verify(secondTimeseriesCollector).onSessionStart() } @Test @@ -2276,7 +2282,7 @@ internal class RumSessionScopeTest { ) // Then - verifyNoInteractions(mockTimeseries) + verifyNoInteractions(mockTimeseriesCollector) } // endregion @@ -2298,7 +2304,7 @@ internal class RumSessionScopeTest { sessionSampler: Sampler = mockSessionSampler, withMockChildScope: Boolean = true, backgroundTrackingEnabled: Boolean? = null, - timeseriesFactory: Timeseries.Factory = NoOpTimeseriesFactory() + timeseriesCollectorFactory: TimeseriesCollector.Factory = NoOpTimeseriesCollectorFactory() ) { testedScope = RumSessionScope( parentScope = mockParentScope, @@ -2326,7 +2332,7 @@ internal class RumSessionScopeTest { rumSessionScopeStartupManagerFactory = { mockRumSessionScopeStartupManager }, insightsCollector = mockInsightsCollector, heatmapIdentifierRegistry = null, - timeseriesFactory = timeseriesFactory + timeseriesCollectorFactory = timeseriesCollectorFactory ) if (withMockChildScope) { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt index 5d32fefa68..d9a88327c0 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt @@ -56,7 +56,7 @@ import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.monitor.DatadogRumMonitor.Companion.OPERATION_ERROR_INVALID_NAME import com.datadog.android.rum.internal.monitor.DatadogRumMonitor.Companion.OPERATION_ERROR_INVALID_NAME_CHARACTERS import com.datadog.android.rum.internal.monitor.DatadogRumMonitor.Companion.OPERATION_ERROR_INVALID_OPERATION_KEY -import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesFactory +import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesCollectorFactory import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -307,7 +307,7 @@ internal class DatadogRumMonitorTest { insightsCollector = mockInsightsCollector, appPackageName = fakeApplicationPackageName, heatmapIdentifierRegistry = null, - timeseriesFactory = NoOpTimeseriesFactory() + timeseriesCollectorFactory = NoOpTimeseriesCollectorFactory() ) testedMonitor.rootScope = mockApplicationScope } @@ -342,7 +342,7 @@ internal class DatadogRumMonitorTest { insightsCollector = mockInsightsCollector, appPackageName = fakeApplicationPackageName, heatmapIdentifierRegistry = null, - timeseriesFactory = NoOpTimeseriesFactory() + timeseriesCollectorFactory = NoOpTimeseriesCollectorFactory() ) // When @@ -420,7 +420,7 @@ internal class DatadogRumMonitorTest { insightsCollector = mockInsightsCollector, appPackageName = fakeApplicationPackageName, heatmapIdentifierRegistry = null, - timeseriesFactory = NoOpTimeseriesFactory() + timeseriesCollectorFactory = NoOpTimeseriesCollectorFactory() ) testedMonitor.start() val mockCallback = mock<(String?) -> Unit>() @@ -466,7 +466,7 @@ internal class DatadogRumMonitorTest { insightsCollector = mockInsightsCollector, appPackageName = fakeApplicationPackageName, heatmapIdentifierRegistry = null, - timeseriesFactory = NoOpTimeseriesFactory() + timeseriesCollectorFactory = NoOpTimeseriesCollectorFactory() ) testedMonitor.start() val mockCallback = mock<(String?) -> Unit>() @@ -2228,7 +2228,7 @@ internal class DatadogRumMonitorTest { insightsCollector = mockInsightsCollector, appPackageName = fakeApplicationPackageName, heatmapIdentifierRegistry = null, - timeseriesFactory = NoOpTimeseriesFactory() + timeseriesCollectorFactory = NoOpTimeseriesCollectorFactory() ) // When @@ -2271,7 +2271,7 @@ internal class DatadogRumMonitorTest { insightsCollector = mockInsightsCollector, appPackageName = fakeApplicationPackageName, heatmapIdentifierRegistry = null, - timeseriesFactory = NoOpTimeseriesFactory() + timeseriesCollectorFactory = NoOpTimeseriesCollectorFactory() ) // When @@ -2315,7 +2315,7 @@ internal class DatadogRumMonitorTest { insightsCollector = mockInsightsCollector, appPackageName = fakeApplicationPackageName, heatmapIdentifierRegistry = null, - timeseriesFactory = NoOpTimeseriesFactory() + timeseriesCollectorFactory = NoOpTimeseriesCollectorFactory() ) whenever(mockExecutorService.isShutdown).thenReturn(true) @@ -2541,7 +2541,7 @@ internal class DatadogRumMonitorTest { insightsCollector = mockInsightsCollector, appPackageName = fakeApplicationPackageName, heatmapIdentifierRegistry = null, - timeseriesFactory = NoOpTimeseriesFactory() + timeseriesCollectorFactory = NoOpTimeseriesCollectorFactory() ) testedMonitor.startView(key, name, attributes) // When diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt similarity index 93% rename from features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt rename to features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt index 16c418b19a..8d7002c889 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesFactoryTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt @@ -36,7 +36,7 @@ import java.util.concurrent.ScheduledExecutorService ) @MockitoSettings(strictness = Strictness.LENIENT) @ForgeConfiguration(Configurator::class) -internal class RumSessionScopeTimeseriesFactoryTest { +internal class DefaultTimeseriesCollectorFactoryTest { @Mock lateinit var mockSdkCore: FeatureSdkCore @@ -51,7 +51,7 @@ internal class RumSessionScopeTimeseriesFactoryTest { lateinit var mockExecutor: ScheduledExecutorService @Test - fun `M build RumSessionScopeTimeseries with provided pipelines W create()`( + fun `M build DefaultTimeseriesCollector with provided pipelines W create()`( @StringForgery fakeSessionId: String, @StringForgery fakeApplicationId: String ) { @@ -64,7 +64,7 @@ internal class RumSessionScopeTimeseriesFactoryTest { whenever(this(fakeSessionId, fakeApplicationId, fakeSessionType)) .thenReturn(listOf(pipelineA, pipelineB)) } - val testedFactory = RumSessionScopeTimeseriesFactory( + val testedFactory = DefaultTimeseriesCollectorFactory( internalLogger = mockInternalLogger, collectInBackground = false, scheduledExecutorService = mockExecutor, @@ -86,7 +86,7 @@ internal class RumSessionScopeTimeseriesFactoryTest { ) { // Given val emptyProvider: (String, String, RumSessionType) -> List> = { _, _, _ -> emptyList() } - val testedFactory = RumSessionScopeTimeseriesFactory( + val testedFactory = DefaultTimeseriesCollectorFactory( internalLogger = mockInternalLogger, collectInBackground = true, scheduledExecutorService = mockExecutor, diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt similarity index 87% rename from features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesTest.kt rename to features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt index 4429538af9..fc75784716 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/RumSessionScopeTimeseriesTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt @@ -55,9 +55,9 @@ import java.util.concurrent.TimeUnit ) @MockitoSettings(strictness = Strictness.LENIENT) @ForgeConfiguration(Configurator::class) -internal class RumSessionScopeTimeseriesTest { +internal class DefaultTimeseriesCollectorTest { - private lateinit var testedTimeseries: RumSessionScopeTimeseries + private lateinit var testedTimeseriesCollector: DefaultTimeseriesCollector @Mock lateinit var mockDataWriter: DataWriter @@ -127,20 +127,20 @@ internal class RumSessionScopeTimeseriesTest { pipelineA = Pipeline(mockSdkCore, mockReaderA, bufferA, mockSerializerA, mockDataWriter, mockInternalLogger) pipelineB = Pipeline(mockSdkCore, mockReaderB, bufferB, mockSerializerB, mockDataWriter, mockInternalLogger) - testedTimeseries = RumSessionScopeTimeseries( + testedTimeseriesCollector = DefaultTimeseriesCollector( pipelines = listOf(pipelineA, pipelineB), internalLogger = mockInternalLogger, collectInBackground = false, scheduledExecutorService = mockExecutor ) // Seed FOREGROUND so sampling tests don't suspend on first tick - testedTimeseries.onViewTypeUpdate(RumViewType.FOREGROUND) + testedTimeseriesCollector.onViewTypeUpdate(RumViewType.FOREGROUND) } @Test fun `M schedule one runnable per pipeline W onSessionStart()`() { // When - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() // Then verify(mockExecutor).schedule(any(), eq(fakeIntervalAMs), eq(TimeUnit.MILLISECONDS)) @@ -150,7 +150,7 @@ internal class RumSessionScopeTimeseriesTest { @Test fun `M not write events W onSessionStop() { never started }`() { // When - testedTimeseries.onSessionStop() + testedTimeseriesCollector.onSessionStop() // Then verify(mockDataWriter, never()).write(any(), any(), any()) @@ -159,10 +159,10 @@ internal class RumSessionScopeTimeseriesTest { @Test fun `M not shut down shared executor W onSessionStop()`() { // Given - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() // When - testedTimeseries.onSessionStop() + testedTimeseriesCollector.onSessionStop() // Then — executor is owned by the SDK core and reused across components verify(mockExecutor, never()).shutdown() @@ -172,10 +172,10 @@ internal class RumSessionScopeTimeseriesTest { @Test fun `M not write events W onSessionStart()+onSessionStop() { buffers empty }`() { // Given - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() // When - testedTimeseries.onSessionStop() + testedTimeseriesCollector.onSessionStop() // Then verify(mockDataWriter, never()).write(any(), any(), any()) @@ -190,14 +190,14 @@ internal class RumSessionScopeTimeseriesTest { whenever(mockReaderB.read()) doReturn forge.getForgery>() whenever(mockSerializerA.serialize(any(), any())) doThrow fakeError whenever(mockSerializerB.serialize(any(), any())) doReturn fakeJson - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) val runnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) runnableA.run() runnableB.run() // When - testedTimeseries.onSessionStop() + testedTimeseriesCollector.onSessionStop() // Then — error logged for pipelineA, pipelineB still flushed mockInternalLogger.verifyLog( @@ -215,12 +215,12 @@ internal class RumSessionScopeTimeseriesTest { val fakeJson = JsonObject().apply { addProperty("k", "v") } whenever(mockReaderA.read()) doReturn forge.getForgery>() whenever(mockSerializerA.serialize(any(), any())) doReturn fakeJson - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) runnableA.run() // When - testedTimeseries.onSessionStop() + testedTimeseriesCollector.onSessionStop() // Then verify(mockSerializerA).serialize(any(), any()) @@ -236,7 +236,7 @@ internal class RumSessionScopeTimeseriesTest { whenever(mockReaderA.read()) doReturn element whenever(mockSerializerA.serialize(any(), eq(dataPoints))) doReturn fakeJson - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When @@ -251,7 +251,7 @@ internal class RumSessionScopeTimeseriesTest { fun `M reschedule sample W sample tick runs`(forge: Forge) { // Given whenever(mockReaderA.read()) doReturn forge.getForgery>() - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When @@ -266,7 +266,7 @@ internal class RumSessionScopeTimeseriesTest { fun `M not write events W sample tick { buffer not full }`(forge: Forge) { // Given whenever(mockReaderA.read()) doReturn forge.getForgery>() - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When @@ -282,7 +282,7 @@ internal class RumSessionScopeTimeseriesTest { // Given - fill the buffer so Pipeline.drain() reaches the serializer; serializer returns null whenever(mockReaderA.read()) doReturn forge.getForgery>() whenever(mockSerializerA.serialize(any(), any())) doReturn null - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When @@ -296,8 +296,8 @@ internal class RumSessionScopeTimeseriesTest { @Test fun `M not start twice W onSessionStart() { called twice }`() { // When - testedTimeseries.onSessionStart() - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() + testedTimeseriesCollector.onSessionStart() // Then — executor.schedule called exactly once per pipeline (not doubled) verify(mockExecutor).schedule( @@ -315,21 +315,21 @@ internal class RumSessionScopeTimeseriesTest { @Test fun `M not throw W onSessionStop() { called twice }`() { // Given - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() // When + Then — must not throw - testedTimeseries.onSessionStop() - testedTimeseries.onSessionStop() + testedTimeseriesCollector.onSessionStop() + testedTimeseriesCollector.onSessionStop() } @Test fun `M not start after stop W onSessionStart() { called after onSessionStop() }`() { // Given - testedTimeseries.onSessionStart() - testedTimeseries.onSessionStop() + testedTimeseriesCollector.onSessionStart() + testedTimeseriesCollector.onSessionStop() // When - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() // Then — no additional schedules beyond the original start verify(mockExecutor).schedule( @@ -349,7 +349,7 @@ internal class RumSessionScopeTimeseriesTest { // Given val fakeError = RuntimeException("reader failure") whenever(mockReaderA.read()) doThrow fakeError - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When @@ -369,7 +369,7 @@ internal class RumSessionScopeTimeseriesTest { // Given val fakeError = RuntimeException("reader failure") whenever(mockReaderA.read()) doThrow fakeError - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When @@ -379,7 +379,7 @@ internal class RumSessionScopeTimeseriesTest { mockInternalLogger.verifyLog( InternalLogger.Level.ERROR, targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), - RumSessionScopeTimeseries.ERROR_SAMPLING_FAILED, + DefaultTimeseriesCollector.ERROR_SAMPLING_FAILED, fakeError ) } @@ -387,7 +387,7 @@ internal class RumSessionScopeTimeseriesTest { @Test fun `M log error and reschedule W sample tick { buffer drain throws }`(forge: Forge) { // Given — drain() throws outside Pipeline's own try/catch, so it must be - // caught by RumSessionScopeTimeseries' sampling try/catch instead. + // caught by DefaultTimeseriesCollector' sampling try/catch instead. val fakeError = RuntimeException("drain failure") val mockBuffer = mock>() whenever(mockBuffer.isFull()) doReturn true @@ -395,7 +395,7 @@ internal class RumSessionScopeTimeseriesTest { whenever(mockReaderA.read()) doReturn forge.getForgery>() val pipeline = Pipeline(mockSdkCore, mockReaderA, mockBuffer, mockSerializerA, mockDataWriter, mockInternalLogger) - val timeseries = RumSessionScopeTimeseries( + val timeseries = DefaultTimeseriesCollector( pipelines = listOf(pipeline), internalLogger = mockInternalLogger, collectInBackground = false, @@ -412,7 +412,7 @@ internal class RumSessionScopeTimeseriesTest { mockInternalLogger.verifyLog( InternalLogger.Level.ERROR, targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), - RumSessionScopeTimeseries.ERROR_SAMPLING_FAILED, + DefaultTimeseriesCollector.ERROR_SAMPLING_FAILED, fakeError ) verify(mockExecutor, times(2)) @@ -426,7 +426,7 @@ internal class RumSessionScopeTimeseriesTest { val fakeError = RuntimeException("serializer failure") whenever(mockReaderA.read()) doReturn forge.getForgery>() whenever(mockSerializerA.serialize(any(), any())) doThrow fakeError - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When @@ -434,7 +434,7 @@ internal class RumSessionScopeTimeseriesTest { // Then // times(1) default on verifyLog also confirms this is the only ERROR log with that - // throwable — i.e. RumSessionScopeTimeseries' own sampling catch never fired here. + // throwable — i.e. DefaultTimeseriesCollector' own sampling catch never fired here. mockInternalLogger.verifyLog( InternalLogger.Level.ERROR, targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), @@ -453,7 +453,7 @@ internal class RumSessionScopeTimeseriesTest { val fakeError = RuntimeException("write context resolution failure") whenever(mockReaderA.read()) doReturn forge.getForgery>() whenever(mockRumFeatureScope.withWriteContext(any(), any())) doThrow fakeError - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When @@ -463,7 +463,7 @@ internal class RumSessionScopeTimeseriesTest { mockInternalLogger.verifyLog( InternalLogger.Level.ERROR, targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), - RumSessionScopeTimeseries.ERROR_SAMPLING_FAILED, + DefaultTimeseriesCollector.ERROR_SAMPLING_FAILED, fakeError ) verify(mockExecutor, times(fakeBufferSize + 1)) @@ -473,9 +473,9 @@ internal class RumSessionScopeTimeseriesTest { @Test fun `M not reschedule W stopped { sample tick skips try block }`() { // Given - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) - testedTimeseries.onSessionStop() + testedTimeseriesCollector.onSessionStop() // When runnableA.run() @@ -492,8 +492,8 @@ internal class RumSessionScopeTimeseriesTest { fun `M suspend chain W sample tick { background, collectInBackground = false }`(forge: Forge) { // Given whenever(mockReaderA.read()) doReturn forge.getForgery>() - testedTimeseries.onSessionStart() - testedTimeseries.onViewTypeUpdate(RumViewType.BACKGROUND) + testedTimeseriesCollector.onSessionStart() + testedTimeseriesCollector.onViewTypeUpdate(RumViewType.BACKGROUND) val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When @@ -512,7 +512,7 @@ internal class RumSessionScopeTimeseriesTest { fun `M sample W sample tick { background, collectInBackground = true }`(forge: Forge) { // Given whenever(mockReaderA.read()) doReturn forge.getForgery>() - val bgTimeseries = RumSessionScopeTimeseries( + val bgTimeseries = DefaultTimeseriesCollector( pipelines = listOf(pipelineA, pipelineB), internalLogger = mockInternalLogger, collectInBackground = true, @@ -532,15 +532,15 @@ internal class RumSessionScopeTimeseriesTest { @Test fun `M resume scheduling W onViewTypeUpdate() { background → foreground }`() { // Given - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() // Transition to background: suspends chains - testedTimeseries.onViewTypeUpdate(RumViewType.BACKGROUND) + testedTimeseriesCollector.onViewTypeUpdate(RumViewType.BACKGROUND) val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) runnableA.run() // State is already SUSPENDED; tick exits without reschedule val schedulesAfterSuspend = 1 // only the one from start() // When — app returns to foreground - testedTimeseries.onViewTypeUpdate(RumViewType.FOREGROUND) + testedTimeseriesCollector.onViewTypeUpdate(RumViewType.FOREGROUND) // Then — scheduling resumed (one new schedule per pipeline) verify(mockExecutor, times(schedulesAfterSuspend + 1)) @@ -554,16 +554,16 @@ internal class RumSessionScopeTimeseriesTest { // // Given whenever(mockReaderB.read()) doReturn forge.getForgery>() - testedTimeseries.onSessionStart() + testedTimeseriesCollector.onSessionStart() // Pipeline A suspends the instance (short interval fires first in background) - testedTimeseries.onViewTypeUpdate(RumViewType.BACKGROUND) + testedTimeseriesCollector.onViewTypeUpdate(RumViewType.BACKGROUND) val oldRunnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) val oldRunnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) oldRunnableA.run() // State is already SUSPENDED (set by onViewTypeUpdate); B's tick is still queued // Resume creates a new generation and schedules fresh ticks for both pipelines - testedTimeseries.onViewTypeUpdate(RumViewType.FOREGROUND) + testedTimeseriesCollector.onViewTypeUpdate(RumViewType.FOREGROUND) // When — B's old (stale) runnable fires despite the new generation oldRunnableB.run() @@ -581,11 +581,11 @@ internal class RumSessionScopeTimeseriesTest { // even when state is currently RUNNING. Ticks never write state. // // Given - testedTimeseries.onSessionStart() - testedTimeseries.onViewTypeUpdate(RumViewType.BACKGROUND) + testedTimeseriesCollector.onSessionStart() + testedTimeseriesCollector.onViewTypeUpdate(RumViewType.BACKGROUND) val oldRunnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) // Resume: state = RUNNING, gen = 2; A and B each get a new schedule - testedTimeseries.onViewTypeUpdate(RumViewType.FOREGROUND) + testedTimeseriesCollector.onViewTypeUpdate(RumViewType.FOREGROUND) // When — B's stale gen-1 tick fires while state is RUNNING oldRunnableB.run() diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt similarity index 91% rename from features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt rename to features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt index 7444584bd0..d61f8175fb 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvTimeseries.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt @@ -11,21 +11,21 @@ import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.internal.domain.scope.RumViewType import com.datadog.android.rum.internal.timeseries.Buffer -import com.datadog.android.rum.internal.timeseries.Timeseries +import com.datadog.android.rum.internal.timeseries.TimeseriesCollector import com.datadog.android.rum.internal.timeseries.serializer.CpuEventSerializer import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer import com.datadog.android.rum.internal.timeseries.serializer.MemoryEventSerializer import com.google.gson.JsonObject /** - * A test-only [Timeseries] that mirrors the wiring done by `RumSessionScopeTimeseriesFactory`, + * A test-only [Timeseries] that mirrors the wiring done by `DefaultTimeseriesCollectorFactory`, * but pulls samples from CSV-backed readers instead of `VitalReaderWrapper` and drives the * pipelines synchronously (no executor) so callers can assert on every emitted JSON. */ -internal class CsvTimeseries( +internal class CsvCollector( private val pipelines: List, JsonSerializer>>, private val datadogContext: DatadogContext -) : Timeseries { +) : TimeseriesCollector { private val emitted = mutableListOf() @@ -58,7 +58,7 @@ internal class CsvTimeseries( companion object { /** - * Builds a [CsvTimeseries] with the same shape as `RumSessionScopeTimeseriesFactory`: + * Builds a [CsvCollector] with the same shape as `DefaultTimeseriesCollectorFactory`: * a memory pipeline followed by a CPU pipeline, each backed by [CSVReader] and the * production [MemoryEventSerializer] / [CpuEventSerializer]. */ @@ -72,7 +72,7 @@ internal class CsvTimeseries( bufferSize: Int, timeProvider: TimeProvider, datadogContext: DatadogContext - ) = CsvTimeseries( + ) = CsvCollector( datadogContext = datadogContext, pipelines = listOf( Triple( diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesCollectorEndToEndTest.kt similarity index 97% rename from features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt rename to features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesCollectorEndToEndTest.kt index 444b41f7ca..f423f371e7 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesEndToEndTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesCollectorEndToEndTest.kt @@ -40,7 +40,7 @@ import java.util.UUID ) @MockitoSettings(strictness = Strictness.LENIENT) @ForgeConfiguration(Configurator::class) -internal class TimeseriesEndToEndTest { +internal class TimeseriesCollectorEndToEndTest { @Mock lateinit var mockTimeProvider: TimeProvider @@ -56,7 +56,7 @@ internal class TimeseriesEndToEndTest { whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn FIXED_DATE_MS csvContent = loadResource("fixtures/timeseries/input_memory_cpu.csv") - val testedTimeseries = CsvTimeseries.create( + val testedTimeseriesCollector = CsvCollector.create( csvContent = csvContent, sessionId = SESSION_ID, applicationId = APPLICATION_ID, @@ -66,9 +66,9 @@ internal class TimeseriesEndToEndTest { timeProvider = mockTimeProvider, datadogContext = mockDatadogContext ) - testedTimeseries.onSessionStart() - testedTimeseries.onSessionStop() - emitted = testedTimeseries.captured + testedTimeseriesCollector.onSessionStart() + testedTimeseriesCollector.onSessionStop() + emitted = testedTimeseriesCollector.captured } @Test diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/VitalReaderDataPointsReaderTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/VitalReaderWrapperTest.kt similarity index 94% rename from features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/VitalReaderDataPointsReaderTest.kt rename to features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/VitalReaderWrapperTest.kt index 3650eec407..61b45e83c3 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/VitalReaderDataPointsReaderTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/VitalReaderWrapperTest.kt @@ -4,10 +4,9 @@ * Copyright 2016-Present Datadog, Inc. */ -package com.datadog.android.rum.internal.timeseries +package com.datadog.android.rum.internal.timeseries.provider import com.datadog.android.internal.time.TimeProvider -import com.datadog.android.rum.internal.timeseries.provider.VitalReaderWrapper import com.datadog.android.rum.internal.vitals.VitalReader import com.datadog.android.rum.utils.forge.Configurator import fr.xgouchet.elmyr.annotation.DoubleForgery @@ -34,7 +33,7 @@ import java.util.concurrent.TimeUnit ) @MockitoSettings(strictness = Strictness.LENIENT) @ForgeConfiguration(Configurator::class) -internal class VitalReaderDataPointsReaderTest { +internal class VitalReaderWrapperTest { @LongForgery(min = 1L) var fakeTimestamp: Long = 0L From 4ce57a519c30140531edd23fa9d648685add9af8 Mon Sep 17 00:00:00 2001 From: satween <1608994+satween@users.noreply.github.com> Date: Fri, 14 Aug 2026 16:25:52 +0100 Subject: [PATCH 22/32] RUM-17613: Fix stale TimeseriesCollector KDoc reference in CsvCollector Co-authored-by: Francisco Veiga --- .../datadog/android/rum/internal/timeseries/csv/CsvCollector.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt index d61f8175fb..bde2287135 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt @@ -18,7 +18,7 @@ import com.datadog.android.rum.internal.timeseries.serializer.MemoryEventSeriali import com.google.gson.JsonObject /** - * A test-only [Timeseries] that mirrors the wiring done by `DefaultTimeseriesCollectorFactory`, + * A test-only [TimeseriesCollector] that mirrors the wiring done by `DefaultTimeseriesCollectorFactory`, * but pulls samples from CSV-backed readers instead of `VitalReaderWrapper` and drives the * pipelines synchronously (no executor) so callers can assert on every emitted JSON. */ From 7be7bac4b91515a3f8138341a9e408e45f43d225 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Fri, 14 Aug 2026 15:49:13 +0100 Subject: [PATCH 23/32] RUM-17613: Build timeseries events from the shared RUM schema Derive the CPU and memory timeseries schemas from _common-schema.json and replace the hand-written JSON serializers with typed event factories that populate the full common event shape (os, device, dd, ddtags). Restrict collection through TimeseriesConfiguration.collectOnly. Ref: RUM-17613 --- detekt_custom_safe_calls_third_party.yml | 2 + features/dd-sdk-android-rum/api/apiSurface | 506 +++- .../api/dd-sdk-android-rum.api | 2030 ++++++++++++++--- .../src/main/json/rum/_common-schema.json | 39 +- .../main/json/rum/_view-container-schema.json | 2 +- .../main/json/rum/timeseries-cpu-schema.json | 194 +- .../json/rum/timeseries-memory-schema.json | 206 +- .../datadog/android/rum/RumConfiguration.kt | 14 +- .../android/rum/internal/RumFeature.kt | 85 +- .../android/rum/internal/RumSessionTypeExt.kt | 16 +- .../rum/internal/domain/RumDataWriter.kt | 4 + .../internal/domain/event/RumEventMapper.kt | 4 + .../domain/event/RumEventSerializer.kt | 8 + .../rum/internal/domain/scope/RumEventExt.kt | 230 ++ .../internal/domain/scope/RumSessionScope.kt | 6 +- .../rum/internal/domain/scope/RumViewScope.kt | 14 +- .../DefaultTimeseriesCollectorFactory.kt | 115 +- .../rum/internal/timeseries/Pipeline.kt | 38 +- .../timeseries/TimeseriesCollector.kt | 6 +- .../timeseries/factory/CpuEventFactory.kt | 110 + .../timeseries/factory/EventFactory.kt | 21 + .../timeseries/factory/MemoryEventFactory.kt | 123 + .../timeseries/provider/CpuDatapointReader.kt | 13 +- .../timeseries/provider/DataPointsReader.kt | 2 +- .../serializer/CpuEventSerializer.kt | 47 - .../serializer/MemoryEventSerializer.kt | 60 - ...nSerializer.kt => TimeseriesAttributes.kt} | 8 - .../rum/timeseries/TimeseriesConfiguration.kt | 45 +- .../android/rum/timeseries/TimeseriesType.kt | 19 + .../rum/RumConfigurationBuilderTest.kt | 30 +- .../android/rum/assertj/ActionEventAssert.kt | 24 +- .../android/rum/assertj/ErrorEventAssert.kt | 24 +- .../rum/assertj/LongTaskEventAssert.kt | 8 +- .../rum/assertj/ResourceEventAssert.kt | 12 +- .../rum/assertj/TimeseriesCpuEventAssert.kt | 116 - .../rum/assertj/VitalAppLaunchEventAssert.kt | 12 +- .../android/rum/assertj/VitalEventAssert.kt | 12 +- .../android/rum/internal/RumFeatureTest.kt | 113 +- .../rum/internal/RumSessionTypeExtTest.kt | 19 +- .../rum/internal/domain/RumDataWriterTest.kt | 26 + .../domain/event/RumEventMapperTest.kt | 19 + .../domain/event/RumEventSerializerTest.kt | 77 +- ...pplicationScopeAttributePropagationTest.kt | 11 +- .../domain/scope/RumApplicationScopeTest.kt | 64 +- .../internal/domain/scope/RumEventExtTest.kt | 135 ++ ...RumSessionScopeAttributePropagationTest.kt | 11 +- .../domain/scope/RumSessionScopeTest.kt | 368 +-- .../internal/domain/scope/RumViewScopeTest.kt | 4 +- .../rum/internal/timeseries/BufferTest.kt | 15 +- .../DefaultTimeseriesCollectorFactoryTest.kt | 279 ++- .../DefaultTimeseriesCollectorTest.kt | 61 +- .../rum/internal/timeseries/PipelineTest.kt | 72 +- .../rum/internal/timeseries/csv/CSVReader.kt | 3 - .../internal/timeseries/csv/CsvCollector.kt | 107 +- .../csv/TimeseriesCollectorEndToEndTest.kt | 245 +- .../timeseries/factory/CpuEventFactoryTest.kt | 469 ++++ .../factory/MemoryEventFactoryTest.kt | 501 ++++ .../provider/CpuDatapointReaderTest.kt | 18 +- .../provider/VitalReaderWrapperTest.kt | 22 +- .../serializer/CpuEventSerializerTest.kt | 147 -- .../serializer/MemoryEventSerializerTest.kt | 177 -- .../rum/internal/vitals/CpuStatReaderTest.kt | 8 +- .../timeseries/TimeseriesConfigurationTest.kt | 84 - .../TimeseriesConfigurationTests.kt | 71 + .../timeseries/expected_cpu_batch1.json | 71 +- .../timeseries/expected_cpu_batch2.json | 71 +- .../timeseries/expected_memory_batch1.json | 79 +- .../timeseries/expected_memory_batch2.json | 79 +- .../rum/assertj/TimeseriesCpuEventAssert.kt | 169 ++ .../assertj/TimeseriesMemoryEventAssert.kt | 203 ++ 70 files changed, 5737 insertions(+), 2266 deletions(-) create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/factory/CpuEventFactory.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/factory/EventFactory.kt create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/factory/MemoryEventFactory.kt delete mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt delete mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt rename features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/{JsonSerializer.kt => TimeseriesAttributes.kt} (61%) create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesType.kt delete mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/factory/CpuEventFactoryTest.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/factory/MemoryEventFactoryTest.kt delete mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt delete mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt delete mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTests.kt create mode 100644 features/dd-sdk-android-rum/src/testFixtures/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt create mode 100644 features/dd-sdk-android-rum/src/testFixtures/kotlin/com/datadog/android/rum/assertj/TimeseriesMemoryEventAssert.kt diff --git a/detekt_custom_safe_calls_third_party.yml b/detekt_custom_safe_calls_third_party.yml index 9f3686b425..5a7c07ad32 100644 --- a/detekt_custom_safe_calls_third_party.yml +++ b/detekt_custom_safe_calls_third_party.yml @@ -761,6 +761,8 @@ datadog: - "kotlin.collections.listOf(com.datadog.android.rum.model.ErrorEvent.Interface)" - "kotlin.collections.listOf(com.datadog.android.rum.model.LongTaskEvent.Interface)" - "kotlin.collections.listOf(com.datadog.android.rum.model.ResourceEvent.Interface)" + - "kotlin.collections.listOf(com.datadog.android.rum.model.TimeseriesCpuEvent.Interface)" + - "kotlin.collections.listOf(com.datadog.android.rum.model.TimeseriesMemoryEvent.Interface)" - "kotlin.collections.listOf(com.datadog.android.rum.model.ViewEvent.Interface)" - "kotlin.collections.listOf(com.datadog.android.rum.model.VitalAppLaunchEvent.Interface)" - "kotlin.collections.listOf(com.datadog.android.rum.model.VitalOperationStepEvent.Interface)" diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index d218d217cf..47312d37dc 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -90,7 +90,7 @@ data class com.datadog.android.rum.RumConfiguration fun setSlowFramesConfiguration(com.datadog.android.rum.configuration.SlowFramesConfiguration?): Builder fun trackAnonymousUser(Boolean): Builder fun setAppStartupActivityPredicate(com.datadog.android.rum.startup.AppStartupActivityPredicate): Builder - fun setTimeseriesConfiguration(com.datadog.android.rum.timeseries.TimeseriesConfiguration?): Builder + fun setTimeseriesConfiguration(com.datadog.android.rum.timeseries.TimeseriesConfiguration): Builder fun build(): RumConfiguration enum com.datadog.android.rum.RumErrorSource - NETWORK @@ -312,11 +312,13 @@ interface com.datadog.android.rum.startup.AppStartupActivityPredicate fun shouldTrackStartup(android.app.Activity): Boolean class com.datadog.android.rum.timeseries.TimeseriesConfiguration class Builder - fun setBufferSize(Int): Builder - fun setIntervalMs(Long): Builder + fun collectOnly(TimeseriesType): Builder fun build(): TimeseriesConfiguration companion object val DEFAULT: TimeseriesConfiguration +enum com.datadog.android.rum.timeseries.TimeseriesType + - CPU + - MEMORY open class com.datadog.android.rum.tracking.AcceptAllActivities : ComponentPredicate override fun accept(android.app.Activity): Boolean override fun getViewName(android.app.Activity): String? @@ -400,7 +402,7 @@ class com.datadog.android.sqlite.DatadogDatabaseErrorHandler : android.database. constructor(String? = null, android.database.DatabaseErrorHandler = DefaultDatabaseErrorHandler()) override fun onCorruption(android.database.sqlite.SQLiteDatabase) data class com.datadog.android.rum.model.ActionEvent - constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, ActionEventSession, ActionEventSource? = null, ActionEventView, Usr? = null, Account? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Container? = null, ActionEventAction) + constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, ActionEventSession, ActionEventSource? = null, ActionEventView? = null, Usr? = null, Account? = null, Tab? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Stream? = null, Container? = null, ActionEventAction) val type: kotlin.String fun toJson(): com.google.gson.JsonElement companion object @@ -436,6 +438,12 @@ data class com.datadog.android.rum.model.ActionEvent companion object fun fromJson(kotlin.String): Account fun fromJsonObject(com.google.gson.JsonObject): Account + data class Tab + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Tab + fun fromJsonObject(com.google.gson.JsonObject): Tab data class Connectivity constructor(Status, kotlin.collections.List? = null, EffectiveType? = null, Cellular? = null) fun toJson(): com.google.gson.JsonElement @@ -485,6 +493,12 @@ data class com.datadog.android.rum.model.ActionEvent companion object fun fromJson(kotlin.String): Context fun fromJsonObject(com.google.gson.JsonObject): Context + data class Stream + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Stream + fun fromJsonObject(com.google.gson.JsonObject): Stream data class Container constructor(ContainerView, ActionEventSource) fun toJson(): com.google.gson.JsonElement @@ -516,7 +530,7 @@ data class com.datadog.android.rum.model.ActionEvent fun fromJson(kotlin.String): DdSession fun fromJsonObject(com.google.gson.JsonObject): DdSession data class Configuration - constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null) + constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.collections.List? = null) fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Configuration @@ -592,7 +606,7 @@ data class com.datadog.android.rum.model.ActionEvent - UNITY - KOTLIN_MULTIPLATFORM - ELECTRON - - RUM_CPP + - CPP - MAUI fun toJson(): com.google.gson.JsonElement companion object @@ -701,7 +715,7 @@ data class com.datadog.android.rum.model.ActionEvent companion object fun fromJson(kotlin.String): Type data class com.datadog.android.rum.model.ErrorEvent - constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, ErrorEventSession, ErrorEventSource? = null, ErrorEventView, Usr? = null, Account? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Action? = null, Container? = null, Error, Freeze? = null, Context? = null) + constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, ErrorEventSession, ErrorEventSource? = null, ErrorEventView? = null, Usr? = null, Account? = null, Tab? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Stream? = null, Action? = null, Container? = null, Error, Freeze? = null, Context? = null) val type: kotlin.String fun toJson(): com.google.gson.JsonElement companion object @@ -737,6 +751,12 @@ data class com.datadog.android.rum.model.ErrorEvent companion object fun fromJson(kotlin.String): Account fun fromJsonObject(com.google.gson.JsonObject): Account + data class Tab + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Tab + fun fromJsonObject(com.google.gson.JsonObject): Tab data class Connectivity constructor(ConnectivityStatus, kotlin.collections.List? = null, EffectiveType? = null, Cellular? = null) fun toJson(): com.google.gson.JsonElement @@ -786,6 +806,12 @@ data class com.datadog.android.rum.model.ErrorEvent companion object fun fromJson(kotlin.String): Context fun fromJsonObject(com.google.gson.JsonObject): Context + data class Stream + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Stream + fun fromJsonObject(com.google.gson.JsonObject): Stream data class Action constructor(kotlin.collections.List) fun toJson(): com.google.gson.JsonElement @@ -829,7 +855,7 @@ data class com.datadog.android.rum.model.ErrorEvent fun fromJson(kotlin.String): DdSession fun fromJsonObject(com.google.gson.JsonObject): DdSession data class Configuration - constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null) + constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.collections.List? = null) fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Configuration @@ -899,7 +925,7 @@ data class com.datadog.android.rum.model.ErrorEvent - UNITY - KOTLIN_MULTIPLATFORM - ELECTRON - - RUM_CPP + - CPP - MAUI fun toJson(): com.google.gson.JsonElement companion object @@ -1082,7 +1108,7 @@ data class com.datadog.android.rum.model.ErrorEvent companion object fun fromJson(kotlin.String): ProviderType data class com.datadog.android.rum.model.LongTaskEvent - constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, LongTaskEventSession, LongTaskEventSource? = null, LongTaskEventView, Usr? = null, Account? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Action? = null, Container? = null, LongTask) + constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, LongTaskEventSession, LongTaskEventSource? = null, LongTaskEventView? = null, Usr? = null, Account? = null, Tab? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Stream? = null, Action? = null, Container? = null, LongTask) val type: kotlin.String fun toJson(): com.google.gson.JsonElement companion object @@ -1118,6 +1144,12 @@ data class com.datadog.android.rum.model.LongTaskEvent companion object fun fromJson(kotlin.String): Account fun fromJsonObject(com.google.gson.JsonObject): Account + data class Tab + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Tab + fun fromJsonObject(com.google.gson.JsonObject): Tab data class Connectivity constructor(ConnectivityStatus, kotlin.collections.List? = null, EffectiveType? = null, Cellular? = null) fun toJson(): com.google.gson.JsonElement @@ -1167,6 +1199,12 @@ data class com.datadog.android.rum.model.LongTaskEvent companion object fun fromJson(kotlin.String): Context fun fromJsonObject(com.google.gson.JsonObject): Context + data class Stream + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Stream + fun fromJsonObject(com.google.gson.JsonObject): Stream data class Action constructor(kotlin.collections.List) fun toJson(): com.google.gson.JsonElement @@ -1204,7 +1242,7 @@ data class com.datadog.android.rum.model.LongTaskEvent fun fromJson(kotlin.String): DdSession fun fromJsonObject(com.google.gson.JsonObject): DdSession data class Configuration - constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null) + constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.collections.List? = null) fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Configuration @@ -1238,7 +1276,7 @@ data class com.datadog.android.rum.model.LongTaskEvent - UNITY - KOTLIN_MULTIPLATFORM - ELECTRON - - RUM_CPP + - CPP - MAUI fun toJson(): com.google.gson.JsonElement companion object @@ -1350,7 +1388,7 @@ data class com.datadog.android.rum.model.LongTaskEvent companion object fun fromJson(kotlin.String): InvokerType data class com.datadog.android.rum.model.ResourceEvent - constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, ResourceEventSession, ResourceEventSource? = null, ResourceEventView, Usr? = null, Account? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Action? = null, Container? = null, Resource) + constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, ResourceEventSession, ResourceEventSource? = null, ResourceEventView? = null, Usr? = null, Account? = null, Tab? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Stream? = null, Action? = null, Container? = null, Resource) val type: kotlin.String fun toJson(): com.google.gson.JsonElement companion object @@ -1386,6 +1424,12 @@ data class com.datadog.android.rum.model.ResourceEvent companion object fun fromJson(kotlin.String): Account fun fromJsonObject(com.google.gson.JsonObject): Account + data class Tab + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Tab + fun fromJsonObject(com.google.gson.JsonObject): Tab data class Connectivity constructor(Status, kotlin.collections.List? = null, EffectiveType? = null, Cellular? = null) fun toJson(): com.google.gson.JsonElement @@ -1435,6 +1479,12 @@ data class com.datadog.android.rum.model.ResourceEvent companion object fun fromJson(kotlin.String): Context fun fromJsonObject(com.google.gson.JsonObject): Context + data class Stream + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Stream + fun fromJsonObject(com.google.gson.JsonObject): Stream data class Action constructor(kotlin.collections.List) fun toJson(): com.google.gson.JsonElement @@ -1472,7 +1522,7 @@ data class com.datadog.android.rum.model.ResourceEvent fun fromJson(kotlin.String): DdSession fun fromJsonObject(com.google.gson.JsonObject): DdSession data class Configuration - constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null) + constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.collections.List? = null) fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Configuration @@ -1595,7 +1645,7 @@ data class com.datadog.android.rum.model.ResourceEvent - UNITY - KOTLIN_MULTIPLATFORM - ELECTRON - - RUM_CPP + - CPP - MAUI fun toJson(): com.google.gson.JsonElement companion object @@ -1743,30 +1793,103 @@ data class com.datadog.android.rum.model.ResourceEvent companion object fun fromJson(kotlin.String): OperationType data class com.datadog.android.rum.model.TimeseriesCpuEvent - constructor(Dd, Application, Session, Source, kotlin.Long, kotlin.String? = null, kotlin.String? = null, Timeseries) + constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, TimeseriesCpuEventSession, Source? = null, View? = null, Usr? = null, Account? = null, Tab? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Stream? = null, Timeseries) val type: kotlin.String fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): TimeseriesCpuEvent fun fromJsonObject(com.google.gson.JsonObject): TimeseriesCpuEvent - class Dd + data class Application + constructor(kotlin.String, kotlin.String? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Application + fun fromJsonObject(com.google.gson.JsonObject): Application + data class TimeseriesCpuEventSession + constructor(kotlin.String, TimeseriesCpuEventSessionType, kotlin.Boolean? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): TimeseriesCpuEventSession + fun fromJsonObject(com.google.gson.JsonObject): TimeseriesCpuEventSession + data class View + constructor(kotlin.String, kotlin.String? = null, kotlin.String, kotlin.String? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): View + fun fromJsonObject(com.google.gson.JsonObject): View + data class Usr + constructor(kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.collections.MutableMap = mutableMapOf()) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Usr + fun fromJsonObject(com.google.gson.JsonObject): Usr + data class Account + constructor(kotlin.String, kotlin.String? = null, kotlin.collections.MutableMap = mutableMapOf()) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Account + fun fromJsonObject(com.google.gson.JsonObject): Account + data class Tab + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Tab + fun fromJsonObject(com.google.gson.JsonObject): Tab + data class Connectivity + constructor(Status, kotlin.collections.List? = null, EffectiveType? = null, Cellular? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Connectivity + fun fromJsonObject(com.google.gson.JsonObject): Connectivity + data class Display + constructor(Viewport? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Display + fun fromJsonObject(com.google.gson.JsonObject): Display + data class Synthetics + constructor(kotlin.String, kotlin.String, kotlin.Boolean? = null, kotlin.collections.MutableMap = mutableMapOf()) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Synthetics + fun fromJsonObject(com.google.gson.JsonObject): Synthetics + data class CiTest + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): CiTest + fun fromJsonObject(com.google.gson.JsonObject): CiTest + data class Os + constructor(kotlin.String, kotlin.String, kotlin.String? = null, kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Os + fun fromJsonObject(com.google.gson.JsonObject): Os + data class Device + constructor(DeviceType? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.collections.List? = null, kotlin.String? = null, kotlin.Number? = null, kotlin.Boolean? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.Boolean? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Device + fun fromJsonObject(com.google.gson.JsonObject): Device + data class Dd + constructor(DdSession? = null, Configuration? = null, kotlin.String? = null, kotlin.String? = null) val formatVersion: kotlin.Long fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Dd fun fromJsonObject(com.google.gson.JsonObject): Dd - data class Application - constructor(kotlin.String) + data class Context + constructor(kotlin.collections.MutableMap = mutableMapOf()) fun toJson(): com.google.gson.JsonElement companion object - fun fromJson(kotlin.String): Application - fun fromJsonObject(com.google.gson.JsonObject): Application - data class Session - constructor(kotlin.String, Type) + fun fromJson(kotlin.String): Context + fun fromJsonObject(com.google.gson.JsonObject): Context + data class Stream + constructor(kotlin.String) fun toJson(): com.google.gson.JsonElement companion object - fun fromJson(kotlin.String): Session - fun fromJsonObject(com.google.gson.JsonObject): Session + fun fromJson(kotlin.String): Stream + fun fromJsonObject(com.google.gson.JsonObject): Stream data class Timeseries constructor(kotlin.String, kotlin.Long, kotlin.Long, Data) val name: kotlin.String @@ -1775,6 +1898,30 @@ data class com.datadog.android.rum.model.TimeseriesCpuEvent companion object fun fromJson(kotlin.String): Timeseries fun fromJsonObject(com.google.gson.JsonObject): Timeseries + data class Cellular + constructor(kotlin.String? = null, kotlin.String? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Cellular + fun fromJsonObject(com.google.gson.JsonObject): Cellular + data class Viewport + constructor(kotlin.Number, kotlin.Number) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Viewport + fun fromJsonObject(com.google.gson.JsonObject): Viewport + data class DdSession + constructor(Plan? = null, SessionPrecondition? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): DdSession + fun fromJsonObject(com.google.gson.JsonObject): DdSession + data class Configuration + constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.collections.List? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Configuration + fun fromJsonObject(com.google.gson.JsonObject): Configuration data class Data constructor(kotlin.collections.List, Values) fun toJson(): com.google.gson.JsonElement @@ -1798,43 +1945,179 @@ data class com.datadog.android.rum.model.TimeseriesCpuEvent - UNITY - KOTLIN_MULTIPLATFORM - ELECTRON - - RUM_CPP + - CPP + - MAUI fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Source - enum Type + enum TimeseriesCpuEventSessionType constructor(kotlin.String) - USER - SYNTHETICS - CI_TEST fun toJson(): com.google.gson.JsonElement companion object - fun fromJson(kotlin.String): Type + fun fromJson(kotlin.String): TimeseriesCpuEventSessionType + enum Status + constructor(kotlin.String) + - CONNECTED + - NOT_CONNECTED + - MAYBE + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Status + enum Interface + constructor(kotlin.String) + - BLUETOOTH + - CELLULAR + - ETHERNET + - WIFI + - WIMAX + - MIXED + - OTHER + - UNKNOWN + - NONE + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Interface + enum EffectiveType + constructor(kotlin.String) + - SLOW_2G + - `2G` + - `3G` + - `4G` + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): EffectiveType + enum DeviceType + constructor(kotlin.String) + - MOBILE + - DESKTOP + - TABLET + - TV + - GAMING_CONSOLE + - BOT + - OTHER + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): DeviceType + enum Plan + constructor(kotlin.Number) + - PLAN_1 + - PLAN_2 + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Plan + enum SessionPrecondition + constructor(kotlin.String) + - USER_APP_LAUNCH + - INACTIVITY_TIMEOUT + - MAX_DURATION + - BACKGROUND_LAUNCH + - PREWARM + - FROM_NON_INTERACTIVE_SESSION + - EXPLICIT_STOP + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): SessionPrecondition data class com.datadog.android.rum.model.TimeseriesMemoryEvent - constructor(Dd, Application, Session, Source, kotlin.Long, kotlin.String? = null, kotlin.String? = null, Timeseries) + constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, TimeseriesMemoryEventSession, Source? = null, View? = null, Usr? = null, Account? = null, Tab? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Stream? = null, Timeseries) val type: kotlin.String fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): TimeseriesMemoryEvent fun fromJsonObject(com.google.gson.JsonObject): TimeseriesMemoryEvent - class Dd + data class Application + constructor(kotlin.String, kotlin.String? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Application + fun fromJsonObject(com.google.gson.JsonObject): Application + data class TimeseriesMemoryEventSession + constructor(kotlin.String, TimeseriesMemoryEventSessionType, kotlin.Boolean? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): TimeseriesMemoryEventSession + fun fromJsonObject(com.google.gson.JsonObject): TimeseriesMemoryEventSession + data class View + constructor(kotlin.String, kotlin.String? = null, kotlin.String, kotlin.String? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): View + fun fromJsonObject(com.google.gson.JsonObject): View + data class Usr + constructor(kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.collections.MutableMap = mutableMapOf()) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Usr + fun fromJsonObject(com.google.gson.JsonObject): Usr + data class Account + constructor(kotlin.String, kotlin.String? = null, kotlin.collections.MutableMap = mutableMapOf()) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Account + fun fromJsonObject(com.google.gson.JsonObject): Account + data class Tab + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Tab + fun fromJsonObject(com.google.gson.JsonObject): Tab + data class Connectivity + constructor(Status, kotlin.collections.List? = null, EffectiveType? = null, Cellular? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Connectivity + fun fromJsonObject(com.google.gson.JsonObject): Connectivity + data class Display + constructor(Viewport? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Display + fun fromJsonObject(com.google.gson.JsonObject): Display + data class Synthetics + constructor(kotlin.String, kotlin.String, kotlin.Boolean? = null, kotlin.collections.MutableMap = mutableMapOf()) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Synthetics + fun fromJsonObject(com.google.gson.JsonObject): Synthetics + data class CiTest + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): CiTest + fun fromJsonObject(com.google.gson.JsonObject): CiTest + data class Os + constructor(kotlin.String, kotlin.String, kotlin.String? = null, kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Os + fun fromJsonObject(com.google.gson.JsonObject): Os + data class Device + constructor(DeviceType? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.collections.List? = null, kotlin.String? = null, kotlin.Number? = null, kotlin.Boolean? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.Boolean? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Device + fun fromJsonObject(com.google.gson.JsonObject): Device + data class Dd + constructor(DdSession? = null, Configuration? = null, kotlin.String? = null, kotlin.String? = null) val formatVersion: kotlin.Long fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Dd fun fromJsonObject(com.google.gson.JsonObject): Dd - data class Application - constructor(kotlin.String) + data class Context + constructor(kotlin.collections.MutableMap = mutableMapOf()) fun toJson(): com.google.gson.JsonElement companion object - fun fromJson(kotlin.String): Application - fun fromJsonObject(com.google.gson.JsonObject): Application - data class Session - constructor(kotlin.String, Type) + fun fromJson(kotlin.String): Context + fun fromJsonObject(com.google.gson.JsonObject): Context + data class Stream + constructor(kotlin.String) fun toJson(): com.google.gson.JsonElement companion object - fun fromJson(kotlin.String): Session - fun fromJsonObject(com.google.gson.JsonObject): Session + fun fromJson(kotlin.String): Stream + fun fromJsonObject(com.google.gson.JsonObject): Stream data class Timeseries constructor(kotlin.String, kotlin.Long, kotlin.Long, Data) val name: kotlin.String @@ -1843,6 +2126,30 @@ data class com.datadog.android.rum.model.TimeseriesMemoryEvent companion object fun fromJson(kotlin.String): Timeseries fun fromJsonObject(com.google.gson.JsonObject): Timeseries + data class Cellular + constructor(kotlin.String? = null, kotlin.String? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Cellular + fun fromJsonObject(com.google.gson.JsonObject): Cellular + data class Viewport + constructor(kotlin.Number, kotlin.Number) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Viewport + fun fromJsonObject(com.google.gson.JsonObject): Viewport + data class DdSession + constructor(Plan? = null, SessionPrecondition? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): DdSession + fun fromJsonObject(com.google.gson.JsonObject): DdSession + data class Configuration + constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.collections.List? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Configuration + fun fromJsonObject(com.google.gson.JsonObject): Configuration data class Data constructor(kotlin.collections.List, Values) fun toJson(): com.google.gson.JsonElement @@ -1866,20 +2173,83 @@ data class com.datadog.android.rum.model.TimeseriesMemoryEvent - UNITY - KOTLIN_MULTIPLATFORM - ELECTRON - - RUM_CPP + - CPP + - MAUI fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Source - enum Type + enum TimeseriesMemoryEventSessionType constructor(kotlin.String) - USER - SYNTHETICS - CI_TEST fun toJson(): com.google.gson.JsonElement companion object - fun fromJson(kotlin.String): Type + fun fromJson(kotlin.String): TimeseriesMemoryEventSessionType + enum Status + constructor(kotlin.String) + - CONNECTED + - NOT_CONNECTED + - MAYBE + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Status + enum Interface + constructor(kotlin.String) + - BLUETOOTH + - CELLULAR + - ETHERNET + - WIFI + - WIMAX + - MIXED + - OTHER + - UNKNOWN + - NONE + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Interface + enum EffectiveType + constructor(kotlin.String) + - SLOW_2G + - `2G` + - `3G` + - `4G` + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): EffectiveType + enum DeviceType + constructor(kotlin.String) + - MOBILE + - DESKTOP + - TABLET + - TV + - GAMING_CONSOLE + - BOT + - OTHER + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): DeviceType + enum Plan + constructor(kotlin.Number) + - PLAN_1 + - PLAN_2 + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Plan + enum SessionPrecondition + constructor(kotlin.String) + - USER_APP_LAUNCH + - INACTIVITY_TIMEOUT + - MAX_DURATION + - BACKGROUND_LAUNCH + - PREWARM + - FROM_NON_INTERACTIVE_SESSION + - EXPLICIT_STOP + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): SessionPrecondition data class com.datadog.android.rum.model.ViewEvent - constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, ViewEventSession, ViewEventSource? = null, ViewEventView, Usr? = null, Account? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Container? = null, Context? = null, Privacy? = null) + constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, ViewEventSession, ViewEventSource? = null, ViewEventView, Usr? = null, Account? = null, Tab? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Stream? = null, Container? = null, Context? = null, Privacy? = null) val type: kotlin.String fun toJson(): com.google.gson.JsonElement companion object @@ -1915,6 +2285,12 @@ data class com.datadog.android.rum.model.ViewEvent companion object fun fromJson(kotlin.String): Account fun fromJsonObject(com.google.gson.JsonObject): Account + data class Tab + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Tab + fun fromJsonObject(com.google.gson.JsonObject): Tab data class Connectivity constructor(ConnectivityStatus, kotlin.collections.List? = null, EffectiveType? = null, Cellular? = null) fun toJson(): com.google.gson.JsonElement @@ -1964,6 +2340,12 @@ data class com.datadog.android.rum.model.ViewEvent companion object fun fromJson(kotlin.String): Context fun fromJsonObject(com.google.gson.JsonObject): Context + data class Stream + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Stream + fun fromJsonObject(com.google.gson.JsonObject): Stream data class Container constructor(ContainerView, ViewEventSource) fun toJson(): com.google.gson.JsonElement @@ -2079,7 +2461,7 @@ data class com.datadog.android.rum.model.ViewEvent fun fromJson(kotlin.String): DdSession fun fromJsonObject(com.google.gson.JsonObject): DdSession data class Configuration - constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.Boolean? = null) + constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.collections.List? = null, kotlin.Boolean? = null) fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Configuration @@ -2179,7 +2561,7 @@ data class com.datadog.android.rum.model.ViewEvent - UNITY - KOTLIN_MULTIPLATFORM - ELECTRON - - RUM_CPP + - CPP - MAUI fun toJson(): com.google.gson.JsonElement companion object @@ -2304,7 +2686,7 @@ data class com.datadog.android.rum.model.ViewEvent companion object fun fromJson(kotlin.String): ErrorReason data class com.datadog.android.rum.model.VitalAppLaunchEvent - constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, VitalAppLaunchEventSession, VitalAppLaunchEventSource? = null, VitalAppLaunchEventView, Usr? = null, Account? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Container? = null, Vital) + constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, VitalAppLaunchEventSession, VitalAppLaunchEventSource? = null, VitalAppLaunchEventView? = null, Usr? = null, Account? = null, Tab? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Stream? = null, Container? = null, Vital) val type: kotlin.String fun toJson(): com.google.gson.JsonElement companion object @@ -2340,6 +2722,12 @@ data class com.datadog.android.rum.model.VitalAppLaunchEvent companion object fun fromJson(kotlin.String): Account fun fromJsonObject(com.google.gson.JsonObject): Account + data class Tab + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Tab + fun fromJsonObject(com.google.gson.JsonObject): Tab data class Connectivity constructor(ConnectivityStatus, kotlin.collections.List? = null, EffectiveType? = null, Cellular? = null) fun toJson(): com.google.gson.JsonElement @@ -2389,6 +2777,12 @@ data class com.datadog.android.rum.model.VitalAppLaunchEvent companion object fun fromJson(kotlin.String): Context fun fromJsonObject(com.google.gson.JsonObject): Context + data class Stream + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Stream + fun fromJsonObject(com.google.gson.JsonObject): Stream data class Container constructor(ContainerView, VitalAppLaunchEventSource) fun toJson(): com.google.gson.JsonElement @@ -2421,7 +2815,7 @@ data class com.datadog.android.rum.model.VitalAppLaunchEvent fun fromJson(kotlin.String): DdSession fun fromJsonObject(com.google.gson.JsonObject): DdSession data class Configuration - constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null) + constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.collections.List? = null) fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Configuration @@ -2449,7 +2843,7 @@ data class com.datadog.android.rum.model.VitalAppLaunchEvent - UNITY - KOTLIN_MULTIPLATFORM - ELECTRON - - RUM_CPP + - CPP - MAUI fun toJson(): com.google.gson.JsonElement companion object @@ -2557,7 +2951,7 @@ data class com.datadog.android.rum.model.VitalAppLaunchEvent companion object fun fromJson(kotlin.String): ErrorReason data class com.datadog.android.rum.model.VitalOperationStepEvent - constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, VitalOperationStepEventSession, VitalOperationStepEventSource? = null, VitalOperationStepEventView, Usr? = null, Account? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Container? = null, Vital) + constructor(kotlin.Long, Application, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, VitalOperationStepEventSession, VitalOperationStepEventSource? = null, VitalOperationStepEventView? = null, Usr? = null, Account? = null, Tab? = null, Connectivity? = null, Display? = null, Synthetics? = null, CiTest? = null, Os? = null, Device? = null, Dd, Context? = null, Stream? = null, Container? = null, Vital) val type: kotlin.String fun toJson(): com.google.gson.JsonElement companion object @@ -2593,6 +2987,12 @@ data class com.datadog.android.rum.model.VitalOperationStepEvent companion object fun fromJson(kotlin.String): Account fun fromJsonObject(com.google.gson.JsonObject): Account + data class Tab + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Tab + fun fromJsonObject(com.google.gson.JsonObject): Tab data class Connectivity constructor(ConnectivityStatus, kotlin.collections.List? = null, EffectiveType? = null, Cellular? = null) fun toJson(): com.google.gson.JsonElement @@ -2642,6 +3042,12 @@ data class com.datadog.android.rum.model.VitalOperationStepEvent companion object fun fromJson(kotlin.String): Context fun fromJsonObject(com.google.gson.JsonObject): Context + data class Stream + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Stream + fun fromJsonObject(com.google.gson.JsonObject): Stream data class Container constructor(ContainerView, VitalOperationStepEventSource) fun toJson(): com.google.gson.JsonElement @@ -2674,7 +3080,7 @@ data class com.datadog.android.rum.model.VitalOperationStepEvent fun fromJson(kotlin.String): DdSession fun fromJsonObject(com.google.gson.JsonObject): DdSession data class Configuration - constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null) + constructor(kotlin.Number, kotlin.Number? = null, kotlin.Number? = null, kotlin.Number? = null, kotlin.collections.List? = null) fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Configuration @@ -2702,7 +3108,7 @@ data class com.datadog.android.rum.model.VitalOperationStepEvent - UNITY - KOTLIN_MULTIPLATFORM - ELECTRON - - RUM_CPP + - CPP - MAUI fun toJson(): com.google.gson.JsonElement companion object diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index df7bbe50c8..d74b870b5f 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -517,23 +517,25 @@ public final class com/datadog/android/rum/metric/networksettled/TimeBasedInitia public final class com/datadog/android/rum/model/ActionEvent { public static final field Companion Lcom/datadog/android/rum/model/ActionEvent$Companion; - public fun (JLcom/datadog/android/rum/model/ActionEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSession;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource;Lcom/datadog/android/rum/model/ActionEvent$ActionEventView;Lcom/datadog/android/rum/model/ActionEvent$Usr;Lcom/datadog/android/rum/model/ActionEvent$Account;Lcom/datadog/android/rum/model/ActionEvent$Connectivity;Lcom/datadog/android/rum/model/ActionEvent$Display;Lcom/datadog/android/rum/model/ActionEvent$Synthetics;Lcom/datadog/android/rum/model/ActionEvent$CiTest;Lcom/datadog/android/rum/model/ActionEvent$Os;Lcom/datadog/android/rum/model/ActionEvent$Device;Lcom/datadog/android/rum/model/ActionEvent$Dd;Lcom/datadog/android/rum/model/ActionEvent$Context;Lcom/datadog/android/rum/model/ActionEvent$Container;Lcom/datadog/android/rum/model/ActionEvent$ActionEventAction;)V - public synthetic fun (JLcom/datadog/android/rum/model/ActionEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSession;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource;Lcom/datadog/android/rum/model/ActionEvent$ActionEventView;Lcom/datadog/android/rum/model/ActionEvent$Usr;Lcom/datadog/android/rum/model/ActionEvent$Account;Lcom/datadog/android/rum/model/ActionEvent$Connectivity;Lcom/datadog/android/rum/model/ActionEvent$Display;Lcom/datadog/android/rum/model/ActionEvent$Synthetics;Lcom/datadog/android/rum/model/ActionEvent$CiTest;Lcom/datadog/android/rum/model/ActionEvent$Os;Lcom/datadog/android/rum/model/ActionEvent$Device;Lcom/datadog/android/rum/model/ActionEvent$Dd;Lcom/datadog/android/rum/model/ActionEvent$Context;Lcom/datadog/android/rum/model/ActionEvent$Container;Lcom/datadog/android/rum/model/ActionEvent$ActionEventAction;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (JLcom/datadog/android/rum/model/ActionEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSession;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource;Lcom/datadog/android/rum/model/ActionEvent$ActionEventView;Lcom/datadog/android/rum/model/ActionEvent$Usr;Lcom/datadog/android/rum/model/ActionEvent$Account;Lcom/datadog/android/rum/model/ActionEvent$Tab;Lcom/datadog/android/rum/model/ActionEvent$Connectivity;Lcom/datadog/android/rum/model/ActionEvent$Display;Lcom/datadog/android/rum/model/ActionEvent$Synthetics;Lcom/datadog/android/rum/model/ActionEvent$CiTest;Lcom/datadog/android/rum/model/ActionEvent$Os;Lcom/datadog/android/rum/model/ActionEvent$Device;Lcom/datadog/android/rum/model/ActionEvent$Dd;Lcom/datadog/android/rum/model/ActionEvent$Context;Lcom/datadog/android/rum/model/ActionEvent$Stream;Lcom/datadog/android/rum/model/ActionEvent$Container;Lcom/datadog/android/rum/model/ActionEvent$ActionEventAction;)V + public synthetic fun (JLcom/datadog/android/rum/model/ActionEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSession;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource;Lcom/datadog/android/rum/model/ActionEvent$ActionEventView;Lcom/datadog/android/rum/model/ActionEvent$Usr;Lcom/datadog/android/rum/model/ActionEvent$Account;Lcom/datadog/android/rum/model/ActionEvent$Tab;Lcom/datadog/android/rum/model/ActionEvent$Connectivity;Lcom/datadog/android/rum/model/ActionEvent$Display;Lcom/datadog/android/rum/model/ActionEvent$Synthetics;Lcom/datadog/android/rum/model/ActionEvent$CiTest;Lcom/datadog/android/rum/model/ActionEvent$Os;Lcom/datadog/android/rum/model/ActionEvent$Device;Lcom/datadog/android/rum/model/ActionEvent$Dd;Lcom/datadog/android/rum/model/ActionEvent$Context;Lcom/datadog/android/rum/model/ActionEvent$Stream;Lcom/datadog/android/rum/model/ActionEvent$Container;Lcom/datadog/android/rum/model/ActionEvent$ActionEventAction;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()J public final fun component10 ()Lcom/datadog/android/rum/model/ActionEvent$ActionEventView; public final fun component11 ()Lcom/datadog/android/rum/model/ActionEvent$Usr; public final fun component12 ()Lcom/datadog/android/rum/model/ActionEvent$Account; - public final fun component13 ()Lcom/datadog/android/rum/model/ActionEvent$Connectivity; - public final fun component14 ()Lcom/datadog/android/rum/model/ActionEvent$Display; - public final fun component15 ()Lcom/datadog/android/rum/model/ActionEvent$Synthetics; - public final fun component16 ()Lcom/datadog/android/rum/model/ActionEvent$CiTest; - public final fun component17 ()Lcom/datadog/android/rum/model/ActionEvent$Os; - public final fun component18 ()Lcom/datadog/android/rum/model/ActionEvent$Device; - public final fun component19 ()Lcom/datadog/android/rum/model/ActionEvent$Dd; + public final fun component13 ()Lcom/datadog/android/rum/model/ActionEvent$Tab; + public final fun component14 ()Lcom/datadog/android/rum/model/ActionEvent$Connectivity; + public final fun component15 ()Lcom/datadog/android/rum/model/ActionEvent$Display; + public final fun component16 ()Lcom/datadog/android/rum/model/ActionEvent$Synthetics; + public final fun component17 ()Lcom/datadog/android/rum/model/ActionEvent$CiTest; + public final fun component18 ()Lcom/datadog/android/rum/model/ActionEvent$Os; + public final fun component19 ()Lcom/datadog/android/rum/model/ActionEvent$Device; public final fun component2 ()Lcom/datadog/android/rum/model/ActionEvent$Application; - public final fun component20 ()Lcom/datadog/android/rum/model/ActionEvent$Context; - public final fun component21 ()Lcom/datadog/android/rum/model/ActionEvent$Container; - public final fun component22 ()Lcom/datadog/android/rum/model/ActionEvent$ActionEventAction; + public final fun component20 ()Lcom/datadog/android/rum/model/ActionEvent$Dd; + public final fun component21 ()Lcom/datadog/android/rum/model/ActionEvent$Context; + public final fun component22 ()Lcom/datadog/android/rum/model/ActionEvent$Stream; + public final fun component23 ()Lcom/datadog/android/rum/model/ActionEvent$Container; + public final fun component24 ()Lcom/datadog/android/rum/model/ActionEvent$ActionEventAction; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; @@ -541,8 +543,8 @@ public final class com/datadog/android/rum/model/ActionEvent { public final fun component7 ()Ljava/lang/String; public final fun component8 ()Lcom/datadog/android/rum/model/ActionEvent$ActionEventSession; public final fun component9 ()Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource; - public final fun copy (JLcom/datadog/android/rum/model/ActionEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSession;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource;Lcom/datadog/android/rum/model/ActionEvent$ActionEventView;Lcom/datadog/android/rum/model/ActionEvent$Usr;Lcom/datadog/android/rum/model/ActionEvent$Account;Lcom/datadog/android/rum/model/ActionEvent$Connectivity;Lcom/datadog/android/rum/model/ActionEvent$Display;Lcom/datadog/android/rum/model/ActionEvent$Synthetics;Lcom/datadog/android/rum/model/ActionEvent$CiTest;Lcom/datadog/android/rum/model/ActionEvent$Os;Lcom/datadog/android/rum/model/ActionEvent$Device;Lcom/datadog/android/rum/model/ActionEvent$Dd;Lcom/datadog/android/rum/model/ActionEvent$Context;Lcom/datadog/android/rum/model/ActionEvent$Container;Lcom/datadog/android/rum/model/ActionEvent$ActionEventAction;)Lcom/datadog/android/rum/model/ActionEvent; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ActionEvent;JLcom/datadog/android/rum/model/ActionEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSession;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource;Lcom/datadog/android/rum/model/ActionEvent$ActionEventView;Lcom/datadog/android/rum/model/ActionEvent$Usr;Lcom/datadog/android/rum/model/ActionEvent$Account;Lcom/datadog/android/rum/model/ActionEvent$Connectivity;Lcom/datadog/android/rum/model/ActionEvent$Display;Lcom/datadog/android/rum/model/ActionEvent$Synthetics;Lcom/datadog/android/rum/model/ActionEvent$CiTest;Lcom/datadog/android/rum/model/ActionEvent$Os;Lcom/datadog/android/rum/model/ActionEvent$Device;Lcom/datadog/android/rum/model/ActionEvent$Dd;Lcom/datadog/android/rum/model/ActionEvent$Context;Lcom/datadog/android/rum/model/ActionEvent$Container;Lcom/datadog/android/rum/model/ActionEvent$ActionEventAction;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ActionEvent; + public final fun copy (JLcom/datadog/android/rum/model/ActionEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSession;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource;Lcom/datadog/android/rum/model/ActionEvent$ActionEventView;Lcom/datadog/android/rum/model/ActionEvent$Usr;Lcom/datadog/android/rum/model/ActionEvent$Account;Lcom/datadog/android/rum/model/ActionEvent$Tab;Lcom/datadog/android/rum/model/ActionEvent$Connectivity;Lcom/datadog/android/rum/model/ActionEvent$Display;Lcom/datadog/android/rum/model/ActionEvent$Synthetics;Lcom/datadog/android/rum/model/ActionEvent$CiTest;Lcom/datadog/android/rum/model/ActionEvent$Os;Lcom/datadog/android/rum/model/ActionEvent$Device;Lcom/datadog/android/rum/model/ActionEvent$Dd;Lcom/datadog/android/rum/model/ActionEvent$Context;Lcom/datadog/android/rum/model/ActionEvent$Stream;Lcom/datadog/android/rum/model/ActionEvent$Container;Lcom/datadog/android/rum/model/ActionEvent$ActionEventAction;)Lcom/datadog/android/rum/model/ActionEvent; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ActionEvent;JLcom/datadog/android/rum/model/ActionEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSession;Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource;Lcom/datadog/android/rum/model/ActionEvent$ActionEventView;Lcom/datadog/android/rum/model/ActionEvent$Usr;Lcom/datadog/android/rum/model/ActionEvent$Account;Lcom/datadog/android/rum/model/ActionEvent$Tab;Lcom/datadog/android/rum/model/ActionEvent$Connectivity;Lcom/datadog/android/rum/model/ActionEvent$Display;Lcom/datadog/android/rum/model/ActionEvent$Synthetics;Lcom/datadog/android/rum/model/ActionEvent$CiTest;Lcom/datadog/android/rum/model/ActionEvent$Os;Lcom/datadog/android/rum/model/ActionEvent$Device;Lcom/datadog/android/rum/model/ActionEvent$Dd;Lcom/datadog/android/rum/model/ActionEvent$Context;Lcom/datadog/android/rum/model/ActionEvent$Stream;Lcom/datadog/android/rum/model/ActionEvent$Container;Lcom/datadog/android/rum/model/ActionEvent$ActionEventAction;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ActionEvent; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ActionEvent; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ActionEvent; @@ -564,7 +566,9 @@ public final class com/datadog/android/rum/model/ActionEvent { public final fun getService ()Ljava/lang/String; public final fun getSession ()Lcom/datadog/android/rum/model/ActionEvent$ActionEventSession; public final fun getSource ()Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource; + public final fun getStream ()Lcom/datadog/android/rum/model/ActionEvent$Stream; public final fun getSynthetics ()Lcom/datadog/android/rum/model/ActionEvent$Synthetics; + public final fun getTab ()Lcom/datadog/android/rum/model/ActionEvent$Tab; public final fun getType ()Ljava/lang/String; public final fun getUsr ()Lcom/datadog/android/rum/model/ActionEvent$Usr; public final fun getVersion ()Ljava/lang/String; @@ -719,6 +723,7 @@ public final class com/datadog/android/rum/model/ActionEvent$ActionEventSessionT public final class com/datadog/android/rum/model/ActionEvent$ActionEventSource : java/lang/Enum { public static final field ANDROID Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource; public static final field BROWSER Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource; + public static final field CPP Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource; public static final field Companion Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource$Companion; public static final field ELECTRON Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource; public static final field FLUTTER Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource; @@ -727,7 +732,6 @@ public final class com/datadog/android/rum/model/ActionEvent$ActionEventSource : public static final field MAUI Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource; public static final field REACT_NATIVE Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource; public static final field ROKU Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource; - public static final field RUM_CPP Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource; public static final field UNITY Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource; public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ActionEvent$ActionEventSource; public final fun toJson ()Lcom/google/gson/JsonElement; @@ -845,18 +849,20 @@ public final class com/datadog/android/rum/model/ActionEvent$Companion { public final class com/datadog/android/rum/model/ActionEvent$Configuration { public static final field Companion Lcom/datadog/android/rum/model/ActionEvent$Configuration$Companion; - public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)V - public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)V + public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/Number; public final fun component2 ()Ljava/lang/Number; public final fun component3 ()Ljava/lang/Number; public final fun component4 ()Ljava/lang/Number; - public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)Lcom/datadog/android/rum/model/ActionEvent$Configuration; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ActionEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ActionEvent$Configuration; + public final fun component5 ()Ljava/util/List; + public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)Lcom/datadog/android/rum/model/ActionEvent$Configuration; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ActionEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ActionEvent$Configuration; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ActionEvent$Configuration; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ActionEvent$Configuration; public final fun getProfilingSampleRate ()Ljava/lang/Number; + public final fun getSessionReplayExperimentalFeatures ()Ljava/util/List; public final fun getSessionReplaySampleRate ()Ljava/lang/Number; public final fun getSessionSampleRate ()Ljava/lang/Number; public final fun getTraceSampleRate ()Ljava/lang/Number; @@ -1414,6 +1420,26 @@ public final class com/datadog/android/rum/model/ActionEvent$Status$Companion { public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ActionEvent$Status; } +public final class com/datadog/android/rum/model/ActionEvent$Stream { + public static final field Companion Lcom/datadog/android/rum/model/ActionEvent$Stream$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/ActionEvent$Stream; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ActionEvent$Stream;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ActionEvent$Stream; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ActionEvent$Stream; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ActionEvent$Stream; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/ActionEvent$Stream$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ActionEvent$Stream; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ActionEvent$Stream; +} + public final class com/datadog/android/rum/model/ActionEvent$Synthetics { public static final field Companion Lcom/datadog/android/rum/model/ActionEvent$Synthetics$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;)V @@ -1441,6 +1467,26 @@ public final class com/datadog/android/rum/model/ActionEvent$Synthetics$Companio public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ActionEvent$Synthetics; } +public final class com/datadog/android/rum/model/ActionEvent$Tab { + public static final field Companion Lcom/datadog/android/rum/model/ActionEvent$Tab$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/ActionEvent$Tab; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ActionEvent$Tab;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ActionEvent$Tab; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ActionEvent$Tab; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ActionEvent$Tab; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/ActionEvent$Tab$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ActionEvent$Tab; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ActionEvent$Tab; +} + public final class com/datadog/android/rum/model/ActionEvent$Type : java/lang/Enum { public static final field Companion Lcom/datadog/android/rum/model/ActionEvent$Type$Companion; public static final field DEAD_CLICK Lcom/datadog/android/rum/model/ActionEvent$Type; @@ -1512,26 +1558,28 @@ public final class com/datadog/android/rum/model/ActionEvent$Viewport$Companion public final class com/datadog/android/rum/model/ErrorEvent { public static final field Companion Lcom/datadog/android/rum/model/ErrorEvent$Companion; - public fun (JLcom/datadog/android/rum/model/ErrorEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSession;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventView;Lcom/datadog/android/rum/model/ErrorEvent$Usr;Lcom/datadog/android/rum/model/ErrorEvent$Account;Lcom/datadog/android/rum/model/ErrorEvent$Connectivity;Lcom/datadog/android/rum/model/ErrorEvent$Display;Lcom/datadog/android/rum/model/ErrorEvent$Synthetics;Lcom/datadog/android/rum/model/ErrorEvent$CiTest;Lcom/datadog/android/rum/model/ErrorEvent$Os;Lcom/datadog/android/rum/model/ErrorEvent$Device;Lcom/datadog/android/rum/model/ErrorEvent$Dd;Lcom/datadog/android/rum/model/ErrorEvent$Context;Lcom/datadog/android/rum/model/ErrorEvent$Action;Lcom/datadog/android/rum/model/ErrorEvent$Container;Lcom/datadog/android/rum/model/ErrorEvent$Error;Lcom/datadog/android/rum/model/ErrorEvent$Freeze;Lcom/datadog/android/rum/model/ErrorEvent$Context;)V - public synthetic fun (JLcom/datadog/android/rum/model/ErrorEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSession;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventView;Lcom/datadog/android/rum/model/ErrorEvent$Usr;Lcom/datadog/android/rum/model/ErrorEvent$Account;Lcom/datadog/android/rum/model/ErrorEvent$Connectivity;Lcom/datadog/android/rum/model/ErrorEvent$Display;Lcom/datadog/android/rum/model/ErrorEvent$Synthetics;Lcom/datadog/android/rum/model/ErrorEvent$CiTest;Lcom/datadog/android/rum/model/ErrorEvent$Os;Lcom/datadog/android/rum/model/ErrorEvent$Device;Lcom/datadog/android/rum/model/ErrorEvent$Dd;Lcom/datadog/android/rum/model/ErrorEvent$Context;Lcom/datadog/android/rum/model/ErrorEvent$Action;Lcom/datadog/android/rum/model/ErrorEvent$Container;Lcom/datadog/android/rum/model/ErrorEvent$Error;Lcom/datadog/android/rum/model/ErrorEvent$Freeze;Lcom/datadog/android/rum/model/ErrorEvent$Context;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (JLcom/datadog/android/rum/model/ErrorEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSession;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventView;Lcom/datadog/android/rum/model/ErrorEvent$Usr;Lcom/datadog/android/rum/model/ErrorEvent$Account;Lcom/datadog/android/rum/model/ErrorEvent$Tab;Lcom/datadog/android/rum/model/ErrorEvent$Connectivity;Lcom/datadog/android/rum/model/ErrorEvent$Display;Lcom/datadog/android/rum/model/ErrorEvent$Synthetics;Lcom/datadog/android/rum/model/ErrorEvent$CiTest;Lcom/datadog/android/rum/model/ErrorEvent$Os;Lcom/datadog/android/rum/model/ErrorEvent$Device;Lcom/datadog/android/rum/model/ErrorEvent$Dd;Lcom/datadog/android/rum/model/ErrorEvent$Context;Lcom/datadog/android/rum/model/ErrorEvent$Stream;Lcom/datadog/android/rum/model/ErrorEvent$Action;Lcom/datadog/android/rum/model/ErrorEvent$Container;Lcom/datadog/android/rum/model/ErrorEvent$Error;Lcom/datadog/android/rum/model/ErrorEvent$Freeze;Lcom/datadog/android/rum/model/ErrorEvent$Context;)V + public synthetic fun (JLcom/datadog/android/rum/model/ErrorEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSession;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventView;Lcom/datadog/android/rum/model/ErrorEvent$Usr;Lcom/datadog/android/rum/model/ErrorEvent$Account;Lcom/datadog/android/rum/model/ErrorEvent$Tab;Lcom/datadog/android/rum/model/ErrorEvent$Connectivity;Lcom/datadog/android/rum/model/ErrorEvent$Display;Lcom/datadog/android/rum/model/ErrorEvent$Synthetics;Lcom/datadog/android/rum/model/ErrorEvent$CiTest;Lcom/datadog/android/rum/model/ErrorEvent$Os;Lcom/datadog/android/rum/model/ErrorEvent$Device;Lcom/datadog/android/rum/model/ErrorEvent$Dd;Lcom/datadog/android/rum/model/ErrorEvent$Context;Lcom/datadog/android/rum/model/ErrorEvent$Stream;Lcom/datadog/android/rum/model/ErrorEvent$Action;Lcom/datadog/android/rum/model/ErrorEvent$Container;Lcom/datadog/android/rum/model/ErrorEvent$Error;Lcom/datadog/android/rum/model/ErrorEvent$Freeze;Lcom/datadog/android/rum/model/ErrorEvent$Context;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()J public final fun component10 ()Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventView; public final fun component11 ()Lcom/datadog/android/rum/model/ErrorEvent$Usr; public final fun component12 ()Lcom/datadog/android/rum/model/ErrorEvent$Account; - public final fun component13 ()Lcom/datadog/android/rum/model/ErrorEvent$Connectivity; - public final fun component14 ()Lcom/datadog/android/rum/model/ErrorEvent$Display; - public final fun component15 ()Lcom/datadog/android/rum/model/ErrorEvent$Synthetics; - public final fun component16 ()Lcom/datadog/android/rum/model/ErrorEvent$CiTest; - public final fun component17 ()Lcom/datadog/android/rum/model/ErrorEvent$Os; - public final fun component18 ()Lcom/datadog/android/rum/model/ErrorEvent$Device; - public final fun component19 ()Lcom/datadog/android/rum/model/ErrorEvent$Dd; + public final fun component13 ()Lcom/datadog/android/rum/model/ErrorEvent$Tab; + public final fun component14 ()Lcom/datadog/android/rum/model/ErrorEvent$Connectivity; + public final fun component15 ()Lcom/datadog/android/rum/model/ErrorEvent$Display; + public final fun component16 ()Lcom/datadog/android/rum/model/ErrorEvent$Synthetics; + public final fun component17 ()Lcom/datadog/android/rum/model/ErrorEvent$CiTest; + public final fun component18 ()Lcom/datadog/android/rum/model/ErrorEvent$Os; + public final fun component19 ()Lcom/datadog/android/rum/model/ErrorEvent$Device; public final fun component2 ()Lcom/datadog/android/rum/model/ErrorEvent$Application; - public final fun component20 ()Lcom/datadog/android/rum/model/ErrorEvent$Context; - public final fun component21 ()Lcom/datadog/android/rum/model/ErrorEvent$Action; - public final fun component22 ()Lcom/datadog/android/rum/model/ErrorEvent$Container; - public final fun component23 ()Lcom/datadog/android/rum/model/ErrorEvent$Error; - public final fun component24 ()Lcom/datadog/android/rum/model/ErrorEvent$Freeze; - public final fun component25 ()Lcom/datadog/android/rum/model/ErrorEvent$Context; + public final fun component20 ()Lcom/datadog/android/rum/model/ErrorEvent$Dd; + public final fun component21 ()Lcom/datadog/android/rum/model/ErrorEvent$Context; + public final fun component22 ()Lcom/datadog/android/rum/model/ErrorEvent$Stream; + public final fun component23 ()Lcom/datadog/android/rum/model/ErrorEvent$Action; + public final fun component24 ()Lcom/datadog/android/rum/model/ErrorEvent$Container; + public final fun component25 ()Lcom/datadog/android/rum/model/ErrorEvent$Error; + public final fun component26 ()Lcom/datadog/android/rum/model/ErrorEvent$Freeze; + public final fun component27 ()Lcom/datadog/android/rum/model/ErrorEvent$Context; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; @@ -1539,8 +1587,8 @@ public final class com/datadog/android/rum/model/ErrorEvent { public final fun component7 ()Ljava/lang/String; public final fun component8 ()Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSession; public final fun component9 ()Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource; - public final fun copy (JLcom/datadog/android/rum/model/ErrorEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSession;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventView;Lcom/datadog/android/rum/model/ErrorEvent$Usr;Lcom/datadog/android/rum/model/ErrorEvent$Account;Lcom/datadog/android/rum/model/ErrorEvent$Connectivity;Lcom/datadog/android/rum/model/ErrorEvent$Display;Lcom/datadog/android/rum/model/ErrorEvent$Synthetics;Lcom/datadog/android/rum/model/ErrorEvent$CiTest;Lcom/datadog/android/rum/model/ErrorEvent$Os;Lcom/datadog/android/rum/model/ErrorEvent$Device;Lcom/datadog/android/rum/model/ErrorEvent$Dd;Lcom/datadog/android/rum/model/ErrorEvent$Context;Lcom/datadog/android/rum/model/ErrorEvent$Action;Lcom/datadog/android/rum/model/ErrorEvent$Container;Lcom/datadog/android/rum/model/ErrorEvent$Error;Lcom/datadog/android/rum/model/ErrorEvent$Freeze;Lcom/datadog/android/rum/model/ErrorEvent$Context;)Lcom/datadog/android/rum/model/ErrorEvent; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ErrorEvent;JLcom/datadog/android/rum/model/ErrorEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSession;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventView;Lcom/datadog/android/rum/model/ErrorEvent$Usr;Lcom/datadog/android/rum/model/ErrorEvent$Account;Lcom/datadog/android/rum/model/ErrorEvent$Connectivity;Lcom/datadog/android/rum/model/ErrorEvent$Display;Lcom/datadog/android/rum/model/ErrorEvent$Synthetics;Lcom/datadog/android/rum/model/ErrorEvent$CiTest;Lcom/datadog/android/rum/model/ErrorEvent$Os;Lcom/datadog/android/rum/model/ErrorEvent$Device;Lcom/datadog/android/rum/model/ErrorEvent$Dd;Lcom/datadog/android/rum/model/ErrorEvent$Context;Lcom/datadog/android/rum/model/ErrorEvent$Action;Lcom/datadog/android/rum/model/ErrorEvent$Container;Lcom/datadog/android/rum/model/ErrorEvent$Error;Lcom/datadog/android/rum/model/ErrorEvent$Freeze;Lcom/datadog/android/rum/model/ErrorEvent$Context;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ErrorEvent; + public final fun copy (JLcom/datadog/android/rum/model/ErrorEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSession;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventView;Lcom/datadog/android/rum/model/ErrorEvent$Usr;Lcom/datadog/android/rum/model/ErrorEvent$Account;Lcom/datadog/android/rum/model/ErrorEvent$Tab;Lcom/datadog/android/rum/model/ErrorEvent$Connectivity;Lcom/datadog/android/rum/model/ErrorEvent$Display;Lcom/datadog/android/rum/model/ErrorEvent$Synthetics;Lcom/datadog/android/rum/model/ErrorEvent$CiTest;Lcom/datadog/android/rum/model/ErrorEvent$Os;Lcom/datadog/android/rum/model/ErrorEvent$Device;Lcom/datadog/android/rum/model/ErrorEvent$Dd;Lcom/datadog/android/rum/model/ErrorEvent$Context;Lcom/datadog/android/rum/model/ErrorEvent$Stream;Lcom/datadog/android/rum/model/ErrorEvent$Action;Lcom/datadog/android/rum/model/ErrorEvent$Container;Lcom/datadog/android/rum/model/ErrorEvent$Error;Lcom/datadog/android/rum/model/ErrorEvent$Freeze;Lcom/datadog/android/rum/model/ErrorEvent$Context;)Lcom/datadog/android/rum/model/ErrorEvent; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ErrorEvent;JLcom/datadog/android/rum/model/ErrorEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSession;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource;Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventView;Lcom/datadog/android/rum/model/ErrorEvent$Usr;Lcom/datadog/android/rum/model/ErrorEvent$Account;Lcom/datadog/android/rum/model/ErrorEvent$Tab;Lcom/datadog/android/rum/model/ErrorEvent$Connectivity;Lcom/datadog/android/rum/model/ErrorEvent$Display;Lcom/datadog/android/rum/model/ErrorEvent$Synthetics;Lcom/datadog/android/rum/model/ErrorEvent$CiTest;Lcom/datadog/android/rum/model/ErrorEvent$Os;Lcom/datadog/android/rum/model/ErrorEvent$Device;Lcom/datadog/android/rum/model/ErrorEvent$Dd;Lcom/datadog/android/rum/model/ErrorEvent$Context;Lcom/datadog/android/rum/model/ErrorEvent$Stream;Lcom/datadog/android/rum/model/ErrorEvent$Action;Lcom/datadog/android/rum/model/ErrorEvent$Container;Lcom/datadog/android/rum/model/ErrorEvent$Error;Lcom/datadog/android/rum/model/ErrorEvent$Freeze;Lcom/datadog/android/rum/model/ErrorEvent$Context;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ErrorEvent; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ErrorEvent; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ErrorEvent; @@ -1565,7 +1613,9 @@ public final class com/datadog/android/rum/model/ErrorEvent { public final fun getService ()Ljava/lang/String; public final fun getSession ()Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSession; public final fun getSource ()Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource; + public final fun getStream ()Lcom/datadog/android/rum/model/ErrorEvent$Stream; public final fun getSynthetics ()Lcom/datadog/android/rum/model/ErrorEvent$Synthetics; + public final fun getTab ()Lcom/datadog/android/rum/model/ErrorEvent$Tab; public final fun getType ()Ljava/lang/String; public final fun getUsr ()Lcom/datadog/android/rum/model/ErrorEvent$Usr; public final fun getVersion ()Ljava/lang/String; @@ -1772,18 +1822,20 @@ public final class com/datadog/android/rum/model/ErrorEvent$Companion { public final class com/datadog/android/rum/model/ErrorEvent$Configuration { public static final field Companion Lcom/datadog/android/rum/model/ErrorEvent$Configuration$Companion; - public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)V - public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)V + public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/Number; public final fun component2 ()Ljava/lang/Number; public final fun component3 ()Ljava/lang/Number; public final fun component4 ()Ljava/lang/Number; - public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)Lcom/datadog/android/rum/model/ErrorEvent$Configuration; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ErrorEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ErrorEvent$Configuration; + public final fun component5 ()Ljava/util/List; + public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)Lcom/datadog/android/rum/model/ErrorEvent$Configuration; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ErrorEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ErrorEvent$Configuration; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ErrorEvent$Configuration; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ErrorEvent$Configuration; public final fun getProfilingSampleRate ()Ljava/lang/Number; + public final fun getSessionReplayExperimentalFeatures ()Ljava/util/List; public final fun getSessionReplaySampleRate ()Ljava/lang/Number; public final fun getSessionSampleRate ()Ljava/lang/Number; public final fun getTraceSampleRate ()Ljava/lang/Number; @@ -2204,6 +2256,7 @@ public final class com/datadog/android/rum/model/ErrorEvent$ErrorEventSessionTyp public final class com/datadog/android/rum/model/ErrorEvent$ErrorEventSource : java/lang/Enum { public static final field ANDROID Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource; public static final field BROWSER Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource; + public static final field CPP Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource; public static final field Companion Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource$Companion; public static final field ELECTRON Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource; public static final field FLUTTER Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource; @@ -2212,7 +2265,6 @@ public final class com/datadog/android/rum/model/ErrorEvent$ErrorEventSource : j public static final field MAUI Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource; public static final field REACT_NATIVE Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource; public static final field ROKU Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource; - public static final field RUM_CPP Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource; public static final field UNITY Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource; public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ErrorEvent$ErrorEventSource; public final fun toJson ()Lcom/google/gson/JsonElement; @@ -2611,6 +2663,26 @@ public final class com/datadog/android/rum/model/ErrorEvent$SourceType$Companion public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ErrorEvent$SourceType; } +public final class com/datadog/android/rum/model/ErrorEvent$Stream { + public static final field Companion Lcom/datadog/android/rum/model/ErrorEvent$Stream$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/ErrorEvent$Stream; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ErrorEvent$Stream;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ErrorEvent$Stream; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ErrorEvent$Stream; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ErrorEvent$Stream; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/ErrorEvent$Stream$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ErrorEvent$Stream; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ErrorEvent$Stream; +} + public final class com/datadog/android/rum/model/ErrorEvent$Synthetics { public static final field Companion Lcom/datadog/android/rum/model/ErrorEvent$Synthetics$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;)V @@ -2638,6 +2710,26 @@ public final class com/datadog/android/rum/model/ErrorEvent$Synthetics$Companion public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ErrorEvent$Synthetics; } +public final class com/datadog/android/rum/model/ErrorEvent$Tab { + public static final field Companion Lcom/datadog/android/rum/model/ErrorEvent$Tab$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/ErrorEvent$Tab; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ErrorEvent$Tab;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ErrorEvent$Tab; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ErrorEvent$Tab; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ErrorEvent$Tab; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/ErrorEvent$Tab$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ErrorEvent$Tab; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ErrorEvent$Tab; +} + public final class com/datadog/android/rum/model/ErrorEvent$Thread { public static final field Companion Lcom/datadog/android/rum/model/ErrorEvent$Thread$Companion; public fun (Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V @@ -2719,24 +2811,26 @@ public final class com/datadog/android/rum/model/ErrorEvent$Viewport$Companion { public final class com/datadog/android/rum/model/LongTaskEvent { public static final field Companion Lcom/datadog/android/rum/model/LongTaskEvent$Companion; - public fun (JLcom/datadog/android/rum/model/LongTaskEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSession;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventView;Lcom/datadog/android/rum/model/LongTaskEvent$Usr;Lcom/datadog/android/rum/model/LongTaskEvent$Account;Lcom/datadog/android/rum/model/LongTaskEvent$Connectivity;Lcom/datadog/android/rum/model/LongTaskEvent$Display;Lcom/datadog/android/rum/model/LongTaskEvent$Synthetics;Lcom/datadog/android/rum/model/LongTaskEvent$CiTest;Lcom/datadog/android/rum/model/LongTaskEvent$Os;Lcom/datadog/android/rum/model/LongTaskEvent$Device;Lcom/datadog/android/rum/model/LongTaskEvent$Dd;Lcom/datadog/android/rum/model/LongTaskEvent$Context;Lcom/datadog/android/rum/model/LongTaskEvent$Action;Lcom/datadog/android/rum/model/LongTaskEvent$Container;Lcom/datadog/android/rum/model/LongTaskEvent$LongTask;)V - public synthetic fun (JLcom/datadog/android/rum/model/LongTaskEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSession;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventView;Lcom/datadog/android/rum/model/LongTaskEvent$Usr;Lcom/datadog/android/rum/model/LongTaskEvent$Account;Lcom/datadog/android/rum/model/LongTaskEvent$Connectivity;Lcom/datadog/android/rum/model/LongTaskEvent$Display;Lcom/datadog/android/rum/model/LongTaskEvent$Synthetics;Lcom/datadog/android/rum/model/LongTaskEvent$CiTest;Lcom/datadog/android/rum/model/LongTaskEvent$Os;Lcom/datadog/android/rum/model/LongTaskEvent$Device;Lcom/datadog/android/rum/model/LongTaskEvent$Dd;Lcom/datadog/android/rum/model/LongTaskEvent$Context;Lcom/datadog/android/rum/model/LongTaskEvent$Action;Lcom/datadog/android/rum/model/LongTaskEvent$Container;Lcom/datadog/android/rum/model/LongTaskEvent$LongTask;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (JLcom/datadog/android/rum/model/LongTaskEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSession;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventView;Lcom/datadog/android/rum/model/LongTaskEvent$Usr;Lcom/datadog/android/rum/model/LongTaskEvent$Account;Lcom/datadog/android/rum/model/LongTaskEvent$Tab;Lcom/datadog/android/rum/model/LongTaskEvent$Connectivity;Lcom/datadog/android/rum/model/LongTaskEvent$Display;Lcom/datadog/android/rum/model/LongTaskEvent$Synthetics;Lcom/datadog/android/rum/model/LongTaskEvent$CiTest;Lcom/datadog/android/rum/model/LongTaskEvent$Os;Lcom/datadog/android/rum/model/LongTaskEvent$Device;Lcom/datadog/android/rum/model/LongTaskEvent$Dd;Lcom/datadog/android/rum/model/LongTaskEvent$Context;Lcom/datadog/android/rum/model/LongTaskEvent$Stream;Lcom/datadog/android/rum/model/LongTaskEvent$Action;Lcom/datadog/android/rum/model/LongTaskEvent$Container;Lcom/datadog/android/rum/model/LongTaskEvent$LongTask;)V + public synthetic fun (JLcom/datadog/android/rum/model/LongTaskEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSession;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventView;Lcom/datadog/android/rum/model/LongTaskEvent$Usr;Lcom/datadog/android/rum/model/LongTaskEvent$Account;Lcom/datadog/android/rum/model/LongTaskEvent$Tab;Lcom/datadog/android/rum/model/LongTaskEvent$Connectivity;Lcom/datadog/android/rum/model/LongTaskEvent$Display;Lcom/datadog/android/rum/model/LongTaskEvent$Synthetics;Lcom/datadog/android/rum/model/LongTaskEvent$CiTest;Lcom/datadog/android/rum/model/LongTaskEvent$Os;Lcom/datadog/android/rum/model/LongTaskEvent$Device;Lcom/datadog/android/rum/model/LongTaskEvent$Dd;Lcom/datadog/android/rum/model/LongTaskEvent$Context;Lcom/datadog/android/rum/model/LongTaskEvent$Stream;Lcom/datadog/android/rum/model/LongTaskEvent$Action;Lcom/datadog/android/rum/model/LongTaskEvent$Container;Lcom/datadog/android/rum/model/LongTaskEvent$LongTask;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()J public final fun component10 ()Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventView; public final fun component11 ()Lcom/datadog/android/rum/model/LongTaskEvent$Usr; public final fun component12 ()Lcom/datadog/android/rum/model/LongTaskEvent$Account; - public final fun component13 ()Lcom/datadog/android/rum/model/LongTaskEvent$Connectivity; - public final fun component14 ()Lcom/datadog/android/rum/model/LongTaskEvent$Display; - public final fun component15 ()Lcom/datadog/android/rum/model/LongTaskEvent$Synthetics; - public final fun component16 ()Lcom/datadog/android/rum/model/LongTaskEvent$CiTest; - public final fun component17 ()Lcom/datadog/android/rum/model/LongTaskEvent$Os; - public final fun component18 ()Lcom/datadog/android/rum/model/LongTaskEvent$Device; - public final fun component19 ()Lcom/datadog/android/rum/model/LongTaskEvent$Dd; + public final fun component13 ()Lcom/datadog/android/rum/model/LongTaskEvent$Tab; + public final fun component14 ()Lcom/datadog/android/rum/model/LongTaskEvent$Connectivity; + public final fun component15 ()Lcom/datadog/android/rum/model/LongTaskEvent$Display; + public final fun component16 ()Lcom/datadog/android/rum/model/LongTaskEvent$Synthetics; + public final fun component17 ()Lcom/datadog/android/rum/model/LongTaskEvent$CiTest; + public final fun component18 ()Lcom/datadog/android/rum/model/LongTaskEvent$Os; + public final fun component19 ()Lcom/datadog/android/rum/model/LongTaskEvent$Device; public final fun component2 ()Lcom/datadog/android/rum/model/LongTaskEvent$Application; - public final fun component20 ()Lcom/datadog/android/rum/model/LongTaskEvent$Context; - public final fun component21 ()Lcom/datadog/android/rum/model/LongTaskEvent$Action; - public final fun component22 ()Lcom/datadog/android/rum/model/LongTaskEvent$Container; - public final fun component23 ()Lcom/datadog/android/rum/model/LongTaskEvent$LongTask; + public final fun component20 ()Lcom/datadog/android/rum/model/LongTaskEvent$Dd; + public final fun component21 ()Lcom/datadog/android/rum/model/LongTaskEvent$Context; + public final fun component22 ()Lcom/datadog/android/rum/model/LongTaskEvent$Stream; + public final fun component23 ()Lcom/datadog/android/rum/model/LongTaskEvent$Action; + public final fun component24 ()Lcom/datadog/android/rum/model/LongTaskEvent$Container; + public final fun component25 ()Lcom/datadog/android/rum/model/LongTaskEvent$LongTask; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; @@ -2744,8 +2838,8 @@ public final class com/datadog/android/rum/model/LongTaskEvent { public final fun component7 ()Ljava/lang/String; public final fun component8 ()Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSession; public final fun component9 ()Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource; - public final fun copy (JLcom/datadog/android/rum/model/LongTaskEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSession;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventView;Lcom/datadog/android/rum/model/LongTaskEvent$Usr;Lcom/datadog/android/rum/model/LongTaskEvent$Account;Lcom/datadog/android/rum/model/LongTaskEvent$Connectivity;Lcom/datadog/android/rum/model/LongTaskEvent$Display;Lcom/datadog/android/rum/model/LongTaskEvent$Synthetics;Lcom/datadog/android/rum/model/LongTaskEvent$CiTest;Lcom/datadog/android/rum/model/LongTaskEvent$Os;Lcom/datadog/android/rum/model/LongTaskEvent$Device;Lcom/datadog/android/rum/model/LongTaskEvent$Dd;Lcom/datadog/android/rum/model/LongTaskEvent$Context;Lcom/datadog/android/rum/model/LongTaskEvent$Action;Lcom/datadog/android/rum/model/LongTaskEvent$Container;Lcom/datadog/android/rum/model/LongTaskEvent$LongTask;)Lcom/datadog/android/rum/model/LongTaskEvent; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/LongTaskEvent;JLcom/datadog/android/rum/model/LongTaskEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSession;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventView;Lcom/datadog/android/rum/model/LongTaskEvent$Usr;Lcom/datadog/android/rum/model/LongTaskEvent$Account;Lcom/datadog/android/rum/model/LongTaskEvent$Connectivity;Lcom/datadog/android/rum/model/LongTaskEvent$Display;Lcom/datadog/android/rum/model/LongTaskEvent$Synthetics;Lcom/datadog/android/rum/model/LongTaskEvent$CiTest;Lcom/datadog/android/rum/model/LongTaskEvent$Os;Lcom/datadog/android/rum/model/LongTaskEvent$Device;Lcom/datadog/android/rum/model/LongTaskEvent$Dd;Lcom/datadog/android/rum/model/LongTaskEvent$Context;Lcom/datadog/android/rum/model/LongTaskEvent$Action;Lcom/datadog/android/rum/model/LongTaskEvent$Container;Lcom/datadog/android/rum/model/LongTaskEvent$LongTask;ILjava/lang/Object;)Lcom/datadog/android/rum/model/LongTaskEvent; + public final fun copy (JLcom/datadog/android/rum/model/LongTaskEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSession;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventView;Lcom/datadog/android/rum/model/LongTaskEvent$Usr;Lcom/datadog/android/rum/model/LongTaskEvent$Account;Lcom/datadog/android/rum/model/LongTaskEvent$Tab;Lcom/datadog/android/rum/model/LongTaskEvent$Connectivity;Lcom/datadog/android/rum/model/LongTaskEvent$Display;Lcom/datadog/android/rum/model/LongTaskEvent$Synthetics;Lcom/datadog/android/rum/model/LongTaskEvent$CiTest;Lcom/datadog/android/rum/model/LongTaskEvent$Os;Lcom/datadog/android/rum/model/LongTaskEvent$Device;Lcom/datadog/android/rum/model/LongTaskEvent$Dd;Lcom/datadog/android/rum/model/LongTaskEvent$Context;Lcom/datadog/android/rum/model/LongTaskEvent$Stream;Lcom/datadog/android/rum/model/LongTaskEvent$Action;Lcom/datadog/android/rum/model/LongTaskEvent$Container;Lcom/datadog/android/rum/model/LongTaskEvent$LongTask;)Lcom/datadog/android/rum/model/LongTaskEvent; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/LongTaskEvent;JLcom/datadog/android/rum/model/LongTaskEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSession;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource;Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventView;Lcom/datadog/android/rum/model/LongTaskEvent$Usr;Lcom/datadog/android/rum/model/LongTaskEvent$Account;Lcom/datadog/android/rum/model/LongTaskEvent$Tab;Lcom/datadog/android/rum/model/LongTaskEvent$Connectivity;Lcom/datadog/android/rum/model/LongTaskEvent$Display;Lcom/datadog/android/rum/model/LongTaskEvent$Synthetics;Lcom/datadog/android/rum/model/LongTaskEvent$CiTest;Lcom/datadog/android/rum/model/LongTaskEvent$Os;Lcom/datadog/android/rum/model/LongTaskEvent$Device;Lcom/datadog/android/rum/model/LongTaskEvent$Dd;Lcom/datadog/android/rum/model/LongTaskEvent$Context;Lcom/datadog/android/rum/model/LongTaskEvent$Stream;Lcom/datadog/android/rum/model/LongTaskEvent$Action;Lcom/datadog/android/rum/model/LongTaskEvent$Container;Lcom/datadog/android/rum/model/LongTaskEvent$LongTask;ILjava/lang/Object;)Lcom/datadog/android/rum/model/LongTaskEvent; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/LongTaskEvent; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/LongTaskEvent; @@ -2768,7 +2862,9 @@ public final class com/datadog/android/rum/model/LongTaskEvent { public final fun getService ()Ljava/lang/String; public final fun getSession ()Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSession; public final fun getSource ()Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource; + public final fun getStream ()Lcom/datadog/android/rum/model/LongTaskEvent$Stream; public final fun getSynthetics ()Lcom/datadog/android/rum/model/LongTaskEvent$Synthetics; + public final fun getTab ()Lcom/datadog/android/rum/model/LongTaskEvent$Tab; public final fun getType ()Ljava/lang/String; public final fun getUsr ()Lcom/datadog/android/rum/model/LongTaskEvent$Usr; public final fun getVersion ()Ljava/lang/String; @@ -2897,18 +2993,20 @@ public final class com/datadog/android/rum/model/LongTaskEvent$Companion { public final class com/datadog/android/rum/model/LongTaskEvent$Configuration { public static final field Companion Lcom/datadog/android/rum/model/LongTaskEvent$Configuration$Companion; - public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)V - public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)V + public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/Number; public final fun component2 ()Ljava/lang/Number; public final fun component3 ()Ljava/lang/Number; public final fun component4 ()Ljava/lang/Number; - public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)Lcom/datadog/android/rum/model/LongTaskEvent$Configuration; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/LongTaskEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/LongTaskEvent$Configuration; + public final fun component5 ()Ljava/util/List; + public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)Lcom/datadog/android/rum/model/LongTaskEvent$Configuration; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/LongTaskEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/LongTaskEvent$Configuration; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/LongTaskEvent$Configuration; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/LongTaskEvent$Configuration; public final fun getProfilingSampleRate ()Ljava/lang/Number; + public final fun getSessionReplayExperimentalFeatures ()Ljava/util/List; public final fun getSessionReplaySampleRate ()Ljava/lang/Number; public final fun getSessionSampleRate ()Ljava/lang/Number; public final fun getTraceSampleRate ()Ljava/lang/Number; @@ -3341,6 +3439,7 @@ public final class com/datadog/android/rum/model/LongTaskEvent$LongTaskEventSess public final class com/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource : java/lang/Enum { public static final field ANDROID Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource; public static final field BROWSER Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource; + public static final field CPP Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource; public static final field Companion Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource$Companion; public static final field ELECTRON Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource; public static final field FLUTTER Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource; @@ -3349,7 +3448,6 @@ public final class com/datadog/android/rum/model/LongTaskEvent$LongTaskEventSour public static final field MAUI Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource; public static final field REACT_NATIVE Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource; public static final field ROKU Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource; - public static final field RUM_CPP Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource; public static final field UNITY Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource; public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/LongTaskEvent$LongTaskEventSource; public final fun toJson ()Lcom/google/gson/JsonElement; @@ -3537,6 +3635,26 @@ public final class com/datadog/android/rum/model/LongTaskEvent$SessionPreconditi public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/LongTaskEvent$SessionPrecondition; } +public final class com/datadog/android/rum/model/LongTaskEvent$Stream { + public static final field Companion Lcom/datadog/android/rum/model/LongTaskEvent$Stream$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/LongTaskEvent$Stream; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/LongTaskEvent$Stream;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/LongTaskEvent$Stream; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/LongTaskEvent$Stream; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/LongTaskEvent$Stream; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/LongTaskEvent$Stream$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/LongTaskEvent$Stream; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/LongTaskEvent$Stream; +} + public final class com/datadog/android/rum/model/LongTaskEvent$Synthetics { public static final field Companion Lcom/datadog/android/rum/model/LongTaskEvent$Synthetics$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;)V @@ -3564,6 +3682,26 @@ public final class com/datadog/android/rum/model/LongTaskEvent$Synthetics$Compan public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/LongTaskEvent$Synthetics; } +public final class com/datadog/android/rum/model/LongTaskEvent$Tab { + public static final field Companion Lcom/datadog/android/rum/model/LongTaskEvent$Tab$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/LongTaskEvent$Tab; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/LongTaskEvent$Tab;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/LongTaskEvent$Tab; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/LongTaskEvent$Tab; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/LongTaskEvent$Tab; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/LongTaskEvent$Tab$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/LongTaskEvent$Tab; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/LongTaskEvent$Tab; +} + public final class com/datadog/android/rum/model/LongTaskEvent$Usr { public static final field Companion Lcom/datadog/android/rum/model/LongTaskEvent$Usr$Companion; public fun ()V @@ -3618,24 +3756,26 @@ public final class com/datadog/android/rum/model/LongTaskEvent$Viewport$Companio public final class com/datadog/android/rum/model/ResourceEvent { public static final field Companion Lcom/datadog/android/rum/model/ResourceEvent$Companion; - public fun (JLcom/datadog/android/rum/model/ResourceEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSession;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventView;Lcom/datadog/android/rum/model/ResourceEvent$Usr;Lcom/datadog/android/rum/model/ResourceEvent$Account;Lcom/datadog/android/rum/model/ResourceEvent$Connectivity;Lcom/datadog/android/rum/model/ResourceEvent$Display;Lcom/datadog/android/rum/model/ResourceEvent$Synthetics;Lcom/datadog/android/rum/model/ResourceEvent$CiTest;Lcom/datadog/android/rum/model/ResourceEvent$Os;Lcom/datadog/android/rum/model/ResourceEvent$Device;Lcom/datadog/android/rum/model/ResourceEvent$Dd;Lcom/datadog/android/rum/model/ResourceEvent$Context;Lcom/datadog/android/rum/model/ResourceEvent$Action;Lcom/datadog/android/rum/model/ResourceEvent$Container;Lcom/datadog/android/rum/model/ResourceEvent$Resource;)V - public synthetic fun (JLcom/datadog/android/rum/model/ResourceEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSession;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventView;Lcom/datadog/android/rum/model/ResourceEvent$Usr;Lcom/datadog/android/rum/model/ResourceEvent$Account;Lcom/datadog/android/rum/model/ResourceEvent$Connectivity;Lcom/datadog/android/rum/model/ResourceEvent$Display;Lcom/datadog/android/rum/model/ResourceEvent$Synthetics;Lcom/datadog/android/rum/model/ResourceEvent$CiTest;Lcom/datadog/android/rum/model/ResourceEvent$Os;Lcom/datadog/android/rum/model/ResourceEvent$Device;Lcom/datadog/android/rum/model/ResourceEvent$Dd;Lcom/datadog/android/rum/model/ResourceEvent$Context;Lcom/datadog/android/rum/model/ResourceEvent$Action;Lcom/datadog/android/rum/model/ResourceEvent$Container;Lcom/datadog/android/rum/model/ResourceEvent$Resource;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (JLcom/datadog/android/rum/model/ResourceEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSession;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventView;Lcom/datadog/android/rum/model/ResourceEvent$Usr;Lcom/datadog/android/rum/model/ResourceEvent$Account;Lcom/datadog/android/rum/model/ResourceEvent$Tab;Lcom/datadog/android/rum/model/ResourceEvent$Connectivity;Lcom/datadog/android/rum/model/ResourceEvent$Display;Lcom/datadog/android/rum/model/ResourceEvent$Synthetics;Lcom/datadog/android/rum/model/ResourceEvent$CiTest;Lcom/datadog/android/rum/model/ResourceEvent$Os;Lcom/datadog/android/rum/model/ResourceEvent$Device;Lcom/datadog/android/rum/model/ResourceEvent$Dd;Lcom/datadog/android/rum/model/ResourceEvent$Context;Lcom/datadog/android/rum/model/ResourceEvent$Stream;Lcom/datadog/android/rum/model/ResourceEvent$Action;Lcom/datadog/android/rum/model/ResourceEvent$Container;Lcom/datadog/android/rum/model/ResourceEvent$Resource;)V + public synthetic fun (JLcom/datadog/android/rum/model/ResourceEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSession;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventView;Lcom/datadog/android/rum/model/ResourceEvent$Usr;Lcom/datadog/android/rum/model/ResourceEvent$Account;Lcom/datadog/android/rum/model/ResourceEvent$Tab;Lcom/datadog/android/rum/model/ResourceEvent$Connectivity;Lcom/datadog/android/rum/model/ResourceEvent$Display;Lcom/datadog/android/rum/model/ResourceEvent$Synthetics;Lcom/datadog/android/rum/model/ResourceEvent$CiTest;Lcom/datadog/android/rum/model/ResourceEvent$Os;Lcom/datadog/android/rum/model/ResourceEvent$Device;Lcom/datadog/android/rum/model/ResourceEvent$Dd;Lcom/datadog/android/rum/model/ResourceEvent$Context;Lcom/datadog/android/rum/model/ResourceEvent$Stream;Lcom/datadog/android/rum/model/ResourceEvent$Action;Lcom/datadog/android/rum/model/ResourceEvent$Container;Lcom/datadog/android/rum/model/ResourceEvent$Resource;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()J public final fun component10 ()Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventView; public final fun component11 ()Lcom/datadog/android/rum/model/ResourceEvent$Usr; public final fun component12 ()Lcom/datadog/android/rum/model/ResourceEvent$Account; - public final fun component13 ()Lcom/datadog/android/rum/model/ResourceEvent$Connectivity; - public final fun component14 ()Lcom/datadog/android/rum/model/ResourceEvent$Display; - public final fun component15 ()Lcom/datadog/android/rum/model/ResourceEvent$Synthetics; - public final fun component16 ()Lcom/datadog/android/rum/model/ResourceEvent$CiTest; - public final fun component17 ()Lcom/datadog/android/rum/model/ResourceEvent$Os; - public final fun component18 ()Lcom/datadog/android/rum/model/ResourceEvent$Device; - public final fun component19 ()Lcom/datadog/android/rum/model/ResourceEvent$Dd; + public final fun component13 ()Lcom/datadog/android/rum/model/ResourceEvent$Tab; + public final fun component14 ()Lcom/datadog/android/rum/model/ResourceEvent$Connectivity; + public final fun component15 ()Lcom/datadog/android/rum/model/ResourceEvent$Display; + public final fun component16 ()Lcom/datadog/android/rum/model/ResourceEvent$Synthetics; + public final fun component17 ()Lcom/datadog/android/rum/model/ResourceEvent$CiTest; + public final fun component18 ()Lcom/datadog/android/rum/model/ResourceEvent$Os; + public final fun component19 ()Lcom/datadog/android/rum/model/ResourceEvent$Device; public final fun component2 ()Lcom/datadog/android/rum/model/ResourceEvent$Application; - public final fun component20 ()Lcom/datadog/android/rum/model/ResourceEvent$Context; - public final fun component21 ()Lcom/datadog/android/rum/model/ResourceEvent$Action; - public final fun component22 ()Lcom/datadog/android/rum/model/ResourceEvent$Container; - public final fun component23 ()Lcom/datadog/android/rum/model/ResourceEvent$Resource; + public final fun component20 ()Lcom/datadog/android/rum/model/ResourceEvent$Dd; + public final fun component21 ()Lcom/datadog/android/rum/model/ResourceEvent$Context; + public final fun component22 ()Lcom/datadog/android/rum/model/ResourceEvent$Stream; + public final fun component23 ()Lcom/datadog/android/rum/model/ResourceEvent$Action; + public final fun component24 ()Lcom/datadog/android/rum/model/ResourceEvent$Container; + public final fun component25 ()Lcom/datadog/android/rum/model/ResourceEvent$Resource; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; @@ -3643,8 +3783,8 @@ public final class com/datadog/android/rum/model/ResourceEvent { public final fun component7 ()Ljava/lang/String; public final fun component8 ()Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSession; public final fun component9 ()Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource; - public final fun copy (JLcom/datadog/android/rum/model/ResourceEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSession;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventView;Lcom/datadog/android/rum/model/ResourceEvent$Usr;Lcom/datadog/android/rum/model/ResourceEvent$Account;Lcom/datadog/android/rum/model/ResourceEvent$Connectivity;Lcom/datadog/android/rum/model/ResourceEvent$Display;Lcom/datadog/android/rum/model/ResourceEvent$Synthetics;Lcom/datadog/android/rum/model/ResourceEvent$CiTest;Lcom/datadog/android/rum/model/ResourceEvent$Os;Lcom/datadog/android/rum/model/ResourceEvent$Device;Lcom/datadog/android/rum/model/ResourceEvent$Dd;Lcom/datadog/android/rum/model/ResourceEvent$Context;Lcom/datadog/android/rum/model/ResourceEvent$Action;Lcom/datadog/android/rum/model/ResourceEvent$Container;Lcom/datadog/android/rum/model/ResourceEvent$Resource;)Lcom/datadog/android/rum/model/ResourceEvent; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ResourceEvent;JLcom/datadog/android/rum/model/ResourceEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSession;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventView;Lcom/datadog/android/rum/model/ResourceEvent$Usr;Lcom/datadog/android/rum/model/ResourceEvent$Account;Lcom/datadog/android/rum/model/ResourceEvent$Connectivity;Lcom/datadog/android/rum/model/ResourceEvent$Display;Lcom/datadog/android/rum/model/ResourceEvent$Synthetics;Lcom/datadog/android/rum/model/ResourceEvent$CiTest;Lcom/datadog/android/rum/model/ResourceEvent$Os;Lcom/datadog/android/rum/model/ResourceEvent$Device;Lcom/datadog/android/rum/model/ResourceEvent$Dd;Lcom/datadog/android/rum/model/ResourceEvent$Context;Lcom/datadog/android/rum/model/ResourceEvent$Action;Lcom/datadog/android/rum/model/ResourceEvent$Container;Lcom/datadog/android/rum/model/ResourceEvent$Resource;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ResourceEvent; + public final fun copy (JLcom/datadog/android/rum/model/ResourceEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSession;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventView;Lcom/datadog/android/rum/model/ResourceEvent$Usr;Lcom/datadog/android/rum/model/ResourceEvent$Account;Lcom/datadog/android/rum/model/ResourceEvent$Tab;Lcom/datadog/android/rum/model/ResourceEvent$Connectivity;Lcom/datadog/android/rum/model/ResourceEvent$Display;Lcom/datadog/android/rum/model/ResourceEvent$Synthetics;Lcom/datadog/android/rum/model/ResourceEvent$CiTest;Lcom/datadog/android/rum/model/ResourceEvent$Os;Lcom/datadog/android/rum/model/ResourceEvent$Device;Lcom/datadog/android/rum/model/ResourceEvent$Dd;Lcom/datadog/android/rum/model/ResourceEvent$Context;Lcom/datadog/android/rum/model/ResourceEvent$Stream;Lcom/datadog/android/rum/model/ResourceEvent$Action;Lcom/datadog/android/rum/model/ResourceEvent$Container;Lcom/datadog/android/rum/model/ResourceEvent$Resource;)Lcom/datadog/android/rum/model/ResourceEvent; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ResourceEvent;JLcom/datadog/android/rum/model/ResourceEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSession;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource;Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventView;Lcom/datadog/android/rum/model/ResourceEvent$Usr;Lcom/datadog/android/rum/model/ResourceEvent$Account;Lcom/datadog/android/rum/model/ResourceEvent$Tab;Lcom/datadog/android/rum/model/ResourceEvent$Connectivity;Lcom/datadog/android/rum/model/ResourceEvent$Display;Lcom/datadog/android/rum/model/ResourceEvent$Synthetics;Lcom/datadog/android/rum/model/ResourceEvent$CiTest;Lcom/datadog/android/rum/model/ResourceEvent$Os;Lcom/datadog/android/rum/model/ResourceEvent$Device;Lcom/datadog/android/rum/model/ResourceEvent$Dd;Lcom/datadog/android/rum/model/ResourceEvent$Context;Lcom/datadog/android/rum/model/ResourceEvent$Stream;Lcom/datadog/android/rum/model/ResourceEvent$Action;Lcom/datadog/android/rum/model/ResourceEvent$Container;Lcom/datadog/android/rum/model/ResourceEvent$Resource;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ResourceEvent; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ResourceEvent; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ResourceEvent; @@ -3667,7 +3807,9 @@ public final class com/datadog/android/rum/model/ResourceEvent { public final fun getService ()Ljava/lang/String; public final fun getSession ()Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSession; public final fun getSource ()Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource; + public final fun getStream ()Lcom/datadog/android/rum/model/ResourceEvent$Stream; public final fun getSynthetics ()Lcom/datadog/android/rum/model/ResourceEvent$Synthetics; + public final fun getTab ()Lcom/datadog/android/rum/model/ResourceEvent$Tab; public final fun getType ()Ljava/lang/String; public final fun getUsr ()Lcom/datadog/android/rum/model/ResourceEvent$Usr; public final fun getVersion ()Ljava/lang/String; @@ -3796,18 +3938,20 @@ public final class com/datadog/android/rum/model/ResourceEvent$Companion { public final class com/datadog/android/rum/model/ResourceEvent$Configuration { public static final field Companion Lcom/datadog/android/rum/model/ResourceEvent$Configuration$Companion; - public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)V - public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)V + public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/Number; public final fun component2 ()Ljava/lang/Number; public final fun component3 ()Ljava/lang/Number; public final fun component4 ()Ljava/lang/Number; - public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)Lcom/datadog/android/rum/model/ResourceEvent$Configuration; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ResourceEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ResourceEvent$Configuration; + public final fun component5 ()Ljava/util/List; + public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)Lcom/datadog/android/rum/model/ResourceEvent$Configuration; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ResourceEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ResourceEvent$Configuration; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ResourceEvent$Configuration; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ResourceEvent$Configuration; public final fun getProfilingSampleRate ()Ljava/lang/Number; + public final fun getSessionReplayExperimentalFeatures ()Ljava/util/List; public final fun getSessionReplaySampleRate ()Ljava/lang/Number; public final fun getSessionSampleRate ()Ljava/lang/Number; public final fun getTraceSampleRate ()Ljava/lang/Number; @@ -4663,6 +4807,7 @@ public final class com/datadog/android/rum/model/ResourceEvent$ResourceEventSess public final class com/datadog/android/rum/model/ResourceEvent$ResourceEventSource : java/lang/Enum { public static final field ANDROID Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource; public static final field BROWSER Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource; + public static final field CPP Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource; public static final field Companion Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource$Companion; public static final field ELECTRON Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource; public static final field FLUTTER Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource; @@ -4671,7 +4816,6 @@ public final class com/datadog/android/rum/model/ResourceEvent$ResourceEventSour public static final field MAUI Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource; public static final field REACT_NATIVE Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource; public static final field ROKU Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource; - public static final field RUM_CPP Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource; public static final field UNITY Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource; public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ResourceEvent$ResourceEventSource; public final fun toJson ()Lcom/google/gson/JsonElement; @@ -4814,6 +4958,26 @@ public final class com/datadog/android/rum/model/ResourceEvent$Status$Companion public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ResourceEvent$Status; } +public final class com/datadog/android/rum/model/ResourceEvent$Stream { + public static final field Companion Lcom/datadog/android/rum/model/ResourceEvent$Stream$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/ResourceEvent$Stream; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ResourceEvent$Stream;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ResourceEvent$Stream; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ResourceEvent$Stream; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ResourceEvent$Stream; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/ResourceEvent$Stream$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ResourceEvent$Stream; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ResourceEvent$Stream; +} + public final class com/datadog/android/rum/model/ResourceEvent$Synthetics { public static final field Companion Lcom/datadog/android/rum/model/ResourceEvent$Synthetics$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;)V @@ -4841,6 +5005,26 @@ public final class com/datadog/android/rum/model/ResourceEvent$Synthetics$Compan public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ResourceEvent$Synthetics; } +public final class com/datadog/android/rum/model/ResourceEvent$Tab { + public static final field Companion Lcom/datadog/android/rum/model/ResourceEvent$Tab$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/ResourceEvent$Tab; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ResourceEvent$Tab;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ResourceEvent$Tab; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ResourceEvent$Tab; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ResourceEvent$Tab; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/ResourceEvent$Tab$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ResourceEvent$Tab; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ResourceEvent$Tab; +} + public final class com/datadog/android/rum/model/ResourceEvent$Usr { public static final field Companion Lcom/datadog/android/rum/model/ResourceEvent$Usr$Companion; public fun ()V @@ -4917,44 +5101,102 @@ public final class com/datadog/android/rum/model/ResourceEvent$Worker$Companion public final class com/datadog/android/rum/model/TimeseriesCpuEvent { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Companion; - public fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;)V - public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; + public fun (JLcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;)V + public synthetic fun (JLcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()J + public final fun component10 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View; + public final fun component11 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr; + public final fun component12 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account; + public final fun component13 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab; + public final fun component14 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity; + public final fun component15 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display; + public final fun component16 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics; + public final fun component17 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest; + public final fun component18 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os; + public final fun component19 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device; public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; - public final fun component3 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; - public final fun component4 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; - public final fun component5 ()J + public final fun component20 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; + public final fun component21 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context; + public final fun component22 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream; + public final fun component23 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/lang/String; - public final fun component8 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; - public final fun copy (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; + public final fun component8 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession; + public final fun component9 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public final fun copy (JLcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent;JLcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; + public final fun getAccount ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account; public final fun getApplication ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; + public final fun getBuildId ()Ljava/lang/String; + public final fun getBuildVersion ()Ljava/lang/String; + public final fun getCiTest ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest; + public final fun getConnectivity ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity; + public final fun getContext ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context; public final fun getDate ()J public final fun getDd ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; + public final fun getDdtags ()Ljava/lang/String; + public final fun getDevice ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device; + public final fun getDisplay ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display; + public final fun getOs ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os; public final fun getService ()Ljava/lang/String; - public final fun getSession ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; + public final fun getSession ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession; public final fun getSource ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public final fun getStream ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream; + public final fun getSynthetics ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics; + public final fun getTab ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab; public final fun getTimeseries ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; public final fun getType ()Ljava/lang/String; + public final fun getUsr ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr; public final fun getVersion ()Ljava/lang/String; + public final fun getView ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Account { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account; + public final fun getAdditionalProperties ()Ljava/util/Map; + public final fun getId ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Account$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Account; +} + public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Application { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application$Companion; - public fun (Ljava/lang/String;)V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; + public final fun getCurrentLocale ()Ljava/lang/String; public final fun getId ()Ljava/lang/String; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; @@ -4966,11 +5208,133 @@ public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Application$ public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Application; } +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Cellular { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular; + public final fun getCarrierName ()Ljava/lang/String; + public final fun getTechnology ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Cellular$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$CiTest { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest; + public final fun getTestExecutionId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$CiTest$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$CiTest; +} + public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Companion { public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent; } +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Configuration { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration$Companion; + public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)V + public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Number; + public final fun component2 ()Ljava/lang/Number; + public final fun component3 ()Ljava/lang/Number; + public final fun component4 ()Ljava/lang/Number; + public final fun component5 ()Ljava/util/List; + public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration; + public final fun getProfilingSampleRate ()Ljava/lang/Number; + public final fun getSessionReplayExperimentalFeatures ()Ljava/util/List; + public final fun getSessionReplaySampleRate ()Ljava/lang/Number; + public final fun getSessionSampleRate ()Ljava/lang/Number; + public final fun getTraceSampleRate ()Ljava/lang/Number; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Configuration$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity$Companion; + public fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status;Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular;)V + public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status;Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType; + public final fun component4 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular; + public final fun copy (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status;Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status;Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity; + public final fun getCellular ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Cellular; + public final fun getEffectiveType ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType; + public final fun getInterfaces ()Ljava/util/List; + public final fun getStatus ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Connectivity; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Context { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context$Companion; + public fun ()V + public fun (Ljava/util/Map;)V + public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/Map; + public final fun copy (Ljava/util/Map;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context; + public final fun getAdditionalProperties ()Ljava/util/Map; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Context$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Context; +} + public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Data { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data$Companion; public fun (Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values;)V @@ -4996,10 +5360,25 @@ public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Data$Compani public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Dd { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd$Companion; public fun ()V + public fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; + public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; + public final fun getBrowserSdkVersion ()Ljava/lang/String; + public final fun getConfiguration ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Configuration; public final fun getFormatVersion ()J + public final fun getSdkName ()Ljava/lang/String; + public final fun getSession ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession; + public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; } public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Dd$Companion { @@ -5007,56 +5386,327 @@ public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Dd$Companion public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Dd; } -public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Session { - public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session$Companion; - public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; - public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$DdSession { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession$Companion; + public fun ()V + public fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Plan;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition;)V + public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Plan;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Plan; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition; + public final fun copy (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Plan;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Plan;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; - public final fun getId ()Ljava/lang/String; - public final fun getType ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession; + public final fun getPlan ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Plan; + public final fun getSessionPrecondition ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Session$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Session; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$DdSession$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DdSession; } -public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Source : java/lang/Enum { - public static final field ANDROID Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; - public static final field BROWSER Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; - public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source$Companion; - public static final field ELECTRON Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; - public static final field FLUTTER Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; - public static final field IOS Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; - public static final field KOTLIN_MULTIPLATFORM Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; - public static final field REACT_NATIVE Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; - public static final field ROKU Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; - public static final field RUM_CPP Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; - public static final field UNITY Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Device { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device$Companion; + public fun ()V + public fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/Number;Ljava/lang/Boolean;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Boolean;)V + public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/Number;Ljava/lang/Boolean;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType; + public final fun component10 ()Ljava/lang/Boolean; + public final fun component11 ()Ljava/lang/Number; + public final fun component12 ()Ljava/lang/Number; + public final fun component13 ()Ljava/lang/Number; + public final fun component14 ()Ljava/lang/Boolean; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/util/List; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/lang/Number; + public final fun copy (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/Number;Ljava/lang/Boolean;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Boolean;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/Number;Ljava/lang/Boolean;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device; + public final fun getArchitecture ()Ljava/lang/String; + public final fun getBatteryLevel ()Ljava/lang/Number; + public final fun getBrand ()Ljava/lang/String; + public final fun getBrightnessLevel ()Ljava/lang/Number; + public final fun getLocale ()Ljava/lang/String; + public final fun getLocales ()Ljava/util/List; + public final fun getLogicalCpuCount ()Ljava/lang/Number; + public final fun getModel ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getPowerSavingMode ()Ljava/lang/Boolean; + public final fun getTimeZone ()Ljava/lang/String; + public final fun getTotalRam ()Ljava/lang/Number; + public final fun getType ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType; + public fun hashCode ()I + public final fun isLowRam ()Ljava/lang/Boolean; public final fun toJson ()Lcom/google/gson/JsonElement; - public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; - public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Source$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Device$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Device; } -public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries { - public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries$Companion; - public fun (Ljava/lang/String;JJLcom/datadog/android/rum/model/TimeseriesCpuEvent$Data;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()J - public final fun component3 ()J +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType : java/lang/Enum { + public static final field BOT Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType$Companion; + public static final field DESKTOP Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType; + public static final field GAMING_CONSOLE Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType; + public static final field MOBILE Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType; + public static final field OTHER Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType; + public static final field TABLET Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType; + public static final field TV Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$DeviceType; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Display { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display$Companion; + public fun ()V + public fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport;)V + public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport; + public final fun copy (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display; + public final fun getViewport ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Display$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Display; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType : java/lang/Enum { + public static final field 2G Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType; + public static final field 3G Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType; + public static final field 4G Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType$Companion; + public static final field SLOW_2G Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$EffectiveType; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Interface : java/lang/Enum { + public static final field BLUETOOTH Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface; + public static final field CELLULAR Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface$Companion; + public static final field ETHERNET Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface; + public static final field MIXED Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface; + public static final field NONE Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface; + public static final field OTHER Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface; + public static final field UNKNOWN Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface; + public static final field WIFI Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface; + public static final field WIMAX Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Interface$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Interface; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Os { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os; + public final fun getBuild ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getVersion ()Ljava/lang/String; + public final fun getVersionMajor ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Os$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Os; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Plan : java/lang/Enum { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Plan$Companion; + public static final field PLAN_1 Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Plan; + public static final field PLAN_2 Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Plan; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Plan; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Plan; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Plan; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Plan$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Plan; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition : java/lang/Enum { + public static final field BACKGROUND_LAUNCH Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition$Companion; + public static final field EXPLICIT_STOP Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition; + public static final field FROM_NON_INTERACTIVE_SESSION Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition; + public static final field INACTIVITY_TIMEOUT Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition; + public static final field MAX_DURATION Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition; + public static final field PREWARM Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition; + public static final field USER_APP_LAUNCH Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$SessionPrecondition; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Source : java/lang/Enum { + public static final field ANDROID Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field BROWSER Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field CPP Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source$Companion; + public static final field ELECTRON Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field FLUTTER Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field IOS Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field KOTLIN_MULTIPLATFORM Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field MAUI Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field REACT_NATIVE Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field ROKU Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final field UNITY Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Source$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Source; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Status : java/lang/Enum { + public static final field CONNECTED Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status$Companion; + public static final field MAYBE Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status; + public static final field NOT_CONNECTED Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Status$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Status; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Stream { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Stream$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Stream; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Boolean; + public final fun component4 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics; + public final fun getAdditionalProperties ()Ljava/util/Map; + public final fun getInjected ()Ljava/lang/Boolean; + public final fun getResultId ()Ljava/lang/String; + public final fun getTestId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Synthetics; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Tab { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Tab$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Tab; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries$Companion; + public fun (Ljava/lang/String;JJLcom/datadog/android/rum/model/TimeseriesCpuEvent$Data;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()J + public final fun component3 ()J public final fun component4 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Data; public final fun copy (Ljava/lang/String;JJLcom/datadog/android/rum/model/TimeseriesCpuEvent$Data;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries;Ljava/lang/String;JJLcom/datadog/android/rum/model/TimeseriesCpuEvent$Data;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; @@ -5079,19 +5729,74 @@ public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries$C public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Timeseries; } -public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Type : java/lang/Enum { - public static final field CI_TEST Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; - public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type$Companion; - public static final field SYNTHETICS Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; - public static final field USER Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession$Companion; + public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType; + public final fun component3 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType;Ljava/lang/Boolean;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession; + public final fun getHasReplay ()Ljava/lang/Boolean; + public final fun getId ()Ljava/lang/String; + public final fun getType ()Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSession; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType : java/lang/Enum { + public static final field CI_TEST Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType$Companion; + public static final field SYNTHETICS Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType; + public static final field USER Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$TimeseriesCpuEventSessionType; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Usr { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr; + public final fun getAdditionalProperties ()Ljava/util/Map; + public final fun getAnonymousId ()Ljava/lang/String; + public final fun getEmail ()Ljava/lang/String; + public final fun getId ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; - public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; - public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; + public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Type$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Type; +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Usr$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Usr; } public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Values { @@ -5114,131 +5819,567 @@ public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Values$Compa public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Values; } +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$View { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View; + public final fun getId ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getReferrer ()Ljava/lang/String; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public final fun setName (Ljava/lang/String;)V + public final fun setReferrer (Ljava/lang/String;)V + public final fun setUrl (Ljava/lang/String;)V + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$View$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$View; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Viewport { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport$Companion; + public fun (Ljava/lang/Number;Ljava/lang/Number;)V + public final fun component1 ()Ljava/lang/Number; + public final fun component2 ()Ljava/lang/Number; + public final fun copy (Ljava/lang/Number;Ljava/lang/Number;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport;Ljava/lang/Number;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport; + public final fun getHeight ()Ljava/lang/Number; + public final fun getWidth ()Ljava/lang/Number; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesCpuEvent$Viewport$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesCpuEvent$Viewport; +} + public final class com/datadog/android/rum/model/TimeseriesMemoryEvent { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Companion; - public fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;)V - public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; + public fun (JLcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;)V + public synthetic fun (JLcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()J + public final fun component10 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View; + public final fun component11 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr; + public final fun component12 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account; + public final fun component13 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab; + public final fun component14 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity; + public final fun component15 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display; + public final fun component16 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics; + public final fun component17 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest; + public final fun component18 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os; + public final fun component19 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device; public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; - public final fun component3 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; - public final fun component4 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; - public final fun component5 ()J + public final fun component20 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; + public final fun component21 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context; + public final fun component22 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream; + public final fun component23 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/lang/String; - public final fun component8 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; - public final fun copy (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source;JLjava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; + public final fun component8 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession; + public final fun component9 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public final fun copy (JLcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent;JLcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; + public final fun getAccount ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account; public final fun getApplication ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; + public final fun getBuildId ()Ljava/lang/String; + public final fun getBuildVersion ()Ljava/lang/String; + public final fun getCiTest ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest; + public final fun getConnectivity ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity; + public final fun getContext ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context; public final fun getDate ()J public final fun getDd ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; + public final fun getDdtags ()Ljava/lang/String; + public final fun getDevice ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device; + public final fun getDisplay ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display; + public final fun getOs ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os; public final fun getService ()Ljava/lang/String; - public final fun getSession ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; + public final fun getSession ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession; public final fun getSource ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public final fun getStream ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream; + public final fun getSynthetics ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics; + public final fun getTab ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab; public final fun getTimeseries ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; public final fun getType ()Ljava/lang/String; + public final fun getUsr ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr; public final fun getVersion ()Ljava/lang/String; + public final fun getView ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Account { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account; + public final fun getAdditionalProperties ()Ljava/util/Map; + public final fun getId ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Account$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Account; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Application { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; + public final fun getCurrentLocale ()Ljava/lang/String; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Application$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular; + public final fun getCarrierName ()Ljava/lang/String; + public final fun getTechnology ()Ljava/lang/String; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Application { - public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; - public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; - public final fun getId ()Ljava/lang/String; - public fun hashCode ()I - public final fun toJson ()Lcom/google/gson/JsonElement; - public fun toString ()Ljava/lang/String; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest; + public final fun getTestExecutionId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$CiTest; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration$Companion; + public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)V + public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Number; + public final fun component2 ()Ljava/lang/Number; + public final fun component3 ()Ljava/lang/Number; + public final fun component4 ()Ljava/lang/Number; + public final fun component5 ()Ljava/util/List; + public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration; + public final fun getProfilingSampleRate ()Ljava/lang/Number; + public final fun getSessionReplayExperimentalFeatures ()Ljava/util/List; + public final fun getSessionReplaySampleRate ()Ljava/lang/Number; + public final fun getSessionSampleRate ()Ljava/lang/Number; + public final fun getTraceSampleRate ()Ljava/lang/Number; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity$Companion; + public fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status;Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular;)V + public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status;Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType; + public final fun component4 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular; + public final fun copy (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status;Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status;Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity; + public final fun getCellular ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Cellular; + public final fun getEffectiveType ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType; + public final fun getInterfaces ()Ljava/util/List; + public final fun getStatus ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Connectivity; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Context { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context$Companion; + public fun ()V + public fun (Ljava/util/Map;)V + public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/Map; + public final fun copy (Ljava/util/Map;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context; + public final fun getAdditionalProperties ()Ljava/util/Map; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Context$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Context; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Data { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data$Companion; + public fun (Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values; + public final fun copy (Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data;Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; + public final fun getTimestamps ()Ljava/util/List; + public final fun getValues ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Data$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Dd { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd$Companion; + public fun ()V + public fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; + public final fun getBrowserSdkVersion ()Ljava/lang/String; + public final fun getConfiguration ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Configuration; + public final fun getFormatVersion ()J + public final fun getSdkName ()Ljava/lang/String; + public final fun getSession ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Dd$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession$Companion; + public fun ()V + public fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Plan;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition;)V + public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Plan;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Plan; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition; + public final fun copy (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Plan;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Plan;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession; + public final fun getPlan ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Plan; + public final fun getSessionPrecondition ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DdSession; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Device { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device$Companion; + public fun ()V + public fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/Number;Ljava/lang/Boolean;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Boolean;)V + public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/Number;Ljava/lang/Boolean;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType; + public final fun component10 ()Ljava/lang/Boolean; + public final fun component11 ()Ljava/lang/Number; + public final fun component12 ()Ljava/lang/Number; + public final fun component13 ()Ljava/lang/Number; + public final fun component14 ()Ljava/lang/Boolean; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/util/List; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/lang/Number; + public final fun copy (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/Number;Ljava/lang/Boolean;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Boolean;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/Number;Ljava/lang/Boolean;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device; + public final fun getArchitecture ()Ljava/lang/String; + public final fun getBatteryLevel ()Ljava/lang/Number; + public final fun getBrand ()Ljava/lang/String; + public final fun getBrightnessLevel ()Ljava/lang/Number; + public final fun getLocale ()Ljava/lang/String; + public final fun getLocales ()Ljava/util/List; + public final fun getLogicalCpuCount ()Ljava/lang/Number; + public final fun getModel ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getPowerSavingMode ()Ljava/lang/Boolean; + public final fun getTimeZone ()Ljava/lang/String; + public final fun getTotalRam ()Ljava/lang/Number; + public final fun getType ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType; + public fun hashCode ()I + public final fun isLowRam ()Ljava/lang/Boolean; + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Device$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Device; } -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Application$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Application; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType : java/lang/Enum { + public static final field BOT Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType$Companion; + public static final field DESKTOP Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType; + public static final field GAMING_CONSOLE Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType; + public static final field MOBILE Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType; + public static final field OTHER Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType; + public static final field TABLET Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType; + public static final field TV Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType; } -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$DeviceType; } -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Data { - public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data$Companion; - public fun (Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values; - public final fun copy (Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data;Ljava/util/List;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Display { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display$Companion; + public fun ()V + public fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport;)V + public synthetic fun (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport; + public final fun copy (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; - public final fun getTimestamps ()Ljava/util/List; - public final fun getValues ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display; + public final fun getViewport ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Data$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Display$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Display; } -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Dd { - public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd$Companion; - public fun ()V - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; - public final fun getFormatVersion ()J +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType : java/lang/Enum { + public static final field 2G Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType; + public static final field 3G Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType; + public static final field 4G Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType$Companion; + public static final field SLOW_2G Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType; public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType; } -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Dd$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Dd; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$EffectiveType; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Interface : java/lang/Enum { + public static final field BLUETOOTH Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface; + public static final field CELLULAR Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface$Companion; + public static final field ETHERNET Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface; + public static final field MIXED Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface; + public static final field NONE Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface; + public static final field OTHER Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface; + public static final field UNKNOWN Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface; + public static final field WIFI Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface; + public static final field WIMAX Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Interface$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Interface; } -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Session { - public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session$Companion; - public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type;)V +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Os { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; - public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os; public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; - public final fun getId ()Ljava/lang/String; - public final fun getType ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os; + public final fun getBuild ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getVersion ()Ljava/lang/String; + public final fun getVersionMajor ()Ljava/lang/String; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Session$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Session; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Os$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Os; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Plan : java/lang/Enum { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Plan$Companion; + public static final field PLAN_1 Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Plan; + public static final field PLAN_2 Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Plan; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Plan; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Plan; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Plan; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Plan$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Plan; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition : java/lang/Enum { + public static final field BACKGROUND_LAUNCH Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition$Companion; + public static final field EXPLICIT_STOP Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition; + public static final field FROM_NON_INTERACTIVE_SESSION Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition; + public static final field INACTIVITY_TIMEOUT Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition; + public static final field MAX_DURATION Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition; + public static final field PREWARM Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition; + public static final field USER_APP_LAUNCH Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$SessionPrecondition; } public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Source : java/lang/Enum { public static final field ANDROID Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; public static final field BROWSER Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public static final field CPP Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source$Companion; public static final field ELECTRON Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; public static final field FLUTTER Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; public static final field IOS Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; public static final field KOTLIN_MULTIPLATFORM Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; + public static final field MAUI Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; public static final field REACT_NATIVE Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; public static final field ROKU Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; - public static final field RUM_CPP Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; public static final field UNITY Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; public final fun toJson ()Lcom/google/gson/JsonElement; @@ -5250,6 +6391,88 @@ public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Source$Co public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Source; } +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Status : java/lang/Enum { + public static final field CONNECTED Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status$Companion; + public static final field MAYBE Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status; + public static final field NOT_CONNECTED Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Status$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Status; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Stream { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Stream$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Stream; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Boolean; + public final fun component4 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics; + public final fun getAdditionalProperties ()Ljava/util/Map; + public final fun getInjected ()Ljava/lang/Boolean; + public final fun getResultId ()Ljava/lang/String; + public final fun getTestId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Synthetics; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Tab { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Tab$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Tab; +} + public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries { public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries$Companion; public fun (Ljava/lang/String;JJLcom/datadog/android/rum/model/TimeseriesMemoryEvent$Data;)V @@ -5278,19 +6501,74 @@ public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Timeserie public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Timeseries; } -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Type : java/lang/Enum { - public static final field CI_TEST Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; - public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type$Companion; - public static final field SYNTHETICS Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; - public static final field USER Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession$Companion; + public fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType; + public final fun component3 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType;Ljava/lang/Boolean;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession;Ljava/lang/String;Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession; + public final fun getHasReplay ()Ljava/lang/Boolean; + public final fun getId ()Ljava/lang/String; + public final fun getType ()Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSession; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType : java/lang/Enum { + public static final field CI_TEST Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType; + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType$Companion; + public static final field SYNTHETICS Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType; + public static final field USER Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType; + public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$TimeseriesMemoryEventSessionType; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Usr { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr; + public final fun getAdditionalProperties ()Ljava/util/Map; + public final fun getAnonymousId ()Ljava/lang/String; + public final fun getEmail ()Ljava/lang/String; + public final fun getId ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; - public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; - public static fun values ()[Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; + public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Type$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Type; +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Usr$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Usr; } public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Values { @@ -5315,26 +6593,80 @@ public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Values$Co public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Values; } +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$View { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View; + public final fun getId ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getReferrer ()Ljava/lang/String; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public final fun setName (Ljava/lang/String;)V + public final fun setReferrer (Ljava/lang/String;)V + public final fun setUrl (Ljava/lang/String;)V + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$View$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$View; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport { + public static final field Companion Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport$Companion; + public fun (Ljava/lang/Number;Ljava/lang/Number;)V + public final fun component1 ()Ljava/lang/Number; + public final fun component2 ()Ljava/lang/Number; + public final fun copy (Ljava/lang/Number;Ljava/lang/Number;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport;Ljava/lang/Number;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport; + public final fun getHeight ()Ljava/lang/Number; + public final fun getWidth ()Ljava/lang/Number; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/TimeseriesMemoryEvent$Viewport; +} + public final class com/datadog/android/rum/model/ViewEvent { public static final field Companion Lcom/datadog/android/rum/model/ViewEvent$Companion; - public fun (JLcom/datadog/android/rum/model/ViewEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource;Lcom/datadog/android/rum/model/ViewEvent$ViewEventView;Lcom/datadog/android/rum/model/ViewEvent$Usr;Lcom/datadog/android/rum/model/ViewEvent$Account;Lcom/datadog/android/rum/model/ViewEvent$Connectivity;Lcom/datadog/android/rum/model/ViewEvent$Display;Lcom/datadog/android/rum/model/ViewEvent$Synthetics;Lcom/datadog/android/rum/model/ViewEvent$CiTest;Lcom/datadog/android/rum/model/ViewEvent$Os;Lcom/datadog/android/rum/model/ViewEvent$Device;Lcom/datadog/android/rum/model/ViewEvent$Dd;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Container;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Privacy;)V - public synthetic fun (JLcom/datadog/android/rum/model/ViewEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource;Lcom/datadog/android/rum/model/ViewEvent$ViewEventView;Lcom/datadog/android/rum/model/ViewEvent$Usr;Lcom/datadog/android/rum/model/ViewEvent$Account;Lcom/datadog/android/rum/model/ViewEvent$Connectivity;Lcom/datadog/android/rum/model/ViewEvent$Display;Lcom/datadog/android/rum/model/ViewEvent$Synthetics;Lcom/datadog/android/rum/model/ViewEvent$CiTest;Lcom/datadog/android/rum/model/ViewEvent$Os;Lcom/datadog/android/rum/model/ViewEvent$Device;Lcom/datadog/android/rum/model/ViewEvent$Dd;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Container;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Privacy;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (JLcom/datadog/android/rum/model/ViewEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource;Lcom/datadog/android/rum/model/ViewEvent$ViewEventView;Lcom/datadog/android/rum/model/ViewEvent$Usr;Lcom/datadog/android/rum/model/ViewEvent$Account;Lcom/datadog/android/rum/model/ViewEvent$Tab;Lcom/datadog/android/rum/model/ViewEvent$Connectivity;Lcom/datadog/android/rum/model/ViewEvent$Display;Lcom/datadog/android/rum/model/ViewEvent$Synthetics;Lcom/datadog/android/rum/model/ViewEvent$CiTest;Lcom/datadog/android/rum/model/ViewEvent$Os;Lcom/datadog/android/rum/model/ViewEvent$Device;Lcom/datadog/android/rum/model/ViewEvent$Dd;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Stream;Lcom/datadog/android/rum/model/ViewEvent$Container;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Privacy;)V + public synthetic fun (JLcom/datadog/android/rum/model/ViewEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource;Lcom/datadog/android/rum/model/ViewEvent$ViewEventView;Lcom/datadog/android/rum/model/ViewEvent$Usr;Lcom/datadog/android/rum/model/ViewEvent$Account;Lcom/datadog/android/rum/model/ViewEvent$Tab;Lcom/datadog/android/rum/model/ViewEvent$Connectivity;Lcom/datadog/android/rum/model/ViewEvent$Display;Lcom/datadog/android/rum/model/ViewEvent$Synthetics;Lcom/datadog/android/rum/model/ViewEvent$CiTest;Lcom/datadog/android/rum/model/ViewEvent$Os;Lcom/datadog/android/rum/model/ViewEvent$Device;Lcom/datadog/android/rum/model/ViewEvent$Dd;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Stream;Lcom/datadog/android/rum/model/ViewEvent$Container;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Privacy;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()J public final fun component10 ()Lcom/datadog/android/rum/model/ViewEvent$ViewEventView; public final fun component11 ()Lcom/datadog/android/rum/model/ViewEvent$Usr; public final fun component12 ()Lcom/datadog/android/rum/model/ViewEvent$Account; - public final fun component13 ()Lcom/datadog/android/rum/model/ViewEvent$Connectivity; - public final fun component14 ()Lcom/datadog/android/rum/model/ViewEvent$Display; - public final fun component15 ()Lcom/datadog/android/rum/model/ViewEvent$Synthetics; - public final fun component16 ()Lcom/datadog/android/rum/model/ViewEvent$CiTest; - public final fun component17 ()Lcom/datadog/android/rum/model/ViewEvent$Os; - public final fun component18 ()Lcom/datadog/android/rum/model/ViewEvent$Device; - public final fun component19 ()Lcom/datadog/android/rum/model/ViewEvent$Dd; + public final fun component13 ()Lcom/datadog/android/rum/model/ViewEvent$Tab; + public final fun component14 ()Lcom/datadog/android/rum/model/ViewEvent$Connectivity; + public final fun component15 ()Lcom/datadog/android/rum/model/ViewEvent$Display; + public final fun component16 ()Lcom/datadog/android/rum/model/ViewEvent$Synthetics; + public final fun component17 ()Lcom/datadog/android/rum/model/ViewEvent$CiTest; + public final fun component18 ()Lcom/datadog/android/rum/model/ViewEvent$Os; + public final fun component19 ()Lcom/datadog/android/rum/model/ViewEvent$Device; public final fun component2 ()Lcom/datadog/android/rum/model/ViewEvent$Application; - public final fun component20 ()Lcom/datadog/android/rum/model/ViewEvent$Context; - public final fun component21 ()Lcom/datadog/android/rum/model/ViewEvent$Container; - public final fun component22 ()Lcom/datadog/android/rum/model/ViewEvent$Context; - public final fun component23 ()Lcom/datadog/android/rum/model/ViewEvent$Privacy; + public final fun component20 ()Lcom/datadog/android/rum/model/ViewEvent$Dd; + public final fun component21 ()Lcom/datadog/android/rum/model/ViewEvent$Context; + public final fun component22 ()Lcom/datadog/android/rum/model/ViewEvent$Stream; + public final fun component23 ()Lcom/datadog/android/rum/model/ViewEvent$Container; + public final fun component24 ()Lcom/datadog/android/rum/model/ViewEvent$Context; + public final fun component25 ()Lcom/datadog/android/rum/model/ViewEvent$Privacy; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; @@ -5342,8 +6674,8 @@ public final class com/datadog/android/rum/model/ViewEvent { public final fun component7 ()Ljava/lang/String; public final fun component8 ()Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession; public final fun component9 ()Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource; - public final fun copy (JLcom/datadog/android/rum/model/ViewEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource;Lcom/datadog/android/rum/model/ViewEvent$ViewEventView;Lcom/datadog/android/rum/model/ViewEvent$Usr;Lcom/datadog/android/rum/model/ViewEvent$Account;Lcom/datadog/android/rum/model/ViewEvent$Connectivity;Lcom/datadog/android/rum/model/ViewEvent$Display;Lcom/datadog/android/rum/model/ViewEvent$Synthetics;Lcom/datadog/android/rum/model/ViewEvent$CiTest;Lcom/datadog/android/rum/model/ViewEvent$Os;Lcom/datadog/android/rum/model/ViewEvent$Device;Lcom/datadog/android/rum/model/ViewEvent$Dd;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Container;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Privacy;)Lcom/datadog/android/rum/model/ViewEvent; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ViewEvent;JLcom/datadog/android/rum/model/ViewEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource;Lcom/datadog/android/rum/model/ViewEvent$ViewEventView;Lcom/datadog/android/rum/model/ViewEvent$Usr;Lcom/datadog/android/rum/model/ViewEvent$Account;Lcom/datadog/android/rum/model/ViewEvent$Connectivity;Lcom/datadog/android/rum/model/ViewEvent$Display;Lcom/datadog/android/rum/model/ViewEvent$Synthetics;Lcom/datadog/android/rum/model/ViewEvent$CiTest;Lcom/datadog/android/rum/model/ViewEvent$Os;Lcom/datadog/android/rum/model/ViewEvent$Device;Lcom/datadog/android/rum/model/ViewEvent$Dd;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Container;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Privacy;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ViewEvent; + public final fun copy (JLcom/datadog/android/rum/model/ViewEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource;Lcom/datadog/android/rum/model/ViewEvent$ViewEventView;Lcom/datadog/android/rum/model/ViewEvent$Usr;Lcom/datadog/android/rum/model/ViewEvent$Account;Lcom/datadog/android/rum/model/ViewEvent$Tab;Lcom/datadog/android/rum/model/ViewEvent$Connectivity;Lcom/datadog/android/rum/model/ViewEvent$Display;Lcom/datadog/android/rum/model/ViewEvent$Synthetics;Lcom/datadog/android/rum/model/ViewEvent$CiTest;Lcom/datadog/android/rum/model/ViewEvent$Os;Lcom/datadog/android/rum/model/ViewEvent$Device;Lcom/datadog/android/rum/model/ViewEvent$Dd;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Stream;Lcom/datadog/android/rum/model/ViewEvent$Container;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Privacy;)Lcom/datadog/android/rum/model/ViewEvent; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ViewEvent;JLcom/datadog/android/rum/model/ViewEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession;Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource;Lcom/datadog/android/rum/model/ViewEvent$ViewEventView;Lcom/datadog/android/rum/model/ViewEvent$Usr;Lcom/datadog/android/rum/model/ViewEvent$Account;Lcom/datadog/android/rum/model/ViewEvent$Tab;Lcom/datadog/android/rum/model/ViewEvent$Connectivity;Lcom/datadog/android/rum/model/ViewEvent$Display;Lcom/datadog/android/rum/model/ViewEvent$Synthetics;Lcom/datadog/android/rum/model/ViewEvent$CiTest;Lcom/datadog/android/rum/model/ViewEvent$Os;Lcom/datadog/android/rum/model/ViewEvent$Device;Lcom/datadog/android/rum/model/ViewEvent$Dd;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Stream;Lcom/datadog/android/rum/model/ViewEvent$Container;Lcom/datadog/android/rum/model/ViewEvent$Context;Lcom/datadog/android/rum/model/ViewEvent$Privacy;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ViewEvent; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ViewEvent; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ViewEvent; @@ -5366,7 +6698,9 @@ public final class com/datadog/android/rum/model/ViewEvent { public final fun getService ()Ljava/lang/String; public final fun getSession ()Lcom/datadog/android/rum/model/ViewEvent$ViewEventSession; public final fun getSource ()Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource; + public final fun getStream ()Lcom/datadog/android/rum/model/ViewEvent$Stream; public final fun getSynthetics ()Lcom/datadog/android/rum/model/ViewEvent$Synthetics; + public final fun getTab ()Lcom/datadog/android/rum/model/ViewEvent$Tab; public final fun getType ()Ljava/lang/String; public final fun getUsr ()Lcom/datadog/android/rum/model/ViewEvent$Usr; public final fun getVersion ()Ljava/lang/String; @@ -5559,19 +6893,21 @@ public final class com/datadog/android/rum/model/ViewEvent$Companion { public final class com/datadog/android/rum/model/ViewEvent$Configuration { public static final field Companion Lcom/datadog/android/rum/model/ViewEvent$Configuration$Companion; - public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/Number; public final fun component2 ()Ljava/lang/Number; public final fun component3 ()Ljava/lang/Number; public final fun component4 ()Ljava/lang/Number; - public final fun component5 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Boolean;)Lcom/datadog/android/rum/model/ViewEvent$Configuration; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ViewEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ViewEvent$Configuration; + public final fun component5 ()Ljava/util/List; + public final fun component6 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;Ljava/lang/Boolean;)Lcom/datadog/android/rum/model/ViewEvent$Configuration; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ViewEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ViewEvent$Configuration; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ViewEvent$Configuration; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ViewEvent$Configuration; public final fun getProfilingSampleRate ()Ljava/lang/Number; + public final fun getSessionReplayExperimentalFeatures ()Ljava/util/List; public final fun getSessionReplaySampleRate ()Ljava/lang/Number; public final fun getSessionSampleRate ()Ljava/lang/Number; public final fun getStartSessionReplayRecordingManually ()Ljava/lang/Boolean; @@ -6638,6 +7974,26 @@ public final class com/datadog/android/rum/model/ViewEvent$State$Companion { public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ViewEvent$State; } +public final class com/datadog/android/rum/model/ViewEvent$Stream { + public static final field Companion Lcom/datadog/android/rum/model/ViewEvent$Stream$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/ViewEvent$Stream; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ViewEvent$Stream;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ViewEvent$Stream; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ViewEvent$Stream; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ViewEvent$Stream; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/ViewEvent$Stream$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ViewEvent$Stream; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ViewEvent$Stream; +} + public final class com/datadog/android/rum/model/ViewEvent$Synthetics { public static final field Companion Lcom/datadog/android/rum/model/ViewEvent$Synthetics$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;)V @@ -6665,6 +8021,26 @@ public final class com/datadog/android/rum/model/ViewEvent$Synthetics$Companion public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ViewEvent$Synthetics; } +public final class com/datadog/android/rum/model/ViewEvent$Tab { + public static final field Companion Lcom/datadog/android/rum/model/ViewEvent$Tab$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/ViewEvent$Tab; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/ViewEvent$Tab;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/ViewEvent$Tab; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ViewEvent$Tab; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ViewEvent$Tab; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/ViewEvent$Tab$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ViewEvent$Tab; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/ViewEvent$Tab; +} + public final class com/datadog/android/rum/model/ViewEvent$Usr { public static final field Companion Lcom/datadog/android/rum/model/ViewEvent$Usr$Companion; public fun ()V @@ -6742,6 +8118,7 @@ public final class com/datadog/android/rum/model/ViewEvent$ViewEventSessionType$ public final class com/datadog/android/rum/model/ViewEvent$ViewEventSource : java/lang/Enum { public static final field ANDROID Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource; public static final field BROWSER Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource; + public static final field CPP Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource; public static final field Companion Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource$Companion; public static final field ELECTRON Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource; public static final field FLUTTER Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource; @@ -6750,7 +8127,6 @@ public final class com/datadog/android/rum/model/ViewEvent$ViewEventSource : jav public static final field MAUI Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource; public static final field REACT_NATIVE Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource; public static final field ROKU Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource; - public static final field RUM_CPP Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource; public static final field UNITY Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource; public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ViewEvent$ViewEventSource; public final fun toJson ()Lcom/google/gson/JsonElement; @@ -6910,23 +8286,25 @@ public final class com/datadog/android/rum/model/ViewEvent$Viewport$Companion { public final class com/datadog/android/rum/model/VitalAppLaunchEvent { public static final field Companion Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Companion; - public fun (JLcom/datadog/android/rum/model/VitalAppLaunchEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSession;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventView;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Usr;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Account;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Connectivity;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Display;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$CiTest;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Os;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Device;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Dd;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Context;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Container;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Vital;)V - public synthetic fun (JLcom/datadog/android/rum/model/VitalAppLaunchEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSession;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventView;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Usr;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Account;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Connectivity;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Display;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$CiTest;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Os;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Device;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Dd;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Context;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Container;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Vital;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (JLcom/datadog/android/rum/model/VitalAppLaunchEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSession;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventView;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Usr;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Account;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Connectivity;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Display;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$CiTest;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Os;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Device;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Dd;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Context;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Container;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Vital;)V + public synthetic fun (JLcom/datadog/android/rum/model/VitalAppLaunchEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSession;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventView;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Usr;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Account;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Connectivity;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Display;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$CiTest;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Os;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Device;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Dd;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Context;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Container;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Vital;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()J public final fun component10 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventView; public final fun component11 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Usr; public final fun component12 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Account; - public final fun component13 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Connectivity; - public final fun component14 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Display; - public final fun component15 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics; - public final fun component16 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$CiTest; - public final fun component17 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Os; - public final fun component18 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Device; - public final fun component19 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Dd; + public final fun component13 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab; + public final fun component14 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Connectivity; + public final fun component15 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Display; + public final fun component16 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics; + public final fun component17 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$CiTest; + public final fun component18 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Os; + public final fun component19 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Device; public final fun component2 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Application; - public final fun component20 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Context; - public final fun component21 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Container; - public final fun component22 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Vital; + public final fun component20 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Dd; + public final fun component21 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Context; + public final fun component22 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream; + public final fun component23 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Container; + public final fun component24 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Vital; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; @@ -6934,8 +8312,8 @@ public final class com/datadog/android/rum/model/VitalAppLaunchEvent { public final fun component7 ()Ljava/lang/String; public final fun component8 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSession; public final fun component9 ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource; - public final fun copy (JLcom/datadog/android/rum/model/VitalAppLaunchEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSession;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventView;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Usr;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Account;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Connectivity;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Display;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$CiTest;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Os;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Device;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Dd;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Context;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Container;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Vital;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/VitalAppLaunchEvent;JLcom/datadog/android/rum/model/VitalAppLaunchEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSession;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventView;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Usr;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Account;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Connectivity;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Display;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$CiTest;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Os;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Device;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Dd;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Context;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Container;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Vital;ILjava/lang/Object;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent; + public final fun copy (JLcom/datadog/android/rum/model/VitalAppLaunchEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSession;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventView;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Usr;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Account;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Connectivity;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Display;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$CiTest;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Os;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Device;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Dd;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Context;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Container;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Vital;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/VitalAppLaunchEvent;JLcom/datadog/android/rum/model/VitalAppLaunchEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSession;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventView;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Usr;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Account;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Connectivity;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Display;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$CiTest;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Os;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Device;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Dd;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Context;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Container;Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Vital;ILjava/lang/Object;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent; @@ -6956,7 +8334,9 @@ public final class com/datadog/android/rum/model/VitalAppLaunchEvent { public final fun getService ()Ljava/lang/String; public final fun getSession ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSession; public final fun getSource ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource; + public final fun getStream ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream; public final fun getSynthetics ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics; + public final fun getTab ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab; public final fun getType ()Ljava/lang/String; public final fun getUsr ()Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Usr; public final fun getVersion ()Ljava/lang/String; @@ -7080,18 +8460,20 @@ public final class com/datadog/android/rum/model/VitalAppLaunchEvent$Companion { public final class com/datadog/android/rum/model/VitalAppLaunchEvent$Configuration { public static final field Companion Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Configuration$Companion; - public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)V - public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)V + public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/Number; public final fun component2 ()Ljava/lang/Number; public final fun component3 ()Ljava/lang/Number; public final fun component4 ()Ljava/lang/Number; - public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Configuration; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Configuration; + public final fun component5 ()Ljava/util/List; + public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Configuration; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Configuration; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Configuration; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Configuration; public final fun getProfilingSampleRate ()Ljava/lang/Number; + public final fun getSessionReplayExperimentalFeatures ()Ljava/util/List; public final fun getSessionReplaySampleRate ()Ljava/lang/Number; public final fun getSessionSampleRate ()Ljava/lang/Number; public final fun getTraceSampleRate ()Ljava/lang/Number; @@ -7526,6 +8908,26 @@ public final class com/datadog/android/rum/model/VitalAppLaunchEvent$StartupType public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$StartupType; } +public final class com/datadog/android/rum/model/VitalAppLaunchEvent$Stream { + public static final field Companion Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/VitalAppLaunchEvent$Stream$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Stream; +} + public final class com/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics { public static final field Companion Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;)V @@ -7553,6 +8955,26 @@ public final class com/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics$ public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Synthetics; } +public final class com/datadog/android/rum/model/VitalAppLaunchEvent$Tab { + public static final field Companion Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/VitalAppLaunchEvent$Tab$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Tab; +} + public final class com/datadog/android/rum/model/VitalAppLaunchEvent$Usr { public static final field Companion Lcom/datadog/android/rum/model/VitalAppLaunchEvent$Usr$Companion; public fun ()V @@ -7684,6 +9106,7 @@ public final class com/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLau public final class com/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource : java/lang/Enum { public static final field ANDROID Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource; public static final field BROWSER Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource; + public static final field CPP Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource; public static final field Companion Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource$Companion; public static final field ELECTRON Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource; public static final field FLUTTER Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource; @@ -7692,7 +9115,6 @@ public final class com/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLau public static final field MAUI Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource; public static final field REACT_NATIVE Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource; public static final field ROKU Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource; - public static final field RUM_CPP Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource; public static final field UNITY Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource; public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLaunchEventSource; public final fun toJson ()Lcom/google/gson/JsonElement; @@ -7736,23 +9158,25 @@ public final class com/datadog/android/rum/model/VitalAppLaunchEvent$VitalAppLau public final class com/datadog/android/rum/model/VitalOperationStepEvent { public static final field Companion Lcom/datadog/android/rum/model/VitalOperationStepEvent$Companion; - public fun (JLcom/datadog/android/rum/model/VitalOperationStepEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSession;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventView;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Usr;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Account;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Connectivity;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Display;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Synthetics;Lcom/datadog/android/rum/model/VitalOperationStepEvent$CiTest;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Os;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Device;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Dd;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Context;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Container;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Vital;)V - public synthetic fun (JLcom/datadog/android/rum/model/VitalOperationStepEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSession;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventView;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Usr;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Account;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Connectivity;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Display;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Synthetics;Lcom/datadog/android/rum/model/VitalOperationStepEvent$CiTest;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Os;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Device;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Dd;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Context;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Container;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Vital;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (JLcom/datadog/android/rum/model/VitalOperationStepEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSession;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventView;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Usr;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Account;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Connectivity;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Display;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Synthetics;Lcom/datadog/android/rum/model/VitalOperationStepEvent$CiTest;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Os;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Device;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Dd;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Context;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Container;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Vital;)V + public synthetic fun (JLcom/datadog/android/rum/model/VitalOperationStepEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSession;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventView;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Usr;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Account;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Connectivity;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Display;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Synthetics;Lcom/datadog/android/rum/model/VitalOperationStepEvent$CiTest;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Os;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Device;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Dd;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Context;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Container;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Vital;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()J public final fun component10 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventView; public final fun component11 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Usr; public final fun component12 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Account; - public final fun component13 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Connectivity; - public final fun component14 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Display; - public final fun component15 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Synthetics; - public final fun component16 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$CiTest; - public final fun component17 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Os; - public final fun component18 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Device; - public final fun component19 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Dd; + public final fun component13 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab; + public final fun component14 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Connectivity; + public final fun component15 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Display; + public final fun component16 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Synthetics; + public final fun component17 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$CiTest; + public final fun component18 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Os; + public final fun component19 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Device; public final fun component2 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Application; - public final fun component20 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Context; - public final fun component21 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Container; - public final fun component22 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Vital; + public final fun component20 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Dd; + public final fun component21 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Context; + public final fun component22 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream; + public final fun component23 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Container; + public final fun component24 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Vital; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; @@ -7760,8 +9184,8 @@ public final class com/datadog/android/rum/model/VitalOperationStepEvent { public final fun component7 ()Ljava/lang/String; public final fun component8 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSession; public final fun component9 ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource; - public final fun copy (JLcom/datadog/android/rum/model/VitalOperationStepEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSession;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventView;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Usr;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Account;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Connectivity;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Display;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Synthetics;Lcom/datadog/android/rum/model/VitalOperationStepEvent$CiTest;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Os;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Device;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Dd;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Context;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Container;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Vital;)Lcom/datadog/android/rum/model/VitalOperationStepEvent; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/VitalOperationStepEvent;JLcom/datadog/android/rum/model/VitalOperationStepEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSession;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventView;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Usr;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Account;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Connectivity;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Display;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Synthetics;Lcom/datadog/android/rum/model/VitalOperationStepEvent$CiTest;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Os;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Device;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Dd;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Context;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Container;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Vital;ILjava/lang/Object;)Lcom/datadog/android/rum/model/VitalOperationStepEvent; + public final fun copy (JLcom/datadog/android/rum/model/VitalOperationStepEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSession;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventView;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Usr;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Account;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Connectivity;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Display;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Synthetics;Lcom/datadog/android/rum/model/VitalOperationStepEvent$CiTest;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Os;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Device;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Dd;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Context;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Container;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Vital;)Lcom/datadog/android/rum/model/VitalOperationStepEvent; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/VitalOperationStepEvent;JLcom/datadog/android/rum/model/VitalOperationStepEvent$Application;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSession;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource;Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventView;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Usr;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Account;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Connectivity;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Display;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Synthetics;Lcom/datadog/android/rum/model/VitalOperationStepEvent$CiTest;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Os;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Device;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Dd;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Context;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Container;Lcom/datadog/android/rum/model/VitalOperationStepEvent$Vital;ILjava/lang/Object;)Lcom/datadog/android/rum/model/VitalOperationStepEvent; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalOperationStepEvent; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalOperationStepEvent; @@ -7782,7 +9206,9 @@ public final class com/datadog/android/rum/model/VitalOperationStepEvent { public final fun getService ()Ljava/lang/String; public final fun getSession ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSession; public final fun getSource ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource; + public final fun getStream ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream; public final fun getSynthetics ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Synthetics; + public final fun getTab ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab; public final fun getType ()Ljava/lang/String; public final fun getUsr ()Lcom/datadog/android/rum/model/VitalOperationStepEvent$Usr; public final fun getVersion ()Ljava/lang/String; @@ -7892,18 +9318,20 @@ public final class com/datadog/android/rum/model/VitalOperationStepEvent$Compani public final class com/datadog/android/rum/model/VitalOperationStepEvent$Configuration { public static final field Companion Lcom/datadog/android/rum/model/VitalOperationStepEvent$Configuration$Companion; - public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)V - public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)V + public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/Number; public final fun component2 ()Ljava/lang/Number; public final fun component3 ()Ljava/lang/Number; public final fun component4 ()Ljava/lang/Number; - public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Configuration; - public static synthetic fun copy$default (Lcom/datadog/android/rum/model/VitalOperationStepEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;ILjava/lang/Object;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Configuration; + public final fun component5 ()Ljava/util/List; + public final fun copy (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Configuration; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/VitalOperationStepEvent$Configuration;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;Ljava/util/List;ILjava/lang/Object;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Configuration; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Configuration; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Configuration; public final fun getProfilingSampleRate ()Ljava/lang/Number; + public final fun getSessionReplayExperimentalFeatures ()Ljava/util/List; public final fun getSessionReplaySampleRate ()Ljava/lang/Number; public final fun getSessionSampleRate ()Ljava/lang/Number; public final fun getTraceSampleRate ()Ljava/lang/Number; @@ -8355,6 +9783,26 @@ public final class com/datadog/android/rum/model/VitalOperationStepEvent$StepTyp public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$StepType; } +public final class com/datadog/android/rum/model/VitalOperationStepEvent$Stream { + public static final field Companion Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/VitalOperationStepEvent$Stream$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Stream; +} + public final class com/datadog/android/rum/model/VitalOperationStepEvent$Synthetics { public static final field Companion Lcom/datadog/android/rum/model/VitalOperationStepEvent$Synthetics$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;)V @@ -8382,6 +9830,26 @@ public final class com/datadog/android/rum/model/VitalOperationStepEvent$Synthet public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Synthetics; } +public final class com/datadog/android/rum/model/VitalOperationStepEvent$Tab { + public static final field Companion Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab; + public static synthetic fun copy$default (Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/rum/model/VitalOperationStepEvent$Tab$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$Tab; +} + public final class com/datadog/android/rum/model/VitalOperationStepEvent$Usr { public static final field Companion Lcom/datadog/android/rum/model/VitalOperationStepEvent$Usr$Companion; public fun ()V @@ -8509,6 +9977,7 @@ public final class com/datadog/android/rum/model/VitalOperationStepEvent$VitalOp public final class com/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource : java/lang/Enum { public static final field ANDROID Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource; public static final field BROWSER Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource; + public static final field CPP Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource; public static final field Companion Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource$Companion; public static final field ELECTRON Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource; public static final field FLUTTER Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource; @@ -8517,7 +9986,6 @@ public final class com/datadog/android/rum/model/VitalOperationStepEvent$VitalOp public static final field MAUI Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource; public static final field REACT_NATIVE Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource; public static final field ROKU Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource; - public static final field RUM_CPP Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource; public static final field UNITY Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource; public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/VitalOperationStepEvent$VitalOperationStepEventSource; public final fun toJson ()Lcom/google/gson/JsonElement; @@ -8640,14 +10108,20 @@ public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration { public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder { public fun ()V public final fun build ()Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration; - public final fun setBufferSize (I)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; - public final fun setIntervalMs (J)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; + public final fun collectOnly ([Lcom/datadog/android/rum/timeseries/TimeseriesType;)Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration$Builder; } public final class com/datadog/android/rum/timeseries/TimeseriesConfiguration$Companion { public final fun getDEFAULT ()Lcom/datadog/android/rum/timeseries/TimeseriesConfiguration; } +public final class com/datadog/android/rum/timeseries/TimeseriesType : java/lang/Enum { + public static final field CPU Lcom/datadog/android/rum/timeseries/TimeseriesType; + public static final field MEMORY Lcom/datadog/android/rum/timeseries/TimeseriesType; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/timeseries/TimeseriesType; + public static fun values ()[Lcom/datadog/android/rum/timeseries/TimeseriesType; +} + public class com/datadog/android/rum/tracking/AcceptAllActivities : com/datadog/android/rum/tracking/ComponentPredicate { public fun ()V public fun accept (Landroid/app/Activity;)Z diff --git a/features/dd-sdk-android-rum/src/main/json/rum/_common-schema.json b/features/dd-sdk-android-rum/src/main/json/rum/_common-schema.json index 991e883116..014d762542 100644 --- a/features/dd-sdk-android-rum/src/main/json/rum/_common-schema.json +++ b/features/dd-sdk-android-rum/src/main/json/rum/_common-schema.json @@ -4,7 +4,7 @@ "title": "CommonProperties", "type": "object", "description": "Schema of common properties of RUM events", - "required": ["date", "application", "session", "view", "_dd"], + "required": ["date", "application", "session", "_dd"], "properties": { "date": { "type": "integer", @@ -92,7 +92,7 @@ "unity", "kotlin-multiplatform", "electron", - "rum-cpp", + "cpp", "maui" ], "readOnly": true @@ -173,6 +173,20 @@ }, "readOnly": true }, + "tab": { + "type": "object", + "description": "Tab properties", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "description": "UUID of the browser tab", + "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", + "readOnly": true + } + }, + "readOnly": true + }, "connectivity": { "type": "object", "description": "Device connectivity properties", @@ -455,6 +469,14 @@ "minimum": 0, "maximum": 100, "readOnly": true + }, + "session_replay_experimental_features": { + "type": "array", + "description": "Session Replay experimental features enabled in the SDK configuration", + "items": { + "type": "string" + }, + "readOnly": true } } }, @@ -475,6 +497,19 @@ "type": "object", "description": "User provided context", "additionalProperties": true + }, + "stream": { + "type": "object", + "description": "Stream properties", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "description": "UUID of the stream", + "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", + "readOnly": true + } + } } } } diff --git a/features/dd-sdk-android-rum/src/main/json/rum/_view-container-schema.json b/features/dd-sdk-android-rum/src/main/json/rum/_view-container-schema.json index e22b81dcca..62942aa429 100644 --- a/features/dd-sdk-android-rum/src/main/json/rum/_view-container-schema.json +++ b/features/dd-sdk-android-rum/src/main/json/rum/_view-container-schema.json @@ -37,7 +37,7 @@ "unity", "kotlin-multiplatform", "electron", - "rum-cpp", + "cpp", "maui" ], "readOnly": true diff --git a/features/dd-sdk-android-rum/src/main/json/rum/timeseries-cpu-schema.json b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-cpu-schema.json index bc7c72f850..9e77f8885c 100644 --- a/features/dd-sdk-android-rum/src/main/json/rum/timeseries-cpu-schema.json +++ b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-cpu-schema.json @@ -4,153 +4,82 @@ "title": "RumTimeseriesCpuEvent", "type": "object", "description": "Schema for a CPU timeseries event.", - "required": ["_dd", "application", "date", "session", "source", "timeseries", "type"], - "properties": { - "type": { - "type": "string", - "description": "RUM event type", - "const": "timeseries", - "readOnly": true + "allOf": [ + { + "$ref": "_common-schema.json" }, - "_dd": { - "type": "object", - "description": "Internal properties", - "required": ["format_version"], + { + "required": ["type", "timeseries"], "properties": { - "format_version": { - "type": "integer", - "const": 2, - "description": "Version of the RUM event format", - "readOnly": true - } - }, - "readOnly": true - }, - "application": { - "type": "object", - "description": "Application properties", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "description": "UUID of the application", - "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", - "readOnly": true - } - }, - "readOnly": true - }, - "session": { - "type": "object", - "description": "Session properties", - "required": ["id", "type"], - "properties": { - "id": { - "type": "string", - "description": "UUID of the session", - "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", - "readOnly": true - }, "type": { "type": "string", - "description": "Type of the session", - "enum": ["user", "synthetics", "ci_test"], - "readOnly": true - } - }, - "readOnly": true - }, - "source": { - "type": "string", - "description": "The source of this event", - "enum": [ - "android", - "ios", - "browser", - "flutter", - "react-native", - "roku", - "unity", - "kotlin-multiplatform", - "electron", - "rum-cpp" - ], - "readOnly": true - }, - "date": { - "type": "integer", - "description": "Start of the event in ms from epoch", - "minimum": 0, - "readOnly": true - }, - "service": { - "type": "string", - "description": "The service name for this application" - }, - "version": { - "type": "string", - "description": "The version for this application" - }, - "timeseries": { - "type": "object", - "description": "CPU timeseries properties", - "required": ["id", "name", "schema", "start", "end", "data"], - "properties": { - "id": { - "type": "string", - "description": "UUID of the timeseries batch", - "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", + "description": "RUM event type", + "const": "timeseries", "readOnly": true }, - "name": { - "type": "string", - "description": "Name identifying the timeseries metric", - "const": "cpu", - "readOnly": true - }, - "schema": { - "type": "string", - "description": "Wire-shape discriminator for the data field", - "const": "object-v2", - "readOnly": true - }, - "start": { - "type": "integer", - "description": "Timestamp of the first sample in nanoseconds from epoch", - "readOnly": true - }, - "end": { - "type": "integer", - "description": "Timestamp of the last sample in nanoseconds from epoch", - "readOnly": true - }, - "data": { + "timeseries": { "type": "object", - "description": "Flattened CPU data points", - "required": ["timestamps", "values"], + "description": "CPU timeseries properties", + "required": ["id", "name", "schema", "start", "end", "data"], "properties": { - "timestamps": { - "type": "array", - "description": "Sample timestamps in nanoseconds from epoch", - "items": { - "type": "integer", - "readOnly": true - }, + "id": { + "type": "string", + "description": "UUID of the timeseries batch", + "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", "readOnly": true }, - "values": { + "name": { + "type": "string", + "description": "Name identifying the timeseries metric", + "const": "cpu", + "readOnly": true + }, + "schema": { + "type": "string", + "description": "Wire-shape discriminator for the data field", + "const": "object-v2", + "readOnly": true + }, + "start": { + "type": "integer", + "description": "Timestamp of the first sample in nanoseconds from epoch", + "readOnly": true + }, + "end": { + "type": "integer", + "description": "Timestamp of the last sample in nanoseconds from epoch", + "readOnly": true + }, + "data": { "type": "object", - "description": "CPU measurements, aligned index-for-index with timestamps", - "required": ["cpu_usage"], + "description": "Flattened CPU data points", + "required": ["timestamps", "values"], "properties": { - "cpu_usage": { + "timestamps": { "type": "array", - "description": "CPU usage as a percentage (0.0 to 100.0)", + "description": "Sample timestamps in nanoseconds from epoch", "items": { - "type": "number", + "type": "integer", "readOnly": true }, "readOnly": true + }, + "values": { + "type": "object", + "description": "CPU measurements, aligned index-for-index with timestamps", + "required": ["cpu_usage"], + "properties": { + "cpu_usage": { + "type": "array", + "description": "CPU usage as a percentage (0.0 to 100.0)", + "items": { + "type": "number", + "readOnly": true + }, + "readOnly": true + } + }, + "readOnly": true } }, "readOnly": true @@ -158,8 +87,7 @@ }, "readOnly": true } - }, - "readOnly": true + } } - } + ] } diff --git a/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json index 9e642e1a8d..122f58d670 100644 --- a/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json +++ b/features/dd-sdk-android-rum/src/main/json/rum/timeseries-memory-schema.json @@ -4,160 +4,89 @@ "title": "RumTimeseriesMemoryEvent", "type": "object", "description": "Schema for a memory timeseries event.", - "required": ["_dd", "application", "date", "session", "source", "timeseries", "type"], - "properties": { - "type": { - "type": "string", - "description": "RUM event type", - "const": "timeseries", - "readOnly": true + "allOf": [ + { + "$ref": "_common-schema.json" }, - "_dd": { - "type": "object", - "description": "Internal properties", - "required": ["format_version"], + { + "required": ["type", "timeseries"], "properties": { - "format_version": { - "type": "integer", - "const": 2, - "description": "Version of the RUM event format", - "readOnly": true - } - }, - "readOnly": true - }, - "application": { - "type": "object", - "description": "Application properties", - "required": ["id"], - "properties": { - "id": { - "type": "string", - "description": "UUID of the application", - "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", - "readOnly": true - } - }, - "readOnly": true - }, - "session": { - "type": "object", - "description": "Session properties", - "required": ["id", "type"], - "properties": { - "id": { - "type": "string", - "description": "UUID of the session", - "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", - "readOnly": true - }, "type": { "type": "string", - "description": "Type of the session", - "enum": ["user", "synthetics", "ci_test"], - "readOnly": true - } - }, - "readOnly": true - }, - "source": { - "type": "string", - "description": "The source of this event", - "enum": [ - "android", - "ios", - "browser", - "flutter", - "react-native", - "roku", - "unity", - "kotlin-multiplatform", - "electron", - "rum-cpp" - ], - "readOnly": true - }, - "date": { - "type": "integer", - "description": "Start of the event in ms from epoch", - "minimum": 0, - "readOnly": true - }, - "service": { - "type": "string", - "description": "The service name for this application" - }, - "version": { - "type": "string", - "description": "The version for this application" - }, - "timeseries": { - "type": "object", - "description": "Memory timeseries properties", - "required": ["id", "name", "schema", "start", "end", "data"], - "properties": { - "id": { - "type": "string", - "description": "UUID of the timeseries batch", - "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", + "description": "RUM event type", + "const": "timeseries", "readOnly": true }, - "name": { - "type": "string", - "description": "Name identifying the timeseries metric", - "const": "memory", - "readOnly": true - }, - "schema": { - "type": "string", - "description": "Wire-shape discriminator for the data field", - "const": "object-v2", - "readOnly": true - }, - "start": { - "type": "integer", - "description": "Timestamp of the first sample in nanoseconds from epoch", - "readOnly": true - }, - "end": { - "type": "integer", - "description": "Timestamp of the last sample in nanoseconds from epoch", - "readOnly": true - }, - "data": { + "timeseries": { "type": "object", - "description": "Flattened memory data points", - "required": ["timestamps", "values"], + "description": "Memory timeseries properties", + "required": ["id", "name", "schema", "start", "end", "data"], "properties": { - "timestamps": { - "type": "array", - "description": "Sample timestamps in nanoseconds from epoch", - "items": { - "type": "integer", - "readOnly": true - }, + "id": { + "type": "string", + "description": "UUID of the timeseries batch", + "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", "readOnly": true }, - "values": { + "name": { + "type": "string", + "description": "Name identifying the timeseries metric", + "const": "memory", + "readOnly": true + }, + "schema": { + "type": "string", + "description": "Wire-shape discriminator for the data field", + "const": "object-v2", + "readOnly": true + }, + "start": { + "type": "integer", + "description": "Timestamp of the first sample in nanoseconds from epoch", + "readOnly": true + }, + "end": { + "type": "integer", + "description": "Timestamp of the last sample in nanoseconds from epoch", + "readOnly": true + }, + "data": { "type": "object", - "description": "Memory measurements, aligned index-for-index with timestamps", - "required": ["memory_footprint", "memory_percent"], + "description": "Flattened memory data points", + "required": ["timestamps", "values"], "properties": { - "memory_footprint": { + "timestamps": { "type": "array", - "description": "Physical memory footprint of the process in kilobytes", + "description": "Sample timestamps in nanoseconds from epoch", "items": { - "type": "number", + "type": "integer", "readOnly": true }, "readOnly": true }, - "memory_percent": { - "type": "array", - "description": "Memory footprint as a percentage of total device RAM", - "items": { - "type": "number", - "readOnly": true + "values": { + "type": "object", + "description": "Memory measurements, aligned index-for-index with timestamps", + "required": ["memory_footprint", "memory_percent"], + "properties": { + "memory_footprint": { + "type": "array", + "description": "Physical memory footprint of the process in kilobytes", + "items": { + "type": "number", + "readOnly": true + }, + "readOnly": true + }, + "memory_percent": { + "type": "array", + "description": "Memory footprint as a percentage of total device RAM", + "items": { + "type": "number", + "readOnly": true + }, + "readOnly": true + } }, "readOnly": true } @@ -167,8 +96,7 @@ }, "readOnly": true } - }, - "readOnly": true + } } - } + ] } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt index 0146aa8773..6049fcaff7 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt @@ -412,20 +412,16 @@ data class RumConfiguration internal constructor( } /** - * Enables memory and CPU timeseries collection. + * Enables device timeseries collection. * - * When enabled, the SDK samples device memory (RSS) and CPU usage at - * [TimeseriesConfiguration.intervalMs] (default 1 second) and emits a batched - * timeseries event every [TimeseriesConfiguration.bufferSize] samples (default 30). - * Sampling runs on the shared RUM vitals background executor. + * By default, all supported timeseries types are collected. Use + * [TimeseriesConfiguration.Builder.collectOnly] to restrict collection to specific types. * - * To disable, pass null. - * - * @param configuration optional fine-tuning of sampling interval and batch size. + * @param configuration configuration for timeseries collection. */ @ExperimentalRumApi fun setTimeseriesConfiguration( - configuration: TimeseriesConfiguration? + configuration: TimeseriesConfiguration ): Builder { rumConfig = rumConfig.copy(timeseriesConfiguration = configuration) return this diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index 89c48906c6..28edfb59b3 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -91,15 +91,9 @@ import com.datadog.android.rum.internal.startup.RumAppStartupDetector import com.datadog.android.rum.internal.startup.RumStartupScenario import com.datadog.android.rum.internal.startup.RumTTIDInfo import com.datadog.android.rum.internal.thread.NoOpScheduledExecutorService -import com.datadog.android.rum.internal.timeseries.Buffer import com.datadog.android.rum.internal.timeseries.DefaultTimeseriesCollectorFactory import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesCollectorFactory -import com.datadog.android.rum.internal.timeseries.Pipeline import com.datadog.android.rum.internal.timeseries.TimeseriesCollector -import com.datadog.android.rum.internal.timeseries.provider.CpuDatapointReader -import com.datadog.android.rum.internal.timeseries.provider.VitalReaderWrapper -import com.datadog.android.rum.internal.timeseries.serializer.CpuEventSerializer -import com.datadog.android.rum.internal.timeseries.serializer.MemoryEventSerializer import com.datadog.android.rum.internal.tracking.JetpackViewAttributesProvider import com.datadog.android.rum.internal.tracking.NoOpInteractionPredicate import com.datadog.android.rum.internal.tracking.NoOpUserActionTrackingStrategy @@ -302,11 +296,16 @@ internal class RumFeature( sessionListener = configuration.sessionListener - configuration.timeseriesConfiguration?.let { configuration -> - timeseriesCollectorFactory = createTimeseriesCollectingFactory( - configuration, - appContext.readTotalRamBytes(sdkCore.internalLogger) ?: 0L, - insightsCollector + configuration.timeseriesConfiguration?.let { timeseriesConfiguration -> + timeseriesCollectorFactory = DefaultTimeseriesCollectorFactory( + sdkCore = sdkCore, + dataWriter = dataWriter, + insightsCollector = insightsCollector, + configuration = timeseriesConfiguration, + scheduledExecutorService = vitalExecutorService, + totalRamBytes = appContext.readTotalRamBytes(sdkCore.internalLogger) ?: 0L, + batteryInfoProvider = batteryInfoProvider, + displayInfoProvider = displayInfoProvider ) } @@ -452,68 +451,6 @@ internal class RumFeature( ) } - @Suppress("LongMethod") - internal fun createTimeseriesCollectingFactory( - configuration: TimeseriesConfiguration, - totalRamBytes: Long, - insightsCollector: InsightsCollector - ): TimeseriesCollector.Factory = DefaultTimeseriesCollectorFactory( - internalLogger = sdkCore.internalLogger, - collectInBackground = configuration.collectInBackground, - scheduledExecutorService = vitalExecutorService, - pipelinesProvider = { applicationId, sessionId, sessionType -> - listOfNotNull( - Pipeline( - sdkCore = sdkCore, - reader = CpuDatapointReader( - cpuStatReader = CpuStatReader(internalLogger = sdkCore.internalLogger), - cpuTimeProvider = sdkCore.timeProvider, - intervalMs = configuration.intervalMs - ), - buffer = Buffer(configuration.bufferSize), - serializer = CpuEventSerializer( - sessionId = sessionId, - applicationId = applicationId, - sessionType = sessionType, - timeProvider = sdkCore.timeProvider - ), - dataWriter = dataWriter, - internalLogger = sdkCore.internalLogger, - insightsCollector = insightsCollector - ), - if (totalRamBytes > 0L) { - Pipeline( - sdkCore = sdkCore, - reader = VitalReaderWrapper( - vitalReader = MemoryVitalReader(internalLogger = sdkCore.internalLogger), - timeProvider = sdkCore.timeProvider, - intervalMs = configuration.intervalMs - ), - buffer = Buffer(configuration.bufferSize), - serializer = MemoryEventSerializer( - sessionId = sessionId, - applicationId = applicationId, - sessionType = sessionType, - totalRamBytes = totalRamBytes, - timeProvider = sdkCore.timeProvider - ), - dataWriter = dataWriter, - internalLogger = sdkCore.internalLogger, - insightsCollector = insightsCollector - ) - } else { - sdkCore.internalLogger.log( - InternalLogger.Level.WARN, - InternalLogger.Target.USER, - { MEMORY_TIMESERIES_DISABLED_MESSAGE }, - onlyOnce = true - ) - null - } - ) - } - ) - // region FeatureEventReceiver override fun onReceive(event: Any) { @@ -975,8 +912,6 @@ internal class RumFeature( "Slow frames monitoring enabled." internal const val SLOW_FRAMES_MONITORING_DISABLED_MESSAGE = "Slow frames monitoring disabled." - internal const val MEMORY_TIMESERIES_DISABLED_MESSAGE = - "Unable to read total device memory; memory timeseries collection is disabled." internal const val RUM_FEATURE_NOT_YET_INITIALIZED = "RUM feature is not initialized yet, you need to register it with a" + " SDK instance by calling SdkCore#registerFeature method." diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumSessionTypeExt.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumSessionTypeExt.kt index 49105fdc95..ff9ee46572 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumSessionTypeExt.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumSessionTypeExt.kt @@ -11,8 +11,8 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent -import com.datadog.android.rum.model.TimeseriesCpuEvent -import com.datadog.android.rum.model.TimeseriesMemoryEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent.TimeseriesCpuEventSessionType +import com.datadog.android.rum.model.TimeseriesMemoryEvent.TimeseriesMemoryEventSessionType import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.model.VitalAppLaunchEvent import com.datadog.android.rum.model.VitalOperationStepEvent @@ -66,12 +66,12 @@ internal fun RumSessionType.toVitalAppLaunch(): VitalAppLaunchEvent.VitalAppLaun } } -internal fun RumSessionType.toTimeseriesMemorySessionType(): TimeseriesMemoryEvent.Type = when (this) { - RumSessionType.SYNTHETICS -> TimeseriesMemoryEvent.Type.SYNTHETICS - RumSessionType.USER -> TimeseriesMemoryEvent.Type.USER +internal fun RumSessionType.toTimeseriesMemorySessionType(): TimeseriesMemoryEventSessionType = when (this) { + RumSessionType.SYNTHETICS -> TimeseriesMemoryEventSessionType.SYNTHETICS + RumSessionType.USER -> TimeseriesMemoryEventSessionType.USER } -internal fun RumSessionType.toTimeseriesCpuSessionType(): TimeseriesCpuEvent.Type = when (this) { - RumSessionType.SYNTHETICS -> TimeseriesCpuEvent.Type.SYNTHETICS - RumSessionType.USER -> TimeseriesCpuEvent.Type.USER +internal fun RumSessionType.toTimeseriesCpuSessionType(): TimeseriesCpuEventSessionType = when (this) { + RumSessionType.SYNTHETICS -> TimeseriesCpuEventSessionType.SYNTHETICS + RumSessionType.USER -> TimeseriesCpuEventSessionType.USER } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/RumDataWriter.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/RumDataWriter.kt index 24c4f018c8..3c8ce66086 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/RumDataWriter.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/RumDataWriter.kt @@ -22,6 +22,8 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.model.VitalAppLaunchEvent import com.datadog.android.rum.model.VitalOperationStepEvent @@ -106,6 +108,8 @@ internal class RumDataWriter( is TelemetryDebugEvent -> event.type is TelemetryErrorEvent -> event.type is TelemetryUsageEvent -> event.type + is TimeseriesCpuEvent -> event.type + is TimeseriesMemoryEvent -> event.type else -> UNKNOWN_EVENT_TYPE } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapper.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapper.kt index e932bea14a..c11ccfcb3d 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapper.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapper.kt @@ -13,6 +13,8 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.model.VitalAppLaunchEvent import com.datadog.android.rum.model.VitalOperationStepEvent @@ -67,6 +69,8 @@ internal data class RumEventMapper( is VitalOperationStepEvent -> vitalOperationStepEventMapper.map(event) is VitalAppLaunchEvent -> vitalAppLaunchEventMapper.map(event) is TelemetryConfigurationEvent -> telemetryConfigurationMapper.map(event) + is TimeseriesCpuEvent, + is TimeseriesMemoryEvent, is TelemetryDebugEvent, is TelemetryUsageEvent, is TelemetryErrorEvent -> event diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt index ea867f6cc9..96915488be 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt @@ -16,6 +16,8 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.model.VitalAppLaunchEvent import com.datadog.android.rum.model.VitalOperationStepEvent @@ -68,6 +70,12 @@ internal class RumEventSerializer( is TelemetryUsageEvent -> { model.toJson().toString() } + is TimeseriesCpuEvent -> { + model.toJson().toString() + } + is TimeseriesMemoryEvent -> { + model.toJson().toString() + } is JsonObject -> { model.toString() } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt index d92742407e..9ecc79ae71 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt @@ -8,19 +8,26 @@ package com.datadog.android.rum.internal.domain.scope import com.datadog.android.api.InternalLogger +import com.datadog.android.api.context.DatadogContext +import com.datadog.android.api.context.DeviceInfo import com.datadog.android.api.context.DeviceType import com.datadog.android.api.context.NetworkInfo +import com.datadog.android.api.feature.Feature import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumResourceKind import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.internal.RumErrorSourceType +import com.datadog.android.rum.internal.domain.battery.BatteryInfo +import com.datadog.android.rum.internal.domain.display.DisplayInfo import com.datadog.android.rum.internal.domain.event.ResourceTiming import com.datadog.android.rum.internal.startup.RumStartupScenario import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.model.VitalAppLaunchEvent import com.datadog.android.rum.model.VitalOperationStepEvent @@ -426,6 +433,80 @@ internal fun NetworkInfo.toAppLaunchVitalConnectivity(): VitalAppLaunchEvent.Con ) } +internal fun NetworkInfo.toTimeseriesCpuConnectivity(): TimeseriesCpuEvent.Connectivity { + val status = if (isConnected()) { + TimeseriesCpuEvent.Status.CONNECTED + } else { + TimeseriesCpuEvent.Status.NOT_CONNECTED + } + val interfaces = when (connectivity) { + NetworkInfo.Connectivity.NETWORK_ETHERNET -> listOf(TimeseriesCpuEvent.Interface.ETHERNET) + NetworkInfo.Connectivity.NETWORK_WIFI -> listOf(TimeseriesCpuEvent.Interface.WIFI) + NetworkInfo.Connectivity.NETWORK_WIMAX -> listOf(TimeseriesCpuEvent.Interface.WIMAX) + NetworkInfo.Connectivity.NETWORK_BLUETOOTH -> listOf(TimeseriesCpuEvent.Interface.BLUETOOTH) + NetworkInfo.Connectivity.NETWORK_2G, + NetworkInfo.Connectivity.NETWORK_3G, + NetworkInfo.Connectivity.NETWORK_4G, + NetworkInfo.Connectivity.NETWORK_5G, + NetworkInfo.Connectivity.NETWORK_MOBILE_OTHER, + NetworkInfo.Connectivity.NETWORK_CELLULAR -> listOf(TimeseriesCpuEvent.Interface.CELLULAR) + + NetworkInfo.Connectivity.NETWORK_OTHER -> listOf(TimeseriesCpuEvent.Interface.OTHER) + NetworkInfo.Connectivity.NETWORK_NOT_CONNECTED -> emptyList() + } + + val cellular = if (cellularTechnology != null || carrierName != null) { + TimeseriesCpuEvent.Cellular( + technology = cellularTechnology, + carrierName = carrierName + ) + } else { + null + } + return TimeseriesCpuEvent.Connectivity( + status, + interfaces, + cellular = cellular + ) +} + +internal fun NetworkInfo.toTimeseriesMemoryConnectivity(): TimeseriesMemoryEvent.Connectivity { + val status = if (isConnected()) { + TimeseriesMemoryEvent.Status.CONNECTED + } else { + TimeseriesMemoryEvent.Status.NOT_CONNECTED + } + val interfaces = when (connectivity) { + NetworkInfo.Connectivity.NETWORK_ETHERNET -> listOf(TimeseriesMemoryEvent.Interface.ETHERNET) + NetworkInfo.Connectivity.NETWORK_WIFI -> listOf(TimeseriesMemoryEvent.Interface.WIFI) + NetworkInfo.Connectivity.NETWORK_WIMAX -> listOf(TimeseriesMemoryEvent.Interface.WIMAX) + NetworkInfo.Connectivity.NETWORK_BLUETOOTH -> listOf(TimeseriesMemoryEvent.Interface.BLUETOOTH) + NetworkInfo.Connectivity.NETWORK_2G, + NetworkInfo.Connectivity.NETWORK_3G, + NetworkInfo.Connectivity.NETWORK_4G, + NetworkInfo.Connectivity.NETWORK_5G, + NetworkInfo.Connectivity.NETWORK_MOBILE_OTHER, + NetworkInfo.Connectivity.NETWORK_CELLULAR -> listOf(TimeseriesMemoryEvent.Interface.CELLULAR) + + NetworkInfo.Connectivity.NETWORK_OTHER -> listOf(TimeseriesMemoryEvent.Interface.OTHER) + NetworkInfo.Connectivity.NETWORK_NOT_CONNECTED -> emptyList() + } + + val cellular = if (cellularTechnology != null || carrierName != null) { + TimeseriesMemoryEvent.Cellular( + technology = cellularTechnology, + carrierName = carrierName + ) + } else { + null + } + return TimeseriesMemoryEvent.Connectivity( + status, + interfaces, + cellular = cellular + ) +} + internal fun NetworkInfo.isConnected(): Boolean { return connectivity != NetworkInfo.Connectivity.NETWORK_NOT_CONNECTED } @@ -518,6 +599,94 @@ internal fun DeviceType.toVitalAppLaunchSchemaType(): VitalAppLaunchEvent.Device } } +internal fun DeviceType.toTimeseriesCpuSchemaType(): TimeseriesCpuEvent.DeviceType { + return when (this) { + DeviceType.MOBILE -> TimeseriesCpuEvent.DeviceType.MOBILE + DeviceType.TABLET -> TimeseriesCpuEvent.DeviceType.TABLET + DeviceType.TV -> TimeseriesCpuEvent.DeviceType.TV + DeviceType.DESKTOP -> TimeseriesCpuEvent.DeviceType.DESKTOP + DeviceType.GAMING_CONSOLE -> TimeseriesCpuEvent.DeviceType.GAMING_CONSOLE + DeviceType.BOT -> TimeseriesCpuEvent.DeviceType.BOT + DeviceType.OTHER -> TimeseriesCpuEvent.DeviceType.OTHER + } +} + +internal fun DeviceType.toTimeseriesMemorySchemaType(): TimeseriesMemoryEvent.DeviceType { + return when (this) { + DeviceType.MOBILE -> TimeseriesMemoryEvent.DeviceType.MOBILE + DeviceType.TABLET -> TimeseriesMemoryEvent.DeviceType.TABLET + DeviceType.TV -> TimeseriesMemoryEvent.DeviceType.TV + DeviceType.DESKTOP -> TimeseriesMemoryEvent.DeviceType.DESKTOP + DeviceType.GAMING_CONSOLE -> TimeseriesMemoryEvent.DeviceType.GAMING_CONSOLE + DeviceType.BOT -> TimeseriesMemoryEvent.DeviceType.BOT + DeviceType.OTHER -> TimeseriesMemoryEvent.DeviceType.OTHER + } +} + +internal fun DeviceInfo.toTimeseriesCpuDevice( + batteryInfo: BatteryInfo, + displayInfo: DisplayInfo +) = TimeseriesCpuEvent.Device( + type = deviceType.toTimeseriesCpuSchemaType(), + name = deviceName, + model = deviceModel, + brand = deviceBrand, + architecture = architecture, + locales = localeInfo.locales, + timeZone = localeInfo.timeZone, + batteryLevel = batteryInfo.batteryLevel, + powerSavingMode = batteryInfo.lowPowerMode, + brightnessLevel = displayInfo.screenBrightness, + logicalCpuCount = logicalCpuCount, + totalRam = totalRam, + isLowRam = isLowRam +) + +internal fun DeviceInfo.toTimeseriesMemoryDevice( + batteryInfo: BatteryInfo, + displayInfo: DisplayInfo +) = TimeseriesMemoryEvent.Device( + type = deviceType.toTimeseriesMemorySchemaType(), + name = deviceName, + model = deviceModel, + brand = deviceBrand, + architecture = architecture, + locales = localeInfo.locales, + timeZone = localeInfo.timeZone, + batteryLevel = batteryInfo.batteryLevel, + powerSavingMode = batteryInfo.lowPowerMode, + brightnessLevel = displayInfo.screenBrightness, + logicalCpuCount = logicalCpuCount, + totalRam = totalRam, + isLowRam = isLowRam +) + +internal fun DeviceInfo.toTimeseriesCpuOs() = TimeseriesCpuEvent.Os( + name = osName, + version = osVersion, + versionMajor = osMajorVersion +) + +internal fun DeviceInfo.toTimeseriesMemoryOs() = TimeseriesMemoryEvent.Os( + name = osName, + version = osVersion, + versionMajor = osMajorVersion +) + +// endregion + +// region Feature context sample rates + +internal fun DatadogContext.resolveSessionReplaySampleRate(): Long? { + val srContext = featuresContext[Feature.SESSION_REPLAY_FEATURE_NAME] + return srContext?.get(RumViewScope.SESSION_REPLAY_SAMPLE_RATE_KEY) as? Long +} + +internal fun DatadogContext.resolveTraceSampleRate(): Float? { + val tracingContext = featuresContext[Feature.TRACING_FEATURE_NAME] + return tracingContext?.get(RumViewScope.TRACE_SAMPLE_RATE) as? Float +} + // endregion // region Source @@ -641,6 +810,40 @@ internal fun VitalAppLaunchEvent.VitalAppLaunchEventSource.Companion.tryFromSour } } +internal fun TimeseriesCpuEvent.Source.Companion.tryFromSource( + source: String, + internalLogger: InternalLogger +): TimeseriesCpuEvent.Source? { + return try { + fromJson(source) + } catch (e: NoSuchElementException) { + internalLogger.log( + InternalLogger.Level.ERROR, + InternalLogger.Target.USER, + { UNKNOWN_SOURCE_WARNING_MESSAGE_FORMAT.format(Locale.US, source) }, + e + ) + null + } +} + +internal fun TimeseriesMemoryEvent.Source.Companion.tryFromSource( + source: String, + internalLogger: InternalLogger +): TimeseriesMemoryEvent.Source? { + return try { + fromJson(source) + } catch (e: NoSuchElementException) { + internalLogger.log( + InternalLogger.Level.ERROR, + InternalLogger.Target.USER, + { UNKNOWN_SOURCE_WARNING_MESSAGE_FORMAT.format(Locale.US, source) }, + e + ) + null + } +} + internal const val UNKNOWN_SOURCE_WARNING_MESSAGE_FORMAT = "You are using an unknown " + "source %s for your events" @@ -713,6 +916,33 @@ internal fun RumSessionScope.StartReason.toLongTaskSessionPrecondition(): LongTa } } +internal fun RumSessionScope.StartReason.toTimeseriesCpuSessionPrecondition(): TimeseriesCpuEvent.SessionPrecondition { + return when (this) { + RumSessionScope.StartReason.USER_APP_LAUNCH -> TimeseriesCpuEvent.SessionPrecondition.USER_APP_LAUNCH + RumSessionScope.StartReason.INACTIVITY_TIMEOUT -> TimeseriesCpuEvent.SessionPrecondition.INACTIVITY_TIMEOUT + RumSessionScope.StartReason.MAX_DURATION -> TimeseriesCpuEvent.SessionPrecondition.MAX_DURATION + RumSessionScope.StartReason.EXPLICIT_STOP -> TimeseriesCpuEvent.SessionPrecondition.EXPLICIT_STOP + RumSessionScope.StartReason.BACKGROUND_LAUNCH -> TimeseriesCpuEvent.SessionPrecondition.BACKGROUND_LAUNCH + RumSessionScope.StartReason.PREWARM -> TimeseriesCpuEvent.SessionPrecondition.PREWARM + RumSessionScope.StartReason.FROM_NON_INTERACTIVE_SESSION -> + TimeseriesCpuEvent.SessionPrecondition.FROM_NON_INTERACTIVE_SESSION + } +} + +internal fun RumSessionScope.StartReason.toTimeseriesMemorySessionPrecondition(): + TimeseriesMemoryEvent.SessionPrecondition { + return when (this) { + RumSessionScope.StartReason.USER_APP_LAUNCH -> TimeseriesMemoryEvent.SessionPrecondition.USER_APP_LAUNCH + RumSessionScope.StartReason.INACTIVITY_TIMEOUT -> TimeseriesMemoryEvent.SessionPrecondition.INACTIVITY_TIMEOUT + RumSessionScope.StartReason.MAX_DURATION -> TimeseriesMemoryEvent.SessionPrecondition.MAX_DURATION + RumSessionScope.StartReason.EXPLICIT_STOP -> TimeseriesMemoryEvent.SessionPrecondition.EXPLICIT_STOP + RumSessionScope.StartReason.BACKGROUND_LAUNCH -> TimeseriesMemoryEvent.SessionPrecondition.BACKGROUND_LAUNCH + RumSessionScope.StartReason.PREWARM -> TimeseriesMemoryEvent.SessionPrecondition.PREWARM + RumSessionScope.StartReason.FROM_NON_INTERACTIVE_SESSION -> + TimeseriesMemoryEvent.SessionPrecondition.FROM_NON_INTERACTIVE_SESSION + } +} + // endregion // region Operation diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt index 618f28c7db..f8ccf0c603 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt @@ -234,10 +234,10 @@ internal class RumSessionScope( } private fun startTimeseries() { + val rumContext = getActiveRumContext() timeseriesCollector = timeseriesCollectorFactory.create( - sessionId = sessionId, - applicationId = parentScope.getRumContext().applicationId, - sessionType = getRumContext().resolveSessionType(rumSessionTypeOverride) + rumContext = rumContext, + sessionType = rumContext.resolveSessionType(rumSessionTypeOverride) ) timeseriesCollector.onSessionStart() } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt index 39540ba7ef..ab07c34939 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt @@ -1448,8 +1448,8 @@ internal open class RumViewScope( replayStats = replayStats, configuration = ViewEvent.Configuration( sessionSampleRate = sampleRate, - sessionReplaySampleRate = resolveSessionReplaySampleRate(datadogContext), - traceSampleRate = resolveTraceSampleRate(datadogContext) + sessionReplaySampleRate = datadogContext.resolveSessionReplaySampleRate(), + traceSampleRate = datadogContext.resolveTraceSampleRate() ), profiling = resolveViewProfilingStatus(datadogContext) ), @@ -1735,16 +1735,6 @@ internal open class RumViewScope( } } - private fun resolveSessionReplaySampleRate(datadogContext: DatadogContext): Long? { - val srContext = datadogContext.featuresContext[Feature.SESSION_REPLAY_FEATURE_NAME] - return srContext?.get(SESSION_REPLAY_SAMPLE_RATE_KEY) as? Long - } - - private fun resolveTraceSampleRate(datadogContext: DatadogContext): Float? { - val tracingContext = datadogContext.featuresContext[Feature.TRACING_FEATURE_NAME] - return tracingContext?.get(TRACE_SAMPLE_RATE) as? Float - } - private fun resolveErrorProfilingStatus(datadogContext: DatadogContext): ErrorEvent.Profiling? { val isRunning = datadogContext.isProfilerRunning() val quotaReason = datadogContext.resolveProfilingQuotaReason(sessionId) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt index bec629e64e..e47aee7c60 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt @@ -7,25 +7,116 @@ package com.datadog.android.rum.internal.timeseries import com.datadog.android.api.InternalLogger +import com.datadog.android.api.feature.FeatureSdkCore +import com.datadog.android.api.storage.DataWriter import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.domain.InfoProvider +import com.datadog.android.rum.internal.domain.RumContext +import com.datadog.android.rum.internal.domain.battery.BatteryInfo +import com.datadog.android.rum.internal.domain.display.DisplayInfo +import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector +import com.datadog.android.rum.internal.timeseries.factory.CpuEventFactory +import com.datadog.android.rum.internal.timeseries.factory.MemoryEventFactory +import com.datadog.android.rum.internal.timeseries.provider.CpuDatapointReader +import com.datadog.android.rum.internal.timeseries.provider.VitalReaderWrapper +import com.datadog.android.rum.internal.vitals.CpuStatReader +import com.datadog.android.rum.internal.vitals.MemoryVitalReader +import com.datadog.android.rum.timeseries.TimeseriesConfiguration +import com.datadog.android.rum.timeseries.TimeseriesType import java.util.concurrent.ScheduledExecutorService +@Suppress("LongParameterList") internal class DefaultTimeseriesCollectorFactory( - private val internalLogger: InternalLogger, - private val collectInBackground: Boolean, + private val sdkCore: FeatureSdkCore, + private val configuration: TimeseriesConfiguration, private val scheduledExecutorService: ScheduledExecutorService, - private val pipelinesProvider: ( - applicationId: String, - sessionId: String, - sessionType: RumSessionType - ) -> List> + private val insightsCollector: InsightsCollector, + private val totalRamBytes: Long, + private val dataWriter: DataWriter, + private val batteryInfoProvider: InfoProvider, + private val displayInfoProvider: InfoProvider ) : TimeseriesCollector.Factory { - override fun create(applicationId: String, sessionId: String, sessionType: RumSessionType) = - DefaultTimeseriesCollector( - internalLogger = internalLogger, - collectInBackground = collectInBackground, + override fun create( + sessionType: RumSessionType, + rumContext: RumContext + ): TimeseriesCollector { + val pipelines = mutableListOf>() + + if (TimeseriesType.CPU in configuration.enabledTypes) { + pipelines += createCpuPipeline(sessionType, rumContext) + } + + if (TimeseriesType.MEMORY in configuration.enabledTypes) { + if (totalRamBytes > 0L) { + pipelines += createMemoryPipeline(sessionType, rumContext) + } else { + sdkCore.internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + { MEMORY_TIMESERIES_DISABLED_MESSAGE }, + onlyOnce = true + ) + } + } + + return DefaultTimeseriesCollector( + internalLogger = sdkCore.internalLogger, + collectInBackground = configuration.collectInBackground, scheduledExecutorService = scheduledExecutorService, - pipelines = pipelinesProvider(applicationId, sessionId, sessionType) + pipelines = pipelines ) + } + + private fun createMemoryPipeline( + sessionType: RumSessionType, + rumContext: RumContext + ) = Pipeline( + sdkCore = sdkCore, + reader = VitalReaderWrapper( + vitalReader = MemoryVitalReader(internalLogger = sdkCore.internalLogger), + timeProvider = sdkCore.timeProvider, + intervalMs = TimeseriesConfiguration.DEFAULT_INTERVAL_MS + ), + buffer = Buffer(TimeseriesConfiguration.DEFAULT_BUFFER_SIZE), + eventFactory = MemoryEventFactory( + sessionType = sessionType, + totalRamBytes = totalRamBytes, + timeProvider = sdkCore.timeProvider, + batteryInfoProvider = batteryInfoProvider, + displayInfoProvider = displayInfoProvider, + internalLogger = sdkCore.internalLogger + ), + dataWriter = dataWriter, + rumContext = rumContext, + insightsCollector = insightsCollector + ) + + private fun createCpuPipeline( + sessionType: RumSessionType, + rumContext: RumContext + ) = Pipeline( + sdkCore = sdkCore, + reader = CpuDatapointReader( + cpuStatReader = CpuStatReader(internalLogger = sdkCore.internalLogger), + timeProvider = sdkCore.timeProvider, + intervalMs = TimeseriesConfiguration.DEFAULT_INTERVAL_MS + ), + buffer = Buffer(TimeseriesConfiguration.DEFAULT_BUFFER_SIZE), + eventFactory = CpuEventFactory( + sessionType = sessionType, + timeProvider = sdkCore.timeProvider, + batteryInfoProvider = batteryInfoProvider, + displayInfoProvider = displayInfoProvider, + internalLogger = sdkCore.internalLogger + ), + dataWriter = dataWriter, + rumContext = rumContext, + insightsCollector = insightsCollector + ) + + internal companion object { + const val MEMORY_TIMESERIES_DISABLED_MESSAGE = + "Unable to read total device memory; memory timeseries collection is disabled." + } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt index 3edeafd402..9865a0395f 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt @@ -14,21 +14,20 @@ import com.datadog.android.api.feature.Feature import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.api.storage.DataWriter import com.datadog.android.api.storage.EventType +import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector import com.datadog.android.rum.internal.instrumentation.insights.NoOpInsightsCollector +import com.datadog.android.rum.internal.timeseries.factory.EventFactory import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader -import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer -import com.datadog.android.rum.internal.timeseries.serializer.TimeseriesAttributes -import com.google.gson.JsonObject @Suppress("LongParameterList") internal class Pipeline( private val sdkCore: FeatureSdkCore, private val reader: DataPointsReader, private val buffer: Buffer, - private val serializer: JsonSerializer, + private val eventFactory: EventFactory, private val dataWriter: DataWriter, - private val internalLogger: InternalLogger, + private val rumContext: RumContext, private val insightsCollector: InsightsCollector = NoOpInsightsCollector() ) { val intervalMs: Long get() = reader.intervalMs @@ -36,22 +35,29 @@ internal class Pipeline( @WorkerThread fun execute() { reader.read()?.let(buffer::add) - if (buffer.isFull()) flush() + if (buffer.isFull()) drainAndWrite() } @WorkerThread - fun flush() { + fun flush() = drainAndWrite() + + private fun drainAndWrite() { val dataPoints = buffer.drain().ifEmpty { return } sdkCore.getFeature(Feature.RUM_FEATURE_NAME) - ?.withWriteContext { datadogContext: DatadogContext, writeScope: EventWriteScope -> - val json = safeCall(ERROR_SERIALIZATION_FAILED) { - serializer.serialize(datadogContext, dataPoints) + ?.withWriteContext( + withFeatureContexts = setOf( + Feature.SESSION_REPLAY_FEATURE_NAME, + Feature.TRACING_FEATURE_NAME + ) + ) { datadogContext: DatadogContext, writeScope: EventWriteScope -> + val event = safeCall(ERROR_EVENT_CREATION_FAILED) { + eventFactory.create(datadogContext, rumContext, dataPoints) } ?: return@withWriteContext writeScope { batchWriter -> safeCall(ERROR_FLUSH_FAILED) { - if (dataWriter.write(batchWriter, json, EventType.DEFAULT)) { - insightsCollector.onTimeseries(json.timeseriesName) + if (dataWriter.write(batchWriter, event, EventType.DEFAULT)) { + insightsCollector.onTimeseries(eventFactory.eventName) } } } @@ -61,7 +67,7 @@ internal class Pipeline( private inline fun safeCall(message: String, block: () -> R): R? = try { block() } catch (@Suppress("TooGenericExceptionCaught") t: Throwable) { - internalLogger.log( + sdkCore.internalLogger.log( level = InternalLogger.Level.ERROR, targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), messageBuilder = { message }, @@ -72,10 +78,6 @@ internal class Pipeline( private companion object { private const val ERROR_FLUSH_FAILED = "Timeseries flush failed" - private const val ERROR_SERIALIZATION_FAILED = "Timeseries serialization failed" - private val JsonObject.timeseriesName: String - get() = getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - ?.get(TimeseriesAttributes.KEY_NAME) - ?.asString.orEmpty() + private const val ERROR_EVENT_CREATION_FAILED = "Timeseries event creation failed" } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesCollector.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesCollector.kt index 93fc71ec3f..8be984d7fe 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesCollector.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesCollector.kt @@ -7,6 +7,7 @@ package com.datadog.android.rum.internal.timeseries import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.scope.RumViewType import com.datadog.tools.annotation.NoOpImplementation @@ -18,6 +19,9 @@ internal interface TimeseriesCollector { @NoOpImplementation interface Factory { - fun create(applicationId: String, sessionId: String, sessionType: RumSessionType): TimeseriesCollector + fun create( + sessionType: RumSessionType, + rumContext: RumContext + ): TimeseriesCollector } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/factory/CpuEventFactory.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/factory/CpuEventFactory.kt new file mode 100644 index 0000000000..739adf8c96 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/factory/CpuEventFactory.kt @@ -0,0 +1,110 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.factory + +import com.datadog.android.api.InternalLogger +import com.datadog.android.api.context.DatadogContext +import com.datadog.android.core.internal.utils.DdTagsUtils +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.FeaturesContextResolver +import com.datadog.android.rum.internal.domain.InfoProvider +import com.datadog.android.rum.internal.domain.RumContext +import com.datadog.android.rum.internal.domain.battery.BatteryInfo +import com.datadog.android.rum.internal.domain.display.DisplayInfo +import com.datadog.android.rum.internal.domain.scope.resolveSessionReplaySampleRate +import com.datadog.android.rum.internal.domain.scope.resolveTraceSampleRate +import com.datadog.android.rum.internal.domain.scope.toTimeseriesCpuDevice +import com.datadog.android.rum.internal.domain.scope.toTimeseriesCpuOs +import com.datadog.android.rum.internal.domain.scope.toTimeseriesCpuSessionPrecondition +import com.datadog.android.rum.internal.domain.scope.tryFromSource +import com.datadog.android.rum.internal.timeseries.DataPoint +import com.datadog.android.rum.internal.toTimeseriesCpuSessionType +import com.datadog.android.rum.model.TimeseriesCpuEvent +import java.util.UUID + +@Suppress("LongParameterList") +internal class CpuEventFactory( + private val sessionType: RumSessionType, + private val timeProvider: TimeProvider, + private val batteryInfoProvider: InfoProvider, + private val displayInfoProvider: InfoProvider, + private val internalLogger: InternalLogger, + private val featuresContextResolver: FeaturesContextResolver = FeaturesContextResolver() +) : EventFactory { + + override val eventName: String = "cpu" + + @Suppress("LongMethod") + override fun create( + datadogContext: DatadogContext, + rumContext: RumContext, + dataPoints: List> + ): TimeseriesCpuEvent? { + if (dataPoints.isEmpty()) return null + + val deviceInfo = datadogContext.deviceInfo + val syntheticsAttribute = if ( + rumContext.syntheticsTestId.isNullOrBlank() || + rumContext.syntheticsResultId.isNullOrBlank() + ) { + null + } else { + TimeseriesCpuEvent.Synthetics( + testId = rumContext.syntheticsTestId, + resultId = rumContext.syntheticsResultId + ) + } + + return TimeseriesCpuEvent( + dd = TimeseriesCpuEvent.Dd( + session = TimeseriesCpuEvent.DdSession( + sessionPrecondition = rumContext.sessionStartReason.toTimeseriesCpuSessionPrecondition() + ), + configuration = TimeseriesCpuEvent.Configuration( + sessionSampleRate = rumContext.sessionSampleRate, + sessionReplaySampleRate = datadogContext.resolveSessionReplaySampleRate(), + traceSampleRate = datadogContext.resolveTraceSampleRate() + ) + ), + application = TimeseriesCpuEvent.Application( + id = rumContext.applicationId, + currentLocale = deviceInfo.localeInfo.currentLocale + ), + session = TimeseriesCpuEvent.TimeseriesCpuEventSession( + id = rumContext.sessionId, + type = sessionType.toTimeseriesCpuSessionType(), + hasReplay = featuresContextResolver.resolveViewHasReplay( + datadogContext, + rumContext.viewId.orEmpty() + ) + ), + synthetics = syntheticsAttribute, + source = TimeseriesCpuEvent.Source.tryFromSource(datadogContext.source, internalLogger), + date = timeProvider.getServerTimestampMillis(), + version = datadogContext.version, + service = datadogContext.service, + buildVersion = datadogContext.versionCode.toString(), + buildId = datadogContext.appBuildId, + ddtags = DdTagsUtils.toDdTagsString(datadogContext), + os = deviceInfo.toTimeseriesCpuOs(), + device = deviceInfo.toTimeseriesCpuDevice( + batteryInfo = batteryInfoProvider.getState(), + displayInfo = displayInfoProvider.getState() + ), + timeseries = TimeseriesCpuEvent.Timeseries( + id = UUID.randomUUID().toString(), + start = dataPoints.firstOrNull()?.timestampNs ?: 0L, + end = dataPoints.lastOrNull()?.timestampNs ?: 0L, + data = TimeseriesCpuEvent.Data( + timestamps = dataPoints.map { it.timestampNs }, + values = TimeseriesCpuEvent.Values(dataPoints.map { it.value }) + ) + ) + ) + } +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/factory/EventFactory.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/factory/EventFactory.kt new file mode 100644 index 0000000000..b6008fa194 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/factory/EventFactory.kt @@ -0,0 +1,21 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.factory + +import com.datadog.android.api.context.DatadogContext +import com.datadog.android.rum.internal.domain.RumContext +import com.datadog.android.rum.internal.timeseries.DataPoint + +internal interface EventFactory { + val eventName: String + + fun create( + datadogContext: DatadogContext, + rumContext: RumContext, + dataPoints: List> + ): E? +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/factory/MemoryEventFactory.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/factory/MemoryEventFactory.kt new file mode 100644 index 0000000000..c597ab7121 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/factory/MemoryEventFactory.kt @@ -0,0 +1,123 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.factory + +import com.datadog.android.api.InternalLogger +import com.datadog.android.api.context.DatadogContext +import com.datadog.android.core.internal.utils.DdTagsUtils +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.FeaturesContextResolver +import com.datadog.android.rum.internal.domain.InfoProvider +import com.datadog.android.rum.internal.domain.RumContext +import com.datadog.android.rum.internal.domain.battery.BatteryInfo +import com.datadog.android.rum.internal.domain.display.DisplayInfo +import com.datadog.android.rum.internal.domain.scope.resolveSessionReplaySampleRate +import com.datadog.android.rum.internal.domain.scope.resolveTraceSampleRate +import com.datadog.android.rum.internal.domain.scope.toTimeseriesMemoryDevice +import com.datadog.android.rum.internal.domain.scope.toTimeseriesMemoryOs +import com.datadog.android.rum.internal.domain.scope.toTimeseriesMemorySessionPrecondition +import com.datadog.android.rum.internal.domain.scope.tryFromSource +import com.datadog.android.rum.internal.timeseries.DataPoint +import com.datadog.android.rum.internal.toTimeseriesMemorySessionType +import com.datadog.android.rum.model.TimeseriesMemoryEvent +import java.util.UUID + +@Suppress("LongParameterList") +internal class MemoryEventFactory( + private val sessionType: RumSessionType, + private val totalRamBytes: Long, + private val timeProvider: TimeProvider, + private val batteryInfoProvider: InfoProvider, + private val displayInfoProvider: InfoProvider, + private val internalLogger: InternalLogger, + private val featuresContextResolver: FeaturesContextResolver = FeaturesContextResolver() +) : EventFactory { + + override val eventName: String = "memory" + + @Suppress("LongMethod") + override fun create( + datadogContext: DatadogContext, + rumContext: RumContext, + dataPoints: List> + ): TimeseriesMemoryEvent? { + if (totalRamBytes <= 0L || dataPoints.isEmpty()) return null + + val deviceInfo = datadogContext.deviceInfo + val syntheticsAttribute = if ( + rumContext.syntheticsTestId.isNullOrBlank() || + rumContext.syntheticsResultId.isNullOrBlank() + ) { + null + } else { + TimeseriesMemoryEvent.Synthetics( + testId = rumContext.syntheticsTestId, + resultId = rumContext.syntheticsResultId + ) + } + + return TimeseriesMemoryEvent( + dd = TimeseriesMemoryEvent.Dd( + session = TimeseriesMemoryEvent.DdSession( + sessionPrecondition = rumContext.sessionStartReason.toTimeseriesMemorySessionPrecondition() + ), + configuration = TimeseriesMemoryEvent.Configuration( + sessionSampleRate = rumContext.sessionSampleRate, + sessionReplaySampleRate = datadogContext.resolveSessionReplaySampleRate(), + traceSampleRate = datadogContext.resolveTraceSampleRate() + ) + ), + application = TimeseriesMemoryEvent.Application( + id = rumContext.applicationId, + currentLocale = deviceInfo.localeInfo.currentLocale + ), + session = TimeseriesMemoryEvent.TimeseriesMemoryEventSession( + id = rumContext.sessionId, + type = sessionType.toTimeseriesMemorySessionType(), + hasReplay = featuresContextResolver.resolveViewHasReplay( + datadogContext, + rumContext.viewId.orEmpty() + ) + ), + synthetics = syntheticsAttribute, + source = TimeseriesMemoryEvent.Source.tryFromSource(datadogContext.source, internalLogger), + date = timeProvider.getServerTimestampMillis(), + version = datadogContext.version, + service = datadogContext.service, + buildVersion = datadogContext.versionCode.toString(), + buildId = datadogContext.appBuildId, + ddtags = DdTagsUtils.toDdTagsString(datadogContext), + os = deviceInfo.toTimeseriesMemoryOs(), + device = deviceInfo.toTimeseriesMemoryDevice( + batteryInfo = batteryInfoProvider.getState(), + displayInfo = displayInfoProvider.getState() + ), + timeseries = TimeseriesMemoryEvent.Timeseries( + id = UUID.randomUUID().toString(), + start = dataPoints.firstOrNull()?.timestampNs ?: 0L, + end = dataPoints.lastOrNull()?.timestampNs ?: 0L, + data = TimeseriesMemoryEvent.Data( + timestamps = dataPoints.map { it.timestampNs }, + values = TimeseriesMemoryEvent.Values( + memoryPercent = dataPoints.map { it.value / totalRamBytes * PERCENT_FACTOR }, + memoryFootprint = dataPoints.map { it.value / BYTES_IN_KB } + ) + ) + ) + ) + } + + private companion object { + const val PERCENT_FACTOR = 100.0 + + // Must match the factor used in MemoryVitalReader when converting VmRSS kB to bytes. + // Datadog memory vitals use decimal (SI) units, so 1000 (not 1024) keeps the emitted + // memory_footprint consistent with the collected value and with memory_percent. + const val BYTES_IN_KB = 1000 + } +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReader.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReader.kt index d48d4fe76f..efcf4a1dde 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReader.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReader.kt @@ -22,20 +22,24 @@ import com.datadog.android.rum.internal.vitals.CpuStatReader */ internal class CpuDatapointReader( private val cpuStatReader: CpuStatReader, - private val cpuTimeProvider: TimeProvider, + private val timeProvider: TimeProvider, override val intervalMs: Long, private val availableProcessors: Int = Runtime.getRuntime().availableProcessors() -) : DataPointsReader(cpuTimeProvider) { +) : DataPointsReader(timeProvider) { private var lastUtime: Double? = null private var lastNs: Long? = null @WorkerThread override fun readValue() = cpuStatReader.readActiveTime()?.let { currentCpuTicks -> - val nowNs = cpuTimeProvider.getDeviceElapsedTimeNanos() + val nowNs = timeProvider.getDeviceElapsedTimeNanos() val (prevCpuTicks, prevNs) = updateValues(currentCpuTicks, nowNs) if (prevCpuTicks == null || prevNs == null) return null val elapsedMs = ((nowNs - prevNs) / NS_PER_MS).coerceAtLeast(1L) + // Sampling is suspended while the app is not in the foreground, so a gap much longer than + // the sampling interval means this delta spans that pause and would smear background CPU + // time over the first foreground sample. Drop it; the read above already re-baselined. + if (elapsedMs > intervalMs * MAX_GAP_FACTOR) return null val corePercent = (currentCpuTicks - prevCpuTicks) * MS_PER_SECOND / elapsedMs (corePercent / availableProcessors.coerceAtLeast(1)).coerceIn(0.0, MAX_CPU_PERCENT) } @@ -55,5 +59,8 @@ internal class CpuDatapointReader( private const val MS_PER_SECOND = 1000.0 private const val NS_PER_MS = 1_000_000L private const val MAX_CPU_PERCENT = 100.0 + + // How many sampling intervals a delta may span before it is treated as a gap. + internal const val MAX_GAP_FACTOR = 2 } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/DataPointsReader.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/DataPointsReader.kt index f936571168..d00bb7bbce 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/DataPointsReader.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/provider/DataPointsReader.kt @@ -22,7 +22,7 @@ internal abstract class DataPointsReader(private val timeProvider: Time open fun read(): DataPoint? = readValue()?.let { value -> DataPoint( value = value, - timestampNs = TimeUnit.MILLISECONDS.toNanos(timeProvider.getDeviceTimestampMillis()) + timestampNs = TimeUnit.MILLISECONDS.toNanos(timeProvider.getServerTimestampMillis()) ) } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt deleted file mode 100644 index 64d7fdf040..0000000000 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializer.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.rum.internal.timeseries.serializer - -import com.datadog.android.api.context.DatadogContext -import com.datadog.android.internal.time.TimeProvider -import com.datadog.android.rum.RumSessionType -import com.datadog.android.rum.internal.timeseries.DataPoint -import com.datadog.android.rum.internal.toTimeseriesCpuSessionType -import com.datadog.android.rum.model.TimeseriesCpuEvent -import com.google.gson.JsonObject -import java.util.UUID - -@Suppress("LongParameterList") -internal class CpuEventSerializer( - private val sessionId: String, - private val applicationId: String, - private val sessionType: RumSessionType, - private val timeProvider: TimeProvider -) : JsonSerializer { - - override fun serialize(datadogContext: DatadogContext, dataPoints: List>): JsonObject? { - if (dataPoints.isEmpty()) return null - return TimeseriesCpuEvent( - dd = TimeseriesCpuEvent.Dd(), - application = TimeseriesCpuEvent.Application(id = applicationId), - session = TimeseriesCpuEvent.Session(id = sessionId, type = sessionType.toTimeseriesCpuSessionType()), - source = TimeseriesCpuEvent.Source.ANDROID, - date = timeProvider.getDeviceTimestampMillis(), - version = datadogContext.version, - service = datadogContext.service, - timeseries = TimeseriesCpuEvent.Timeseries( - id = UUID.randomUUID().toString(), - start = dataPoints.firstOrNull()?.timestampNs ?: 0L, - end = dataPoints.lastOrNull()?.timestampNs ?: 0L, - data = TimeseriesCpuEvent.Data( - timestamps = dataPoints.map { it.timestampNs }, - values = TimeseriesCpuEvent.Values(dataPoints.map { it.value }) - ) - ) - ).toJson().asJsonObject - } -} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt deleted file mode 100644 index 7d9d1c4708..0000000000 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializer.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.rum.internal.timeseries.serializer - -import com.datadog.android.api.context.DatadogContext -import com.datadog.android.internal.time.TimeProvider -import com.datadog.android.rum.RumSessionType -import com.datadog.android.rum.internal.timeseries.DataPoint -import com.datadog.android.rum.internal.toTimeseriesMemorySessionType -import com.datadog.android.rum.model.TimeseriesMemoryEvent -import com.google.gson.JsonObject -import java.util.UUID - -@Suppress("LongParameterList") -internal class MemoryEventSerializer( - private val sessionId: String, - private val applicationId: String, - private val sessionType: RumSessionType, - private val totalRamBytes: Long, - private val timeProvider: TimeProvider -) : JsonSerializer { - - override fun serialize(datadogContext: DatadogContext, dataPoints: List>): JsonObject? { - if (totalRamBytes <= 0L || dataPoints.isEmpty()) return null - return TimeseriesMemoryEvent( - dd = TimeseriesMemoryEvent.Dd(), - application = TimeseriesMemoryEvent.Application(id = applicationId), - session = TimeseriesMemoryEvent.Session(id = sessionId, type = sessionType.toTimeseriesMemorySessionType()), - source = TimeseriesMemoryEvent.Source.ANDROID, - date = timeProvider.getDeviceTimestampMillis(), - version = datadogContext.version, - service = datadogContext.service, - timeseries = TimeseriesMemoryEvent.Timeseries( - id = UUID.randomUUID().toString(), - start = dataPoints.firstOrNull()?.timestampNs ?: 0L, - end = dataPoints.lastOrNull()?.timestampNs ?: 0L, - data = TimeseriesMemoryEvent.Data( - timestamps = dataPoints.map { it.timestampNs }, - values = TimeseriesMemoryEvent.Values( - memoryPercent = dataPoints.map { it.value / totalRamBytes * PERCENT_FACTOR }, - memoryFootprint = dataPoints.map { it.value / BYTES_IN_KB } - ) - ) - ) - ).toJson().asJsonObject - } - - private companion object { - const val PERCENT_FACTOR = 100.0 - - // Must match the factor used in MemoryVitalReader when converting VmRSS kB to bytes. - // Datadog memory vitals use decimal (SI) units, so 1000 (not 1024) keeps the emitted - // memory_footprint consistent with the collected value and with memory_percent. - const val BYTES_IN_KB = 1000 - } -} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/TimeseriesAttributes.kt similarity index 61% rename from features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt rename to features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/TimeseriesAttributes.kt index c796d28f4c..fbdefdc52b 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/JsonSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/TimeseriesAttributes.kt @@ -6,14 +6,6 @@ package com.datadog.android.rum.internal.timeseries.serializer -import com.datadog.android.api.context.DatadogContext -import com.datadog.android.rum.internal.timeseries.DataPoint -import com.google.gson.JsonObject - -internal fun interface JsonSerializer { - fun serialize(datadogContext: DatadogContext, dataPoints: List>): JsonObject? -} - internal object TimeseriesAttributes { const val KEY_TIMESERIES = "timeseries" const val KEY_SCHEMA = "schema" diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt index 2b9e762483..bff085a97a 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt @@ -5,11 +5,7 @@ */ package com.datadog.android.rum.timeseries -import androidx.annotation.IntRange import com.datadog.android.rum.ExperimentalRumApi -import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.DEFAULT_BUFFER_SIZE -import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.DEFAULT_INTERVAL_MS -import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.MIN_INTERVAL_MS /** * Configuration for memory and CPU timeseries collection. @@ -17,8 +13,7 @@ import com.datadog.android.rum.timeseries.TimeseriesConfiguration.Companion.MIN_ * Use [Builder] to create an instance. */ class TimeseriesConfiguration internal constructor( - internal val bufferSize: Int, - internal val intervalMs: Long, + internal val enabledTypes: Set, internal val collectInBackground: Boolean ) { @@ -28,30 +23,22 @@ class TimeseriesConfiguration internal constructor( @ExperimentalRumApi class Builder { - private var bufferSize: Int = DEFAULT_BUFFER_SIZE - private var intervalMs: Long = DEFAULT_INTERVAL_MS - private var collectInBackground: Boolean = false + @Suppress("UnsafeThirdPartyFunctionCall") // Kotlin Array.toSet() is safe for enum values. + private var enabledTypes: Set = TimeseriesType.values().toSet() - /** - * Sets the number of samples accumulated per pipeline before sending a batch event. - * Must be > 0; out-of-range values fall back to [DEFAULT_BUFFER_SIZE]. - * Defaults to [DEFAULT_BUFFER_SIZE] (≈120 seconds at the default 1 s interval). - */ - fun setBufferSize( - @IntRange(from = 1, to = Int.MAX_VALUE.toLong()) bufferSize: Int - ): Builder = apply { - this.bufferSize = if (bufferSize > 0) bufferSize else DEFAULT_BUFFER_SIZE - } + private var collectInBackground: Boolean = false /** - * Sets the sampling interval in milliseconds. - * Must be ≥ [MIN_INTERVAL_MS]; out-of-range values fall back to [DEFAULT_INTERVAL_MS]. - * Defaults to [DEFAULT_INTERVAL_MS]. + * Restricts collection to the provided timeseries types. + * + * By default, all supported timeseries types are collected. + * Passing an empty array disables collection of every timeseries type. + * + * @param types the timeseries types to collect. */ - fun setIntervalMs( - @IntRange(from = MIN_INTERVAL_MS, to = Long.MAX_VALUE) intervalMs: Long - ): Builder = apply { - this.intervalMs = if (intervalMs >= MIN_INTERVAL_MS) intervalMs else DEFAULT_INTERVAL_MS + fun collectOnly(vararg types: TimeseriesType): Builder = apply { + @Suppress("UnsafeThirdPartyFunctionCall") // Kotlin Array.toSet() is safe for enum values. + enabledTypes = types.toSet() } /** @@ -64,8 +51,7 @@ class TimeseriesConfiguration internal constructor( /** Builds a [TimeseriesConfiguration] from the current builder state. */ fun build(): TimeseriesConfiguration = TimeseriesConfiguration( - bufferSize = bufferSize, - intervalMs = intervalMs, + enabledTypes = enabledTypes, collectInBackground = collectInBackground ) } @@ -81,8 +67,5 @@ class TimeseriesConfiguration internal constructor( /** Default sampling interval in milliseconds. */ internal const val DEFAULT_INTERVAL_MS: Long = 1000L - - /** Minimum allowed sampling interval in milliseconds. */ - internal const val MIN_INTERVAL_MS: Long = 100L } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesType.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesType.kt new file mode 100644 index 0000000000..56e41ee01a --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesType.kt @@ -0,0 +1,19 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.timeseries + +/** + * Type of device timeseries that can be collected by RUM. + */ +enum class TimeseriesType { + + /** CPU usage timeseries. */ + CPU, + + /** Memory usage timeseries. */ + MEMORY +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumConfigurationBuilderTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumConfigurationBuilderTest.kt index 05fb33144c..b9be98d4a3 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumConfigurationBuilderTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumConfigurationBuilderTest.kt @@ -31,6 +31,7 @@ import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.model.VitalAppLaunchEvent import com.datadog.android.rum.model.VitalOperationStepEvent import com.datadog.android.rum.timeseries.TimeseriesConfiguration +import com.datadog.android.rum.timeseries.TimeseriesType import com.datadog.android.rum.tracking.ActionTrackingStrategy import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy import com.datadog.android.rum.tracking.InteractionPredicate @@ -743,9 +744,11 @@ internal class RumConfigurationBuilderTest { } @Test - fun `M store provided configuration W setTimeseriesConfiguration(config)`() { + fun `M store provided configuration W setTimeseriesConfiguration(config)`(forge: Forge) { // Given - val fakeConfig = TimeseriesConfiguration.Builder().setBufferSize(10).setIntervalMs(500L).build() + val fakeConfig = TimeseriesConfiguration.Builder() + .collectOnly(forge.aValueFrom(TimeseriesType::class.java)) + .build() // When val rumConfiguration = testedBuilder.setTimeseriesConfiguration(fakeConfig).build() @@ -754,27 +757,4 @@ internal class RumConfigurationBuilderTest { assertThat(rumConfiguration.featureConfiguration.timeseriesConfiguration) .isSameAs(fakeConfig) } - - @Test - fun `M store default configuration W setTimeseriesConfiguration(default)`() { - // When - val rumConfiguration = testedBuilder - .setTimeseriesConfiguration(TimeseriesConfiguration.Builder().build()) - .build() - - // Then - assertThat(rumConfiguration.featureConfiguration.timeseriesConfiguration).isNotNull - } - - @Test - fun `M nullify configuration W setTimeseriesConfiguration(null)`() { - // Given - testedBuilder.setTimeseriesConfiguration(TimeseriesConfiguration.Builder().build()) - - // When - val rumConfiguration = testedBuilder.setTimeseriesConfiguration(null).build() - - // Then - assertThat(rumConfiguration.featureConfiguration.timeseriesConfiguration).isNull() - } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ActionEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ActionEventAssert.kt index 93ab7dc5b9..1c186aa40b 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ActionEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ActionEventAssert.kt @@ -157,19 +157,19 @@ internal class ActionEventAssert(actual: ActionEvent) : expectedName: String?, expectedUrl: String? ): ActionEventAssert { - assertThat(actual.view.id) + assertThat(actual.view?.id) .overridingErrorMessage( - "Expected event data to have view.id $expectedId but was ${actual.view.id}" + "Expected event data to have view.id $expectedId but was ${actual.view?.id}" ) .isEqualTo(expectedId.orEmpty()) - assertThat(actual.view.name) + assertThat(actual.view?.name) .overridingErrorMessage( - "Expected event data to have view.name $expectedName but was ${actual.view.name}" + "Expected event data to have view.name $expectedName but was ${actual.view?.name}" ) .isEqualTo(expectedName) - assertThat(actual.view.url) + assertThat(actual.view?.url) .overridingErrorMessage( - "Expected event data to have view.url $expectedUrl but was ${actual.view.url}" + "Expected event data to have view.url $expectedUrl but was ${actual.view?.url}" ) .isEqualTo(expectedUrl.orEmpty()) return this @@ -178,21 +178,21 @@ internal class ActionEventAssert(actual: ActionEvent) : fun hasView( expected: RumContext ): ActionEventAssert { - assertThat(actual.view.id) + assertThat(actual.view?.id) .overridingErrorMessage( - "Expected event data to have view.id ${expected.viewId} but was ${actual.view.id}" + "Expected event data to have view.id ${expected.viewId} but was ${actual.view?.id}" ) .isEqualTo(expected.viewId.orEmpty()) - assertThat(actual.view.name) + assertThat(actual.view?.name) .overridingErrorMessage( "Expected event data to have view.name ${expected.viewName} " + - "but was ${actual.view.name}" + "but was ${actual.view?.name}" ) .isEqualTo(expected.viewName) - assertThat(actual.view.url) + assertThat(actual.view?.url) .overridingErrorMessage( "Expected event data to have view.url ${expected.viewUrl} " + - "but was ${actual.view.url}" + "but was ${actual.view?.url}" ) .isEqualTo(expected.viewUrl.orEmpty()) return this diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ErrorEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ErrorEventAssert.kt index 2625234924..c7d08d0fe0 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ErrorEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ErrorEventAssert.kt @@ -252,41 +252,41 @@ internal class ErrorEventAssert(actual: ErrorEvent) : expectedName: String?, expectedUrl: String? ): ErrorEventAssert { - assertThat(actual.view.id) + assertThat(actual.view?.id) .overridingErrorMessage( - "Expected event data to have view.id $expectedId but was ${actual.view.id}" + "Expected event data to have view.id $expectedId but was ${actual.view?.id}" ) .isEqualTo(expectedId.orEmpty()) - assertThat(actual.view.name) + assertThat(actual.view?.name) .overridingErrorMessage( - "Expected event data to have view.name $expectedName but was ${actual.view.name}" + "Expected event data to have view.name $expectedName but was ${actual.view?.name}" ) .isEqualTo(expectedName) - assertThat(actual.view.url) + assertThat(actual.view?.url) .overridingErrorMessage( - "Expected event data to have view.url $expectedUrl but was ${actual.view.url}" + "Expected event data to have view.url $expectedUrl but was ${actual.view?.url}" ) .isEqualTo(expectedUrl.orEmpty()) return this } fun hasView(expected: RumContext): ErrorEventAssert { - assertThat(actual.view.id) + assertThat(actual.view?.id) .overridingErrorMessage( "Expected event data to have view.id ${expected.viewId} " + - "but was ${actual.view.id}" + "but was ${actual.view?.id}" ) .isEqualTo(expected.viewId.orEmpty()) - assertThat(actual.view.name) + assertThat(actual.view?.name) .overridingErrorMessage( "Expected event data to have view.name ${expected.viewName} " + - "but was ${actual.view.name}" + "but was ${actual.view?.name}" ) .isEqualTo(expected.viewName) - assertThat(actual.view.url) + assertThat(actual.view?.url) .overridingErrorMessage( "Expected event data to have view.url ${expected.viewUrl} " + - "but was ${actual.view.url}" + "but was ${actual.view?.url}" ) .isEqualTo(expected.viewUrl.orEmpty()) return this diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/LongTaskEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/LongTaskEventAssert.kt index c65cd2807f..143d5f586d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/LongTaskEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/LongTaskEventAssert.kt @@ -180,14 +180,14 @@ internal class LongTaskEventAssert(actual: LongTaskEvent) : } fun hasView(expectedId: String?, expectedUrl: String?): LongTaskEventAssert { - assertThat(actual.view.id) + assertThat(actual.view?.id) .overridingErrorMessage( - "Expected event data to have view.id $expectedId but was ${actual.view.id}" + "Expected event data to have view.id $expectedId but was ${actual.view?.id}" ) .isEqualTo(expectedId.orEmpty()) - assertThat(actual.view.url) + assertThat(actual.view?.url) .overridingErrorMessage( - "Expected event data to have view.id $expectedUrl but was ${actual.view.url}" + "Expected event data to have view.id $expectedUrl but was ${actual.view?.url}" ) .isEqualTo(expectedUrl.orEmpty()) return this diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ResourceEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ResourceEventAssert.kt index 382c050302..74bfc26d79 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ResourceEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/ResourceEventAssert.kt @@ -377,22 +377,22 @@ internal class ResourceEventAssert(actual: ResourceEvent) : } fun hasView(expected: RumContext): ResourceEventAssert { - assertThat(actual.view.id) + assertThat(actual.view?.id) .overridingErrorMessage( "Expected event data to have view.id ${expected.viewId} " + - "but was ${actual.view.id}" + "but was ${actual.view?.id}" ) .isEqualTo(expected.viewId.orEmpty()) - assertThat(actual.view.name) + assertThat(actual.view?.name) .overridingErrorMessage( "Expected event data to have view.name ${expected.viewName} " + - "but was ${actual.view.name}" + "but was ${actual.view?.name}" ) .isEqualTo(expected.viewName) - assertThat(actual.view.url) + assertThat(actual.view?.url) .overridingErrorMessage( "Expected event data to have view.url ${expected.viewUrl} " + - "but was ${actual.view.url}" + "but was ${actual.view?.url}" ) .isEqualTo(expected.viewUrl.orEmpty()) return this diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt deleted file mode 100644 index d03fb25d51..0000000000 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.rum.assertj - -import com.datadog.android.rum.model.TimeseriesCpuEvent -import com.google.gson.JsonObject -import org.assertj.core.api.AbstractObjectAssert -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.data.Offset -import org.junit.jupiter.api.assertDoesNotThrow -import java.util.UUID - -internal class TimeseriesCpuEventAssert private constructor( - actual: TimeseriesCpuEvent -) : AbstractObjectAssert( - actual, - TimeseriesCpuEventAssert::class.java -) { - - fun hasApplicationId(expected: String): TimeseriesCpuEventAssert = apply { - assertThat(actual.application.id) - .overridingErrorMessage { - "Expected event to have application.id $expected but was ${actual.application.id}" - } - .isEqualTo(expected) - } - - fun hasSessionId(expected: String): TimeseriesCpuEventAssert = apply { - assertThat(actual.session.id) - .overridingErrorMessage { "Expected event to have session.id $expected but was ${actual.session.id}" } - .isEqualTo(expected) - } - - fun hasSessionType(expected: TimeseriesCpuEvent.Type): TimeseriesCpuEventAssert = apply { - assertThat(actual.session.type) - .overridingErrorMessage { "Expected event to have session.type $expected but was ${actual.session.type}" } - .isEqualTo(expected) - } - - fun hasService(expected: String): TimeseriesCpuEventAssert = apply { - assertThat(actual.service) - .overridingErrorMessage { "Expected event to have service $expected but was ${actual.service}" } - .isEqualTo(expected) - } - - fun hasVersion(expected: String): TimeseriesCpuEventAssert = apply { - assertThat(actual.version) - .overridingErrorMessage { "Expected event to have version $expected but was ${actual.version}" } - .isEqualTo(expected) - } - - fun hasDate(expected: Long): TimeseriesCpuEventAssert = apply { - assertThat(actual.date) - .overridingErrorMessage { "Expected event to have date $expected but was ${actual.date}" } - .isEqualTo(expected) - } - - fun hasTimeseriesSchema(expected: String): TimeseriesCpuEventAssert = apply { - assertThat(actual.timeseries.schema) - .overridingErrorMessage { - "Expected event to have timeseries.schema $expected but was ${actual.timeseries.schema}" - } - .isEqualTo(expected) - } - - fun hasTimeseriesStart(expected: Long): TimeseriesCpuEventAssert = apply { - assertThat(actual.timeseries.start) - .overridingErrorMessage { - "Expected event to have timeseries.start $expected but was ${actual.timeseries.start}" - } - .isEqualTo(expected) - } - - fun hasTimeseriesEnd(expected: Long): TimeseriesCpuEventAssert = apply { - assertThat(actual.timeseries.end) - .overridingErrorMessage { - "Expected event to have timeseries.end $expected but was ${actual.timeseries.end}" - } - .isEqualTo(expected) - } - - fun hasValidTimeseriesId(): TimeseriesCpuEventAssert = apply { - assertDoesNotThrow("Expected timeseries.id to be a valid UUID but was ${actual.timeseries.id}") { - UUID.fromString(actual.timeseries.id) - } - } - - fun hasTimeseriesDataCount(expected: Int): TimeseriesCpuEventAssert = apply { - assertThat(actual.timeseries.data.timestamps) - .overridingErrorMessage { - "Expected event to have timeseries.data of size $expected " + - "but was ${actual.timeseries.data.timestamps.size}" - } - .hasSize(expected) - } - - fun hasCpuUsage(expected: Double, offset: Offset, position: Int = 0): TimeseriesCpuEventAssert = apply { - val cpuUsage = actual.timeseries.data.values.cpuUsage.getOrNull(position)?.toDouble() - assertThat(cpuUsage) - .overridingErrorMessage { "Expected first data point cpu_usage to be close to $expected but was $cpuUsage" } - .isNotNull() - .isCloseTo(expected, offset) - } - - companion object { - internal fun assertThat(event: TimeseriesCpuEvent): TimeseriesCpuEventAssert = - TimeseriesCpuEventAssert(event) - - internal fun assertThat(json: JsonObject): TimeseriesCpuEventAssert = - assertThat(TimeseriesCpuEvent.fromJsonObject(json)) - } -} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalAppLaunchEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalAppLaunchEventAssert.kt index e084faddca..f6191dde13 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalAppLaunchEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalAppLaunchEventAssert.kt @@ -98,25 +98,25 @@ internal class VitalAppLaunchEventAssert( } fun hasViewId(expectedId: String) = apply { - assertThat(actual.view.id) + assertThat(actual.view?.id) .overridingErrorMessage( - "Expected event data to have view.id $expectedId but was ${actual.view.id}" + "Expected event data to have view.id $expectedId but was ${actual.view?.id}" ) .isEqualTo(expectedId) } fun hasName(expected: String?) = apply { - assertThat(actual.view.name) + assertThat(actual.view?.name) .overridingErrorMessage( - "Expected event data to have view.name $expected but was ${actual.view.name}" + "Expected event data to have view.name $expected but was ${actual.view?.name}" ) .isEqualTo(expected) } fun hasUrl(expected: String) = apply { - assertThat(actual.view.url) + assertThat(actual.view?.url) .overridingErrorMessage( - "Expected event data to have view.url $expected but was ${actual.view.url}" + "Expected event data to have view.url $expected but was ${actual.view?.url}" ) .isEqualTo(expected) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalEventAssert.kt index 7805c55fb3..6342a6f385 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalEventAssert.kt @@ -107,25 +107,25 @@ internal class VitalEventAssert( } fun hasViewId(expectedId: String) = apply { - assertThat(actual.view.id) + assertThat(actual.view?.id) .overridingErrorMessage( - "Expected event data to have view.id $expectedId but was ${actual.view.id}" + "Expected event data to have view.id $expectedId but was ${actual.view?.id}" ) .isEqualTo(expectedId) } fun hasName(expected: String) = apply { - assertThat(actual.view.name) + assertThat(actual.view?.name) .overridingErrorMessage( - "Expected event data to have view.name $expected but was ${actual.view.name}" + "Expected event data to have view.name $expected but was ${actual.view?.name}" ) .isEqualTo(expected) } fun hasUrl(expected: String) = apply { - assertThat(actual.view.url) + assertThat(actual.view?.url) .overridingErrorMessage( - "Expected event data to have view.url $expected but was ${actual.view.url}" + "Expected event data to have view.url $expected but was ${actual.view?.url}" ) .isEqualTo(expected) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt index bb3139225d..a01e91dfa7 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt @@ -33,10 +33,8 @@ import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumAttributes import com.datadog.android.rum.RumErrorSource -import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.assertj.RumFeatureAssert import com.datadog.android.rum.configuration.VitalsUpdateFrequency -import com.datadog.android.rum.internal.RumFeature.Companion.MEMORY_TIMESERIES_DISABLED_MESSAGE import com.datadog.android.rum.internal.RumFeature.Companion.SLOW_FRAMES_MONITORING_DISABLED_MESSAGE import com.datadog.android.rum.internal.RumFeature.Companion.SLOW_FRAMES_MONITORING_ENABLED_MESSAGE import com.datadog.android.rum.internal.anr.ANRDetectorRunnable @@ -50,14 +48,14 @@ import com.datadog.android.rum.internal.domain.accessibility.NoOpAccessibilitySn import com.datadog.android.rum.internal.domain.battery.DefaultBatteryInfoProvider import com.datadog.android.rum.internal.domain.display.DefaultDisplayInfoProvider import com.datadog.android.rum.internal.domain.event.RumEventMapper +import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.monitor.AdvancedRumMonitor import com.datadog.android.rum.internal.monitor.DatadogRumMonitor import com.datadog.android.rum.internal.monitor.NoOpAdvancedRumMonitor import com.datadog.android.rum.internal.startup.RumAppStartupDetector import com.datadog.android.rum.internal.thread.NoOpScheduledExecutorService -import com.datadog.android.rum.internal.timeseries.DefaultTimeseriesCollector -import com.datadog.android.rum.internal.timeseries.TimeseriesCollector +import com.datadog.android.rum.internal.timeseries.DefaultTimeseriesCollectorFactory import com.datadog.android.rum.internal.tracking.NoOpInteractionPredicate import com.datadog.android.rum.internal.tracking.NoOpUserActionTrackingStrategy import com.datadog.android.rum.internal.tracking.UserActionTrackingStrategy @@ -859,66 +857,77 @@ internal class RumFeatureTest { } @Test - fun `M flush timeseries before resetting writer W onStop()`() { + fun `M stop active timeseries before resetting writer W onStop()`() { // Given testedFeature.onInitialize(appContext.mockInstance) - val mockDatadogMonitor = mock() + val stubDatadogMonitor = mock() GlobalRumMonitor.clear() - GlobalRumMonitor.registerIfAbsent(mockDatadogMonitor, mockSdkCore) + GlobalRumMonitor.registerIfAbsent(stubDatadogMonitor, mockSdkCore) - var writerAtFlushTime: DataWriter? = null + var writerAtStopTime: DataWriter? = null doAnswer { - writerAtFlushTime = testedFeature.dataWriter - }.whenever(mockDatadogMonitor).stopTimeseries() + writerAtStopTime = testedFeature.dataWriter + }.whenever(stubDatadogMonitor).stopTimeseries() // When testedFeature.onStop() - // Then - assertThat(writerAtFlushTime).isNotNull - assertThat(writerAtFlushTime).isNotInstanceOf(NoOpDataWriter::class.java) + // Then — the last timeseries batch must still reach the real writer + assertThat(writerAtStopTime).isNotNull + assertThat(writerAtStopTime).isNotInstanceOf(NoOpDataWriter::class.java) assertThat(testedFeature.dataWriter).isInstanceOf(NoOpDataWriter::class.java) } @Test - fun `M log USER message W timeseries create() { total RAM unavailable }`( - @StringForgery fakeSessionId: String + @OptIn(ExperimentalRumApi::class) + fun `M wire timeseries factory W initialize { timeseries enabled }`( + @LongForgery(min = 1L) fakeTotalRamBytes: Long ) { - // When - createTimeseries(totalRamBytes = 0L, sessionId = fakeSessionId) - - // Then - mockInternalLogger.verifyLog( - InternalLogger.Level.WARN, - InternalLogger.Target.USER, - MEMORY_TIMESERIES_DISABLED_MESSAGE, - onlyOnce = true + // Given + fakeConfiguration = fakeConfiguration.copy( + timeseriesConfiguration = TimeseriesConfiguration.Builder().build() ) - } - - @Test - fun `M not collect memory W timeseries create() { total RAM unavailable }`( - @StringForgery fakeSessionId: String - ) { - // When - val timeseries = createTimeseries(totalRamBytes = 0L, sessionId = fakeSessionId) - - // Then: only the CPU pipeline is created; the memory pipeline is skipped. - check(timeseries is DefaultTimeseriesCollector) - assertThat(timeseries.pipelines).hasSize(1) - } + testedFeature = RumFeature( + mockSdkCore, + fakeApplicationId.toString(), + fakeConfiguration, + lateCrashReporterFactory = { mockLateCrashReporter } + ) + val stubActivityManager = mock() + doAnswer { invocation -> + invocation.getArgument(0).totalMem = fakeTotalRamBytes + null + }.whenever(stubActivityManager).getMemoryInfo(any()) + whenever( + appContext.mockInstance.getSystemService(Context.ACTIVITY_SERVICE) + ) doReturn stubActivityManager - @Test - fun `M collect memory W timeseries create() { total RAM available }`( - @StringForgery fakeSessionId: String, - @LongForgery(min = 1L) fakeTotalRamBytes: Long - ) { // When - val timeseries = createTimeseries(totalRamBytes = fakeTotalRamBytes, sessionId = fakeSessionId) + testedFeature.onInitialize(appContext.mockInstance) - // Then: both the CPU and memory pipelines are created. - check(timeseries is DefaultTimeseriesCollector) - assertThat(timeseries.pipelines).hasSize(2) + // Then + val timeseriesFactory = testedFeature.timeseriesCollectorFactory + check(timeseriesFactory is DefaultTimeseriesCollectorFactory) + assertThat(timeseriesFactory.getFieldValue, DefaultTimeseriesCollectorFactory>("dataWriter")) + .isSameAs(testedFeature.dataWriter) + assertThat( + timeseriesFactory.getFieldValue("insightsCollector") + ) + .isSameAs(testedFeature.insightsCollector) + assertThat( + timeseriesFactory + .getFieldValue("scheduledExecutorService") + ).isSameAs(testedFeature.vitalExecutorService) + assertThat(timeseriesFactory.getFieldValue("totalRamBytes")) + .isEqualTo(fakeTotalRamBytes) + assertThat( + timeseriesFactory.getFieldValue, DefaultTimeseriesCollectorFactory>("batteryInfoProvider") + ) + .isSameAs(testedFeature.batteryInfoProvider) + assertThat( + timeseriesFactory.getFieldValue, DefaultTimeseriesCollectorFactory>("displayInfoProvider") + ) + .isSameAs(testedFeature.displayInfoProvider) } @Test @@ -1915,18 +1924,6 @@ internal class RumFeatureTest { ) } - // Builds a timeseries collector for the given total device RAM (in bytes), bypassing the - // ActivityManager read so the skip/collect/log behavior can be exercised deterministically. - @OptIn(ExperimentalRumApi::class) - private fun createTimeseries(totalRamBytes: Long, sessionId: String): TimeseriesCollector = - testedFeature - .createTimeseriesCollectingFactory( - TimeseriesConfiguration.Builder().build(), - totalRamBytes, - testedFeature.configuration.insightsCollector - ) - .create(fakeApplicationId.toString(), sessionId, RumSessionType.USER) - private fun Forge.anApplicationExitInfoList( mustInclude: Int? = null ): List { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumSessionTypeExtTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumSessionTypeExtTest.kt index f5283f6b4e..ac9174b1ac 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumSessionTypeExtTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumSessionTypeExtTest.kt @@ -11,8 +11,8 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent -import com.datadog.android.rum.model.TimeseriesCpuEvent -import com.datadog.android.rum.model.TimeseriesMemoryEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent.TimeseriesCpuEventSessionType +import com.datadog.android.rum.model.TimeseriesMemoryEvent.TimeseriesMemoryEventSessionType import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.model.VitalAppLaunchEvent import com.datadog.android.rum.model.VitalOperationStepEvent @@ -75,14 +75,17 @@ internal class RumSessionTypeExtTest { @MethodSource("toTimeseriesMemorySessionTypeMappings") fun `M map session type W toTimeseriesMemorySessionType()`( input: RumSessionType, - expected: TimeseriesMemoryEvent.Type + expected: TimeseriesMemoryEventSessionType ) { assertThat(input.toTimeseriesMemorySessionType()).isEqualTo(expected) } @ParameterizedTest @MethodSource("toTimeseriesCpuSessionTypeMappings") - fun `M map session type W toTimeseriesCpuSessionType()`(input: RumSessionType, expected: TimeseriesCpuEvent.Type) { + fun `M map session type W toTimeseriesCpuSessionType()`( + input: RumSessionType, + expected: TimeseriesCpuEventSessionType + ) { assertThat(input.toTimeseriesCpuSessionType()).isEqualTo(expected) } @@ -144,14 +147,14 @@ internal class RumSessionTypeExtTest { @JvmStatic fun toTimeseriesMemorySessionTypeMappings(): List = listOf( - Arguments.of(RumSessionType.USER, TimeseriesMemoryEvent.Type.USER), - Arguments.of(RumSessionType.SYNTHETICS, TimeseriesMemoryEvent.Type.SYNTHETICS) + Arguments.of(RumSessionType.USER, TimeseriesMemoryEventSessionType.USER), + Arguments.of(RumSessionType.SYNTHETICS, TimeseriesMemoryEventSessionType.SYNTHETICS) ) @JvmStatic fun toTimeseriesCpuSessionTypeMappings(): List = listOf( - Arguments.of(RumSessionType.USER, TimeseriesCpuEvent.Type.USER), - Arguments.of(RumSessionType.SYNTHETICS, TimeseriesCpuEvent.Type.SYNTHETICS) + Arguments.of(RumSessionType.USER, TimeseriesCpuEventSessionType.USER), + Arguments.of(RumSessionType.SYNTHETICS, TimeseriesCpuEventSessionType.SYNTHETICS) ) } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/RumDataWriterTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/RumDataWriterTest.kt index e951353b12..8abd37a4ff 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/RumDataWriterTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/RumDataWriterTest.kt @@ -17,6 +17,8 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.utils.config.GlobalRumMonitorTestConfiguration import com.datadog.android.rum.utils.forge.Configurator @@ -46,6 +48,7 @@ import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions @@ -204,6 +207,29 @@ internal class RumDataWriterTest { assertThat(captor.firstValue.eventType).isEqualTo("unknown") } + @Test + fun `M resolve timeseries event type W write() { generated timeseries events }`() { + // Given + val fakeEvents = listOf( + mock { on { type } doReturn "timeseries" }, + mock { on { type } doReturn "timeseries" } + ) + fakeEvents.forEach { whenever(mockSerializer.serialize(it)) doReturn fakeSerializedEvent } + + // When + fakeEvents.forEach { testedWriter.write(mockEventBatchWriter, it, fakeEventType) } + + // Then + val captor = argumentCaptor() + verify(mockEventBatchWriter, times(fakeEvents.size)).write( + any(), + anyOrNull(), + any(), + captor.capture() + ) + assertThat(captor.allValues.map { it.eventType }).containsOnly("timeseries") + } + @Test fun `M write data with event meta W write() {View Event}`( @Forgery fakeViewEvent: ViewEvent, diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapperTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapperTest.kt index 04e838179d..d98586c7a8 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapperTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapperTest.kt @@ -12,6 +12,8 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.model.VitalAppLaunchEvent import com.datadog.android.rum.model.VitalOperationStepEvent @@ -35,6 +37,7 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any +import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever @@ -283,6 +286,22 @@ internal class RumEventMapperTest { assertThat(mappedRumEvent).isSameAs(telemetryErrorEvent) } + @Test + fun `M return the original event W map() { timeseries events }`() { + // Given + val fakeEvents = listOf( + mock(), + mock() + ) + + // When + val mappedEvents = fakeEvents.map(testedRumEventMapper::map) + + // Then + verifyNoInteractions(mockInternalLogger) + assertThat(mappedEvents).containsExactlyElementsOf(fakeEvents) + } + @Test fun `M return the bundled event W map { TelemetryConfigurationEvent }`( @Forgery telemetryConfigurationEvent: TelemetryConfigurationEvent diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt index 5f645700a4..acca076c63 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt @@ -14,6 +14,8 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.model.VitalAppLaunchEvent import com.datadog.android.rum.model.VitalOperationStepEvent @@ -92,8 +94,8 @@ internal class RumEventSerializerTest { hasField("type", event.session.type.name.lowercase(Locale.US)) } .hasField("view") { - hasField("id", event.view.id) - hasField("url", event.view.url) + hasNullableField("id", event.view?.id) + hasNullableField("url", event.view?.url) } .hasField("_dd") { hasField("format_version", 2L) @@ -209,8 +211,8 @@ internal class RumEventSerializerTest { hasField("type", event.session.type.name.lowercase(Locale.US)) } .hasField("view") { - hasField("id", event.view.id) - hasField("url", event.view.url) + hasNullableField("id", event.view?.id) + hasNullableField("url", event.view?.url) } .hasField("_dd") { hasField("format_version", 2L) @@ -401,8 +403,8 @@ internal class RumEventSerializerTest { hasField("type", event.session.type.name.lowercase(Locale.US)) } .hasField("view") { - hasField("id", event.view.id) - hasField("url", event.view.url) + hasNullableField("id", event.view?.id) + hasNullableField("url", event.view?.url) } .hasField("_dd") { hasField("format_version", 2L) @@ -488,8 +490,8 @@ internal class RumEventSerializerTest { hasField("type", event.session.type.name.lowercase(Locale.US)) } .hasField("view") { - hasField("id", event.view.id) - hasField("url", event.view.url) + hasNullableField("id", event.view?.id) + hasNullableField("url", event.view?.url) } .hasField("_dd") { hasField("format_version", 2L) @@ -667,7 +669,7 @@ internal class RumEventSerializerTest { } .hasNullableField("service", event.service) - event.view.let { + event.view?.let { assertThat(jsonObject).hasField("view") { val view = checkNotNull(event.view) hasField("id", view.id) @@ -1198,6 +1200,19 @@ internal class RumEventSerializerTest { assertThat(serialized).isEqualTo(fakeJsonObject.toString()) } + @Test + fun `M serialize generated event W serialize() { timeseries events }`() { + // Given + val fakeEvents = createTimeseriesEvents() + + // When + val serializedEvents = fakeEvents.map(testedSerializer::serialize) + + // Then + assertThat(serializedEvents.map { JsonParser.parseString(it).asJsonObject.get("type").asString }) + .containsOnly("timeseries") + } + @Test fun `M keep known custom attributes as is W serialize()`(forge: Forge) { val key = forge.anElementFrom(RumEventSerializer.knownAttributes) @@ -2176,6 +2191,50 @@ internal class RumEventSerializerTest { // region Internal + private fun createTimeseriesEvents(): List { + return listOf( + TimeseriesCpuEvent( + date = 0L, + application = TimeseriesCpuEvent.Application(id = "application-id"), + session = TimeseriesCpuEvent.TimeseriesCpuEventSession( + id = "session-id", + type = TimeseriesCpuEvent.TimeseriesCpuEventSessionType.USER + ), + dd = TimeseriesCpuEvent.Dd(), + timeseries = TimeseriesCpuEvent.Timeseries( + id = "timeseries-id", + start = 0L, + end = 0L, + data = TimeseriesCpuEvent.Data( + timestamps = emptyList(), + values = TimeseriesCpuEvent.Values(cpuUsage = emptyList()) + ) + ) + ), + TimeseriesMemoryEvent( + date = 0L, + application = TimeseriesMemoryEvent.Application(id = "application-id"), + session = TimeseriesMemoryEvent.TimeseriesMemoryEventSession( + id = "session-id", + type = TimeseriesMemoryEvent.TimeseriesMemoryEventSessionType.USER + ), + dd = TimeseriesMemoryEvent.Dd(), + timeseries = TimeseriesMemoryEvent.Timeseries( + id = "timeseries-id", + start = 0L, + end = 0L, + data = TimeseriesMemoryEvent.Data( + timestamps = emptyList(), + values = TimeseriesMemoryEvent.Values( + memoryFootprint = emptyList(), + memoryPercent = emptyList() + ) + ) + ) + ) + ) + } + private fun Forge.forgeRumEvent( attributes: MutableMap = mutableMapOf(), userAttributes: MutableMap = mutableMapOf() diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt index b0735e5c7d..a89a7692f0 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt @@ -27,7 +27,7 @@ import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollect import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter -import com.datadog.android.rum.internal.timeseries.TimeseriesCollector +import com.datadog.android.rum.internal.timeseries.NoOpTimeseriesCollectorFactory import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -136,12 +136,6 @@ internal class RumApplicationScopeAttributePropagationTest { @Mock lateinit var mockSessionSampler: Sampler - @Mock - lateinit var mockTimeseriesCollectorFactory: TimeseriesCollector.Factory - - @Mock - lateinit var mockTimeseriesCollector: TimeseriesCollector - @Forgery lateinit var fakeEventTime: Time @@ -201,7 +195,6 @@ internal class RumApplicationScopeAttributePropagationTest { fakeRumSessionType = forge.aNullable { aValueFrom(RumSessionType::class.java) } whenever(mockSessionSampler.getSampleRate()).thenReturn(fakeSampleRate) whenever(mockSessionSampler.sample(any())).thenReturn(true) - whenever(mockTimeseriesCollectorFactory.create(any(), any(), any())) doReturn mockTimeseriesCollector testedScope = RumApplicationScope( applicationId = fakeApplicationId, sdkCore = rumMonitor.mockSdkCore, @@ -224,7 +217,7 @@ internal class RumApplicationScopeAttributePropagationTest { rumSessionScopeStartupManagerFactory = mock(), insightsCollector = mockInsightsCollector, heatmapIdentifierRegistry = null, - timeseriesCollectorFactory = mockTimeseriesCollectorFactory + timeseriesCollectorFactory = NoOpTimeseriesCollectorFactory() ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt index 315f84fccb..b5a0faae3d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt @@ -22,6 +22,7 @@ import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.internal.domain.InfoProvider +import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time import com.datadog.android.rum.internal.domain.accessibility.AccessibilitySnapshotManager import com.datadog.android.rum.internal.domain.battery.BatteryInfo @@ -185,7 +186,7 @@ internal class RumApplicationScopeTest { whenever(mockSessionSampler.getSampleRate()).thenReturn(fakeSampleRate) whenever(mockSessionSampler.sample(any())).thenReturn(true) - whenever(mockTimeseriesCollectorFactory.create(any(), any(), any())) doReturn mockTimeseriesCollector + whenever(mockTimeseriesCollectorFactory.create(any(), any())) doReturn mockTimeseriesCollector testedScope = RumApplicationScope( applicationId = fakeApplicationId, @@ -226,42 +227,17 @@ internal class RumApplicationScopeTest { } @Test - fun `M propagate timeseriesFactory to initial child session scope W handleEvent { startView }`( + fun `M propagate timeseriesFactory to every child session W handleEvent { startView, stop, startView }`( @StringForgery viewKey: String, @StringForgery viewName: String ) { - // When - delegating an interactive event triggers renewSession on the child session - testedScope.handleEvent( - RumRawEvent.StartView( - key = RumScopeKey.from(viewKey, viewName), - attributes = emptyMap(), - eventTime = fakeEventTime - ), - fakeDatadogContext, - mockEventWriteScope, - mockWriter - ) - - // Then - the child session uses the same factory we gave to the application scope - verify(mockTimeseriesCollectorFactory).create(eq(fakeApplicationId), any(), any()) - } - - @Test - fun `M propagate timeseriesFactory to a new child session W handleEvent { stop+startView }`( - @StringForgery viewKey: String, - @StringForgery viewName: String - ) { - // Given - first session that already used the factory - testedScope.handleEvent( - RumRawEvent.StartView( - key = RumScopeKey.from(viewKey, viewName), - attributes = emptyMap(), - eventTime = fakeEventTime - ), - fakeDatadogContext, - mockEventWriteScope, - mockWriter + // Given - the initial child session, renewed by an interactive event + val fakeStartViewEvent = RumRawEvent.StartView( + key = RumScopeKey.from(viewKey, viewName), + attributes = emptyMap(), + eventTime = fakeEventTime ) + testedScope.handleEvent(fakeStartViewEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) testedScope.handleEvent( RumRawEvent.StopSession(fakeEventTime), fakeDatadogContext, @@ -269,20 +245,16 @@ internal class RumApplicationScopeTest { mockWriter ) - // When - a brand new session is started; the application scope must reuse the factory - testedScope.handleEvent( - RumRawEvent.StartView( - key = RumScopeKey.from(viewKey, viewName), - attributes = emptyMap(), - eventTime = fakeEventTime - ), - fakeDatadogContext, - mockEventWriteScope, - mockWriter - ) + // When - a brand new child session scope is created by the application scope + testedScope.handleEvent(fakeStartViewEvent, fakeDatadogContext, mockEventWriteScope, mockWriter) - // Then - factory.create is invoked once per tracked session (initial + new) - verify(mockTimeseriesCollectorFactory, times(2)).create(eq(fakeApplicationId), any(), any()) + // Then - both the initial and the new child session use the factory we gave the application scope + val rumContextCaptor = argumentCaptor() + verify(mockTimeseriesCollectorFactory, times(2)).create(any(), rumContextCaptor.capture()) + assertThat(rumContextCaptor.allValues.map { it.applicationId }) + .containsOnly(fakeApplicationId) + assertThat(rumContextCaptor.firstValue.sessionId) + .isNotEqualTo(rumContextCaptor.secondValue.sessionId) } @Test diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExtTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExtTest.kt index 48026d185a..99b9bcbc7c 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExtTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExtTest.kt @@ -17,6 +17,8 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.utils.forge.Configurator import fr.xgouchet.elmyr.annotation.Forgery @@ -1039,6 +1041,86 @@ internal class RumEventExtTest { ) } + @ParameterizedTest + @EnumSource(NetworkInfo.Connectivity::class) + fun `M return connectivity W toTimeseriesCpuConnectivity()`( + connectivity: NetworkInfo.Connectivity, + @Forgery fakeNetworkInfo: NetworkInfo, + @StringForgery fakeCarrierName: String, + @StringForgery fakeCellularTechnology: String + ) { + // Given + val networkInfo = fakeNetworkInfo.copy( + connectivity = connectivity, + carrierName = fakeCarrierName, + cellularTechnology = fakeCellularTechnology + ) + + // When + val result = networkInfo.toTimeseriesCpuConnectivity() + + // Then + assertThat(result.status.name).isEqualTo(expectedStatusName(connectivity)) + assertThat(result.interfaces?.map { it.name }) + .isEqualTo(EXPECTED_INTERFACE_NAMES.getValue(connectivity)) + assertThat(result.cellular) + .isEqualTo(TimeseriesCpuEvent.Cellular(fakeCellularTechnology, fakeCarrierName)) + } + + @Test + fun `M return null cellular W toTimeseriesCpuConnectivity() {no carrier info}`( + @Forgery fakeNetworkInfo: NetworkInfo + ) { + // Given + val networkInfo = fakeNetworkInfo.copy(carrierName = null, cellularTechnology = null) + + // When + val result = networkInfo.toTimeseriesCpuConnectivity() + + // Then + assertThat(result.cellular).isNull() + } + + @ParameterizedTest + @EnumSource(NetworkInfo.Connectivity::class) + fun `M return connectivity W toTimeseriesMemoryConnectivity()`( + connectivity: NetworkInfo.Connectivity, + @Forgery fakeNetworkInfo: NetworkInfo, + @StringForgery fakeCarrierName: String, + @StringForgery fakeCellularTechnology: String + ) { + // Given + val networkInfo = fakeNetworkInfo.copy( + connectivity = connectivity, + carrierName = fakeCarrierName, + cellularTechnology = fakeCellularTechnology + ) + + // When + val result = networkInfo.toTimeseriesMemoryConnectivity() + + // Then + assertThat(result.status.name).isEqualTo(expectedStatusName(connectivity)) + assertThat(result.interfaces?.map { it.name }) + .isEqualTo(EXPECTED_INTERFACE_NAMES.getValue(connectivity)) + assertThat(result.cellular) + .isEqualTo(TimeseriesMemoryEvent.Cellular(fakeCellularTechnology, fakeCarrierName)) + } + + @Test + fun `M return null cellular W toTimeseriesMemoryConnectivity() {no carrier info}`( + @Forgery fakeNetworkInfo: NetworkInfo + ) { + // Given + val networkInfo = fakeNetworkInfo.copy(carrierName = null, cellularTechnology = null) + + // When + val result = networkInfo.toTimeseriesMemoryConnectivity() + + // Then + assertThat(result.cellular).isNull() + } + // endregion // region device type conversion @@ -1103,5 +1185,58 @@ internal class RumEventExtTest { assertThat(schemaDeviceType.name).isEqualTo(deviceType.name) } + @ParameterizedTest + @EnumSource(DeviceType::class) + fun `M return schema device type W toTimeseriesCpuSchemaType()`( + deviceType: DeviceType + ) { + // When + val schemaDeviceType = deviceType.toTimeseriesCpuSchemaType() + + // Then + assertThat(schemaDeviceType.name).isEqualTo(deviceType.name) + } + + @ParameterizedTest + @EnumSource(DeviceType::class) + fun `M return schema device type W toTimeseriesMemorySchemaType()`( + deviceType: DeviceType + ) { + // When + val schemaDeviceType = deviceType.toTimeseriesMemorySchemaType() + + // Then + assertThat(schemaDeviceType.name).isEqualTo(deviceType.name) + } + // endregion + + companion object { + + // Interface enums are generated per event type but share their names, so a single + // name-based table covers both timeseries schemas. getValue() fails loudly if a new + // Connectivity value is added without updating the mapping. + private val EXPECTED_INTERFACE_NAMES = mapOf( + NetworkInfo.Connectivity.NETWORK_NOT_CONNECTED to emptyList(), + NetworkInfo.Connectivity.NETWORK_ETHERNET to listOf("ETHERNET"), + NetworkInfo.Connectivity.NETWORK_WIFI to listOf("WIFI"), + NetworkInfo.Connectivity.NETWORK_WIMAX to listOf("WIMAX"), + NetworkInfo.Connectivity.NETWORK_BLUETOOTH to listOf("BLUETOOTH"), + NetworkInfo.Connectivity.NETWORK_2G to listOf("CELLULAR"), + NetworkInfo.Connectivity.NETWORK_3G to listOf("CELLULAR"), + NetworkInfo.Connectivity.NETWORK_4G to listOf("CELLULAR"), + NetworkInfo.Connectivity.NETWORK_5G to listOf("CELLULAR"), + NetworkInfo.Connectivity.NETWORK_MOBILE_OTHER to listOf("CELLULAR"), + NetworkInfo.Connectivity.NETWORK_CELLULAR to listOf("CELLULAR"), + NetworkInfo.Connectivity.NETWORK_OTHER to listOf("OTHER") + ) + + private fun expectedStatusName(connectivity: NetworkInfo.Connectivity): String { + return if (connectivity == NetworkInfo.Connectivity.NETWORK_NOT_CONNECTED) { + "NOT_CONNECTED" + } else { + "CONNECTED" + } + } + } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt index 15c7b7a948..20f4e310c4 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt @@ -26,7 +26,6 @@ import com.datadog.android.rum.internal.domain.display.DisplayInfo import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener -import com.datadog.android.rum.internal.timeseries.TimeseriesCollector import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier @@ -129,12 +128,6 @@ internal class RumSessionScopeAttributePropagationTest { @Mock lateinit var mockSessionSampler: Sampler - @Mock - lateinit var mockTimeseriesCollectorFactory: TimeseriesCollector.Factory - - @Mock - lateinit var mockTimeseriesCollector: TimeseriesCollector - lateinit var fakeParentAttributes: Map @Forgery @@ -171,7 +164,6 @@ internal class RumSessionScopeAttributePropagationTest { fakeRumSessionType = forge.aNullable { aValueFrom(RumSessionType::class.java) } whenever(mockSessionSampler.getSampleRate()).thenReturn(fakeSampleRate) whenever(mockSessionSampler.sample(any())).thenReturn(true) - whenever(mockTimeseriesCollectorFactory.create(any(), any(), any())) doReturn mockTimeseriesCollector testedScope = RumSessionScope( parentScope = mockParentScope, sdkCore = mockSdkCore, @@ -197,8 +189,7 @@ internal class RumSessionScopeAttributePropagationTest { displayInfoProvider = mockDisplayInfoProvider, rumSessionScopeStartupManagerFactory = mock(), insightsCollector = mockInsightsCollector, - heatmapIdentifierRegistry = null, - timeseriesCollectorFactory = mockTimeseriesCollectorFactory + heatmapIdentifierRegistry = null ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt index 8a9966d37e..3b0f508aff 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt @@ -50,7 +50,6 @@ import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.FloatForgery import fr.xgouchet.elmyr.annotation.Forgery -import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -60,6 +59,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.Mockito @@ -67,11 +67,9 @@ import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor -import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq -import org.mockito.kotlin.inOrder import org.mockito.kotlin.isA import org.mockito.kotlin.mock import org.mockito.kotlin.never @@ -230,7 +228,7 @@ internal class RumSessionScopeTest { whenever(mockDisplayInfoProvider.getState()) doReturn fakeDisplayInfo whenever(mockSessionSampler.sample(any())).thenReturn(true) whenever(mockSessionSampler.getSampleRate()).thenReturn(100f) - whenever(mockTimeseriesCollectorFactory.create(any(), any(), any())) doReturn mockTimeseriesCollector + whenever(mockTimeseriesCollectorFactory.create(any(), any())) doReturn mockTimeseriesCollector fakeParentAttributes = forge.exhaustiveAttributes() whenever(mockParentScope.getCustomAttributes()) doReturn fakeParentAttributes @@ -1855,9 +1853,10 @@ internal class RumSessionScopeTest { // region Timeseries @Test - fun `M start timeseries via factory W renewSession { keepSession = true }`(forge: Forge) { + fun `M start timeseries with session context W renewSession { keepSession = true, no active view }`( + forge: Forge + ) { // Given - fakeParentContext = fakeParentContext.copy(syntheticsTestId = null, syntheticsResultId = null) initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) // When @@ -1869,160 +1868,55 @@ internal class RumSessionScopeTest { ) // Then - verify(mockTimeseriesCollectorFactory).create( - fakeParentContext.applicationId, - testedScope.sessionId, - fakeRumSessionType ?: RumSessionType.USER - ) + val rumContextCaptor = argumentCaptor() + verify(mockTimeseriesCollectorFactory).create(any(), rumContextCaptor.capture()) + assertThat(rumContextCaptor.firstValue).isEqualTo(testedScope.getRumContext()) verify(mockTimeseriesCollector).onSessionStart() } @Test - fun `M pass USER session type to factory W renewSession { no synthetics, no override }`(forge: Forge) { + fun `M pass active view context to factory W renewSession { foreground view active }`(forge: Forge) { // Given - fakeParentContext = fakeParentContext.copy(syntheticsTestId = null, syntheticsResultId = null) - fakeRumSessionType = null + val mockViewScope = mock() + val fakeViewContext = forge.getForgery().copy(viewType = RumViewType.FOREGROUND) + whenever(mockChildScope.activeView) doReturn mockViewScope + whenever(mockViewScope.getRumContext()) doReturn fakeViewContext initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) // When testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - // Then - verify(mockTimeseriesCollectorFactory).create( - fakeParentContext.applicationId, - testedScope.sessionId, - RumSessionType.USER - ) + // Then — the view context, not the session scope one (which has viewType NONE) + val rumContextCaptor = argumentCaptor() + verify(mockTimeseriesCollectorFactory).create(any(), rumContextCaptor.capture()) + assertThat(rumContextCaptor.firstValue).isEqualTo(fakeViewContext) + assertThat(rumContextCaptor.firstValue).isNotEqualTo(testedScope.getRumContext()) } - @Test - fun `M pass SYNTHETICS session type to factory W renewSession { synthetics IDs in context, no override }`( - forge: Forge, - @StringForgery(regex = "[a-z]{8}") fakeTestId: String, - @StringForgery(regex = "[a-z]{8}") fakeResultId: String + @ParameterizedTest + @MethodSource("sessionTypeResolutions") + fun `M pass resolved session type to factory W renewSession`( + fakeSyntheticsTestId: String?, + fakeSyntheticsResultId: String?, + fakeSessionTypeOverride: RumSessionType?, + expectedSessionType: RumSessionType, + forge: Forge ) { // Given - fakeParentContext = fakeParentContext.copy(syntheticsTestId = fakeTestId, syntheticsResultId = fakeResultId) - fakeRumSessionType = null - initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) - - // When - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // Then - verify(mockTimeseriesCollectorFactory).create( - fakeParentContext.applicationId, - testedScope.sessionId, - RumSessionType.SYNTHETICS + fakeParentContext = fakeParentContext.copy( + syntheticsTestId = fakeSyntheticsTestId, + syntheticsResultId = fakeSyntheticsResultId ) - } - - @Test - fun `M pass override session type to factory W renewSession { synthetics IDs in context, override set }`( - forge: Forge, - @StringForgery(regex = "[a-z]{8}") fakeTestId: String, - @StringForgery(regex = "[a-z]{8}") fakeResultId: String - ) { - // Given - fakeParentContext = fakeParentContext.copy(syntheticsTestId = fakeTestId, syntheticsResultId = fakeResultId) - val expectedSessionType = forge.aValueFrom(RumSessionType::class.java) - fakeRumSessionType = expectedSessionType - initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) - - // When - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // Then - verify(mockTimeseriesCollectorFactory).create( - fakeParentContext.applicationId, - testedScope.sessionId, - expectedSessionType + initializeTestedScope( + timeseriesCollectorFactory = mockTimeseriesCollectorFactory, + rumSessionTypeOverride = fakeSessionTypeOverride ) - } - - @Test - fun `M pass override session type to factory W renewSession { no synthetics, override set }`( - forge: Forge - ) { - // Given - fakeParentContext = fakeParentContext.copy(syntheticsTestId = null, syntheticsResultId = null) - val expectedSessionType = forge.aValueFrom(RumSessionType::class.java) - fakeRumSessionType = expectedSessionType - initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) - - // When - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // Then - verify(mockTimeseriesCollectorFactory).create(any(), any(), eq(expectedSessionType)) - } - - @Test - fun `M pass USER session type to factory W renewSession { testId null, no override }`( - forge: Forge, - @StringForgery(regex = "[a-z]{8}") fakeResultId: String - ) { - // Given - fakeParentContext = fakeParentContext.copy(syntheticsTestId = null, syntheticsResultId = fakeResultId) - fakeRumSessionType = null - initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) // When testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // Then - verify(mockTimeseriesCollectorFactory).create(any(), any(), eq(RumSessionType.USER)) - } - - @Test - fun `M pass USER session type to factory W renewSession { resultId null, no override }`( - forge: Forge, - @StringForgery(regex = "[a-z]{8}") fakeTestId: String - ) { - // Given - fakeParentContext = fakeParentContext.copy(syntheticsTestId = fakeTestId, syntheticsResultId = null) - fakeRumSessionType = null - initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) - - // When - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // Then - verify(mockTimeseriesCollectorFactory).create(any(), any(), eq(RumSessionType.USER)) - } - - @Test - fun `M pass USER session type to factory W renewSession { testId blank, no override }`( - forge: Forge, - @StringForgery(regex = "[a-z]{8}") fakeResultId: String - ) { - // Given - fakeParentContext = fakeParentContext.copy(syntheticsTestId = " ", syntheticsResultId = fakeResultId) - fakeRumSessionType = null - initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) - - // When - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // Then - verify(mockTimeseriesCollectorFactory).create(any(), any(), eq(RumSessionType.USER)) - } - - @Test - fun `M pass USER session type to factory W renewSession { resultId blank, no override }`( - forge: Forge, - @StringForgery(regex = "[a-z]{8}") fakeTestId: String - ) { - // Given - fakeParentContext = fakeParentContext.copy(syntheticsTestId = fakeTestId, syntheticsResultId = " ") - fakeRumSessionType = null - initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) - - // When - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // Then - verify(mockTimeseriesCollectorFactory).create(any(), any(), eq(RumSessionType.USER)) + verify(mockTimeseriesCollectorFactory).create(eq(expectedSessionType), any()) } @Test @@ -2040,34 +1934,33 @@ internal class RumSessionScopeTest { ) // Then - verify(mockTimeseriesCollectorFactory, never()).create(any(), any(), any()) + verify(mockTimeseriesCollectorFactory, never()).create(any(), any()) verify(mockTimeseriesCollector, never()).onSessionStart() } @Test - fun `M stop previous timeseries W renewSession { another tracked session }`(forge: Forge) { + fun `M stop previous and feed fresh timeseries W renewSession { another tracked session }`(forge: Forge) { // Given + val mockViewScope = mock() + val fakeViewContext = forge.getForgery().copy(viewType = RumViewType.FOREGROUND) + whenever(mockChildScope.activeView) doReturn mockViewScope + whenever(mockViewScope.getRumContext()) doReturn fakeViewContext initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) - testedScope.handleEvent( - forge.startViewEvent(), - fakeDatadogContext, - mockEventWriteScope, - mockWriter - ) + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) + val firstTimeseriesCollector = mockTimeseriesCollector + val secondTimeseriesCollector: TimeseriesCollector = mock() + whenever(mockTimeseriesCollectorFactory.create(any(), any())) doReturn secondTimeseriesCollector // When — second session via inactivity expiration advanceTimeByMs(TEST_INACTIVITY_MS) - testedScope.handleEvent( - forge.startViewEvent(), - fakeDatadogContext, - mockEventWriteScope, - mockWriter - ) + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - // Then - verify(mockTimeseriesCollector).onSessionStop() - verify(mockTimeseriesCollectorFactory, times(2)).create(any(), any(), any()) - verify(mockTimeseriesCollector, times(2)).onSessionStart() + // Then — one timeseries per tracked session, updates go to the live one only + verify(mockTimeseriesCollectorFactory, times(2)).create(any(), any()) + verify(firstTimeseriesCollector).onSessionStop() + verify(firstTimeseriesCollector, times(1)).onViewTypeUpdate(fakeViewContext.viewType) + verify(secondTimeseriesCollector).onSessionStart() + verify(secondTimeseriesCollector).onViewTypeUpdate(fakeViewContext.viewType) } @Test @@ -2117,122 +2010,51 @@ internal class RumSessionScopeTest { } @Test - fun `M not interact with timeseries W renewSession { default NoOp factory }`(forge: Forge) { - // When - val result = testedScope.handleEvent( - forge.startViewEvent(), - fakeDatadogContext, - mockEventWriteScope, - mockWriter - ) - - // Then - assertThat(result).isSameAs(testedScope) - verifyNoInteractions(mockTimeseriesCollector, mockTimeseriesCollectorFactory) - } - - @Test - fun `M notify timeseries of view type W handleEvent`(forge: Forge) { - // Given + fun `M pass session context W onRumContextUpdate() { no active view }`(forge: Forge) { + // Given — activeView is null by default (mockChildScope.activeView not stubbed) initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) - testedScope.handleEvent( - forge.startViewEvent(), - fakeDatadogContext, - mockEventWriteScope, - mockWriter - ) + testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) // When - testedScope.handleEvent( - forge.addErrorEvent(), - fakeDatadogContext, - mockEventWriteScope, - mockWriter - ) + testedScope.handleEvent(forge.addErrorEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - // Then — called once per handleEvent invocation - verify(mockTimeseriesCollector, times(2)).onViewTypeUpdate(any()) + // Then — the session scope view type, once per handled event + val viewTypeCaptor = argumentCaptor() + verify(mockTimeseriesCollector, times(2)).onViewTypeUpdate(viewTypeCaptor.capture()) + assertThat(viewTypeCaptor.allValues).containsOnly(testedScope.getRumContext().viewType) } @Test - fun `M pass foreground view type W onViewTypeUpdate { first StartView creates view }`(forge: Forge) { - // Given + fun `M pass active view type W onViewTypeUpdate() { StartView creates a foreground view }`(forge: Forge) { + // Given — a real view manager child scope, so the context comes from an actual RumViewScope initializeTestedScope( withMockChildScope = false, timeseriesCollectorFactory = mockTimeseriesCollectorFactory ) + val fakeStartViewEvent = forge.startViewEvent() // When testedScope.handleEvent( - forge.startViewEvent(), + fakeStartViewEvent, fakeDatadogContext, mockEventWriteScope, mockWriter ) // Then - verify(mockTimeseriesCollector).onViewTypeUpdate(RumViewType.FOREGROUND) - verify(mockTimeseriesCollector, never()).onViewTypeUpdate(RumViewType.NONE) + val viewTypeCaptor = argumentCaptor() + verify(mockTimeseriesCollector).onViewTypeUpdate(viewTypeCaptor.capture()) + assertThat(viewTypeCaptor.firstValue).isEqualTo(RumViewType.FOREGROUND) } @Test - fun `M pass NONE W onViewTypeUpdate { no active view }`(forge: Forge) { - // Given — activeView is null by default (mockChildScope.activeView not stubbed) - initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // When - testedScope.handleEvent(forge.addErrorEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // Then - verify(mockTimeseriesCollector, times(2)).onViewTypeUpdate(RumViewType.NONE) - } - - @Test - fun `M pass active view type W onViewTypeUpdate { foreground view active }`(forge: Forge) { + fun `M stop previous timeseries W handleEvent { ResetSession }`(forge: Forge) { // Given - val mockViewScope = mock() - val fakeRumContext = forge.getForgery().copy(viewType = RumViewType.FOREGROUND) - whenever(mockChildScope.activeView) doReturn mockViewScope - whenever(mockViewScope.getRumContext()) doReturn fakeRumContext initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // When - testedScope.handleEvent(forge.addErrorEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // Then — at least the last call carries FOREGROUND - verify(mockTimeseriesCollector, atLeastOnce()).onViewTypeUpdate(RumViewType.FOREGROUND) - } - - @Test - fun `M pass view type to fresh timeseries W renewSession { session renewed }`(forge: Forge) { - // Given — active foreground view throughout - val mockViewScope = mock() - val fakeRumContext = forge.getForgery().copy(viewType = RumViewType.FOREGROUND) - whenever(mockChildScope.activeView) doReturn mockViewScope - whenever(mockViewScope.getRumContext()) doReturn fakeRumContext - initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // When — expire session and start a new one - advanceTimeByMs(TEST_INACTIVITY_MS) - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // Then — second timeseries (same factory mock) also receives FOREGROUND right after start() - val inOrder = inOrder(mockTimeseriesCollector) - inOrder.verify(mockTimeseriesCollector, times(2)).onSessionStart() - inOrder.verify(mockTimeseriesCollector).onViewTypeUpdate(RumViewType.FOREGROUND) - } - - @Test - fun `M stop previous timeseries W renewSession { ResetSession renews tracked session }`(forge: Forge) { - // Given - initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - val firstCollector = mockTimeseriesCollector + val firstTimeseriesCollector = mockTimeseriesCollector val secondTimeseriesCollector: TimeseriesCollector = mock() - whenever(mockTimeseriesCollectorFactory.create(any(), any(), any())) doReturn secondTimeseriesCollector + whenever(mockTimeseriesCollectorFactory.create(any(), any())) doReturn secondTimeseriesCollector // When testedScope.handleEvent( @@ -2243,36 +2065,15 @@ internal class RumSessionScopeTest { ) // Then - verify(firstCollector).onSessionStart() - verify(firstCollector).onSessionStop() + verify(firstTimeseriesCollector).onSessionStop() verify(secondTimeseriesCollector).onSessionStart() } @Test - fun `M stop previous timeseries W renewSession { expired session then new interaction }`(forge: Forge) { + fun `M not create timeseries W handleEvent { StopSession, no tracked session }`() { // Given - initializeTestedScope( - backgroundTrackingEnabled = false, - timeseriesCollectorFactory = mockTimeseriesCollectorFactory - ) - testedScope.handleEvent(forge.startViewEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - val firstCollector = mockTimeseriesCollector - val secondTimeseriesCollector: TimeseriesCollector = mock() - whenever(mockTimeseriesCollectorFactory.create(any(), any(), any())) doReturn secondTimeseriesCollector - advanceTimeByMs(TEST_INACTIVITY_MS) - testedScope.handleEvent(mock(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // When - testedScope.handleEvent(forge.startActionEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - - // Then - verify(firstCollector).onSessionStart() - verify(firstCollector).onSessionStop() - verify(secondTimeseriesCollector).onSessionStart() - } + initializeTestedScope(timeseriesCollectorFactory = mockTimeseriesCollectorFactory) - @Test - fun `M not stop timeseries W handleEvent { StopSession, no tracked session }`() { // When testedScope.handleEvent( RumRawEvent.StopSession(eventTime = currentFakeTime()), @@ -2282,6 +2083,7 @@ internal class RumSessionScopeTest { ) // Then + verify(mockTimeseriesCollectorFactory, never()).create(any(), any()) verifyNoInteractions(mockTimeseriesCollector) } @@ -2304,7 +2106,8 @@ internal class RumSessionScopeTest { sessionSampler: Sampler = mockSessionSampler, withMockChildScope: Boolean = true, backgroundTrackingEnabled: Boolean? = null, - timeseriesCollectorFactory: TimeseriesCollector.Factory = NoOpTimeseriesCollectorFactory() + timeseriesCollectorFactory: TimeseriesCollector.Factory = NoOpTimeseriesCollectorFactory(), + rumSessionTypeOverride: RumSessionType? = fakeRumSessionType ) { testedScope = RumSessionScope( parentScope = mockParentScope, @@ -2325,7 +2128,7 @@ internal class RumSessionScopeTest { slowFramesListener = mockSlowFramesListener, sessionInactivityNanos = TEST_INACTIVITY_NS, sessionMaxDurationNanos = TEST_MAX_DURATION_NS, - rumSessionTypeOverride = fakeRumSessionType, + rumSessionTypeOverride = rumSessionTypeOverride, accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, @@ -2351,6 +2154,27 @@ internal class RumSessionScopeTest { private val TEST_INACTIVITY_NS = TimeUnit.MILLISECONDS.toNanos(TEST_INACTIVITY_MS) private val TEST_MAX_DURATION_NS = TimeUnit.MILLISECONDS.toNanos(TEST_MAX_DURATION_MS) + private const val FAKE_SYNTHETICS_TEST_ID = "fake-synthetics-test-id" + private const val FAKE_SYNTHETICS_RESULT_ID = "fake-synthetics-result-id" + + // syntheticsTestId, syntheticsResultId, rumSessionTypeOverride, expected session type + @JvmStatic + fun sessionTypeResolutions(): List = listOf( + Arguments.of(null, null, null, RumSessionType.USER), + Arguments.of(FAKE_SYNTHETICS_TEST_ID, FAKE_SYNTHETICS_RESULT_ID, null, RumSessionType.SYNTHETICS), + Arguments.of(null, FAKE_SYNTHETICS_RESULT_ID, null, RumSessionType.USER), + Arguments.of(FAKE_SYNTHETICS_TEST_ID, null, null, RumSessionType.USER), + Arguments.of(" ", FAKE_SYNTHETICS_RESULT_ID, null, RumSessionType.USER), + Arguments.of(FAKE_SYNTHETICS_TEST_ID, " ", null, RumSessionType.USER), + Arguments.of( + FAKE_SYNTHETICS_TEST_ID, + FAKE_SYNTHETICS_RESULT_ID, + RumSessionType.USER, + RumSessionType.USER + ), + Arguments.of(null, null, RumSessionType.SYNTHETICS, RumSessionType.SYNTHETICS) + ) + @JvmStatic fun testScenarios(): List { val forge = Forge().apply { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt index f89602f30c..411b721980 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt @@ -10603,8 +10603,8 @@ internal class RumViewScopeTest { assertThat(profilerEvent.rumContext.applicationId).isEqualTo(writtenVital.application.id) assertThat(profilerEvent.rumContext.sessionId).isEqualTo(writtenVital.session.id) - assertThat(profilerEvent.rumContext.viewId).isEqualTo(writtenVital.view.id) - assertThat(profilerEvent.rumContext.viewName).isEqualTo(writtenVital.view.name) + assertThat(profilerEvent.rumContext.viewId).isEqualTo(writtenVital.view?.id) + assertThat(profilerEvent.rumContext.viewName).isEqualTo(writtenVital.view?.name) } @Test diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/BufferTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/BufferTest.kt index a480423d92..fc390c59b1 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/BufferTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/BufferTest.kt @@ -69,7 +69,7 @@ internal class BufferTest { } @Test - fun `M return null W drain() { buffer is empty }`(@IntForgery(min = 1, max = 256) fakeSize: Int) { + fun `M return empty list W drain() { buffer is empty }`(@IntForgery(min = 1, max = 256) fakeSize: Int) { // Given val testedBuffer = Buffer(fakeSize) @@ -100,19 +100,20 @@ internal class BufferTest { } @Test - fun `M return immutable snapshot W drain() { caller cannot affect internal state }`( - @IntForgery(min = 1, max = 16) fakeSize: Int, + fun `M return a detached copy W drain() { buffer is reused afterwards }`( + @IntForgery(min = 2, max = 16) fakeSize: Int, forge: Forge ) { // Given val testedBuffer = Buffer(fakeSize) - repeat(fakeSize) { testedBuffer.add(forge.getForgery>()) } + val fakePoints = (0 until fakeSize).map { forge.getForgery>() } + fakePoints.forEach { testedBuffer.add(it) } val drained = testedBuffer.drain() - // When - the buffer should be cleared after drain + // When testedBuffer.add(forge.getForgery>()) - // Then - drained list size unchanged regardless of further mutations - assertThat(drained).hasSize(fakeSize) + // Then + assertThat(drained).containsExactlyElementsOf(fakePoints) } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt index 8d7002c889..42e7b70cff 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt @@ -9,23 +9,38 @@ package com.datadog.android.rum.internal.timeseries import com.datadog.android.api.InternalLogger import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.api.storage.DataWriter +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.RumSessionType -import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader -import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer +import com.datadog.android.rum.internal.domain.InfoProvider +import com.datadog.android.rum.internal.domain.RumContext +import com.datadog.android.rum.internal.domain.battery.BatteryInfo +import com.datadog.android.rum.internal.domain.display.DisplayInfo +import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector +import com.datadog.android.rum.internal.timeseries.factory.CpuEventFactory +import com.datadog.android.rum.internal.timeseries.factory.MemoryEventFactory +import com.datadog.android.rum.timeseries.TimeseriesConfiguration +import com.datadog.android.rum.timeseries.TimeseriesType import com.datadog.android.rum.utils.forge.Configurator -import fr.xgouchet.elmyr.annotation.StringForgery +import com.datadog.android.utils.verifyLog +import com.datadog.tools.unit.getFieldValue +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings -import org.mockito.kotlin.mock -import org.mockito.kotlin.verify +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.util.concurrent.ScheduledExecutorService @@ -36,6 +51,7 @@ import java.util.concurrent.ScheduledExecutorService ) @MockitoSettings(strictness = Strictness.LENIENT) @ForgeConfiguration(Configurator::class) +@OptIn(ExperimentalRumApi::class) internal class DefaultTimeseriesCollectorFactoryTest { @Mock @@ -44,64 +60,243 @@ internal class DefaultTimeseriesCollectorFactoryTest { @Mock lateinit var mockInternalLogger: InternalLogger + @Mock + lateinit var mockTimeProvider: TimeProvider + @Mock lateinit var mockWriter: DataWriter @Mock lateinit var mockExecutor: ScheduledExecutorService + @Mock + lateinit var mockInsightsCollector: InsightsCollector + + @Mock + lateinit var mockBatteryInfoProvider: InfoProvider + + @Mock + lateinit var mockDisplayInfoProvider: InfoProvider + + @Forgery + lateinit var fakeRumContext: RumContext + + lateinit var fakeSessionType: RumSessionType + + @BeforeEach + fun `set up`(forge: Forge) { + whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger + whenever(mockSdkCore.timeProvider) doReturn mockTimeProvider + + fakeSessionType = forge.aValueFrom(RumSessionType::class.java) + } + + private fun createFactory( + totalRamBytes: Long, + configuration: TimeseriesConfiguration = TimeseriesConfiguration.Builder().build() + ) = DefaultTimeseriesCollectorFactory( + sdkCore = mockSdkCore, + configuration = configuration, + scheduledExecutorService = mockExecutor, + insightsCollector = mockInsightsCollector, + totalRamBytes = totalRamBytes, + dataWriter = mockWriter, + batteryInfoProvider = mockBatteryInfoProvider, + displayInfoProvider = mockDisplayInfoProvider + ) + + @Test + fun `M build cpu and memory pipelines W create() { totalRamBytes greater than zero }`( + @LongForgery(min = 1L) fakeTotalRamBytes: Long + ) { + // Given + val testedFactory = createFactory(fakeTotalRamBytes) + + // When + val timeseries = testedFactory.create(fakeSessionType, fakeRumContext) + + // Then + check(timeseries is DefaultTimeseriesCollector) + assertThat(timeseries.pipelines).hasSize(2) + } + @Test - fun `M build DefaultTimeseriesCollector with provided pipelines W create()`( - @StringForgery fakeSessionId: String, - @StringForgery fakeApplicationId: String + fun `M use default sampling settings W create()`( + @LongForgery(min = 1L) fakeTotalRamBytes: Long ) { // Given - val fakeSessionType = RumSessionType.USER - val pipelineA = mockPipeline() - val pipelineB = mockPipeline() - val provider: (String, String, RumSessionType) -> List> = - mock<(String, String, RumSessionType) -> List>>().apply { - whenever(this(fakeSessionId, fakeApplicationId, fakeSessionType)) - .thenReturn(listOf(pipelineA, pipelineB)) + val testedFactory = createFactory(fakeTotalRamBytes) + + // When + val timeseries = testedFactory.create(fakeSessionType, fakeRumContext) + + // Then + check(timeseries is DefaultTimeseriesCollector) + assertThat(timeseries.pipelines.map { it.intervalMs }) + .containsOnly(TimeseriesConfiguration.DEFAULT_INTERVAL_MS) + assertThat( + timeseries.pipelines.map { pipeline -> + pipeline.getFieldValue, Pipeline<*>>("buffer") + .getFieldValue>("size") } - val testedFactory = DefaultTimeseriesCollectorFactory( - internalLogger = mockInternalLogger, - collectInBackground = false, - scheduledExecutorService = mockExecutor, - pipelinesProvider = provider + ).containsOnly(TimeseriesConfiguration.DEFAULT_BUFFER_SIZE) + } + + @Test + fun `M build only cpu pipeline W create() { totalRamBytes is zero }`() { + // Given + val testedFactory = createFactory(0L) + + // When + val timeseries = testedFactory.create(fakeSessionType, fakeRumContext) + + // Then + check(timeseries is DefaultTimeseriesCollector) + assertThat(timeseries.pipelines).hasSize(1) + mockInternalLogger.verifyLog( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + DefaultTimeseriesCollectorFactory.MEMORY_TIMESERIES_DISABLED_MESSAGE, + onlyOnce = true ) + } + + @Test + fun `M build only cpu pipeline W create() { configuration collects only cpu }`() { + // Given + val fakeConfiguration = TimeseriesConfiguration.Builder() + .collectOnly(TimeseriesType.CPU) + .build() + val testedFactory = createFactory(0L, fakeConfiguration) // When - val timeseries = testedFactory.create(fakeSessionId, fakeApplicationId, fakeSessionType) + val timeseries = testedFactory.create(fakeSessionType, fakeRumContext) // Then - verify(provider).invoke(fakeSessionId, fakeApplicationId, fakeSessionType) - assertThat(timeseries.scheduledExecutorService).isSameAs(mockExecutor) + check(timeseries is DefaultTimeseriesCollector) + assertThat(timeseries.pipelines).hasSize(1) + val eventFactory = timeseries.pipelines.single() + .getFieldValue>("eventFactory") + assertThat(eventFactory).isInstanceOf(CpuEventFactory::class.java) + verifyNoInteractions(mockInternalLogger) } @Test - fun `M propagate collectInBackground flag W create()`( - @StringForgery fakeSessionId: String, - @StringForgery fakeApplicationId: String + fun `M build only memory pipeline W create() { configuration collects only memory }`( + @LongForgery(min = 1L) fakeTotalRamBytes: Long ) { // Given - val emptyProvider: (String, String, RumSessionType) -> List> = { _, _, _ -> emptyList() } - val testedFactory = DefaultTimeseriesCollectorFactory( - internalLogger = mockInternalLogger, - collectInBackground = true, - scheduledExecutorService = mockExecutor, - pipelinesProvider = emptyProvider - ) + val fakeConfiguration = TimeseriesConfiguration.Builder() + .collectOnly(TimeseriesType.MEMORY) + .build() + val testedFactory = createFactory(fakeTotalRamBytes, fakeConfiguration) + + // When + val timeseries = testedFactory.create(fakeSessionType, fakeRumContext) + + // Then + check(timeseries is DefaultTimeseriesCollector) + assertThat(timeseries.pipelines).hasSize(1) + val eventFactory = timeseries.pipelines.single() + .getFieldValue>("eventFactory") + assertThat(eventFactory).isInstanceOf(MemoryEventFactory::class.java) + } + + @Test + fun `M build no pipelines W create() { configuration collects no types }`( + @LongForgery(min = 1L) fakeTotalRamBytes: Long + ) { + // Given + val fakeConfiguration = TimeseriesConfiguration.Builder() + .collectOnly(*emptyArray()) + .build() + val testedFactory = createFactory(fakeTotalRamBytes, fakeConfiguration) + + // When + val timeseries = testedFactory.create(fakeSessionType, fakeRumContext) + + // Then + check(timeseries is DefaultTimeseriesCollector) + assertThat(timeseries.pipelines).isEmpty() + } + + @Test + fun `M propagate executor to the collector and rum context to the pipelines W create()`( + @LongForgery(min = 1L) fakeTotalRamBytes: Long + ) { + // Given + val testedFactory = createFactory(fakeTotalRamBytes) + + // When + val timeseries = testedFactory.create(fakeSessionType, fakeRumContext) + + // Then + check(timeseries is DefaultTimeseriesCollector) + assertThat(timeseries.scheduledExecutorService).isSameAs(mockExecutor) + assertThat( + timeseries.pipelines.map { it.getFieldValue>("rumContext") } + ).containsOnly(fakeRumContext) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `M propagate collectInBackground to the collector W create()`( + fakeCollectInBackground: Boolean + ) { + // Given + val fakeConfiguration = TimeseriesConfiguration.Builder() + .collectInBackground(fakeCollectInBackground) + .build() + val testedFactory = createFactory(totalRamBytes = 1L, configuration = fakeConfiguration) + + // When + val timeseries = testedFactory.create(fakeSessionType, fakeRumContext) - // When / Then - assertDoesNotThrow { - testedFactory.create(fakeSessionId, fakeApplicationId, RumSessionType.USER) - } + // Then + check(timeseries is DefaultTimeseriesCollector) + assertThat( + timeseries.getFieldValue("collectInBackground") + ).isEqualTo(fakeCollectInBackground) } - private fun mockPipeline(): Pipeline<*> { - val reader = mock>() - val serializer = mock>() - return Pipeline(mockSdkCore, reader, Buffer(1), serializer, mockWriter, mockInternalLogger) + @Test + fun `M propagate session type and info providers to both factories W create()`( + @LongForgery(min = 1L) fakeTotalRamBytes: Long + ) { + // Given + val testedFactory = createFactory(fakeTotalRamBytes) + + // When + val timeseries = testedFactory.create(fakeSessionType, fakeRumContext) + + // Then + check(timeseries is DefaultTimeseriesCollector) + val cpuEventFactory = timeseries.pipelines[0] + .getFieldValue>("eventFactory") + val memoryEventFactory = timeseries.pipelines[1] + .getFieldValue>("eventFactory") + + assertThat( + cpuEventFactory.getFieldValue("sessionType") + ).isEqualTo(fakeSessionType) + assertThat( + cpuEventFactory.getFieldValue, CpuEventFactory>("batteryInfoProvider") + ).isSameAs(mockBatteryInfoProvider) + assertThat( + cpuEventFactory.getFieldValue, CpuEventFactory>("displayInfoProvider") + ).isSameAs(mockDisplayInfoProvider) + + assertThat( + memoryEventFactory.getFieldValue("sessionType") + ).isEqualTo(fakeSessionType) + assertThat( + memoryEventFactory.getFieldValue("totalRamBytes") + ).isEqualTo(fakeTotalRamBytes) + assertThat( + memoryEventFactory.getFieldValue, MemoryEventFactory>("batteryInfoProvider") + ).isSameAs(mockBatteryInfoProvider) + assertThat( + memoryEventFactory.getFieldValue, MemoryEventFactory>("displayInfoProvider") + ).isSameAs(mockDisplayInfoProvider) } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt index fc75784716..0bb9f5b93d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt @@ -15,13 +15,15 @@ import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.api.storage.DataWriter import com.datadog.android.api.storage.EventBatchWriter import com.datadog.android.api.storage.EventType +import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.scope.RumViewType +import com.datadog.android.rum.internal.timeseries.factory.EventFactory import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader -import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer import com.datadog.android.rum.utils.forge.Configurator import com.datadog.android.utils.verifyLog import com.google.gson.JsonObject import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -87,10 +89,13 @@ internal class DefaultTimeseriesCollectorTest { lateinit var mockReaderB: DataPointsReader @Mock - lateinit var mockSerializerA: JsonSerializer + lateinit var mockEventFactoryA: EventFactory @Mock - lateinit var mockSerializerB: JsonSerializer + lateinit var mockEventFactoryB: EventFactory + + @Forgery + lateinit var fakeRumContext: RumContext private lateinit var bufferA: Buffer private lateinit var bufferB: Buffer @@ -124,8 +129,8 @@ internal class DefaultTimeseriesCollectorTest { bufferA = Buffer(fakeBufferSize) bufferB = Buffer(fakeBufferSize) - pipelineA = Pipeline(mockSdkCore, mockReaderA, bufferA, mockSerializerA, mockDataWriter, mockInternalLogger) - pipelineB = Pipeline(mockSdkCore, mockReaderB, bufferB, mockSerializerB, mockDataWriter, mockInternalLogger) + pipelineA = createPipeline(mockReaderA, bufferA, mockEventFactoryA) + pipelineB = createPipeline(mockReaderB, bufferB, mockEventFactoryB) testedTimeseriesCollector = DefaultTimeseriesCollector( pipelines = listOf(pipelineA, pipelineB), @@ -182,14 +187,14 @@ internal class DefaultTimeseriesCollectorTest { } @Test - fun `M log error and continue W onSessionStop() { serializer throws on pipelineA }`(forge: Forge) { + fun `M log error and continue W onSessionStop() { event factory throws on pipelineA }`(forge: Forge) { // Given val fakeError = RuntimeException("serializer failure") val fakeJson = JsonObject().apply { addProperty("k", "v") } whenever(mockReaderA.read()) doReturn forge.getForgery>() whenever(mockReaderB.read()) doReturn forge.getForgery>() - whenever(mockSerializerA.serialize(any(), any())) doThrow fakeError - whenever(mockSerializerB.serialize(any(), any())) doReturn fakeJson + whenever(mockEventFactoryA.create(any(), any(), any())) doThrow fakeError + whenever(mockEventFactoryB.create(any(), any(), any())) doReturn fakeJson testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) val runnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) @@ -203,7 +208,7 @@ internal class DefaultTimeseriesCollectorTest { mockInternalLogger.verifyLog( InternalLogger.Level.ERROR, targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), - "Timeseries serialization failed", + "Timeseries event creation failed", fakeError ) verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) @@ -214,7 +219,7 @@ internal class DefaultTimeseriesCollectorTest { // Given val fakeJson = JsonObject().apply { addProperty("k", "v") } whenever(mockReaderA.read()) doReturn forge.getForgery>() - whenever(mockSerializerA.serialize(any(), any())) doReturn fakeJson + whenever(mockEventFactoryA.create(any(), any(), any())) doReturn fakeJson testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) runnableA.run() @@ -223,7 +228,7 @@ internal class DefaultTimeseriesCollectorTest { testedTimeseriesCollector.onSessionStop() // Then - verify(mockSerializerA).serialize(any(), any()) + verify(mockEventFactoryA).create(any(), any(), any()) verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) } @@ -234,7 +239,7 @@ internal class DefaultTimeseriesCollectorTest { val dataPoints = List(fakeBufferSize) { element } val fakeJson = JsonObject().apply { addProperty("a", "1") } whenever(mockReaderA.read()) doReturn element - whenever(mockSerializerA.serialize(any(), eq(dataPoints))) doReturn fakeJson + whenever(mockEventFactoryA.create(any(), any(), eq(dataPoints))) doReturn fakeJson testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) @@ -243,7 +248,7 @@ internal class DefaultTimeseriesCollectorTest { repeat(fakeBufferSize) { runnableA.run() } // Then - verify(mockSerializerA).serialize(any(), any()) + verify(mockEventFactoryA).create(any(), any(), any()) verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) } @@ -273,15 +278,15 @@ internal class DefaultTimeseriesCollectorTest { runnableA.run() // Then - verify(mockSerializerA, never()).serialize(any(), any()) + verify(mockEventFactoryA, never()).create(any(), any(), any()) verify(mockDataWriter, never()).write(any(), any(), any()) } @Test - fun `M skip writer W sample tick { serializer returns null }`(forge: Forge) { + fun `M skip writer W sample tick { event factory returns null }`(forge: Forge) { // Given - fill the buffer so Pipeline.drain() reaches the serializer; serializer returns null whenever(mockReaderA.read()) doReturn forge.getForgery>() - whenever(mockSerializerA.serialize(any(), any())) doReturn null + whenever(mockEventFactoryA.create(any(), any(), any())) doReturn null testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) @@ -289,7 +294,7 @@ internal class DefaultTimeseriesCollectorTest { repeat(fakeBufferSize) { runnableA.run() } // Then - verify(mockSerializerA).serialize(any(), any()) + verify(mockEventFactoryA).create(any(), any(), any()) verify(mockDataWriter, never()).write(any(), any(), any()) } @@ -393,8 +398,7 @@ internal class DefaultTimeseriesCollectorTest { whenever(mockBuffer.isFull()) doReturn true whenever(mockBuffer.drain()) doThrow fakeError whenever(mockReaderA.read()) doReturn forge.getForgery>() - val pipeline = - Pipeline(mockSdkCore, mockReaderA, mockBuffer, mockSerializerA, mockDataWriter, mockInternalLogger) + val pipeline = createPipeline(mockReaderA, mockBuffer, mockEventFactoryA) val timeseries = DefaultTimeseriesCollector( pipelines = listOf(pipeline), internalLogger = mockInternalLogger, @@ -420,12 +424,12 @@ internal class DefaultTimeseriesCollectorTest { } @Test - fun `M log error and reschedule W sample tick { serializer throws }`(forge: Forge) { + fun `M log error and reschedule W sample tick { event factory throws }`(forge: Forge) { // Given — serializer throws inside Pipeline's own try/catch: Pipeline logs it itself // and the tick completes normally, so the sampling chain reschedules as usual. val fakeError = RuntimeException("serializer failure") whenever(mockReaderA.read()) doReturn forge.getForgery>() - whenever(mockSerializerA.serialize(any(), any())) doThrow fakeError + whenever(mockEventFactoryA.create(any(), any(), any())) doThrow fakeError testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) @@ -438,7 +442,7 @@ internal class DefaultTimeseriesCollectorTest { mockInternalLogger.verifyLog( InternalLogger.Level.ERROR, targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), - "Timeseries serialization failed", + "Timeseries event creation failed", fakeError ) verify(mockDataWriter, never()).write(any(), any(), any()) @@ -597,6 +601,19 @@ internal class DefaultTimeseriesCollectorTest { .schedule(any(), eq(fakeIntervalBMs), eq(TimeUnit.MILLISECONDS)) } + private fun createPipeline( + reader: DataPointsReader, + buffer: Buffer, + eventFactory: EventFactory + ) = Pipeline( + sdkCore = mockSdkCore, + reader = reader, + buffer = buffer, + eventFactory = eventFactory, + dataWriter = mockDataWriter, + rumContext = fakeRumContext + ) + private fun captureScheduledRunnableForInterval(intervalMs: Long): Runnable { val captor = argumentCaptor() verify(mockExecutor).schedule(captor.capture(), eq(intervalMs), eq(TimeUnit.MILLISECONDS)) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt index 3dfee43c74..072542514f 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt @@ -15,15 +15,17 @@ import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.api.storage.DataWriter import com.datadog.android.api.storage.EventBatchWriter import com.datadog.android.api.storage.EventType +import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector +import com.datadog.android.rum.internal.timeseries.factory.EventFactory import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader -import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer import com.datadog.android.rum.internal.timeseries.serializer.TimeseriesAttributes import com.datadog.android.rum.utils.forge.Configurator import com.datadog.android.utils.verifyLog import com.datadog.tools.unit.forge.aThrowable import com.google.gson.JsonObject import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -55,11 +57,14 @@ import org.mockito.quality.Strictness @ForgeConfiguration(Configurator::class) internal class PipelineTest { + @Forgery + lateinit var fakeRumContext: RumContext + @Mock lateinit var mockReader: DataPointsReader @Mock - lateinit var mockSerializer: JsonSerializer + lateinit var mockEventFactory: EventFactory @Mock lateinit var mockSdkCore: FeatureSdkCore @@ -93,6 +98,7 @@ internal class PipelineTest { @BeforeEach fun `set up`() { + whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope whenever(mockRumFeatureScope.withWriteContext(any(), any())) doAnswer { it.getArgument<(DatadogContext, EventWriteScope) -> Unit>(it.arguments.lastIndex) @@ -102,15 +108,16 @@ internal class PipelineTest { it.getArgument<(EventBatchWriter) -> Unit>(0).invoke(mockEventBatchWriter) } whenever(mockDataWriter.write(any(), any(), any())) doReturn true + whenever(mockEventFactory.eventName) doReturn "view.cpu" buffer = Buffer(fakeBufferSize) testedPipeline = Pipeline( sdkCore = mockSdkCore, reader = mockReader, buffer = buffer, - serializer = mockSerializer, + eventFactory = mockEventFactory, dataWriter = mockDataWriter, - internalLogger = mockInternalLogger, + rumContext = fakeRumContext, insightsCollector = mockInsightsCollector ) } @@ -162,7 +169,8 @@ internal class PipelineTest { val fakeTimeseriesName = "view.cpu" val fakeJson = fakeTimeseriesJson(fakeTimeseriesName) whenever(mockReader.read()) doReturn fakePoint - whenever(mockSerializer.serialize(any(), any())) doReturn fakeJson + whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeJson + whenever(mockEventFactory.eventName) doReturn fakeTimeseriesName repeat(fakeBufferSize - 1) { testedPipeline.execute() } // When @@ -188,10 +196,10 @@ internal class PipelineTest { } @Test - fun `M not flush W execute() { serializer returns null }`(forge: Forge) { + fun `M not flush W execute() { event factory returns null }`(forge: Forge) { // Given whenever(mockReader.read()) doReturn forge.getForgery>() - whenever(mockSerializer.serialize(any(), any())) doReturn null + whenever(mockEventFactory.create(any(), any(), any())) doReturn null repeat(fakeBufferSize - 1) { testedPipeline.execute() } // When @@ -213,7 +221,8 @@ internal class PipelineTest { val fakeTimeseriesName = "view.memory" val fakeJson = fakeTimeseriesJson(fakeTimeseriesName) whenever(mockReader.read()) doReturn fakePoint - whenever(mockSerializer.serialize(any(), any())) doReturn fakeJson + whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeJson + whenever(mockEventFactory.eventName) doReturn fakeTimeseriesName val sampleCount = fakeBufferSize - 1 repeat(sampleCount) { testedPipeline.execute() } @@ -222,7 +231,7 @@ internal class PipelineTest { // Then val captor = argumentCaptor>>() - verify(mockSerializer).serialize(any(), captor.capture()) + verify(mockEventFactory).create(any(), eq(fakeRumContext), captor.capture()) assertThat(captor.firstValue).hasSize(sampleCount).allMatch { it == fakePoint } verify(mockDataWriter).write(mockEventBatchWriter, fakeJson, EventType.DEFAULT) verify(mockInsightsCollector).onTimeseries(fakeTimeseriesName) @@ -234,7 +243,7 @@ internal class PipelineTest { // Given val fakePoint = forge.getForgery>() whenever(mockRumFeatureScope.withWriteContext(any(), any())) doAnswer { Unit } - whenever(mockSerializer.serialize(any(), any())) doReturn fakeTimeseriesJson("view.cpu") + whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeTimeseriesJson("view.cpu") whenever(mockReader.read()) doReturn fakePoint testedPipeline.execute() @@ -243,13 +252,13 @@ internal class PipelineTest { // Then assertThat(buffer.drain()).isEmpty() - verifyNoInteractions(mockSerializer) + verifyNoInteractions(mockEventFactory) verifyNoInteractions(mockDataWriter) verifyNoInteractions(mockInsightsCollector) } @Test - fun `M pass only pre-flush points to serializer W execute() { point added before write context callback invoked }`( + fun `M pass pre-flush points to event factory W execute() { point added before callback }`( forge: Forge ) { // Given @@ -257,7 +266,7 @@ internal class PipelineTest { val fakeNextPoint = forge.getForgery>() val callbackCaptor = argumentCaptor<(DatadogContext, EventWriteScope) -> Unit>() whenever(mockRumFeatureScope.withWriteContext(any(), any())) doAnswer { Unit } - whenever(mockSerializer.serialize(any(), any())) doReturn fakeTimeseriesJson("view.cpu") + whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeTimeseriesJson("view.cpu") whenever(mockReader.read()) doReturn fakePoint testedPipeline.execute() testedPipeline.flush() @@ -270,23 +279,40 @@ internal class PipelineTest { // Then val dataPointsCaptor = argumentCaptor>>() - verify(mockSerializer).serialize(eq(mockDatadogContext), dataPointsCaptor.capture()) + verify(mockEventFactory).create(eq(mockDatadogContext), eq(fakeRumContext), dataPointsCaptor.capture()) assertThat(dataPointsCaptor.firstValue).containsExactly(fakePoint) assertThat(buffer.drain()).containsExactly(fakeNextPoint) } @Test - fun `M pass datadogContext from withWriteContext W flush()`(forge: Forge) { + fun `M pass contexts to event factory W flush()`(forge: Forge) { // Given whenever(mockReader.read()) doReturn forge.getForgery>() - whenever(mockSerializer.serialize(any(), any())) doReturn fakeTimeseriesJson("view.cpu") + whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeTimeseriesJson("view.cpu") testedPipeline.execute() // When testedPipeline.flush() // Then - verify(mockSerializer).serialize(eq(mockDatadogContext), any()) + verify(mockEventFactory).create(eq(mockDatadogContext), eq(fakeRumContext), any()) + } + + @Test + fun `M request event creation feature contexts W flush()`(forge: Forge) { + // Given + whenever(mockReader.read()) doReturn forge.getForgery>() + whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeTimeseriesJson("view.cpu") + testedPipeline.execute() + + // When + testedPipeline.flush() + + // Then + verify(mockRumFeatureScope).withWriteContext( + eq(setOf(Feature.SESSION_REPLAY_FEATURE_NAME, Feature.TRACING_FEATURE_NAME)), + any() + ) } @Test @@ -300,11 +326,11 @@ internal class PipelineTest { } @Test - fun `M log error W flush() { serializer throws }`(forge: Forge) { + fun `M log error W flush() { event factory throws }`(forge: Forge) { // Given val fakeThrowable = forge.aThrowable() whenever(mockReader.read()) doReturn forge.getForgery>() - whenever(mockSerializer.serialize(any(), any())) doThrow fakeThrowable + whenever(mockEventFactory.create(any(), any(), any())) doThrow fakeThrowable testedPipeline.execute() // When @@ -314,7 +340,7 @@ internal class PipelineTest { mockInternalLogger.verifyLog( level = InternalLogger.Level.ERROR, targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), - message = "Timeseries serialization failed", + message = "Timeseries event creation failed", throwable = fakeThrowable ) verifyNoInteractions(mockDataWriter) @@ -328,7 +354,7 @@ internal class PipelineTest { val fakeJson = fakeTimeseriesJson("view.cpu") val writeBlockCaptor = argumentCaptor<(EventBatchWriter) -> Unit>() whenever(mockReader.read()) doReturn forge.getForgery>() - whenever(mockSerializer.serialize(any(), any())) doReturn fakeJson + whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeJson whenever(mockDataWriter.write(any(), any(), any())) doThrow fakeThrowable whenever(mockEventWriteScope.invoke(any())) doAnswer { Unit } testedPipeline.execute() @@ -353,7 +379,7 @@ internal class PipelineTest { // Given val fakeJson = fakeTimeseriesJson("view.cpu") whenever(mockReader.read()) doReturn forge.getForgery>() - whenever(mockSerializer.serialize(any(), any())) doReturn fakeJson + whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeJson whenever(mockDataWriter.write(any(), any(), any())) doReturn false testedPipeline.execute() @@ -371,7 +397,7 @@ internal class PipelineTest { add( TimeseriesAttributes.KEY_TIMESERIES, JsonObject().apply { - addProperty("name", name) + addProperty(TimeseriesAttributes.KEY_NAME, name) } ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CSVReader.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CSVReader.kt index 2944e34a7e..d4ba3b6859 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CSVReader.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CSVReader.kt @@ -29,9 +29,6 @@ internal class CSVReader( fun hasNext(): Boolean = index < samples.size - /** Total number of samples available for the metric. */ - val size: Int get() = samples.size - override fun read(): DataPoint { check(hasNext()) { "CSVReader exhausted" } return samples[index++] diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt index bde2287135..91dbbd9017 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt @@ -6,16 +6,25 @@ package com.datadog.android.rum.internal.timeseries.csv +import com.datadog.android.api.InternalLogger import com.datadog.android.api.context.DatadogContext import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.domain.InfoData +import com.datadog.android.rum.internal.domain.InfoProvider +import com.datadog.android.rum.internal.domain.RumContext +import com.datadog.android.rum.internal.domain.battery.BatteryInfo +import com.datadog.android.rum.internal.domain.display.DisplayInfo +import com.datadog.android.rum.internal.domain.event.RumEventSerializer import com.datadog.android.rum.internal.domain.scope.RumViewType import com.datadog.android.rum.internal.timeseries.Buffer import com.datadog.android.rum.internal.timeseries.TimeseriesCollector -import com.datadog.android.rum.internal.timeseries.serializer.CpuEventSerializer -import com.datadog.android.rum.internal.timeseries.serializer.JsonSerializer -import com.datadog.android.rum.internal.timeseries.serializer.MemoryEventSerializer +import com.datadog.android.rum.internal.timeseries.factory.CpuEventFactory +import com.datadog.android.rum.internal.timeseries.factory.EventFactory +import com.datadog.android.rum.internal.timeseries.factory.MemoryEventFactory import com.google.gson.JsonObject +import com.google.gson.JsonParser +import fr.xgouchet.elmyr.Forge /** * A test-only [TimeseriesCollector] that mirrors the wiring done by `DefaultTimeseriesCollectorFactory`, @@ -23,8 +32,10 @@ import com.google.gson.JsonObject * pipelines synchronously (no executor) so callers can assert on every emitted JSON. */ internal class CsvCollector( - private val pipelines: List, JsonSerializer>>, - private val datadogContext: DatadogContext + private val pipelines: List, EventFactory>>, + private val datadogContext: DatadogContext, + private val rumContext: RumContext, + private val eventSerializer: RumEventSerializer ) : TimeseriesCollector { private val emitted = mutableListOf() @@ -33,23 +44,25 @@ internal class CsvCollector( val captured: List get() = emitted.toList() /** - * Walks each (reader, buffer, serializer) triple synchronously: samples until the reader is + * Walks each (reader, buffer, factory) triple synchronously: samples until the reader is * exhausted, flushing whenever the buffer is full, then emitting any final remainder. */ override fun onSessionStart() { - for ((reader, buffer, serializer) in pipelines) { + for ((reader, buffer, eventFactory) in pipelines) { while (reader.hasNext()) { buffer.add(reader.read()) - if (buffer.isFull()) flush(buffer, serializer) + if (buffer.isFull()) flush(buffer, eventFactory) } - flush(buffer, serializer) + flush(buffer, eventFactory) } } - private fun flush(buffer: Buffer, serializer: JsonSerializer) = buffer.drain() + private fun flush(buffer: Buffer, eventFactory: EventFactory) = buffer.drain() .takeIf { it.isNotEmpty() } - ?.let { serializer.serialize(datadogContext, it) } - ?.let(emitted::add) + ?.let { eventFactory.create(datadogContext, rumContext, it) } + ?.let(eventSerializer::serialize) + ?.let(JsonParser::parseString) + ?.let { emitted.add(it.asJsonObject) } override fun onSessionStop() = Unit @@ -60,44 +73,58 @@ internal class CsvCollector( /** * Builds a [CsvCollector] with the same shape as `DefaultTimeseriesCollectorFactory`: * a memory pipeline followed by a CPU pipeline, each backed by [CSVReader] and the - * production [MemoryEventSerializer] / [CpuEventSerializer]. + * production [MemoryEventFactory] / [CpuEventFactory]. */ - @Suppress("LongParameterList") fun create( csvContent: String, - sessionId: String, - applicationId: String, sessionType: RumSessionType, totalRamBytes: Long, bufferSize: Int, timeProvider: TimeProvider, - datadogContext: DatadogContext - ) = CsvCollector( - datadogContext = datadogContext, - pipelines = listOf( - Triple( - CSVReader(csvContent, METRIC_MEMORY_USAGE, timeProvider), - Buffer(bufferSize), - MemoryEventSerializer( - sessionId = sessionId, - applicationId = applicationId, - sessionType = sessionType, - totalRamBytes = totalRamBytes, - timeProvider = timeProvider - ) - ), - Triple( - CSVReader(csvContent, METRIC_CPU_USAGE, timeProvider), - Buffer(bufferSize), - CpuEventSerializer( - sessionId = sessionId, - applicationId = applicationId, - sessionType = sessionType, - timeProvider = timeProvider + forge: Forge, + internalLogger: InternalLogger + ): CsvCollector { + val rumContext = forge.getForgery() + val datadogContext = forge.getForgery() + val batteryInfo = forge.getForgery() + val displayInfo = forge.getForgery() + + return CsvCollector( + datadogContext = datadogContext, + rumContext = rumContext, + eventSerializer = RumEventSerializer(internalLogger), + pipelines = listOf( + Triple( + CSVReader(csvContent, METRIC_MEMORY_USAGE, timeProvider), + Buffer(bufferSize), + MemoryEventFactory( + sessionType = sessionType, + totalRamBytes = totalRamBytes, + timeProvider = timeProvider, + batteryInfoProvider = FixedInfoProvider(batteryInfo), + displayInfoProvider = FixedInfoProvider(displayInfo), + internalLogger = internalLogger + ) + ), + Triple( + CSVReader(csvContent, METRIC_CPU_USAGE, timeProvider), + Buffer(bufferSize), + CpuEventFactory( + sessionType = sessionType, + timeProvider = timeProvider, + batteryInfoProvider = FixedInfoProvider(batteryInfo), + displayInfoProvider = FixedInfoProvider(displayInfo), + internalLogger = internalLogger + ) ) ) ) - ) + } + + private class FixedInfoProvider(private val state: T) : InfoProvider { + override fun getState(): T = state + override fun cleanup() = Unit + } const val METRIC_MEMORY_USAGE: String = "memory_usage" const val METRIC_CPU_USAGE: String = "cpu_usage" diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesCollectorEndToEndTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesCollectorEndToEndTest.kt index f423f371e7..79ccf67418 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesCollectorEndToEndTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/TimeseriesCollectorEndToEndTest.kt @@ -6,21 +6,17 @@ package com.datadog.android.rum.internal.timeseries.csv -import com.datadog.android.api.context.DatadogContext import com.datadog.android.internal.time.TimeProvider import com.datadog.android.rum.RumSessionType -import com.datadog.android.rum.internal.timeseries.serializer.TimeseriesAttributes +import com.datadog.android.rum.assertj.TimeseriesCpuEventAssert +import com.datadog.android.rum.assertj.TimeseriesMemoryEventAssert +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.utils.forge.Configurator -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonNull -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import com.google.gson.JsonPrimitive +import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.fail import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -29,10 +25,9 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.mockito.quality.Strictness -import java.math.BigDecimal -import java.util.UUID @Extensions( ExtendWith(MockitoExtension::class), @@ -45,208 +40,118 @@ internal class TimeseriesCollectorEndToEndTest { @Mock lateinit var mockTimeProvider: TimeProvider - @Mock - lateinit var mockDatadogContext: DatadogContext - - private lateinit var csvContent: String - private lateinit var emitted: List + private lateinit var capturedMemoryEvents: List + private lateinit var capturedCpuEvents: List @BeforeEach - fun `set up`() { - whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn FIXED_DATE_MS + fun `set up`(forge: Forge) { + whenever(mockTimeProvider.getServerTimestampMillis()) doReturn FIXED_DATE_MS - csvContent = loadResource("fixtures/timeseries/input_memory_cpu.csv") val testedTimeseriesCollector = CsvCollector.create( - csvContent = csvContent, - sessionId = SESSION_ID, - applicationId = APPLICATION_ID, + csvContent = loadResource("fixtures/timeseries/input_memory_cpu.csv"), sessionType = RumSessionType.USER, totalRamBytes = TOTAL_RAM_BYTES, bufferSize = BATCH_SIZE, timeProvider = mockTimeProvider, - datadogContext = mockDatadogContext + forge = forge, + internalLogger = mock() ) testedTimeseriesCollector.onSessionStart() - testedTimeseriesCollector.onSessionStop() - emitted = testedTimeseriesCollector.captured + capturedMemoryEvents = testedTimeseriesCollector.captured.mapNotNull { serializedPayload -> + runCatching { TimeseriesMemoryEvent.fromJsonObject(serializedPayload) } + .getOrNull() + } + capturedCpuEvents = testedTimeseriesCollector.captured.mapNotNull { serializedPayload -> + runCatching { TimeseriesCpuEvent.fromJsonObject(serializedPayload) } + .getOrNull() + } } @Test fun `M emit two memory and two cpu batches W full CSV is fed { batchSize=5, samples=10 }`() { // Then - val memoryEvents = emitted.filter { metricNameOf(it) == NAME_MEMORY } - val cpuEvents = emitted.filter { metricNameOf(it) == NAME_CPU } - - assertThat(memoryEvents).hasSize(2) - assertThat(cpuEvents).hasSize(2) + assertThat(capturedMemoryEvents.size).isEqualTo(2) + assertThat(capturedCpuEvents.size).isEqualTo(2) } @Test - fun `M match expected fixture W memory batch 1`() { - val expected = loadFixture("fixtures/timeseries/expected_memory_batch1.json") - val actual = emitted.filter { metricNameOf(it) == NAME_MEMORY }[0] - assertJsonEquals(expected, maskTimeseriesId(actual)) + fun `M match expected timeseries fixture W memory batch 1`() { + val expected = loadExpectedMemoryTimeseries("fixtures/timeseries/expected_memory_batch1.json") + val actual = capturedMemoryEvents[0] + + TimeseriesMemoryEventAssert.assertThat(actual) + .hasDate(expected.date) + .hasSameTimeseriesAs(expected) } @Test - fun `M match expected fixture W memory batch 2`() { - val expected = loadFixture("fixtures/timeseries/expected_memory_batch2.json") - val actual = emitted.filter { metricNameOf(it) == NAME_MEMORY }[1] - assertJsonEquals(expected, maskTimeseriesId(actual)) + fun `M match expected timeseries fixture W memory batch 2`() { + val expected = loadExpectedMemoryTimeseries("fixtures/timeseries/expected_memory_batch2.json") + val actual = capturedMemoryEvents[1] + + TimeseriesMemoryEventAssert.assertThat(actual) + .hasDate(expected.date) + .hasSameTimeseriesAs(expected) } @Test - fun `M match expected fixture W cpu batch 1`() { - val expected = loadFixture("fixtures/timeseries/expected_cpu_batch1.json") - val actual = emitted.filter { metricNameOf(it) == NAME_CPU }[0] - assertJsonEquals(expected, maskTimeseriesId(actual)) + fun `M match expected timeseries fixture W cpu batch 1`() { + val expected = loadExpectedCpuTimeseries("fixtures/timeseries/expected_cpu_batch1.json") + val actual = capturedCpuEvents[0] + + TimeseriesCpuEventAssert.assertThat(actual) + .hasDate(expected.date) + .hasSameTimeseriesAs(expected) } @Test - fun `M match expected fixture W cpu batch 2`() { - val expected = loadFixture("fixtures/timeseries/expected_cpu_batch2.json") - val actual = emitted.filter { metricNameOf(it) == NAME_CPU }[1] - assertJsonEquals(expected, maskTimeseriesId(actual)) + fun `M match expected timeseries fixture W cpu batch 2`() { + val expected = loadExpectedCpuTimeseries("fixtures/timeseries/expected_cpu_batch2.json") + val actual = capturedCpuEvents[1] + + TimeseriesCpuEventAssert.assertThat(actual) + .hasDate(expected.date) + .hasSameTimeseriesAs(expected) } @Test fun `M emit valid timeseries id W any batch`() { - // Each emitted event must carry a non-blank UUID; randomness is not asserted. - for (event in emitted) { - val id = event.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES).get("id").asString - assertThat(UUID.fromString(id)).isNotNull + capturedMemoryEvents.forEach { actual -> + TimeseriesMemoryEventAssert.assertThat(actual) + .hasValidTimeseriesId() + } + capturedCpuEvents.forEach { actual -> + TimeseriesCpuEventAssert.assertThat(actual) + .hasValidTimeseriesId() } } @Test fun `M produce monotonic start lt or eq to end W any batch`() { - for (event in emitted) { - val ts = event.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - assertThat(ts.get("start").asLong).isLessThanOrEqualTo(ts.get("end").asLong) - } - } - - private fun metricNameOf(event: JsonObject): String = - event.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - .get(TimeseriesAttributes.KEY_NAME) - .asString - - private fun maskTimeseriesId(actual: JsonObject): JsonObject { - val copy = actual.deepCopy() - copy.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - .addProperty(KEY_ID, MASKED_UUID) - return copy - } - - private fun loadFixture(path: String): JsonElement = JsonParser.parseString(loadResource(path)) - - private fun loadResource(path: String): String { - val stream = this::class.java.classLoader?.getResourceAsStream(path) - ?: error("Resource not found: $path") - return stream.bufferedReader().use { it.readText() } - } - - /** - * Structural JSON comparison that ignores key insertion order and treats numerically - * equal primitives as equal regardless of literal representation - * (e.g. `31233300` vs `3.12333E7`). - */ - private fun assertJsonEquals(expected: JsonElement, actual: JsonElement, path: String = "$") { - when { - expected is JsonObject && actual is JsonObject -> { - val expectedKeys: Set = expected.keySet() - val actualKeys: Set = actual.keySet() - assertThat(actualKeys.toList()) - .describedAs("Keys at %s", path) - .containsExactlyInAnyOrderElementsOf(expectedKeys.toList()) - for (key in expectedKeys) { - assertJsonEquals(expected.get(key), actual.get(key), "$path.$key") - } - } - expected is JsonArray && actual is JsonArray -> { - assertThat(actual.size()) - .describedAs("Array size at %s", path) - .isEqualTo(expected.size()) - for (i in 0 until expected.size()) { - assertJsonEquals(expected.get(i), actual.get(i), "$path[$i]") - } - } - expected is JsonPrimitive && actual is JsonPrimitive -> { - assertPrimitiveEquals(expected, actual, path) - } - expected is JsonNull && actual is JsonNull -> Unit - else -> assertThat(actual) - .describedAs("Type mismatch at %s", path) - .isEqualTo(expected) - } - } - - private fun assertPrimitiveEquals(expected: JsonPrimitive, actual: JsonPrimitive, path: String) { - when { - expected.isString != actual.isString -> - fail( - "JSON type mismatch at $path: expected ${if (expected.isString) "string" else "number/boolean"}," + - " got ${if (actual.isString) "string" else "number/boolean"}" + - " (values: ${expected.asString} vs ${actual.asString})" - ) - expected.isBoolean != actual.isBoolean -> - fail( - "JSON type mismatch at $path: expected ${if (expected.isBoolean) "boolean" else "non-boolean"}," + - " got ${if (actual.isBoolean) "boolean" else "non-boolean"}" + - " (values: ${expected.asString} vs ${actual.asString})" - ) - expected.isString -> - assertThat(actual.asString).describedAs("String at %s", path).isEqualTo(expected.asString) - expected.isBoolean -> - assertThat(actual.asBoolean).describedAs("Boolean at %s", path).isEqualTo(expected.asBoolean) - else -> assertNumericPrimitivesEqual(expected, actual, path) + capturedMemoryEvents.forEach { actual -> + TimeseriesMemoryEventAssert.assertThat(actual) + .hasTimeseriesStartNotAfterEnd() } - } - - // Numeric: integer-typed values must match exactly; floating-point values are compared - // with a small relative tolerance to absorb 64-bit FP rounding (e.g. computed - // memory_percent of 3.1233299999999997 vs expected 3.12333). - private fun assertNumericPrimitivesEqual(expected: JsonPrimitive, actual: JsonPrimitive, path: String) { - val expectedBd = expected.asString.toBigDecimalOrNull() - ?: error("Expected numeric value at $path, got: ${expected.asString}") - val actualBd = actual.asString.toBigDecimalOrNull() - ?: error("Actual numeric value at $path is not a number: ${actual.asString}") - - val isFloat = expected.asString.contains('.') || - expected.asString.contains('e', ignoreCase = true) || - actual.asString.contains('.') || - actual.asString.contains('e', ignoreCase = true) - - if (isFloat) { - val expectedDouble = expectedBd.toDouble() - val actualDouble = actualBd.toDouble() - assertThat(actualDouble) - .describedAs("Double at %s: expected=%s, actual=%s", path, expectedDouble, actualDouble) - .isCloseTo(expectedDouble, org.assertj.core.data.Offset.offset(FP_TOLERANCE)) - } else { - assertThat(actualBd.compareTo(expectedBd)) - .describedAs("Integer at %s: expected=%s, actual=%s", path, expectedBd, actualBd) - .isZero + capturedCpuEvents.forEach { actual -> + TimeseriesCpuEventAssert.assertThat(actual) + .hasTimeseriesStartNotAfterEnd() } } - private fun String.toBigDecimalOrNull(): BigDecimal? = try { - BigDecimal(this) - } catch (_: NumberFormatException) { - null - } - - companion object { - private const val SESSION_ID: String = "fake-session-id" - private const val APPLICATION_ID: String = "fake-app-id" + private companion object { private const val FIXED_DATE_MS: Long = 1_700_000_001_000L private const val TOTAL_RAM_BYTES: Long = 1_000_000_000L private const val BATCH_SIZE: Int = 5 - private const val NAME_MEMORY: String = "memory" - private const val NAME_CPU: String = "cpu" - private const val MASKED_UUID: String = "00000000-0000-0000-0000-000000000000" - private const val FP_TOLERANCE: Double = 1e-9 + private fun loadExpectedMemoryTimeseries(path: String): TimeseriesMemoryEvent = + TimeseriesMemoryEvent.fromJson(loadResource(path)) + + private fun loadExpectedCpuTimeseries(path: String): TimeseriesCpuEvent = + TimeseriesCpuEvent.fromJson(loadResource(path)) - private const val KEY_ID: String = "id" + private fun loadResource(path: String): String { + val stream = this::class.java.classLoader?.getResourceAsStream(path) + ?: error("Resource not found: $path") + return stream.bufferedReader().use { it.readText() } + } } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/factory/CpuEventFactoryTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/factory/CpuEventFactoryTest.kt new file mode 100644 index 0000000000..f2da0c57a3 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/factory/CpuEventFactoryTest.kt @@ -0,0 +1,469 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.factory + +import com.datadog.android.api.InternalLogger +import com.datadog.android.api.context.DatadogContext +import com.datadog.android.api.feature.Feature +import com.datadog.android.core.internal.utils.DdTagsUtils +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.assertj.TimeseriesCpuEventAssert +import com.datadog.android.rum.internal.FeaturesContextResolver +import com.datadog.android.rum.internal.domain.InfoProvider +import com.datadog.android.rum.internal.domain.RumContext +import com.datadog.android.rum.internal.domain.battery.BatteryInfo +import com.datadog.android.rum.internal.domain.display.DisplayInfo +import com.datadog.android.rum.internal.domain.scope.RumSessionScope +import com.datadog.android.rum.internal.domain.scope.RumViewScope +import com.datadog.android.rum.internal.domain.scope.toTimeseriesCpuSchemaType +import com.datadog.android.rum.internal.timeseries.DataPoint +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent.TimeseriesCpuEventSessionType +import com.datadog.android.rum.utils.forge.Configurator +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.BoolForgery +import fr.xgouchet.elmyr.annotation.DoubleForgery +import fr.xgouchet.elmyr.annotation.FloatForgery +import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.annotation.LongForgery +import fr.xgouchet.elmyr.annotation.StringForgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(Configurator::class) +internal class CpuEventFactoryTest { + + @Mock + lateinit var mockTimeProvider: TimeProvider + + @Mock + lateinit var mockInternalLogger: InternalLogger + + @Mock + lateinit var stubBatteryInfoProvider: InfoProvider + + @Mock + lateinit var stubDisplayInfoProvider: InfoProvider + + @StringForgery + lateinit var fakeSessionId: String + + @StringForgery + lateinit var fakeApplicationId: String + + @LongForgery(min = 1L) + var fakeNowMs: Long = 0L + + @FloatForgery(min = 0f, max = 1f) + var fakeBatteryLevel: Float = 0f + + @BoolForgery + var fakeLowPowerMode: Boolean = false + + @FloatForgery(min = 0f, max = 1f) + var fakeBrightnessLevel: Float = 0f + + @Forgery + lateinit var fakeDatadogContext: DatadogContext + + private var fakeSource: TimeseriesCpuEvent.Source? = null + + @Forgery + lateinit var fakeRumContext: RumContext + + private val fakeSerializedRumContext: RumContext + get() = fakeRumContext.copy(applicationId = fakeApplicationId, sessionId = fakeSessionId) + + private val oneSample = listOf(DataPoint(1L, 1.0)) + + @BeforeEach + fun `set up`(forge: Forge) { + fakeSource = forge.aNullable { aValueFrom(TimeseriesCpuEvent.Source::class.java) } + fakeDatadogContext = fakeDatadogContext.copy( + source = fakeSource?.toJson()?.asString ?: forge.anAlphabeticalString() + ) + whenever(mockTimeProvider.getServerTimestampMillis()) doReturn fakeNowMs + whenever(stubBatteryInfoProvider.getState()) doReturn BatteryInfo( + batteryLevel = fakeBatteryLevel, + lowPowerMode = fakeLowPowerMode + ) + whenever(stubDisplayInfoProvider.getState()) doReturn DisplayInfo(screenBrightness = fakeBrightnessLevel) + } + + private fun testedFactory( + sessionType: RumSessionType = RumSessionType.USER + ) = CpuEventFactory( + sessionType = sessionType, + timeProvider = mockTimeProvider, + batteryInfoProvider = stubBatteryInfoProvider, + displayInfoProvider = stubDisplayInfoProvider, + internalLogger = mockInternalLogger + ) + + @Test + fun `M return null W create() { empty input }`() { + // When + val result = testedFactory().create(fakeDatadogContext, fakeSerializedRumContext, emptyList()) + + // Then + assertThat(result).isNull() + } + + @Test + fun `M fill timeseries W create() { multiple data points }`( + @DoubleForgery(min = 0.0, max = 100.0) fakeCpuValue: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // Given + val samples = listOf( + DataPoint(fakeTs, fakeCpuValue), + DataPoint(fakeTs + 1L, fakeCpuValue + 0.1) + ) + + // When + val event = createSerializedEvent(dataPoints = samples) + + // Then + TimeseriesCpuEventAssert.assertThat(event) + .hasTimeseriesName(VALUE_NAME_CPU) + .hasTimeseriesSchema(VALUE_SCHEMA_OBJECT) + .hasDate(fakeNowMs) + .hasValidTimeseriesId() + .hasDataPointsCount(2) + .hasTimeseriesStart(fakeTs) + .hasTimeseriesEnd(fakeTs + 1L) + .hasApplicationId(fakeApplicationId) + .hasSessionId(fakeSessionId) + .hasSessionType(TimeseriesCpuEventSessionType.USER) + .hasService(fakeDatadogContext.service) + .hasVersion(fakeDatadogContext.version) + } + + @Test + fun `M map session type W create() { synthetics }`() { + // When + val event = createEvent(sessionType = RumSessionType.SYNTHETICS) + + // Then + TimeseriesCpuEventAssert.assertThat(event).hasSessionType(TimeseriesCpuEventSessionType.SYNTHETICS) + } + + @Test + fun `M return generated timeseries name W eventName`() { + // When / Then + assertThat(testedFactory().eventName).isEqualTo(createEvent().timeseries.name) + } + + @Test + fun `M fill cpu_usage W create() { one value per data point }`( + @DoubleForgery(min = 1.0, max = 100.0) fakeFirstCpu: Double, + @DoubleForgery(min = 1.0, max = 100.0) fakeSecondCpu: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // Given + val samples = listOf( + DataPoint(fakeTs, fakeFirstCpu), + DataPoint(fakeTs + 1L, fakeSecondCpu) + ) + + // When + val event = createSerializedEvent(dataPoints = samples) + + // Then + TimeseriesCpuEventAssert.assertThat(event) + .hasDataPointsCount(2) + .hasCpuUsage(fakeFirstCpu, position = 0) + .hasCpuUsage(fakeSecondCpu, position = 1) + .hasTimestamps(fakeTs, fakeTs + 1L) + } + + @Test + fun `M fill application and build info W create()`( + @StringForgery fakeBuildId: String + ) { + // Given + val datadogContext = fakeDatadogContext.copy(appBuildId = fakeBuildId) + + // When + val event = createEvent(datadogContext = datadogContext) + + // Then + assertThat(event.application.id).isEqualTo(fakeApplicationId) + assertThat(event.application.currentLocale) + .isEqualTo(datadogContext.deviceInfo.localeInfo.currentLocale) + assertThat(event.source).isEqualTo(fakeSource) + assertThat(event.buildVersion).isEqualTo(datadogContext.versionCode.toString()) + assertThat(event.buildId).isEqualTo(fakeBuildId) + assertThat(event.ddtags).isEqualTo(DdTagsUtils.toDdTagsString(datadogContext)) + } + + @Test + fun `M fill os and device W create()`() { + // Given + val deviceInfo = fakeDatadogContext.deviceInfo + + // When + val event = createEvent() + + // Then + val os = checkNotNull(event.os) + assertThat(os.name).isEqualTo(deviceInfo.osName) + assertThat(os.version).isEqualTo(deviceInfo.osVersion) + assertThat(os.versionMajor).isEqualTo(deviceInfo.osMajorVersion) + + val device = checkNotNull(event.device) + assertThat(device.type).isEqualTo(deviceInfo.deviceType.toTimeseriesCpuSchemaType()) + assertThat(device.name).isEqualTo(deviceInfo.deviceName) + assertThat(device.model).isEqualTo(deviceInfo.deviceModel) + assertThat(device.brand).isEqualTo(deviceInfo.deviceBrand) + assertThat(device.architecture).isEqualTo(deviceInfo.architecture) + assertThat(device.locales).isEqualTo(deviceInfo.localeInfo.locales) + assertThat(device.timeZone).isEqualTo(deviceInfo.localeInfo.timeZone) + assertThat(device.logicalCpuCount?.toInt()).isEqualTo(deviceInfo.logicalCpuCount) + assertThat(device.totalRam?.toInt()).isEqualTo(deviceInfo.totalRam) + assertThat(device.isLowRam).isEqualTo(deviceInfo.isLowRam) + assertThat(device.batteryLevel?.toFloat()).isEqualTo(fakeBatteryLevel) + assertThat(device.powerSavingMode).isEqualTo(fakeLowPowerMode) + assertThat(device.brightnessLevel?.toFloat()).isEqualTo(fakeBrightnessLevel) + } + + @Test + fun `M leave device power info null W create() { providers have no data }`() { + // Given + whenever(stubBatteryInfoProvider.getState()) doReturn BatteryInfo() + whenever(stubDisplayInfoProvider.getState()) doReturn DisplayInfo() + + // When + val event = createEvent() + + // Then + val device = checkNotNull(event.device) + assertThat(device.batteryLevel).isNull() + assertThat(device.powerSavingMode).isNull() + assertThat(device.brightnessLevel).isNull() + } + + @Test + fun `M fill dd W create()`( + @DoubleForgery(min = 0.0, max = 100.0) fakeSampleRate: Double + ) { + // Given + val rumContext = fakeSerializedRumContext.copy( + sessionStartReason = RumSessionScope.StartReason.INACTIVITY_TIMEOUT, + sessionSampleRate = fakeSampleRate.toFloat() + ) + + // When + val event = createEvent(rumContext = rumContext) + + // Then + assertThat(event.dd.session?.sessionPrecondition) + .isEqualTo(TimeseriesCpuEvent.SessionPrecondition.INACTIVITY_TIMEOUT) + assertThat(event.dd.configuration?.sessionSampleRate?.toFloat()) + .isEqualTo(fakeSampleRate.toFloat()) + } + + @Test + fun `M fill dd configuration W create() { sample rates in features context }`( + @LongForgery(min = 0L, max = 100L) fakeSessionReplaySampleRate: Long, + @FloatForgery(min = 0f, max = 100f) fakeTraceSampleRate: Float + ) { + // Given + val datadogContext = fakeDatadogContext.copy( + featuresContext = fakeDatadogContext.featuresContext + mapOf( + Feature.SESSION_REPLAY_FEATURE_NAME to mapOf( + RumViewScope.SESSION_REPLAY_SAMPLE_RATE_KEY to fakeSessionReplaySampleRate + ), + Feature.TRACING_FEATURE_NAME to mapOf( + RumViewScope.TRACE_SAMPLE_RATE to fakeTraceSampleRate + ) + ) + ) + + // When + val event = createEvent(datadogContext = datadogContext) + + // Then + val configuration = checkNotNull(event.dd.configuration) + assertThat(configuration.sessionReplaySampleRate?.toLong()).isEqualTo(fakeSessionReplaySampleRate) + assertThat(configuration.traceSampleRate?.toFloat()).isEqualTo(fakeTraceSampleRate) + } + + @Test + fun `M fill dd configuration W create() { sample rates missing or of wrong type }`( + @StringForgery fakeNotASampleRate: String + ) { + // Given + val datadogContext = fakeDatadogContext.copy( + featuresContext = fakeDatadogContext.featuresContext + mapOf( + Feature.SESSION_REPLAY_FEATURE_NAME to emptyMap(), + Feature.TRACING_FEATURE_NAME to mapOf(RumViewScope.TRACE_SAMPLE_RATE to fakeNotASampleRate) + ) + ) + + // When + val event = createEvent(datadogContext = datadogContext) + + // Then + val configuration = checkNotNull(event.dd.configuration) + assertThat(configuration.sessionReplaySampleRate).isNull() + assertThat(configuration.traceSampleRate).isNull() + } + + @Test + fun `M fill session hasReplay W create() { view is recorded }`( + @StringForgery fakeViewId: String + ) { + // Given + val rumContext = fakeSerializedRumContext.copy(viewId = fakeViewId) + val datadogContext = fakeDatadogContext.copy( + featuresContext = fakeDatadogContext.featuresContext + mapOf( + Feature.SESSION_REPLAY_FEATURE_NAME to mapOf( + fakeViewId to mapOf(FeaturesContextResolver.HAS_REPLAY_KEY to true) + ) + ) + ) + + // When + val event = createEvent(datadogContext = datadogContext, rumContext = rumContext) + + // Then + assertThat(event.session.hasReplay).isTrue() + } + + @Test + fun `M fill session hasReplay W create() { view is not recorded }`( + @StringForgery fakeViewId: String + ) { + // Given + val rumContext = fakeSerializedRumContext.copy(viewId = fakeViewId) + val datadogContext = fakeDatadogContext.copy( + featuresContext = fakeDatadogContext.featuresContext + mapOf( + Feature.SESSION_REPLAY_FEATURE_NAME to mapOf( + fakeViewId to mapOf(FeaturesContextResolver.HAS_REPLAY_KEY to false) + ) + ) + ) + + // When + val event = createEvent(datadogContext = datadogContext, rumContext = rumContext) + + // Then + assertThat(event.session.hasReplay).isFalse() + } + + @Test + fun `M fill synthetics W create() { synthetics ids are set }`( + @StringForgery fakeTestId: String, + @StringForgery fakeResultId: String + ) { + // Given + val rumContext = fakeSerializedRumContext.copy( + syntheticsTestId = fakeTestId, + syntheticsResultId = fakeResultId + ) + + // When + val event = createEvent(rumContext = rumContext) + + // Then + assertThat(event.synthetics?.testId).isEqualTo(fakeTestId) + assertThat(event.synthetics?.resultId).isEqualTo(fakeResultId) + } + + @ParameterizedTest + @MethodSource("incompleteSyntheticsIds") + fun `M not fill synthetics W create() { synthetics ids are incomplete }`( + syntheticsTestId: String?, + syntheticsResultId: String? + ) { + // Given + val rumContext = fakeSerializedRumContext.copy( + syntheticsTestId = syntheticsTestId, + syntheticsResultId = syntheticsResultId + ) + + // When + val event = createEvent(rumContext = rumContext) + + // Then + assertThat(event.synthetics).isNull() + } + + @Test + fun `M leave unsupported fields null W create()`() { + // When + val event = createEvent() + + // Then + assertThat(event.connectivity).isNull() + assertThat(event.usr).isNull() + assertThat(event.account).isNull() + assertThat(event.context).isNull() + assertThat(event.view).isNull() + assertThat(event.ciTest).isNull() + assertThat(event.display).isNull() + } + + private fun createSerializedEvent( + datadogContext: DatadogContext = fakeDatadogContext, + rumContext: RumContext = fakeSerializedRumContext, + sessionType: RumSessionType = RumSessionType.USER, + dataPoints: List> = oneSample + ): TimeseriesCpuEvent = TimeseriesCpuEvent.fromJson( + checkNotNull( + testedFactory(sessionType = sessionType) + .create(datadogContext, rumContext, dataPoints) + ).toJson().toString() + ) + + private fun createEvent( + datadogContext: DatadogContext = fakeDatadogContext, + rumContext: RumContext = fakeSerializedRumContext, + sessionType: RumSessionType = RumSessionType.USER + ): TimeseriesCpuEvent = checkNotNull( + testedFactory(sessionType = sessionType) + .create(datadogContext, rumContext, oneSample) + ) + + companion object { + private const val VALUE_NAME_CPU: String = "cpu" + private const val VALUE_SCHEMA_OBJECT: String = "object-v2" + + private const val FAKE_SYNTHETICS_ID: String = "synthetics-id" + + @JvmStatic + fun incompleteSyntheticsIds(): List = listOf( + Arguments.of(null, null), + Arguments.of("", " "), + Arguments.of(FAKE_SYNTHETICS_ID, null), + Arguments.of(null, FAKE_SYNTHETICS_ID), + Arguments.of(FAKE_SYNTHETICS_ID, " "), + Arguments.of("", FAKE_SYNTHETICS_ID) + ) + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/factory/MemoryEventFactoryTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/factory/MemoryEventFactoryTest.kt new file mode 100644 index 0000000000..18c807e857 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/factory/MemoryEventFactoryTest.kt @@ -0,0 +1,501 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.internal.timeseries.factory + +import com.datadog.android.api.InternalLogger +import com.datadog.android.api.context.DatadogContext +import com.datadog.android.api.feature.Feature +import com.datadog.android.core.internal.utils.DdTagsUtils +import com.datadog.android.internal.time.TimeProvider +import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.assertj.TimeseriesMemoryEventAssert +import com.datadog.android.rum.internal.FeaturesContextResolver +import com.datadog.android.rum.internal.domain.InfoProvider +import com.datadog.android.rum.internal.domain.RumContext +import com.datadog.android.rum.internal.domain.battery.BatteryInfo +import com.datadog.android.rum.internal.domain.display.DisplayInfo +import com.datadog.android.rum.internal.domain.scope.RumSessionScope +import com.datadog.android.rum.internal.domain.scope.RumViewScope +import com.datadog.android.rum.internal.domain.scope.toTimeseriesMemorySchemaType +import com.datadog.android.rum.internal.timeseries.DataPoint +import com.datadog.android.rum.model.TimeseriesMemoryEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent.TimeseriesMemoryEventSessionType +import com.datadog.android.rum.utils.forge.Configurator +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.BoolForgery +import fr.xgouchet.elmyr.annotation.DoubleForgery +import fr.xgouchet.elmyr.annotation.FloatForgery +import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.annotation.LongForgery +import fr.xgouchet.elmyr.annotation.StringForgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import org.junit.jupiter.params.provider.ValueSource +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(Configurator::class) +internal class MemoryEventFactoryTest { + + @Mock + lateinit var mockTimeProvider: TimeProvider + + @Mock + lateinit var mockInternalLogger: InternalLogger + + @Mock + lateinit var stubBatteryInfoProvider: InfoProvider + + @Mock + lateinit var stubDisplayInfoProvider: InfoProvider + + @StringForgery + lateinit var fakeSessionId: String + + @StringForgery + lateinit var fakeApplicationId: String + + @LongForgery(min = 1_000_000_000L, max = 16_000_000_000L) + var fakeTotalRamBytes: Long = 0L + + @LongForgery(min = 1L) + var fakeNowMs: Long = 0L + + @FloatForgery(min = 0f, max = 1f) + var fakeBatteryLevel: Float = 0f + + @BoolForgery + var fakeLowPowerMode: Boolean = false + + @FloatForgery(min = 0f, max = 1f) + var fakeBrightnessLevel: Float = 0f + + @Forgery + lateinit var fakeDatadogContext: DatadogContext + + private var fakeSource: TimeseriesMemoryEvent.Source? = null + + @Forgery + lateinit var fakeRumContext: RumContext + + private val fakeSerializedRumContext: RumContext + get() = fakeRumContext.copy(applicationId = fakeApplicationId, sessionId = fakeSessionId) + + private val oneSample = listOf(DataPoint(1L, 1.0)) + + @BeforeEach + fun `set up`(forge: Forge) { + fakeSource = forge.aNullable { aValueFrom(TimeseriesMemoryEvent.Source::class.java) } + fakeDatadogContext = fakeDatadogContext.copy( + source = fakeSource?.toJson()?.asString ?: forge.anAlphabeticalString() + ) + whenever(mockTimeProvider.getServerTimestampMillis()) doReturn fakeNowMs + whenever(stubBatteryInfoProvider.getState()) doReturn BatteryInfo( + batteryLevel = fakeBatteryLevel, + lowPowerMode = fakeLowPowerMode + ) + whenever(stubDisplayInfoProvider.getState()) doReturn DisplayInfo(screenBrightness = fakeBrightnessLevel) + } + + private fun testedFactory( + sessionType: RumSessionType = RumSessionType.USER, + totalRamBytes: Long = fakeTotalRamBytes + ) = MemoryEventFactory( + sessionType = sessionType, + totalRamBytes = totalRamBytes, + timeProvider = mockTimeProvider, + batteryInfoProvider = stubBatteryInfoProvider, + displayInfoProvider = stubDisplayInfoProvider, + internalLogger = mockInternalLogger + ) + + @Test + fun `M return null W create() { empty input }`() { + // When + val result = testedFactory().create(fakeDatadogContext, fakeSerializedRumContext, emptyList()) + + // Then + assertThat(result).isNull() + } + + @ParameterizedTest + @ValueSource(longs = [0L, -1L, Long.MIN_VALUE]) + fun `M return null W create() { totalRamBytes is not positive }`(totalRamBytes: Long) { + // When + val result = testedFactory(totalRamBytes = totalRamBytes) + .create(fakeDatadogContext, fakeSerializedRumContext, oneSample) + + // Then + assertThat(result).isNull() + } + + @Test + fun `M fill timeseries W create() { multiple data points }`( + @DoubleForgery(min = 1.0) fakeMemory: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // Given + val samples = listOf( + DataPoint(fakeTs, fakeMemory), + DataPoint(fakeTs + 1L, fakeMemory + 1.0) + ) + + // When + val event = createSerializedEvent(dataPoints = samples) + + // Then + TimeseriesMemoryEventAssert.assertThat(event) + .hasTimeseriesName(VALUE_NAME_MEMORY) + .hasTimeseriesSchema(VALUE_SCHEMA_OBJECT) + .hasDate(fakeNowMs) + .hasValidTimeseriesId() + .hasDataPointsCount(2) + .hasTimeseriesStart(fakeTs) + .hasTimeseriesEnd(fakeTs + 1L) + .hasApplicationId(fakeApplicationId) + .hasSessionId(fakeSessionId) + .hasSessionType(TimeseriesMemoryEventSessionType.USER) + .hasService(fakeDatadogContext.service) + .hasVersion(fakeDatadogContext.version) + } + + @Test + fun `M map session type W create() { synthetics }`() { + // When + val event = createEvent(sessionType = RumSessionType.SYNTHETICS) + + // Then + TimeseriesMemoryEventAssert.assertThat(event) + .hasSessionType(TimeseriesMemoryEventSessionType.SYNTHETICS) + } + + @Test + fun `M return generated timeseries name W eventName`() { + // When / Then + assertThat(testedFactory().eventName).isEqualTo(createEvent().timeseries.name) + } + + @Test + fun `M fill memory values W create() { one value per data point }`( + @DoubleForgery(min = 1.0, max = 16_000_000_000.0) fakeFirstMemory: Double, + @DoubleForgery(min = 1.0, max = 16_000_000_000.0) fakeSecondMemory: Double, + @LongForgery(min = 1L) fakeTs: Long + ) { + // Given + val samples = listOf( + DataPoint(fakeTs, fakeFirstMemory), + DataPoint(fakeTs + 1L, fakeSecondMemory) + ) + + // When + val event = createSerializedEvent(dataPoints = samples) + + // Then + TimeseriesMemoryEventAssert.assertThat(event) + .hasDataPointsCount(2) + .hasMemoryFootprint(fakeFirstMemory / BYTES_IN_KB, MEMORY_OFFSET, position = 0) + .hasMemoryFootprint(fakeSecondMemory / BYTES_IN_KB, MEMORY_OFFSET, position = 1) + .hasMemoryPercent( + fakeFirstMemory / fakeTotalRamBytes * PERCENT_FACTOR, + MEMORY_OFFSET, + position = 0 + ) + .hasMemoryPercent( + fakeSecondMemory / fakeTotalRamBytes * PERCENT_FACTOR, + MEMORY_OFFSET, + position = 1 + ) + .hasTimestamps(fakeTs, fakeTs + 1L) + } + + @Test + fun `M fill application and build info W create()`( + @StringForgery fakeBuildId: String + ) { + // Given + val datadogContext = fakeDatadogContext.copy(appBuildId = fakeBuildId) + + // When + val event = createEvent(datadogContext = datadogContext) + + // Then + assertThat(event.application.id).isEqualTo(fakeApplicationId) + assertThat(event.application.currentLocale) + .isEqualTo(datadogContext.deviceInfo.localeInfo.currentLocale) + assertThat(event.source).isEqualTo(fakeSource) + assertThat(event.buildVersion).isEqualTo(datadogContext.versionCode.toString()) + assertThat(event.buildId).isEqualTo(fakeBuildId) + assertThat(event.ddtags).isEqualTo(DdTagsUtils.toDdTagsString(datadogContext)) + } + + @Test + fun `M fill os and device W create()`() { + // Given + val deviceInfo = fakeDatadogContext.deviceInfo + + // When + val event = createEvent() + + // Then + val os = checkNotNull(event.os) + assertThat(os.name).isEqualTo(deviceInfo.osName) + assertThat(os.version).isEqualTo(deviceInfo.osVersion) + assertThat(os.versionMajor).isEqualTo(deviceInfo.osMajorVersion) + + val device = checkNotNull(event.device) + assertThat(device.type).isEqualTo(deviceInfo.deviceType.toTimeseriesMemorySchemaType()) + assertThat(device.name).isEqualTo(deviceInfo.deviceName) + assertThat(device.model).isEqualTo(deviceInfo.deviceModel) + assertThat(device.brand).isEqualTo(deviceInfo.deviceBrand) + assertThat(device.architecture).isEqualTo(deviceInfo.architecture) + assertThat(device.locales).isEqualTo(deviceInfo.localeInfo.locales) + assertThat(device.timeZone).isEqualTo(deviceInfo.localeInfo.timeZone) + assertThat(device.logicalCpuCount?.toInt()).isEqualTo(deviceInfo.logicalCpuCount) + assertThat(device.totalRam?.toInt()).isEqualTo(deviceInfo.totalRam) + assertThat(device.isLowRam).isEqualTo(deviceInfo.isLowRam) + assertThat(device.batteryLevel?.toFloat()).isEqualTo(fakeBatteryLevel) + assertThat(device.powerSavingMode).isEqualTo(fakeLowPowerMode) + assertThat(device.brightnessLevel?.toFloat()).isEqualTo(fakeBrightnessLevel) + } + + @Test + fun `M leave device power info null W create() { providers have no data }`() { + // Given + whenever(stubBatteryInfoProvider.getState()) doReturn BatteryInfo() + whenever(stubDisplayInfoProvider.getState()) doReturn DisplayInfo() + + // When + val event = createEvent() + + // Then + val device = checkNotNull(event.device) + assertThat(device.batteryLevel).isNull() + assertThat(device.powerSavingMode).isNull() + assertThat(device.brightnessLevel).isNull() + } + + @Test + fun `M fill dd W create()`( + @DoubleForgery(min = 0.0, max = 100.0) fakeSampleRate: Double + ) { + // Given + val rumContext = fakeSerializedRumContext.copy( + sessionStartReason = RumSessionScope.StartReason.INACTIVITY_TIMEOUT, + sessionSampleRate = fakeSampleRate.toFloat() + ) + + // When + val event = createEvent(rumContext = rumContext) + + // Then + assertThat(event.dd.session?.sessionPrecondition) + .isEqualTo(TimeseriesMemoryEvent.SessionPrecondition.INACTIVITY_TIMEOUT) + assertThat(event.dd.configuration?.sessionSampleRate?.toFloat()) + .isEqualTo(fakeSampleRate.toFloat()) + } + + @Test + fun `M fill dd configuration W create() { sample rates in features context }`( + @LongForgery(min = 0L, max = 100L) fakeSessionReplaySampleRate: Long, + @FloatForgery(min = 0f, max = 100f) fakeTraceSampleRate: Float + ) { + // Given + val datadogContext = fakeDatadogContext.copy( + featuresContext = fakeDatadogContext.featuresContext + mapOf( + Feature.SESSION_REPLAY_FEATURE_NAME to mapOf( + RumViewScope.SESSION_REPLAY_SAMPLE_RATE_KEY to fakeSessionReplaySampleRate + ), + Feature.TRACING_FEATURE_NAME to mapOf( + RumViewScope.TRACE_SAMPLE_RATE to fakeTraceSampleRate + ) + ) + ) + + // When + val event = createEvent(datadogContext = datadogContext) + + // Then + val configuration = checkNotNull(event.dd.configuration) + assertThat(configuration.sessionReplaySampleRate?.toLong()).isEqualTo(fakeSessionReplaySampleRate) + assertThat(configuration.traceSampleRate?.toFloat()).isEqualTo(fakeTraceSampleRate) + } + + @Test + fun `M fill dd configuration W create() { sample rates missing or of wrong type }`( + @StringForgery fakeNotASampleRate: String + ) { + // Given + val datadogContext = fakeDatadogContext.copy( + featuresContext = fakeDatadogContext.featuresContext + mapOf( + Feature.SESSION_REPLAY_FEATURE_NAME to emptyMap(), + Feature.TRACING_FEATURE_NAME to mapOf(RumViewScope.TRACE_SAMPLE_RATE to fakeNotASampleRate) + ) + ) + + // When + val event = createEvent(datadogContext = datadogContext) + + // Then + val configuration = checkNotNull(event.dd.configuration) + assertThat(configuration.sessionReplaySampleRate).isNull() + assertThat(configuration.traceSampleRate).isNull() + } + + @Test + fun `M fill session hasReplay W create() { view is recorded }`( + @StringForgery fakeViewId: String + ) { + // Given + val rumContext = fakeSerializedRumContext.copy(viewId = fakeViewId) + val datadogContext = fakeDatadogContext.copy( + featuresContext = fakeDatadogContext.featuresContext + mapOf( + Feature.SESSION_REPLAY_FEATURE_NAME to mapOf( + fakeViewId to mapOf(FeaturesContextResolver.HAS_REPLAY_KEY to true) + ) + ) + ) + + // When + val event = createEvent(datadogContext = datadogContext, rumContext = rumContext) + + // Then + assertThat(event.session.hasReplay).isTrue() + } + + @Test + fun `M fill session hasReplay W create() { view is not recorded }`( + @StringForgery fakeViewId: String + ) { + // Given + val rumContext = fakeSerializedRumContext.copy(viewId = fakeViewId) + val datadogContext = fakeDatadogContext.copy( + featuresContext = fakeDatadogContext.featuresContext + mapOf( + Feature.SESSION_REPLAY_FEATURE_NAME to mapOf( + fakeViewId to mapOf(FeaturesContextResolver.HAS_REPLAY_KEY to false) + ) + ) + ) + + // When + val event = createEvent(datadogContext = datadogContext, rumContext = rumContext) + + // Then + assertThat(event.session.hasReplay).isFalse() + } + + @Test + fun `M fill synthetics W create() { synthetics ids are set }`( + @StringForgery fakeTestId: String, + @StringForgery fakeResultId: String + ) { + // Given + val rumContext = fakeSerializedRumContext.copy( + syntheticsTestId = fakeTestId, + syntheticsResultId = fakeResultId + ) + + // When + val event = createEvent(rumContext = rumContext) + + // Then + assertThat(event.synthetics?.testId).isEqualTo(fakeTestId) + assertThat(event.synthetics?.resultId).isEqualTo(fakeResultId) + } + + @ParameterizedTest + @MethodSource("incompleteSyntheticsIds") + fun `M not fill synthetics W create() { synthetics ids are incomplete }`( + syntheticsTestId: String?, + syntheticsResultId: String? + ) { + // Given + val rumContext = fakeSerializedRumContext.copy( + syntheticsTestId = syntheticsTestId, + syntheticsResultId = syntheticsResultId + ) + + // When + val event = createEvent(rumContext = rumContext) + + // Then + assertThat(event.synthetics).isNull() + } + + @Test + fun `M leave unsupported fields null W create()`() { + // When + val event = createEvent() + + // Then + assertThat(event.connectivity).isNull() + assertThat(event.usr).isNull() + assertThat(event.account).isNull() + assertThat(event.context).isNull() + assertThat(event.view).isNull() + assertThat(event.ciTest).isNull() + assertThat(event.display).isNull() + } + + private fun createSerializedEvent( + datadogContext: DatadogContext = fakeDatadogContext, + rumContext: RumContext = fakeSerializedRumContext, + sessionType: RumSessionType = RumSessionType.USER, + dataPoints: List> = oneSample + ): TimeseriesMemoryEvent = TimeseriesMemoryEvent.fromJson( + checkNotNull( + testedFactory(sessionType = sessionType) + .create(datadogContext, rumContext, dataPoints) + ).toJson().toString() + ) + + private fun createEvent( + datadogContext: DatadogContext = fakeDatadogContext, + rumContext: RumContext = fakeSerializedRumContext, + sessionType: RumSessionType = RumSessionType.USER + ): TimeseriesMemoryEvent = checkNotNull( + testedFactory(sessionType = sessionType) + .create(datadogContext, rumContext, oneSample) + ) + + companion object { + private const val PERCENT_FACTOR: Double = 100.0 + private const val BYTES_IN_KB: Double = 1000.0 + private const val MEMORY_OFFSET: Double = 0.0001 + + private const val VALUE_NAME_MEMORY: String = "memory" + private const val VALUE_SCHEMA_OBJECT: String = "object-v2" + + private const val FAKE_SYNTHETICS_ID: String = "synthetics-id" + + @JvmStatic + fun incompleteSyntheticsIds(): List = listOf( + Arguments.of(null, null), + Arguments.of("", " "), + Arguments.of(FAKE_SYNTHETICS_ID, null), + Arguments.of(null, FAKE_SYNTHETICS_ID), + Arguments.of(FAKE_SYNTHETICS_ID, " "), + Arguments.of("", FAKE_SYNTHETICS_ID) + ) + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReaderTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReaderTest.kt index 00aed2e75f..ad7b954c7f 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReaderTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/CpuDatapointReaderTest.kt @@ -50,35 +50,35 @@ internal class CpuDatapointReaderTest { val mockCpuStatReader: CpuStatReader = mock() val mockTimeProvider: TimeProvider = mock { - on { getDeviceTimestampMillis() } doAnswer { fakeStartTimestampMs } + on { getServerTimestampMillis() } doAnswer { fakeStartTimestampMs } on { getDeviceElapsedTimeNanos() } doAnswer { fakeStartTimestampMs * NS_PER_MS } } private fun buildReader(availableProcessors: Int = 1) = CpuDatapointReader( cpuStatReader = mockCpuStatReader, - cpuTimeProvider = mockTimeProvider, + timeProvider = mockTimeProvider, intervalMs = fakeIntervalMs, availableProcessors = availableProcessors ) /** - * Primes the reader with [firstTicks] (returns null — no baseline yet), advances the clock - * by [elapsedMs], then returns the second sample built from [secondTicks] (or `null` to + * Primes the reader with [fakeBaselineTicks] (returns null — no baseline yet), advances the + * clock by [elapsedMs], then returns the second sample built from [secondTicks] (or `null` to * simulate the stat read failing between samples). Elapsed time is stubbed as a sequence so * the prime read sees the start instant and the second read sees start + [elapsedMs]. */ private fun readSecondSample( elapsedMs: Long, secondTicks: Double?, - availableProcessors: Int = 1, - firstTicks: Double = fakeBaselineTicks.toDouble() + availableProcessors: Int = 1 ): DataPoint? { whenever(mockTimeProvider.getDeviceElapsedTimeNanos()).doReturn( fakeStartTimestampMs * NS_PER_MS, (fakeStartTimestampMs + elapsedMs) * NS_PER_MS ) - whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn (fakeStartTimestampMs + elapsedMs) - whenever(mockCpuStatReader.readActiveTime()).doReturnConsecutively(listOf(firstTicks, secondTicks)) + whenever(mockTimeProvider.getServerTimestampMillis()) doReturn (fakeStartTimestampMs + elapsedMs) + whenever(mockCpuStatReader.readActiveTime()) + .doReturnConsecutively(listOf(fakeBaselineTicks.toDouble(), secondTicks)) return buildReader(availableProcessors).run { read() read() @@ -182,7 +182,7 @@ internal class CpuDatapointReaderTest { @Test fun `M normalise by core count W read() {multi-core usage}`( - @IntForgery(min = 1, max = 8) fakeCoreCount: Int, + @IntForgery(min = 2, max = 8) fakeCoreCount: Int, @LongForgery(min = 500L, max = 5000L) fakeElapsedMs: Long, @DoubleForgery(min = 1.0, max = 50.0) fakePerCorePct: Double ) { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/VitalReaderWrapperTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/VitalReaderWrapperTest.kt index 61b45e83c3..d863626dfa 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/VitalReaderWrapperTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/provider/VitalReaderWrapperTest.kt @@ -38,19 +38,19 @@ internal class VitalReaderWrapperTest { @LongForgery(min = 1L) var fakeTimestamp: Long = 0L + @LongForgery(min = 1L) + var fakeIntervalMs: Long = 0L + @Mock lateinit var mockTimeProvider: TimeProvider @BeforeEach fun `set up`() { - whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn fakeTimestamp + whenever(mockTimeProvider.getServerTimestampMillis()) doReturn fakeTimestamp } @Test - fun `M return sample W read() { reader has value }`( - @DoubleForgery(min = 0.001) fakeValue: Double, - @LongForgery(min = 1L) fakeIntervalMs: Long - ) { + fun `M return sample W read() { reader has value }`(@DoubleForgery fakeValue: Double) { // Given val mockVitalReader: VitalReader = mock { on { readVitalData() } doReturn fakeValue @@ -67,7 +67,7 @@ internal class VitalReaderWrapperTest { } @Test - fun `M return null W read() { reader has no value }`(@LongForgery(min = 1L) fakeIntervalMs: Long) { + fun `M return null W read() { reader has no value }`() { // Given val mockVitalReader: VitalReader = mock { on { readVitalData() } doReturn null @@ -80,14 +80,4 @@ internal class VitalReaderWrapperTest { // Then assertThat(result).isNull() } - - @Test - fun `M expose intervalMs W intervalMs { construction value }`(@LongForgery(min = 1L) fakeIntervalMs: Long) { - // Given - val mockVitalReader: VitalReader = mock() - val testedReader = VitalReaderWrapper(mockVitalReader, mockTimeProvider, fakeIntervalMs) - - // When / Then - assertThat(testedReader.intervalMs).isEqualTo(fakeIntervalMs) - } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt deleted file mode 100644 index 260998da04..0000000000 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/CpuEventSerializerTest.kt +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.rum.internal.timeseries.serializer - -import com.datadog.android.api.context.DatadogContext -import com.datadog.android.internal.time.TimeProvider -import com.datadog.android.rum.RumSessionType -import com.datadog.android.rum.assertj.TimeseriesCpuEventAssert.Companion.assertThat -import com.datadog.android.rum.internal.timeseries.DataPoint -import com.datadog.android.rum.model.TimeseriesCpuEvent -import com.datadog.android.rum.utils.forge.Configurator -import com.google.gson.JsonObject -import fr.xgouchet.elmyr.annotation.DoubleForgery -import fr.xgouchet.elmyr.annotation.Forgery -import fr.xgouchet.elmyr.annotation.LongForgery -import fr.xgouchet.elmyr.annotation.StringForgery -import fr.xgouchet.elmyr.junit5.ForgeConfiguration -import fr.xgouchet.elmyr.junit5.ForgeExtension -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.data.Offset -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.junit.jupiter.api.extension.Extensions -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.junit.jupiter.MockitoSettings -import org.mockito.kotlin.doAnswer -import org.mockito.kotlin.mock -import org.mockito.quality.Strictness - -@Extensions( - ExtendWith(MockitoExtension::class), - ExtendWith(ForgeExtension::class) -) -@MockitoSettings(strictness = Strictness.LENIENT) -@ForgeConfiguration(Configurator::class) -internal class CpuEventSerializerTest { - - @LongForgery(min = 1L) - var fakeNowMs: Long = 0L - - @StringForgery - lateinit var fakeSessionId: String - - @StringForgery - lateinit var fakeApplicationId: String - - @Forgery - lateinit var fakeDatadogContext: DatadogContext - - val mockTimeProvider: TimeProvider = mock { - on { getDeviceTimestampMillis() } doAnswer { fakeNowMs } - } - - private fun testedSerializer( - sessionType: RumSessionType = RumSessionType.USER - ) = CpuEventSerializer( - sessionId = fakeSessionId, - applicationId = fakeApplicationId, - sessionType = sessionType, - timeProvider = mockTimeProvider - ) - - @Test - fun `M return null W serialize() { empty input }`() { - // When - val result = testedSerializer().serialize(fakeDatadogContext, emptyList()) - - // Then - assertThat(result).isNull() - } - - @Test - fun `M produce object schema W serialize() { compression off }`( - @DoubleForgery(min = 0.0, max = 100.0) fakeCpuValue: Double, - @LongForgery(min = 1L) fakeTs: Long - ) { - // Given - val samples = listOf( - DataPoint(fakeTs, fakeCpuValue), - DataPoint(fakeTs + 1L, fakeCpuValue + 0.1) - ) - - // When - val json = testedSerializer().serialize(fakeDatadogContext, samples).failIfNull() - - // Then - assertThat(json) - .hasDate(fakeNowMs) - .hasValidTimeseriesId() - .hasTimeseriesDataCount(2) - .hasTimeseriesStart(fakeTs) - .hasTimeseriesEnd(fakeTs + 1L) - .hasApplicationId(fakeApplicationId) - .hasSessionType(TimeseriesCpuEvent.Type.USER) - .hasTimeseriesSchema("object-v2") - .hasService(fakeDatadogContext.service) - .hasVersion(fakeDatadogContext.version) - } - - @Test - fun `M map session type W serialize() { synthetics }`( - @DoubleForgery(min = 0.0, max = 100.0) fakeCpuValue: Double, - @LongForgery(min = 1L) fakeTs: Long - ) { - // Given - val samples = listOf(DataPoint(fakeTs, fakeCpuValue)) - - // When - val json = testedSerializer(sessionType = RumSessionType.SYNTHETICS) - .serialize(fakeDatadogContext, samples) - .failIfNull() - - // Then - assertThat(json).hasSessionType(TimeseriesCpuEvent.Type.SYNTHETICS) - } - - @Test - fun `M preserve cpu_usage W serialize() { object schema }`( - @DoubleForgery(min = 1.0, max = 100.0) fakeCpu: Double, - @LongForgery(min = 1L) fakeTs: Long - ) { - // Given - val samples = listOf( - DataPoint(fakeTs, fakeCpu), - DataPoint(fakeTs + 1L, fakeCpu) - ) - - // When - val json = testedSerializer().serialize(fakeDatadogContext, samples).failIfNull() - - // Then - assertThat(json) - .hasCpuUsage(fakeCpu, Offset.offset(CPU_OFFSET), position = 0) - } - - private companion object { - private const val CPU_OFFSET: Double = 0.0001 - private const val NON_NULL_JSON_ERROR: String = "expected non-null json" - private fun JsonObject?.failIfNull(): JsonObject { - if (this == null) error(NON_NULL_JSON_ERROR) else return this - } - } -} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt deleted file mode 100644 index a24132ac68..0000000000 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/serializer/MemoryEventSerializerTest.kt +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.rum.internal.timeseries.serializer - -import com.datadog.android.api.context.DatadogContext -import com.datadog.android.internal.time.TimeProvider -import com.datadog.android.rum.RumSessionType -import com.datadog.android.rum.internal.timeseries.DataPoint -import com.datadog.android.rum.model.TimeseriesMemoryEvent -import com.datadog.android.rum.utils.forge.Configurator -import fr.xgouchet.elmyr.annotation.DoubleForgery -import fr.xgouchet.elmyr.annotation.Forgery -import fr.xgouchet.elmyr.annotation.LongForgery -import fr.xgouchet.elmyr.annotation.StringForgery -import fr.xgouchet.elmyr.junit5.ForgeConfiguration -import fr.xgouchet.elmyr.junit5.ForgeExtension -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.data.Offset -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow -import org.junit.jupiter.api.extension.ExtendWith -import org.junit.jupiter.api.extension.Extensions -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.junit.jupiter.MockitoSettings -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.whenever -import org.mockito.quality.Strictness -import java.util.UUID - -@Extensions( - ExtendWith(MockitoExtension::class), - ExtendWith(ForgeExtension::class) -) -@MockitoSettings(strictness = Strictness.LENIENT) -@ForgeConfiguration(Configurator::class) -internal class MemoryEventSerializerTest { - - @Mock - lateinit var mockTimeProvider: TimeProvider - - @StringForgery - lateinit var fakeSessionId: String - - @StringForgery - lateinit var fakeApplicationId: String - - @LongForgery(min = 1_000_000_000L, max = 16_000_000_000L) - var fakeTotalRamBytes: Long = 0L - - @LongForgery(min = 1L) - var fakeNowMs: Long = 0L - - @Forgery - lateinit var fakeDatadogContext: DatadogContext - - @BeforeEach - fun `set up`() { - whenever(mockTimeProvider.getDeviceTimestampMillis()) doReturn fakeNowMs - } - - private fun testedSerializer( - sessionType: RumSessionType = RumSessionType.USER, - totalRamBytes: Long = fakeTotalRamBytes - ) = MemoryEventSerializer( - sessionId = fakeSessionId, - applicationId = fakeApplicationId, - sessionType = sessionType, - totalRamBytes = totalRamBytes, - timeProvider = mockTimeProvider - ) - - @Test - fun `M return null W serialize() { empty input }`() { - // When - val result = testedSerializer().serialize(fakeDatadogContext, emptyList()) - - // Then - assertThat(result).isNull() - } - - @Test - fun `M return null W serialize() { totalRamBytes is zero }`( - @DoubleForgery(min = 1.0) fakeMemory: Double, - @LongForgery(min = 1L) fakeTs: Long - ) { - // When - val result = testedSerializer(totalRamBytes = 0L) - .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeMemory))) - - // Then - assertThat(result).isNull() - } - - @Test - fun `M return null W serialize() { totalRamBytes is negative }`( - @LongForgery(max = -1L) fakeNegativeRam: Long, - @DoubleForgery(min = 1.0) fakeMemory: Double, - @LongForgery(min = 1L) fakeTs: Long - ) { - // When - val result = testedSerializer(totalRamBytes = fakeNegativeRam) - .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeMemory))) - - // Then - assertThat(result).isNull() - } - - @Test - fun `M produce object schema W serialize() { compression off }`( - @DoubleForgery(min = 1.0) fakeMemory: Double, - @LongForgery(min = 1L) fakeTs: Long - ) { - // Given - val samples = listOf( - DataPoint(fakeTs, fakeMemory), - DataPoint(fakeTs + 1L, fakeMemory + 1.0) - ) - - // When - val result = testedSerializer().serialize(fakeDatadogContext, samples) - - // Then - val json = checkNotNull(result) - val timeseries = json.getAsJsonObject(TimeseriesAttributes.KEY_TIMESERIES) - assertThat(timeseries.get(TimeseriesAttributes.KEY_SCHEMA).asString).isEqualTo(VALUE_SCHEMA_OBJECT) - assertThat(timeseries.get(KEY_START).asLong).isEqualTo(fakeTs) - assertThat(timeseries.get(KEY_END).asLong).isEqualTo(fakeTs + 1L) - assertDoesNotThrow { UUID.fromString(timeseries.get(KEY_ID).asString) } - assertThat(json.get(KEY_SERVICE).asString).isEqualTo(fakeDatadogContext.service) - assertThat(json.get(KEY_VERSION).asString).isEqualTo(fakeDatadogContext.version) - - val parsed = TimeseriesMemoryEvent.fromJsonObject(json) - assertThat(parsed.timeseries.data.timestamps).hasSize(2) - assertThat(parsed.timeseries.data.values.memoryFootprint.first().toDouble()) - .isCloseTo(fakeMemory / 1000.0, Offset.offset(MEMORY_OFFSET)) - assertThat(parsed.timeseries.data.values.memoryPercent.first().toDouble()) - .isCloseTo(fakeMemory / fakeTotalRamBytes * PERCENT_FACTOR, Offset.offset(MEMORY_OFFSET)) - } - - @Test - fun `M map session type W serialize() { synthetics }`( - @DoubleForgery(min = 1.0) fakeMemory: Double, - @LongForgery(min = 1L) fakeTs: Long - ) { - // When - val result = testedSerializer(sessionType = RumSessionType.SYNTHETICS) - .serialize(fakeDatadogContext, listOf(DataPoint(fakeTs, fakeMemory), DataPoint(fakeTs + 1L, fakeMemory))) - - // Then - val json = checkNotNull(result) - assertThat(json.getAsJsonObject(KEY_SESSION).get(KEY_TYPE).asString).isEqualTo(VALUE_TYPE_SYNTHETICS) - } - - private companion object { - private const val PERCENT_FACTOR: Double = 100.0 - private const val MEMORY_OFFSET: Double = 0.0001 - - // JSON keys - private const val KEY_SESSION: String = "session" - private const val KEY_TYPE: String = "type" - private const val KEY_ID: String = "id" - private const val KEY_START: String = "start" - private const val KEY_END: String = "end" - private const val KEY_SERVICE: String = "service" - private const val KEY_VERSION: String = "version" - - // JSON values - private const val VALUE_SCHEMA_OBJECT: String = "object-v2" - private const val VALUE_TYPE_SYNTHETICS: String = "synthetics" - } -} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/vitals/CpuStatReaderTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/vitals/CpuStatReaderTest.kt index 4dd0917e25..edab0078be 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/vitals/CpuStatReaderTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/vitals/CpuStatReaderTest.kt @@ -92,7 +92,7 @@ internal class CpuStatReaderTest { } @Test - fun `M use default stat file W init()`() { + fun `M use default stat file W init() {no file provided}`() { // When val reader = CpuStatReader(internalLogger = mock()) @@ -113,18 +113,18 @@ internal class CpuStatReaderTest { } @Test - fun `M read utime W readUserTime() {multiple times}`(@IntForgery(1) utimes: List) { + fun `M read utime W readUserTime() {multiple times}`(@IntForgery(1) fakeUtimes: List) { // Given val results = mutableListOf() // When - utimes.forEach { utime -> + fakeUtimes.forEach { utime -> fakeFile.writeText(generateStatContent(utime)) results.add(testedReader.readUserTime()) } // Then - assertThat(results).isEqualTo(utimes.map { it.toDouble() }) + assertThat(results).isEqualTo(fakeUtimes.map { it.toDouble() }) } @Test diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt deleted file mode 100644 index 3365add0a7..0000000000 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTest.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.rum.timeseries - -import com.datadog.android.rum.ExperimentalRumApi -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test - -@OptIn(ExperimentalRumApi::class) -internal class TimeseriesConfigurationTest { - - @Test - fun `M use defaults W built with no args`() { - // When - val config = TimeseriesConfiguration.Builder().build() - - // Then - assertThat(config.bufferSize).isEqualTo(TimeseriesConfiguration.DEFAULT_BUFFER_SIZE) - assertThat(config.intervalMs).isEqualTo(TimeseriesConfiguration.DEFAULT_INTERVAL_MS) - } - - @Test - fun `M use default bufferSize W setBufferSize with non-positive value`() { - // When - val configZero = TimeseriesConfiguration.Builder().setBufferSize(0).build() - val configNegative = TimeseriesConfiguration.Builder().setBufferSize(-1).build() - - // Then - assertThat(configZero.bufferSize).isEqualTo(TimeseriesConfiguration.DEFAULT_BUFFER_SIZE) - assertThat(configNegative.bufferSize).isEqualTo(TimeseriesConfiguration.DEFAULT_BUFFER_SIZE) - } - - @Test - fun `M use default intervalMs W setIntervalMs below minimum`() { - // When - val configBelowMin = TimeseriesConfiguration.Builder() - .setIntervalMs(TimeseriesConfiguration.MIN_INTERVAL_MS - 1) - .build() - val configZero = TimeseriesConfiguration.Builder().setIntervalMs(0L).build() - - // Then - assertThat(configBelowMin.intervalMs).isEqualTo(TimeseriesConfiguration.DEFAULT_INTERVAL_MS) - assertThat(configZero.intervalMs).isEqualTo(TimeseriesConfiguration.DEFAULT_INTERVAL_MS) - } - - @Test - fun `M store provided values W built with valid args`() { - // Given - val fakeBufferSize = 10 - val fakeIntervalMs = 500L - - // When - val config = TimeseriesConfiguration.Builder() - .setBufferSize(fakeBufferSize) - .setIntervalMs(fakeIntervalMs) - .build() - - // Then - assertThat(config.bufferSize).isEqualTo(fakeBufferSize) - assertThat(config.intervalMs).isEqualTo(fakeIntervalMs) - } - - @Test - fun `M default collectInBackground to false W built with no args`() { - // When - val config = TimeseriesConfiguration.Builder().build() - - // Then - assertThat(config.collectInBackground).isFalse() - } - - @Test - fun `M store collectInBackground W setCollectInBackground true`() { - // When - val config = TimeseriesConfiguration.Builder().collectInBackground(true).build() - - // Then - assertThat(config.collectInBackground).isTrue() - } -} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTests.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTests.kt new file mode 100644 index 0000000000..2546c4ef99 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTests.kt @@ -0,0 +1,71 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.timeseries + +import com.datadog.android.rum.ExperimentalRumApi +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import org.junit.jupiter.params.provider.ValueSource + +@OptIn(ExperimentalRumApi::class) +internal class TimeseriesConfigurationTests { + + @Test + fun `M collect all types W build() { collectOnly not called }`() { + // When + val config = TimeseriesConfiguration.Builder().build() + + // Then + assertThat(config.enabledTypes).containsExactlyInAnyOrderElementsOf(TimeseriesType.values().toList()) + } + + @ParameterizedTest + @EnumSource(TimeseriesType::class) + fun `M collect selected type W collectOnly()`(fakeType: TimeseriesType) { + // When + val config = TimeseriesConfiguration.Builder() + .collectOnly(fakeType) + .build() + + // Then + assertThat(config.enabledTypes).containsExactly(fakeType) + } + + @Test + fun `M collect no types W collectOnly() { empty array }`() { + // When + val config = TimeseriesConfiguration.Builder() + .collectOnly(*emptyArray()) + .build() + + // Then + assertThat(config.enabledTypes).isEmpty() + } + + @Test + fun `M default collectInBackground to false W build() { collectInBackground not called }`() { + // When + val config = TimeseriesConfiguration.Builder().build() + + // Then + assertThat(config.collectInBackground).isFalse() + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `M store collectInBackground W collectInBackground()`(fakeCollectInBackground: Boolean) { + // When + val config = TimeseriesConfiguration.Builder() + .collectInBackground(fakeCollectInBackground) + .build() + + // Then + assertThat(config.collectInBackground).isEqualTo(fakeCollectInBackground) + } +} diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json index 1d5b4ecc97..9808a056a7 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch1.json @@ -1,10 +1,55 @@ { - "type": "timeseries", - "_dd": {"format_version": 2}, - "application": {"id": "fake-app-id"}, - "session": {"id": "fake-session-id", "type": "user"}, - "source": "android", "date": 1700000001000, + "application": { + "id": "fake-app-id", + "current_locale": "en-US" + }, + "service": "fake-service", + "version": "1.2.3", + "build_version": "42", + "build_id": "fake-app-build-id", + "ddtags": "service:fake-service,version:1.2.3,sdk_version:2.0.0,env:fake-env,variant:fake-variant", + "session": { + "id": "fake-session-id", + "type": "user", + "has_replay": true + }, + "source": "android", + "os": { + "name": "Android", + "version": "13.0.1", + "version_major": "13" + }, + "device": { + "type": "mobile", + "name": "fake-device-name", + "model": "fake-device-model", + "brand": "fake-device-brand", + "architecture": "arm64-v8a", + "locales": [ + "en-US", + "fr-FR" + ], + "time_zone": "America/New_York", + "battery_level": 0.42, + "power_saving_mode": true, + "brightness_level": 128, + "logical_cpu_count": 8, + "total_ram": 4096, + "is_low_ram": false + }, + "_dd": { + "format_version": 2, + "session": { + "session_precondition": "user_app_launch" + }, + "configuration": { + "session_sample_rate": 42.0, + "session_replay_sample_rate": 75, + "trace_sample_rate": 20.0 + } + }, + "type": "timeseries", "timeseries": { "id": "00000000-0000-0000-0000-000000000000", "name": "cpu", @@ -12,9 +57,21 @@ "start": 1700000001000000000, "end": 1700000005000000000, "data": { - "timestamps": [1700000001000000000, 1700000002000000000, 1700000003000000000, 1700000004000000000, 1700000005000000000], + "timestamps": [ + 1700000001000000000, + 1700000002000000000, + 1700000003000000000, + 1700000004000000000, + 1700000005000000000 + ], "values": { - "cpu_usage": [12.5, 14.2, 11.8, 16.3, 13.7] + "cpu_usage": [ + 12.5, + 14.2, + 11.8, + 16.3, + 13.7 + ] } } } diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json index 6415084548..80fce478c4 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_cpu_batch2.json @@ -1,10 +1,55 @@ { - "type": "timeseries", - "_dd": {"format_version": 2}, - "application": {"id": "fake-app-id"}, - "session": {"id": "fake-session-id", "type": "user"}, - "source": "android", "date": 1700000001000, + "application": { + "id": "fake-app-id", + "current_locale": "en-US" + }, + "service": "fake-service", + "version": "1.2.3", + "build_version": "42", + "build_id": "fake-app-build-id", + "ddtags": "service:fake-service,version:1.2.3,sdk_version:2.0.0,env:fake-env,variant:fake-variant", + "session": { + "id": "fake-session-id", + "type": "user", + "has_replay": true + }, + "source": "android", + "os": { + "name": "Android", + "version": "13.0.1", + "version_major": "13" + }, + "device": { + "type": "mobile", + "name": "fake-device-name", + "model": "fake-device-model", + "brand": "fake-device-brand", + "architecture": "arm64-v8a", + "locales": [ + "en-US", + "fr-FR" + ], + "time_zone": "America/New_York", + "battery_level": 0.42, + "power_saving_mode": true, + "brightness_level": 128, + "logical_cpu_count": 8, + "total_ram": 4096, + "is_low_ram": false + }, + "_dd": { + "format_version": 2, + "session": { + "session_precondition": "user_app_launch" + }, + "configuration": { + "session_sample_rate": 42.0, + "session_replay_sample_rate": 75, + "trace_sample_rate": 20.0 + } + }, + "type": "timeseries", "timeseries": { "id": "00000000-0000-0000-0000-000000000000", "name": "cpu", @@ -12,9 +57,21 @@ "start": 1700000006000000000, "end": 1700000010000000000, "data": { - "timestamps": [1700000006000000000, 1700000007000000000, 1700000008000000000, 1700000009000000000, 1700000010000000000], + "timestamps": [ + 1700000006000000000, + 1700000007000000000, + 1700000008000000000, + 1700000009000000000, + 1700000010000000000 + ], "values": { - "cpu_usage": [18.1, 22.4, 19.6, 15.9, 13.2] + "cpu_usage": [ + 18.1, + 22.4, + 19.6, + 15.9, + 13.2 + ] } } } diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json index 4723b0d1cb..c3eb2f54c2 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch1.json @@ -1,10 +1,55 @@ { - "type": "timeseries", - "_dd": {"format_version": 2}, - "application": {"id": "fake-app-id"}, - "session": {"id": "fake-session-id", "type": "user"}, - "source": "android", "date": 1700000001000, + "application": { + "id": "fake-app-id", + "current_locale": "en-US" + }, + "service": "fake-service", + "version": "1.2.3", + "build_version": "42", + "build_id": "fake-app-build-id", + "ddtags": "service:fake-service,version:1.2.3,sdk_version:2.0.0,env:fake-env,variant:fake-variant", + "session": { + "id": "fake-session-id", + "type": "user", + "has_replay": true + }, + "source": "android", + "os": { + "name": "Android", + "version": "13.0.1", + "version_major": "13" + }, + "device": { + "type": "mobile", + "name": "fake-device-name", + "model": "fake-device-model", + "brand": "fake-device-brand", + "architecture": "arm64-v8a", + "locales": [ + "en-US", + "fr-FR" + ], + "time_zone": "America/New_York", + "battery_level": 0.42, + "power_saving_mode": true, + "brightness_level": 128, + "logical_cpu_count": 8, + "total_ram": 4096, + "is_low_ram": false + }, + "_dd": { + "format_version": 2, + "session": { + "session_precondition": "user_app_launch" + }, + "configuration": { + "session_sample_rate": 42.0, + "session_replay_sample_rate": 75, + "trace_sample_rate": 20.0 + } + }, + "type": "timeseries", "timeseries": { "id": "00000000-0000-0000-0000-000000000000", "name": "memory", @@ -12,10 +57,28 @@ "start": 1700000001000000000, "end": 1700000005000000000, "data": { - "timestamps": [1700000001000000000, 1700000002000000000, 1700000003000000000, 1700000004000000000, 1700000005000000000], + "timestamps": [ + 1700000001000000000, + 1700000002000000000, + 1700000003000000000, + 1700000004000000000, + 1700000005000000000 + ], "values": { - "memory_footprint": [31233.3, 31245.5, 31300.8, 31289.1, 31350.0], - "memory_percent": [3.12333, 3.12455, 3.13008, 3.12891, 3.135] + "memory_footprint": [ + 31233.3, + 31245.5, + 31300.8, + 31289.1, + 31350.0 + ], + "memory_percent": [ + 3.12333, + 3.12455, + 3.13008, + 3.12891, + 3.135 + ] } } } diff --git a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json index ab5b91113d..ed1a216f6c 100644 --- a/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json +++ b/features/dd-sdk-android-rum/src/test/resources/fixtures/timeseries/expected_memory_batch2.json @@ -1,10 +1,55 @@ { - "type": "timeseries", - "_dd": {"format_version": 2}, - "application": {"id": "fake-app-id"}, - "session": {"id": "fake-session-id", "type": "user"}, - "source": "android", "date": 1700000001000, + "application": { + "id": "fake-app-id", + "current_locale": "en-US" + }, + "service": "fake-service", + "version": "1.2.3", + "build_version": "42", + "build_id": "fake-app-build-id", + "ddtags": "service:fake-service,version:1.2.3,sdk_version:2.0.0,env:fake-env,variant:fake-variant", + "session": { + "id": "fake-session-id", + "type": "user", + "has_replay": true + }, + "source": "android", + "os": { + "name": "Android", + "version": "13.0.1", + "version_major": "13" + }, + "device": { + "type": "mobile", + "name": "fake-device-name", + "model": "fake-device-model", + "brand": "fake-device-brand", + "architecture": "arm64-v8a", + "locales": [ + "en-US", + "fr-FR" + ], + "time_zone": "America/New_York", + "battery_level": 0.42, + "power_saving_mode": true, + "brightness_level": 128, + "logical_cpu_count": 8, + "total_ram": 4096, + "is_low_ram": false + }, + "_dd": { + "format_version": 2, + "session": { + "session_precondition": "user_app_launch" + }, + "configuration": { + "session_sample_rate": 42.0, + "session_replay_sample_rate": 75, + "trace_sample_rate": 20.0 + } + }, + "type": "timeseries", "timeseries": { "id": "00000000-0000-0000-0000-000000000000", "name": "memory", @@ -12,10 +57,28 @@ "start": 1700000006000000000, "end": 1700000010000000000, "data": { - "timestamps": [1700000006000000000, 1700000007000000000, 1700000008000000000, 1700000009000000000, 1700000010000000000], + "timestamps": [ + 1700000006000000000, + 1700000007000000000, + 1700000008000000000, + 1700000009000000000, + 1700000010000000000 + ], "values": { - "memory_footprint": [31420.2, 31510.4, 31498.7, 31550.3, 31600.1], - "memory_percent": [3.14202, 3.15104, 3.14987, 3.15503, 3.16001] + "memory_footprint": [ + 31420.2, + 31510.4, + 31498.7, + 31550.3, + 31600.1 + ], + "memory_percent": [ + 3.14202, + 3.15104, + 3.14987, + 3.15503, + 3.16001 + ] } } } diff --git a/features/dd-sdk-android-rum/src/testFixtures/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt b/features/dd-sdk-android-rum/src/testFixtures/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt new file mode 100644 index 0000000000..c93475340d --- /dev/null +++ b/features/dd-sdk-android-rum/src/testFixtures/kotlin/com/datadog/android/rum/assertj/TimeseriesCpuEventAssert.kt @@ -0,0 +1,169 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.assertj + +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesCpuEvent.TimeseriesCpuEventSessionType +import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.data.Offset +import java.util.UUID + +class TimeseriesCpuEventAssert private constructor( + private val actual: TimeseriesCpuEvent +) { + + fun hasApplicationId(expected: String) = apply { + assertThat(actual.application.id) + .overridingErrorMessage { + "Expected event to have application.id $expected " + + "but was ${actual.application.id}" + } + .isEqualTo(expected) + } + + fun hasSessionId(expected: String) = apply { + assertThat(actual.session.id) + .overridingErrorMessage { + "Expected event to have session.id $expected but was ${actual.session.id}" + } + .isEqualTo(expected) + } + + fun hasSessionType(expected: TimeseriesCpuEventSessionType) = apply { + assertThat(actual.session.type) + .overridingErrorMessage { + "Expected event to have session.type $expected but was ${actual.session.type}" + } + .isEqualTo(expected) + } + + fun hasService(expected: String) = apply { + assertThat(actual.service) + .overridingErrorMessage { "Expected event to have service $expected but was ${actual.service}" } + .isEqualTo(expected) + } + + fun hasVersion(expected: String) = apply { + assertThat(actual.version) + .overridingErrorMessage { "Expected event to have version $expected but was ${actual.version}" } + .isEqualTo(expected) + } + + fun hasDate(expected: Long) = apply { + assertThat(actual.date) + .overridingErrorMessage { "Expected event to have date $expected but was ${actual.date}" } + .isEqualTo(expected) + } + + fun hasTimeseriesName(expected: String) = apply { + assertThat(actual.timeseries.name) + .overridingErrorMessage { + "Expected event to have timeseries.name $expected but was ${actual.timeseries.name}" + } + .isEqualTo(expected) + } + + fun hasTimeseriesSchema(expected: String) = apply { + assertThat(actual.timeseries.schema) + .overridingErrorMessage { + "Expected event to have timeseries.schema $expected but was ${actual.timeseries.schema}" + } + .isEqualTo(expected) + } + + fun hasTimeseriesStart(expected: Long) = apply { + assertThat(actual.timeseries.start) + .overridingErrorMessage { + "Expected event to have timeseries.start $expected but was ${actual.timeseries.start}" + } + .isEqualTo(expected) + } + + fun hasTimeseriesEnd(expected: Long) = apply { + assertThat(actual.timeseries.end) + .overridingErrorMessage { + "Expected event to have timeseries.end $expected but was ${actual.timeseries.end}" + } + .isEqualTo(expected) + } + + fun hasTimeseriesStartNotAfterEnd() = apply { + assertThat(actual.timeseries.start) + .overridingErrorMessage { + "Expected timeseries.start ${actual.timeseries.start} to be less than or equal to " + + "timeseries.end ${actual.timeseries.end}" + } + .isLessThanOrEqualTo(actual.timeseries.end) + } + + fun hasValidTimeseriesId() = apply { + val parsedTimeseriesId = runCatching { UUID.fromString(actual.timeseries.id) }.getOrNull() + + assertThat(parsedTimeseriesId) + .overridingErrorMessage { "Expected timeseries.id to be a valid UUID but was ${actual.timeseries.id}" } + .isNotNull() + } + + fun hasSameTimeseriesAs(expected: TimeseriesCpuEvent) = apply { + val expectedCpuUsage = expected.timeseries.data.values.cpuUsage + val actualCpuUsage = actual.timeseries.data.values.cpuUsage + + hasTimeseriesName(expected.timeseries.name) + .hasTimeseriesSchema(expected.timeseries.schema) + .hasTimeseriesStart(expected.timeseries.start) + .hasTimeseriesEnd(expected.timeseries.end) + .hasTimestamps(*expected.timeseries.data.timestamps.toLongArray()) + + assertThat(actualCpuUsage) + .overridingErrorMessage { + "Expected CPU values count ${expectedCpuUsage.size} but was ${actualCpuUsage.size}" + } + .hasSameSizeAs(expectedCpuUsage) + + expectedCpuUsage.forEachIndexed { position, expectedValue -> + hasCpuUsage(expectedValue.toDouble(), position) + } + } + + fun hasDataPointsCount(expected: Int) = apply { + val timestampsCount = actual.timeseries.data.timestamps.size + + assertThat(timestampsCount) + .overridingErrorMessage { + "Expected timeseries data to contain $expected datapoints but contained $timestampsCount" + } + .isEqualTo(expected) + } + + fun hasTimestamps(vararg expected: Long) = apply { + assertThat(actual.timeseries.data.timestamps) + .overridingErrorMessage { + "Expected timeseries timestamps ${expected.toList()} but was ${actual.timeseries.data.timestamps}" + } + .containsExactlyElementsOf(expected.toList()) + } + + fun hasCpuUsage(expected: Double, position: Int = 0) = apply { + val cpuUsage = actual.timeseries.data.values.cpuUsage.getOrNull(position)?.toDouble() + + assertThat(checkNotNull(cpuUsage)) + .overridingErrorMessage { "Expected first data point cpu_usage to be close to $expected but was $cpuUsage" } + .isCloseTo(expected, Offset.offset(NUMBER_COMPARISON_TOLERANCE)) + } + + companion object { + private const val NUMBER_COMPARISON_TOLERANCE = 1e-9 + + fun assertThat(actual: TimeseriesCpuEvent?): TimeseriesCpuEventAssert { + Assertions.assertThat(actual) + .overridingErrorMessage { "Expected timeseries event to be present" } + .isNotNull() + return TimeseriesCpuEventAssert(checkNotNull(actual)) + } + } +} diff --git a/features/dd-sdk-android-rum/src/testFixtures/kotlin/com/datadog/android/rum/assertj/TimeseriesMemoryEventAssert.kt b/features/dd-sdk-android-rum/src/testFixtures/kotlin/com/datadog/android/rum/assertj/TimeseriesMemoryEventAssert.kt new file mode 100644 index 0000000000..33837b7b57 --- /dev/null +++ b/features/dd-sdk-android-rum/src/testFixtures/kotlin/com/datadog/android/rum/assertj/TimeseriesMemoryEventAssert.kt @@ -0,0 +1,203 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.assertj + +import com.datadog.android.rum.model.TimeseriesMemoryEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent.TimeseriesMemoryEventSessionType +import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.data.Offset +import java.util.UUID + +class TimeseriesMemoryEventAssert private constructor( + private val actual: TimeseriesMemoryEvent +) { + + fun hasApplicationId(expected: String) = apply { + assertThat(actual.application.id) + .overridingErrorMessage { + "Expected event to have application.id $expected " + + "but was ${actual.application.id}" + } + .isEqualTo(expected) + } + + fun hasSessionId(expected: String) = apply { + assertThat(actual.session.id) + .overridingErrorMessage { + "Expected event to have session.id $expected but was ${actual.session.id}" + } + .isEqualTo(expected) + } + + fun hasSessionType(expected: TimeseriesMemoryEventSessionType) = apply { + assertThat(actual.session.type) + .overridingErrorMessage { + "Expected event to have session.type $expected but was ${actual.session.type}" + } + .isEqualTo(expected) + } + + fun hasService(expected: String) = apply { + assertThat(actual.service) + .overridingErrorMessage { "Expected event to have service $expected but was ${actual.service}" } + .isEqualTo(expected) + } + + fun hasVersion(expected: String) = apply { + assertThat(actual.version) + .overridingErrorMessage { "Expected event to have version $expected but was ${actual.version}" } + .isEqualTo(expected) + } + + fun hasDate(expected: Long) = apply { + assertThat(actual.date) + .overridingErrorMessage { "Expected event to have date $expected but was ${actual.date}" } + .isEqualTo(expected) + } + + fun hasTimeseriesName(expected: String) = apply { + assertThat(actual.timeseries.name) + .overridingErrorMessage { + "Expected event to have timeseries.name $expected but was ${actual.timeseries.name}" + } + .isEqualTo(expected) + } + + fun hasTimeseriesSchema(expected: String) = apply { + assertThat(actual.timeseries.schema) + .overridingErrorMessage { + "Expected event to have timeseries.schema $expected but was ${actual.timeseries.schema}" + } + .isEqualTo(expected) + } + + fun hasTimeseriesStart(expected: Long) = apply { + assertThat(actual.timeseries.start) + .overridingErrorMessage { + "Expected event to have timeseries.start $expected but was ${actual.timeseries.start}" + } + .isEqualTo(expected) + } + + fun hasTimeseriesEnd(expected: Long) = apply { + assertThat(actual.timeseries.end) + .overridingErrorMessage { + "Expected event to have timeseries.end $expected but was ${actual.timeseries.end}" + } + .isEqualTo(expected) + } + + fun hasTimeseriesStartNotAfterEnd() = apply { + assertThat(actual.timeseries.start) + .overridingErrorMessage { + "Expected timeseries.start ${actual.timeseries.start} to be less than or equal to " + + "timeseries.end ${actual.timeseries.end}" + } + .isLessThanOrEqualTo(actual.timeseries.end) + } + + fun hasValidTimeseriesId() = apply { + val parsedTimeseriesId = runCatching { UUID.fromString(actual.timeseries.id) }.getOrNull() + + assertThat(parsedTimeseriesId) + .overridingErrorMessage { "Expected timeseries.id to be a valid UUID but was ${actual.timeseries.id}" } + .isNotNull() + } + + fun hasSameTimeseriesAs(expected: TimeseriesMemoryEvent) = apply { + val expectedTimeseries = expected.timeseries + val expectedMemoryFootprint = expectedTimeseries.data.values.memoryFootprint + val actualMemoryFootprint = actual.timeseries.data.values.memoryFootprint + val expectedMemoryPercent = expectedTimeseries.data.values.memoryPercent + val actualMemoryPercent = actual.timeseries.data.values.memoryPercent + + hasTimeseriesName(expectedTimeseries.name) + .hasTimeseriesSchema(expectedTimeseries.schema) + .hasTimeseriesStart(expectedTimeseries.start) + .hasTimeseriesEnd(expectedTimeseries.end) + .hasTimestamps(*expectedTimeseries.data.timestamps.toLongArray()) + + assertThat(actualMemoryFootprint) + .overridingErrorMessage { + "Expected memory footprint values count ${expectedMemoryFootprint.size} " + + "but was ${actualMemoryFootprint.size}" + } + .hasSameSizeAs(expectedMemoryFootprint) + assertThat(actualMemoryPercent) + .overridingErrorMessage { + "Expected memory percent values count ${expectedMemoryPercent.size} " + + "but was ${actualMemoryPercent.size}" + } + .hasSameSizeAs(expectedMemoryPercent) + expectedMemoryFootprint.forEachIndexed { position, expectedValue -> + hasMemoryFootprint(expectedValue.toDouble(), NUMBER_COMPARISON_TOLERANCE, position) + } + expectedMemoryPercent.forEachIndexed { position, expectedValue -> + hasMemoryPercent(expectedValue.toDouble(), NUMBER_COMPARISON_TOLERANCE, position) + } + } + + fun hasDataPointsCount(expected: Int) = apply { + val timestampsCount = actual.timeseries.data.timestamps.size + + assertThat(timestampsCount) + .overridingErrorMessage { + "Expected timeseries data to contain $expected datapoints but contained $timestampsCount" + } + .isEqualTo(expected) + } + + fun hasTimestamps(vararg expected: Long) = apply { + assertThat(actual.timeseries.data.timestamps) + .overridingErrorMessage { + "Expected timeseries timestamps ${expected.toList()} but was ${actual.timeseries.data.timestamps}" + } + .containsExactlyElementsOf(expected.toList()) + } + + fun hasMemoryFootprint(expected: Double, offset: Double, position: Int = 0) = apply { + val memoryFootprint = actual.timeseries.data.values.memoryFootprint.getOrNull(position)?.toDouble() + + assertThat(memoryFootprint) + .overridingErrorMessage { + "Expected memory footprint data point at position $position to be present" + } + .isNotNull() + assertThat(checkNotNull(memoryFootprint)) + .overridingErrorMessage { + "Expected memory footprint at position $position to be close to $expected but was $memoryFootprint" + } + .isCloseTo(expected, Offset.offset(offset)) + } + + fun hasMemoryPercent(expected: Double, offset: Double, position: Int = 0) = apply { + val memoryPercent = actual.timeseries.data.values.memoryPercent.getOrNull(position)?.toDouble() + + assertThat(memoryPercent) + .overridingErrorMessage { + "Expected memory percent data point at position $position to be present" + } + .isNotNull() + assertThat(checkNotNull(memoryPercent)) + .overridingErrorMessage { + "Expected memory percent at position $position to be close to $expected but was $memoryPercent" + } + .isCloseTo(expected, Offset.offset(offset)) + } + + companion object { + private const val NUMBER_COMPARISON_TOLERANCE = 1e-9 + + fun assertThat(actual: TimeseriesMemoryEvent?): TimeseriesMemoryEventAssert { + Assertions.assertThat(actual) + .overridingErrorMessage { "Expected timeseries event to be present" } + .isNotNull() + return TimeseriesMemoryEventAssert(checkNotNull(actual)) + } + } +} From 2a74f34ee979b120f2a79a24e48ed6ef7b02a41c Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Mon, 17 Aug 2026 09:32:54 +0100 Subject: [PATCH 24/32] RUM-17613: Add Array.toSet() to detekt safe third-party calls list Array.toSet() over an enum's values() is always safe, so whitelist it globally instead of suppressing UnsafeThirdPartyFunctionCall at each call site. --- detekt_custom_safe_calls_third_party.yml | 1 + .../datadog/android/rum/timeseries/TimeseriesConfiguration.kt | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/detekt_custom_safe_calls_third_party.yml b/detekt_custom_safe_calls_third_party.yml index 5a7c07ad32..ba90094994 100644 --- a/detekt_custom_safe_calls_third_party.yml +++ b/detekt_custom_safe_calls_third_party.yml @@ -454,6 +454,7 @@ datadog: - "kotlin.Array.orEmpty()" - "kotlin.Array.sorted()" - "kotlin.Array.toList()" + - "kotlin.Array.toSet()" - "kotlin.byteArrayOf(kotlin.ByteArray)" - "kotlin.ByteArray.any(kotlin.Function1)" - "kotlin.ByteArray.contentEquals(kotlin.ByteArray?)" diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt index bff085a97a..4dcfc83d99 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt @@ -23,7 +23,6 @@ class TimeseriesConfiguration internal constructor( @ExperimentalRumApi class Builder { - @Suppress("UnsafeThirdPartyFunctionCall") // Kotlin Array.toSet() is safe for enum values. private var enabledTypes: Set = TimeseriesType.values().toSet() private var collectInBackground: Boolean = false @@ -37,7 +36,6 @@ class TimeseriesConfiguration internal constructor( * @param types the timeseries types to collect. */ fun collectOnly(vararg types: TimeseriesType): Builder = apply { - @Suppress("UnsafeThirdPartyFunctionCall") // Kotlin Array.toSet() is safe for enum values. enabledTypes = types.toSet() } From c34ab47d4ab3fda888231fa1923f85e31effcc42 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Mon, 17 Aug 2026 10:05:40 +0100 Subject: [PATCH 25/32] RUM-17613: Drop unused timeseries connectivity mappers toTimeseriesCpuConnectivity() and toTimeseriesMemoryConnectivity() were never called: the timeseries event factories don't populate connectivity at all. Remove them along with their tests and the helper table that only those tests used. --- .../rum/internal/domain/scope/RumEventExt.kt | 74 ------------ .../internal/domain/scope/RumEventExtTest.kt | 111 ------------------ 2 files changed, 185 deletions(-) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt index 9ecc79ae71..25f3bea919 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt @@ -433,80 +433,6 @@ internal fun NetworkInfo.toAppLaunchVitalConnectivity(): VitalAppLaunchEvent.Con ) } -internal fun NetworkInfo.toTimeseriesCpuConnectivity(): TimeseriesCpuEvent.Connectivity { - val status = if (isConnected()) { - TimeseriesCpuEvent.Status.CONNECTED - } else { - TimeseriesCpuEvent.Status.NOT_CONNECTED - } - val interfaces = when (connectivity) { - NetworkInfo.Connectivity.NETWORK_ETHERNET -> listOf(TimeseriesCpuEvent.Interface.ETHERNET) - NetworkInfo.Connectivity.NETWORK_WIFI -> listOf(TimeseriesCpuEvent.Interface.WIFI) - NetworkInfo.Connectivity.NETWORK_WIMAX -> listOf(TimeseriesCpuEvent.Interface.WIMAX) - NetworkInfo.Connectivity.NETWORK_BLUETOOTH -> listOf(TimeseriesCpuEvent.Interface.BLUETOOTH) - NetworkInfo.Connectivity.NETWORK_2G, - NetworkInfo.Connectivity.NETWORK_3G, - NetworkInfo.Connectivity.NETWORK_4G, - NetworkInfo.Connectivity.NETWORK_5G, - NetworkInfo.Connectivity.NETWORK_MOBILE_OTHER, - NetworkInfo.Connectivity.NETWORK_CELLULAR -> listOf(TimeseriesCpuEvent.Interface.CELLULAR) - - NetworkInfo.Connectivity.NETWORK_OTHER -> listOf(TimeseriesCpuEvent.Interface.OTHER) - NetworkInfo.Connectivity.NETWORK_NOT_CONNECTED -> emptyList() - } - - val cellular = if (cellularTechnology != null || carrierName != null) { - TimeseriesCpuEvent.Cellular( - technology = cellularTechnology, - carrierName = carrierName - ) - } else { - null - } - return TimeseriesCpuEvent.Connectivity( - status, - interfaces, - cellular = cellular - ) -} - -internal fun NetworkInfo.toTimeseriesMemoryConnectivity(): TimeseriesMemoryEvent.Connectivity { - val status = if (isConnected()) { - TimeseriesMemoryEvent.Status.CONNECTED - } else { - TimeseriesMemoryEvent.Status.NOT_CONNECTED - } - val interfaces = when (connectivity) { - NetworkInfo.Connectivity.NETWORK_ETHERNET -> listOf(TimeseriesMemoryEvent.Interface.ETHERNET) - NetworkInfo.Connectivity.NETWORK_WIFI -> listOf(TimeseriesMemoryEvent.Interface.WIFI) - NetworkInfo.Connectivity.NETWORK_WIMAX -> listOf(TimeseriesMemoryEvent.Interface.WIMAX) - NetworkInfo.Connectivity.NETWORK_BLUETOOTH -> listOf(TimeseriesMemoryEvent.Interface.BLUETOOTH) - NetworkInfo.Connectivity.NETWORK_2G, - NetworkInfo.Connectivity.NETWORK_3G, - NetworkInfo.Connectivity.NETWORK_4G, - NetworkInfo.Connectivity.NETWORK_5G, - NetworkInfo.Connectivity.NETWORK_MOBILE_OTHER, - NetworkInfo.Connectivity.NETWORK_CELLULAR -> listOf(TimeseriesMemoryEvent.Interface.CELLULAR) - - NetworkInfo.Connectivity.NETWORK_OTHER -> listOf(TimeseriesMemoryEvent.Interface.OTHER) - NetworkInfo.Connectivity.NETWORK_NOT_CONNECTED -> emptyList() - } - - val cellular = if (cellularTechnology != null || carrierName != null) { - TimeseriesMemoryEvent.Cellular( - technology = cellularTechnology, - carrierName = carrierName - ) - } else { - null - } - return TimeseriesMemoryEvent.Connectivity( - status, - interfaces, - cellular = cellular - ) -} - internal fun NetworkInfo.isConnected(): Boolean { return connectivity != NetworkInfo.Connectivity.NETWORK_NOT_CONNECTED } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExtTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExtTest.kt index 99b9bcbc7c..9128bd0b38 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExtTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExtTest.kt @@ -17,8 +17,6 @@ import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent -import com.datadog.android.rum.model.TimeseriesCpuEvent -import com.datadog.android.rum.model.TimeseriesMemoryEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.utils.forge.Configurator import fr.xgouchet.elmyr.annotation.Forgery @@ -1041,86 +1039,6 @@ internal class RumEventExtTest { ) } - @ParameterizedTest - @EnumSource(NetworkInfo.Connectivity::class) - fun `M return connectivity W toTimeseriesCpuConnectivity()`( - connectivity: NetworkInfo.Connectivity, - @Forgery fakeNetworkInfo: NetworkInfo, - @StringForgery fakeCarrierName: String, - @StringForgery fakeCellularTechnology: String - ) { - // Given - val networkInfo = fakeNetworkInfo.copy( - connectivity = connectivity, - carrierName = fakeCarrierName, - cellularTechnology = fakeCellularTechnology - ) - - // When - val result = networkInfo.toTimeseriesCpuConnectivity() - - // Then - assertThat(result.status.name).isEqualTo(expectedStatusName(connectivity)) - assertThat(result.interfaces?.map { it.name }) - .isEqualTo(EXPECTED_INTERFACE_NAMES.getValue(connectivity)) - assertThat(result.cellular) - .isEqualTo(TimeseriesCpuEvent.Cellular(fakeCellularTechnology, fakeCarrierName)) - } - - @Test - fun `M return null cellular W toTimeseriesCpuConnectivity() {no carrier info}`( - @Forgery fakeNetworkInfo: NetworkInfo - ) { - // Given - val networkInfo = fakeNetworkInfo.copy(carrierName = null, cellularTechnology = null) - - // When - val result = networkInfo.toTimeseriesCpuConnectivity() - - // Then - assertThat(result.cellular).isNull() - } - - @ParameterizedTest - @EnumSource(NetworkInfo.Connectivity::class) - fun `M return connectivity W toTimeseriesMemoryConnectivity()`( - connectivity: NetworkInfo.Connectivity, - @Forgery fakeNetworkInfo: NetworkInfo, - @StringForgery fakeCarrierName: String, - @StringForgery fakeCellularTechnology: String - ) { - // Given - val networkInfo = fakeNetworkInfo.copy( - connectivity = connectivity, - carrierName = fakeCarrierName, - cellularTechnology = fakeCellularTechnology - ) - - // When - val result = networkInfo.toTimeseriesMemoryConnectivity() - - // Then - assertThat(result.status.name).isEqualTo(expectedStatusName(connectivity)) - assertThat(result.interfaces?.map { it.name }) - .isEqualTo(EXPECTED_INTERFACE_NAMES.getValue(connectivity)) - assertThat(result.cellular) - .isEqualTo(TimeseriesMemoryEvent.Cellular(fakeCellularTechnology, fakeCarrierName)) - } - - @Test - fun `M return null cellular W toTimeseriesMemoryConnectivity() {no carrier info}`( - @Forgery fakeNetworkInfo: NetworkInfo - ) { - // Given - val networkInfo = fakeNetworkInfo.copy(carrierName = null, cellularTechnology = null) - - // When - val result = networkInfo.toTimeseriesMemoryConnectivity() - - // Then - assertThat(result.cellular).isNull() - } - // endregion // region device type conversion @@ -1210,33 +1128,4 @@ internal class RumEventExtTest { } // endregion - - companion object { - - // Interface enums are generated per event type but share their names, so a single - // name-based table covers both timeseries schemas. getValue() fails loudly if a new - // Connectivity value is added without updating the mapping. - private val EXPECTED_INTERFACE_NAMES = mapOf( - NetworkInfo.Connectivity.NETWORK_NOT_CONNECTED to emptyList(), - NetworkInfo.Connectivity.NETWORK_ETHERNET to listOf("ETHERNET"), - NetworkInfo.Connectivity.NETWORK_WIFI to listOf("WIFI"), - NetworkInfo.Connectivity.NETWORK_WIMAX to listOf("WIMAX"), - NetworkInfo.Connectivity.NETWORK_BLUETOOTH to listOf("BLUETOOTH"), - NetworkInfo.Connectivity.NETWORK_2G to listOf("CELLULAR"), - NetworkInfo.Connectivity.NETWORK_3G to listOf("CELLULAR"), - NetworkInfo.Connectivity.NETWORK_4G to listOf("CELLULAR"), - NetworkInfo.Connectivity.NETWORK_5G to listOf("CELLULAR"), - NetworkInfo.Connectivity.NETWORK_MOBILE_OTHER to listOf("CELLULAR"), - NetworkInfo.Connectivity.NETWORK_CELLULAR to listOf("CELLULAR"), - NetworkInfo.Connectivity.NETWORK_OTHER to listOf("OTHER") - ) - - private fun expectedStatusName(connectivity: NetworkInfo.Connectivity): String { - return if (connectivity == NetworkInfo.Connectivity.NETWORK_NOT_CONNECTED) { - "NOT_CONNECTED" - } else { - "CONNECTED" - } - } - } } From a701f3e0053d31d9cd6075f5e629b963bd6b31dc Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Mon, 17 Aug 2026 10:17:27 +0100 Subject: [PATCH 26/32] RUM-17613: Drop orphaned TimeseriesAttributes constants Pipeline used to read the timeseries name back out of the hand-built JSON event; it now takes it from EventFactory.eventName, so nothing in production references these keys anymore. The only remaining caller was a PipelineTest helper whose JSON shape is never asserted on. --- .../timeseries/serializer/TimeseriesAttributes.kt | 13 ------------- .../android/rum/internal/timeseries/PipelineTest.kt | 10 +++------- 2 files changed, 3 insertions(+), 20 deletions(-) delete mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/TimeseriesAttributes.kt diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/TimeseriesAttributes.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/TimeseriesAttributes.kt deleted file mode 100644 index fbdefdc52b..0000000000 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/serializer/TimeseriesAttributes.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.rum.internal.timeseries.serializer - -internal object TimeseriesAttributes { - const val KEY_TIMESERIES = "timeseries" - const val KEY_SCHEMA = "schema" - const val KEY_NAME = "name" -} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt index 072542514f..6e3a94ceae 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt @@ -19,7 +19,6 @@ import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.instrumentation.insights.InsightsCollector import com.datadog.android.rum.internal.timeseries.factory.EventFactory import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader -import com.datadog.android.rum.internal.timeseries.serializer.TimeseriesAttributes import com.datadog.android.rum.utils.forge.Configurator import com.datadog.android.utils.verifyLog import com.datadog.tools.unit.forge.aThrowable @@ -393,12 +392,9 @@ internal class PipelineTest { // endregion + // Pipeline forwards whatever the event factory returns without inspecting it, so the shape + // only needs to be a distinct non-null object per name. private fun fakeTimeseriesJson(name: String): JsonObject = JsonObject().apply { - add( - TimeseriesAttributes.KEY_TIMESERIES, - JsonObject().apply { - addProperty(TimeseriesAttributes.KEY_NAME, name) - } - ) + addProperty("name", name) } } From 340e8c5d33b06620be1db48a95b5ced4b94de8ee Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Fri, 14 Aug 2026 16:51:49 +0100 Subject: [PATCH 27/32] RUM-17613: Pass the live RUM context to timeseries pipelines Timeseries batches carried the RumContext captured when RumSessionScope created the collector, so every event was attributed to whatever view was active at session start. The collector now tracks the context and hands it to Pipeline.execute()/flush() per call, and RumSessionScope feeds it the active context on each handled event. Pipeline takes over its own synchronization as part of that: reader.read() stays outside the lock so a concurrent flush() never waits for /proc I/O. Ref: RUM-17613 --- .../internal/domain/scope/RumSessionScope.kt | 6 +- .../timeseries/DefaultTimeseriesCollector.kt | 30 +-- .../DefaultTimeseriesCollectorFactory.kt | 17 +- .../rum/internal/timeseries/Pipeline.kt | 16 +- .../timeseries/TimeseriesCollector.kt | 3 +- .../domain/scope/RumSessionScopeTest.kt | 23 ++- .../internal/monitor/DatadogRumMonitorTest.kt | 17 ++ .../DefaultTimeseriesCollectorFactoryTest.kt | 6 +- .../DefaultTimeseriesCollectorTest.kt | 89 +++++--- .../rum/internal/timeseries/PipelineTest.kt | 194 +++++++++++++++--- .../internal/timeseries/csv/CsvCollector.kt | 3 +- 11 files changed, 294 insertions(+), 110 deletions(-) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt index f8ccf0c603..611dc3a859 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt @@ -201,7 +201,11 @@ internal class RumSessionScope( } } - timeseriesCollector.onViewTypeUpdate(getActiveRumContext().viewType) + // getActiveRumContext() copies the whole context chain, so it is only built when there is a + // collector to feed: timeseries collection is opt-in and this runs on every RUM event. + if (timeseriesCollector !is NoOpTimeseriesCollector) { + timeseriesCollector.onRumContextUpdate(getActiveRumContext()) + } return if (isSessionComplete()) { null diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt index 506506ac77..c6440857f1 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt @@ -9,6 +9,7 @@ package com.datadog.android.rum.internal.timeseries import androidx.annotation.WorkerThread import com.datadog.android.api.InternalLogger import com.datadog.android.core.internal.utils.scheduleSafe +import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.scope.RumViewType import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit @@ -24,11 +25,11 @@ import java.util.concurrent.atomic.AtomicReference * - After [onSessionStop], the instance must not be restarted; create a new instance. * * Background sampling: - * - When [collectInBackground] is `false`, [onViewTypeUpdate] pauses sampling on leaving foreground + * - When [collectInBackground] is `false`, [onRumContextUpdate] pauses sampling on leaving foreground * and resumes it on returning to foreground. * * Threading: - * - [onSessionStart] / [onSessionStop] / [onViewTypeUpdate] are called from the RUM event-handler thread. + * - [onSessionStart] / [onSessionStop] / [onRumContextUpdate] are called from the RUM event-handler thread. * - Sampling tasks run on a [scheduledExecutorService] shared with other RUM components * (owned by the SDK core); the instance neither owns nor shuts it down. * - [Pipeline] is responsible for its own thread safety via internal synchronization. @@ -40,7 +41,10 @@ internal class DefaultTimeseriesCollector( private val internalLogger: InternalLogger, internal val pipelines: List>, private val collectInBackground: Boolean, - internal val scheduledExecutorService: ScheduledExecutorService + internal val scheduledExecutorService: ScheduledExecutorService, + // Read by the sampling ticks so every batch is attributed to the view that was active when it + // was drained, not to the one active when the collector was created. + @Volatile private var rumContext: RumContext ) : TimeseriesCollector { private enum class State { IDLE, RUNNING, SUSPENDED, STOPPED } @@ -49,9 +53,6 @@ internal class DefaultTimeseriesCollector( // Incremented on every start/resume. Ticks carrying a stale generation self-terminate. private val currentGeneration = AtomicInteger(0) - @Volatile - private var currentViewType: RumViewType? = null - @WorkerThread override fun onSessionStart() { if (state.compareAndSet(State.IDLE, State.RUNNING)) { @@ -64,7 +65,7 @@ internal class DefaultTimeseriesCollector( if (state.getAndSet(State.STOPPED) != State.STOPPED) { pipelines.forEach { pipeline -> try { - synchronized(pipeline, pipeline::flush) + pipeline.flush(rumContext) } catch (@Suppress("TooGenericExceptionCaught") t: Throwable) { internalLogger.log( level = InternalLogger.Level.ERROR, @@ -78,10 +79,13 @@ internal class DefaultTimeseriesCollector( } @WorkerThread - override fun onViewTypeUpdate(newViewType: RumViewType) { - if (newViewType == currentViewType) return - val isEnterForeground = !currentViewType.isForeground && newViewType.isForeground - currentViewType = newViewType + override fun onRumContextUpdate(newRumContext: RumContext) { + val oldViewType = rumContext.viewType + val newViewType = newRumContext.viewType + val isEnterForeground = !oldViewType.isForeground && newViewType.isForeground + + rumContext = newRumContext + if (!collectInBackground) { if (isEnterForeground && state.compareAndSet(State.SUSPENDED, State.RUNNING)) { startSampling() @@ -105,9 +109,7 @@ internal class DefaultTimeseriesCollector( ) { if (!isActive(generation)) return@scheduleSafe try { - synchronized(pipeline) { - if (isActive(generation)) pipeline.execute() - } + pipeline.execute(rumContext) } catch (@Suppress("TooGenericExceptionCaught") t: Throwable) { internalLogger.log( level = InternalLogger.Level.ERROR, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt index e47aee7c60..38c3430e2a 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt @@ -44,12 +44,12 @@ internal class DefaultTimeseriesCollectorFactory( val pipelines = mutableListOf>() if (TimeseriesType.CPU in configuration.enabledTypes) { - pipelines += createCpuPipeline(sessionType, rumContext) + pipelines += createCpuPipeline(sessionType) } if (TimeseriesType.MEMORY in configuration.enabledTypes) { if (totalRamBytes > 0L) { - pipelines += createMemoryPipeline(sessionType, rumContext) + pipelines += createMemoryPipeline(sessionType) } else { sdkCore.internalLogger.log( InternalLogger.Level.WARN, @@ -64,14 +64,12 @@ internal class DefaultTimeseriesCollectorFactory( internalLogger = sdkCore.internalLogger, collectInBackground = configuration.collectInBackground, scheduledExecutorService = scheduledExecutorService, + rumContext = rumContext, pipelines = pipelines ) } - private fun createMemoryPipeline( - sessionType: RumSessionType, - rumContext: RumContext - ) = Pipeline( + private fun createMemoryPipeline(sessionType: RumSessionType) = Pipeline( sdkCore = sdkCore, reader = VitalReaderWrapper( vitalReader = MemoryVitalReader(internalLogger = sdkCore.internalLogger), @@ -88,14 +86,10 @@ internal class DefaultTimeseriesCollectorFactory( internalLogger = sdkCore.internalLogger ), dataWriter = dataWriter, - rumContext = rumContext, insightsCollector = insightsCollector ) - private fun createCpuPipeline( - sessionType: RumSessionType, - rumContext: RumContext - ) = Pipeline( + private fun createCpuPipeline(sessionType: RumSessionType) = Pipeline( sdkCore = sdkCore, reader = CpuDatapointReader( cpuStatReader = CpuStatReader(internalLogger = sdkCore.internalLogger), @@ -111,7 +105,6 @@ internal class DefaultTimeseriesCollectorFactory( internalLogger = sdkCore.internalLogger ), dataWriter = dataWriter, - rumContext = rumContext, insightsCollector = insightsCollector ) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt index 9865a0395f..129455a8c3 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/Pipeline.kt @@ -27,21 +27,25 @@ internal class Pipeline( private val buffer: Buffer, private val eventFactory: EventFactory, private val dataWriter: DataWriter, - private val rumContext: RumContext, private val insightsCollector: InsightsCollector = NoOpInsightsCollector() ) { val intervalMs: Long get() = reader.intervalMs @WorkerThread - fun execute() { - reader.read()?.let(buffer::add) - if (buffer.isFull()) drainAndWrite() + fun execute(rumContext: RumContext) { + // reader.read() may hit the filesystem (/proc); kept outside the lock so a concurrent + // flush() waits only for the buffer and never for I/O. + val dataPoint = reader.read() + synchronized(this) { + dataPoint?.let(buffer::add) + if (buffer.isFull()) drainAndWrite(rumContext) + } } @WorkerThread - fun flush() = drainAndWrite() + fun flush(rumContext: RumContext) = synchronized(this) { drainAndWrite(rumContext) } - private fun drainAndWrite() { + private fun drainAndWrite(rumContext: RumContext) { val dataPoints = buffer.drain().ifEmpty { return } sdkCore.getFeature(Feature.RUM_FEATURE_NAME) ?.withWriteContext( diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesCollector.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesCollector.kt index 8be984d7fe..ccd56677ec 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesCollector.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/TimeseriesCollector.kt @@ -8,14 +8,13 @@ package com.datadog.android.rum.internal.timeseries import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.internal.domain.RumContext -import com.datadog.android.rum.internal.domain.scope.RumViewType import com.datadog.tools.annotation.NoOpImplementation @NoOpImplementation internal interface TimeseriesCollector { fun onSessionStart() fun onSessionStop() - fun onViewTypeUpdate(newViewType: RumViewType) + fun onRumContextUpdate(newRumContext: RumContext) @NoOpImplementation interface Factory { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt index 3b0f508aff..f376b60801 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt @@ -1958,9 +1958,9 @@ internal class RumSessionScopeTest { // Then — one timeseries per tracked session, updates go to the live one only verify(mockTimeseriesCollectorFactory, times(2)).create(any(), any()) verify(firstTimeseriesCollector).onSessionStop() - verify(firstTimeseriesCollector, times(1)).onViewTypeUpdate(fakeViewContext.viewType) + verify(firstTimeseriesCollector, times(1)).onRumContextUpdate(fakeViewContext) verify(secondTimeseriesCollector).onSessionStart() - verify(secondTimeseriesCollector).onViewTypeUpdate(fakeViewContext.viewType) + verify(secondTimeseriesCollector).onRumContextUpdate(fakeViewContext) } @Test @@ -2018,14 +2018,14 @@ internal class RumSessionScopeTest { // When testedScope.handleEvent(forge.addErrorEvent(), fakeDatadogContext, mockEventWriteScope, mockWriter) - // Then — the session scope view type, once per handled event - val viewTypeCaptor = argumentCaptor() - verify(mockTimeseriesCollector, times(2)).onViewTypeUpdate(viewTypeCaptor.capture()) - assertThat(viewTypeCaptor.allValues).containsOnly(testedScope.getRumContext().viewType) + // Then — the session scope context, once per handled event + val rumContextCaptor = argumentCaptor() + verify(mockTimeseriesCollector, times(2)).onRumContextUpdate(rumContextCaptor.capture()) + assertThat(rumContextCaptor.allValues).containsOnly(testedScope.getRumContext()) } @Test - fun `M pass active view type W onViewTypeUpdate() { StartView creates a foreground view }`(forge: Forge) { + fun `M pass active view context W onRumContextUpdate() { StartView creates a foreground view }`(forge: Forge) { // Given — a real view manager child scope, so the context comes from an actual RumViewScope initializeTestedScope( withMockChildScope = false, @@ -2042,9 +2042,12 @@ internal class RumSessionScopeTest { ) // Then - val viewTypeCaptor = argumentCaptor() - verify(mockTimeseriesCollector).onViewTypeUpdate(viewTypeCaptor.capture()) - assertThat(viewTypeCaptor.firstValue).isEqualTo(RumViewType.FOREGROUND) + val rumContextCaptor = argumentCaptor() + verify(mockTimeseriesCollector).onRumContextUpdate(rumContextCaptor.capture()) + assertThat(rumContextCaptor.firstValue.viewType).isEqualTo(RumViewType.FOREGROUND) + assertThat(rumContextCaptor.firstValue.viewName).isEqualTo(fakeStartViewEvent.key.name) + assertThat(rumContextCaptor.firstValue.viewId).isNotNull + assertThat(rumContextCaptor.firstValue.sessionId).isEqualTo(testedScope.sessionId) } @Test diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt index d9a88327c0..da90ee57b8 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt @@ -2971,6 +2971,23 @@ internal class DatadogRumMonitorTest { // endregion + // region timeseries + + @Test + fun `M stop the active session timeseries W stopTimeseries()`() { + // Given + val mockSessionScope = mock() + whenever(mockApplicationScope.activeSession) doReturn mockSessionScope + + // When + testedMonitor.stopTimeseries() + + // Then + verify(mockSessionScope).stopTimeseries() + } + + // endregion + @OptIn(ExperimentalRumApi::class) @Test fun `M produce StartOperation event W startOperation`( diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt index 42e7b70cff..b120d6f420 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt @@ -221,7 +221,7 @@ internal class DefaultTimeseriesCollectorFactoryTest { } @Test - fun `M propagate executor to the collector and rum context to the pipelines W create()`( + fun `M propagate executor and rum context to the collector W create()`( @LongForgery(min = 1L) fakeTotalRamBytes: Long ) { // Given @@ -234,8 +234,8 @@ internal class DefaultTimeseriesCollectorFactoryTest { check(timeseries is DefaultTimeseriesCollector) assertThat(timeseries.scheduledExecutorService).isSameAs(mockExecutor) assertThat( - timeseries.pipelines.map { it.getFieldValue>("rumContext") } - ).containsOnly(fakeRumContext) + timeseries.getFieldValue("rumContext") + ).isSameAs(fakeRumContext) } @ParameterizedTest diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt index 0bb9f5b93d..c7191613c1 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt @@ -47,6 +47,7 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.quality.Strictness +import java.util.UUID import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit @@ -112,6 +113,20 @@ internal class DefaultTimeseriesCollectorTest { @IntForgery(min = 2, max = 16) var fakeBufferSize: Int = 0 + private fun fakeRumContextOf(viewType: RumViewType) = fakeRumContext.copy(viewType = viewType) + + private fun createTimeseries( + initialViewType: RumViewType, + collectInBackground: Boolean = false, + pipelines: List> = listOf(pipelineA, pipelineB) + ) = DefaultTimeseriesCollector( + pipelines = pipelines, + internalLogger = mockInternalLogger, + collectInBackground = collectInBackground, + scheduledExecutorService = mockExecutor, + rumContext = fakeRumContextOf(initialViewType) + ) + @BeforeEach fun `set up`() { whenever(mockReaderA.intervalMs) doReturn fakeIntervalAMs @@ -132,14 +147,8 @@ internal class DefaultTimeseriesCollectorTest { pipelineA = createPipeline(mockReaderA, bufferA, mockEventFactoryA) pipelineB = createPipeline(mockReaderB, bufferB, mockEventFactoryB) - testedTimeseriesCollector = DefaultTimeseriesCollector( - pipelines = listOf(pipelineA, pipelineB), - internalLogger = mockInternalLogger, - collectInBackground = false, - scheduledExecutorService = mockExecutor - ) // Seed FOREGROUND so sampling tests don't suspend on first tick - testedTimeseriesCollector.onViewTypeUpdate(RumViewType.FOREGROUND) + testedTimeseriesCollector = createTimeseries(RumViewType.FOREGROUND) } @Test @@ -228,7 +237,7 @@ internal class DefaultTimeseriesCollectorTest { testedTimeseriesCollector.onSessionStop() // Then - verify(mockEventFactoryA).create(any(), any(), any()) + verify(mockEventFactoryA).create(any(), eq(fakeRumContextOf(RumViewType.FOREGROUND)), any()) verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) } @@ -248,10 +257,34 @@ internal class DefaultTimeseriesCollectorTest { repeat(fakeBufferSize) { runnableA.run() } // Then - verify(mockEventFactoryA).create(any(), any(), any()) + verify(mockEventFactoryA).create( + any(), + eq(fakeRumContextOf(RumViewType.FOREGROUND)), + eq(dataPoints) + ) verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) } + @Test + fun `M use updated context W sample tick fills buffer { foreground view changed }`(forge: Forge) { + // Given + val fakePoint = forge.getForgery>() + val fakeNextContext = fakeRumContextOf(RumViewType.FOREGROUND).copy( + viewId = forge.getForgery().toString() + ) + whenever(mockReaderA.read()) doReturn fakePoint + whenever(mockEventFactoryA.create(any(), eq(fakeNextContext), any())) doReturn JsonObject() + testedTimeseriesCollector.onSessionStart() + testedTimeseriesCollector.onRumContextUpdate(fakeNextContext) + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + + // When + repeat(fakeBufferSize) { runnableA.run() } + + // Then + verify(mockEventFactoryA).create(any(), eq(fakeNextContext), eq(List(fakeBufferSize) { fakePoint })) + } + @Test fun `M reschedule sample W sample tick runs`(forge: Forge) { // Given @@ -399,13 +432,7 @@ internal class DefaultTimeseriesCollectorTest { whenever(mockBuffer.drain()) doThrow fakeError whenever(mockReaderA.read()) doReturn forge.getForgery>() val pipeline = createPipeline(mockReaderA, mockBuffer, mockEventFactoryA) - val timeseries = DefaultTimeseriesCollector( - pipelines = listOf(pipeline), - internalLogger = mockInternalLogger, - collectInBackground = false, - scheduledExecutorService = mockExecutor - ) - timeseries.onViewTypeUpdate(RumViewType.FOREGROUND) + val timeseries = createTimeseries(RumViewType.FOREGROUND, pipelines = listOf(pipeline)) timeseries.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) @@ -497,7 +524,7 @@ internal class DefaultTimeseriesCollectorTest { // Given whenever(mockReaderA.read()) doReturn forge.getForgery>() testedTimeseriesCollector.onSessionStart() - testedTimeseriesCollector.onViewTypeUpdate(RumViewType.BACKGROUND) + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When @@ -516,14 +543,9 @@ internal class DefaultTimeseriesCollectorTest { fun `M sample W sample tick { background, collectInBackground = true }`(forge: Forge) { // Given whenever(mockReaderA.read()) doReturn forge.getForgery>() - val bgTimeseries = DefaultTimeseriesCollector( - pipelines = listOf(pipelineA, pipelineB), - internalLogger = mockInternalLogger, - collectInBackground = true, - scheduledExecutorService = mockExecutor - ) + val bgTimeseries = createTimeseries(RumViewType.FOREGROUND, collectInBackground = true) bgTimeseries.onSessionStart() - bgTimeseries.onViewTypeUpdate(RumViewType.BACKGROUND) + bgTimeseries.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When @@ -534,17 +556,17 @@ internal class DefaultTimeseriesCollectorTest { } @Test - fun `M resume scheduling W onViewTypeUpdate() { background → foreground }`() { + fun `M resume scheduling W onRumContextUpdate() { background → foreground }`() { // Given testedTimeseriesCollector.onSessionStart() // Transition to background: suspends chains - testedTimeseriesCollector.onViewTypeUpdate(RumViewType.BACKGROUND) + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) runnableA.run() // State is already SUSPENDED; tick exits without reschedule val schedulesAfterSuspend = 1 // only the one from start() // When — app returns to foreground - testedTimeseriesCollector.onViewTypeUpdate(RumViewType.FOREGROUND) + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.FOREGROUND)) // Then — scheduling resumed (one new schedule per pipeline) verify(mockExecutor, times(schedulesAfterSuspend + 1)) @@ -561,13 +583,13 @@ internal class DefaultTimeseriesCollectorTest { testedTimeseriesCollector.onSessionStart() // Pipeline A suspends the instance (short interval fires first in background) - testedTimeseriesCollector.onViewTypeUpdate(RumViewType.BACKGROUND) + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) val oldRunnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) val oldRunnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) - oldRunnableA.run() // State is already SUSPENDED (set by onViewTypeUpdate); B's tick is still queued + oldRunnableA.run() // State is already SUSPENDED (set by onRumContextUpdate); B's tick is still queued // Resume creates a new generation and schedules fresh ticks for both pipelines - testedTimeseriesCollector.onViewTypeUpdate(RumViewType.FOREGROUND) + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.FOREGROUND)) // When — B's old (stale) runnable fires despite the new generation oldRunnableB.run() @@ -586,10 +608,10 @@ internal class DefaultTimeseriesCollectorTest { // // Given testedTimeseriesCollector.onSessionStart() - testedTimeseriesCollector.onViewTypeUpdate(RumViewType.BACKGROUND) + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) val oldRunnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) // Resume: state = RUNNING, gen = 2; A and B each get a new schedule - testedTimeseriesCollector.onViewTypeUpdate(RumViewType.FOREGROUND) + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.FOREGROUND)) // When — B's stale gen-1 tick fires while state is RUNNING oldRunnableB.run() @@ -610,8 +632,7 @@ internal class DefaultTimeseriesCollectorTest { reader = reader, buffer = buffer, eventFactory = eventFactory, - dataWriter = mockDataWriter, - rumContext = fakeRumContext + dataWriter = mockDataWriter ) private fun captureScheduledRunnableForInterval(intervalMs: Long): Runnable { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt index 6e3a94ceae..dda9bbc05d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/PipelineTest.kt @@ -47,6 +47,11 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever import org.mockito.quality.Strictness +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.CountDownLatch +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicLong @Extensions( ExtendWith(MockitoExtension::class), @@ -116,7 +121,6 @@ internal class PipelineTest { buffer = buffer, eventFactory = mockEventFactory, dataWriter = mockDataWriter, - rumContext = fakeRumContext, insightsCollector = mockInsightsCollector ) } @@ -139,7 +143,7 @@ internal class PipelineTest { whenever(mockReader.read()) doReturn fakePoint // When - testedPipeline.execute() + testedPipeline.execute(fakeRumContext) // Then assertThat(buffer.drain()).containsExactly(fakePoint) @@ -151,12 +155,35 @@ internal class PipelineTest { whenever(mockReader.read()) doReturn null // When - testedPipeline.execute() + testedPipeline.execute(fakeRumContext) // Then assertThat(buffer.drain()).isEmpty() } + @Test + fun `M not hold the lock while reading W execute()`(forge: Forge) { + // Given — a concurrent flush() races the reader and must not wait for the read to finish + var flushedWhileReading = false + whenever(mockReader.read()) doAnswer { + val flushed = CountDownLatch(1) + Thread { + testedPipeline.flush(fakeRumContext) + flushed.countDown() + }.start() + flushedWhileReading = flushed.await(CONCURRENT_FLUSH_TIMEOUT_MS, TimeUnit.MILLISECONDS) + forge.getForgery>() + } + + // When + testedPipeline.execute(fakeRumContext) + + // Then + assertThat(flushedWhileReading) + .describedAs("flush() completed while reader.read() was still running") + .isTrue() + } + // endregion // region flush() @@ -170,10 +197,10 @@ internal class PipelineTest { whenever(mockReader.read()) doReturn fakePoint whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeJson whenever(mockEventFactory.eventName) doReturn fakeTimeseriesName - repeat(fakeBufferSize - 1) { testedPipeline.execute() } + repeat(fakeBufferSize - 1) { testedPipeline.execute(fakeRumContext) } // When - testedPipeline.execute() + testedPipeline.execute(fakeRumContext) // Then verify(mockDataWriter).write(mockEventBatchWriter, fakeJson, EventType.DEFAULT) @@ -187,7 +214,7 @@ internal class PipelineTest { whenever(mockReader.read()) doReturn forge.getForgery>() // When - repeat(fakeBufferSize - 1) { testedPipeline.execute() } + repeat(fakeBufferSize - 1) { testedPipeline.execute(fakeRumContext) } // Then verifyNoInteractions(mockDataWriter) @@ -199,10 +226,10 @@ internal class PipelineTest { // Given whenever(mockReader.read()) doReturn forge.getForgery>() whenever(mockEventFactory.create(any(), any(), any())) doReturn null - repeat(fakeBufferSize - 1) { testedPipeline.execute() } + repeat(fakeBufferSize - 1) { testedPipeline.execute(fakeRumContext) } // When - testedPipeline.execute() + testedPipeline.execute(fakeRumContext) // Then verifyNoInteractions(mockDataWriter) @@ -223,10 +250,10 @@ internal class PipelineTest { whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeJson whenever(mockEventFactory.eventName) doReturn fakeTimeseriesName val sampleCount = fakeBufferSize - 1 - repeat(sampleCount) { testedPipeline.execute() } + repeat(sampleCount) { testedPipeline.execute(fakeRumContext) } // When - testedPipeline.flush() + testedPipeline.flush(fakeRumContext) // Then val captor = argumentCaptor>>() @@ -244,10 +271,10 @@ internal class PipelineTest { whenever(mockRumFeatureScope.withWriteContext(any(), any())) doAnswer { Unit } whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeTimeseriesJson("view.cpu") whenever(mockReader.read()) doReturn fakePoint - testedPipeline.execute() + testedPipeline.execute(fakeRumContext) // When - testedPipeline.flush() + testedPipeline.flush(fakeRumContext) // Then assertThat(buffer.drain()).isEmpty() @@ -267,12 +294,12 @@ internal class PipelineTest { whenever(mockRumFeatureScope.withWriteContext(any(), any())) doAnswer { Unit } whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeTimeseriesJson("view.cpu") whenever(mockReader.read()) doReturn fakePoint - testedPipeline.execute() - testedPipeline.flush() + testedPipeline.execute(fakeRumContext) + testedPipeline.flush(fakeRumContext) whenever(mockReader.read()) doReturn fakeNextPoint // When - testedPipeline.execute() + testedPipeline.execute(fakeRumContext) verify(mockRumFeatureScope).withWriteContext(any(), callbackCaptor.capture()) callbackCaptor.firstValue.invoke(mockDatadogContext, mockEventWriteScope) @@ -288,10 +315,10 @@ internal class PipelineTest { // Given whenever(mockReader.read()) doReturn forge.getForgery>() whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeTimeseriesJson("view.cpu") - testedPipeline.execute() + testedPipeline.execute(fakeRumContext) // When - testedPipeline.flush() + testedPipeline.flush(fakeRumContext) // Then verify(mockEventFactory).create(eq(mockDatadogContext), eq(fakeRumContext), any()) @@ -302,10 +329,10 @@ internal class PipelineTest { // Given whenever(mockReader.read()) doReturn forge.getForgery>() whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeTimeseriesJson("view.cpu") - testedPipeline.execute() + testedPipeline.execute(fakeRumContext) // When - testedPipeline.flush() + testedPipeline.flush(fakeRumContext) // Then verify(mockRumFeatureScope).withWriteContext( @@ -317,7 +344,7 @@ internal class PipelineTest { @Test fun `M not write W flush() { buffer empty }`() { // When - testedPipeline.flush() + testedPipeline.flush(fakeRumContext) // Then verifyNoInteractions(mockDataWriter) @@ -330,10 +357,10 @@ internal class PipelineTest { val fakeThrowable = forge.aThrowable() whenever(mockReader.read()) doReturn forge.getForgery>() whenever(mockEventFactory.create(any(), any(), any())) doThrow fakeThrowable - testedPipeline.execute() + testedPipeline.execute(fakeRumContext) // When - testedPipeline.flush() + testedPipeline.flush(fakeRumContext) // Then mockInternalLogger.verifyLog( @@ -356,10 +383,10 @@ internal class PipelineTest { whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeJson whenever(mockDataWriter.write(any(), any(), any())) doThrow fakeThrowable whenever(mockEventWriteScope.invoke(any())) doAnswer { Unit } - testedPipeline.execute() + testedPipeline.execute(fakeRumContext) // When - testedPipeline.flush() + testedPipeline.flush(fakeRumContext) verify(mockEventWriteScope).invoke(writeBlockCaptor.capture()) writeBlockCaptor.firstValue.invoke(mockEventBatchWriter) @@ -380,10 +407,10 @@ internal class PipelineTest { whenever(mockReader.read()) doReturn forge.getForgery>() whenever(mockEventFactory.create(any(), any(), any())) doReturn fakeJson whenever(mockDataWriter.write(any(), any(), any())) doReturn false - testedPipeline.execute() + testedPipeline.execute(fakeRumContext) // When - testedPipeline.flush() + testedPipeline.flush(fakeRumContext) // Then verify(mockDataWriter).write(mockEventBatchWriter, fakeJson, EventType.DEFAULT) @@ -392,9 +419,124 @@ internal class PipelineTest { // endregion + // region concurrency + + @Test + fun `M not lose or duplicate points W execute() { concurrent samplers }`() { + // Given — every read() yields a unique point, every drained batch is recorded + val counter = AtomicLong() + val batches = CopyOnWriteArrayList>>() + stubUniqueReads(counter) + stubBatchRecording(batches) + + // When + runConcurrently { testedPipeline.execute(fakeRumContext) } + val batchesBeforeFlush = batches.toList() + testedPipeline.flush(fakeRumContext) + + // Then + assertThat(batchesBeforeFlush.map { it.size }) + .describedAs("batches drained by execute() are always exactly one buffer worth") + .containsOnly(fakeBufferSize) + assertThat(batches.flatten().map { it.timestampNs }.sorted()) + .isEqualTo((1L..counter.get()).toList()) + } + + @Test + fun `M not lose or duplicate points W execute() and flush() { concurrent }`() { + // Given + val counter = AtomicLong() + val batches = CopyOnWriteArrayList>>() + stubUniqueReads(counter) + stubBatchRecording(batches) + + // When — one thread keeps flushing while the others keep sampling + runConcurrently { threadIndex -> + if (threadIndex == 0) { + testedPipeline.flush(fakeRumContext) + } else { + testedPipeline.execute(fakeRumContext) + } + } + testedPipeline.flush(fakeRumContext) + + // Then + assertThat(batches.flatten().map { it.timestampNs }.sorted()) + .isEqualTo((1L..counter.get()).toList()) + } + + @Test + fun `M eventually capture point W flush() races execute() { flush between read() and add() }`( + forge: Forge + ) { + // Given — reader.read() blocks until released, forcing execute() to sit between + // its (unsynchronized) read() and the synchronized buffer add() + val readStarted = CountDownLatch(1) + val releaseRead = CountDownLatch(1) + val fakePoint = forge.getForgery>() + whenever(mockReader.read()) doAnswer { + readStarted.countDown() + releaseRead.await(CONCURRENT_FLUSH_TIMEOUT_MS, TimeUnit.MILLISECONDS) + fakePoint + } + val batches = CopyOnWriteArrayList>>() + stubBatchRecording(batches) + val executor = Executors.newSingleThreadExecutor() + + // When + executor.execute { testedPipeline.execute(fakeRumContext) } + readStarted.await(CONCURRENT_FLUSH_TIMEOUT_MS, TimeUnit.MILLISECONDS) + testedPipeline.flush(fakeRumContext) // races execute(): read() is in flight, not yet added + releaseRead.countDown() + executor.shutdown() + check(executor.awaitTermination(CONCURRENT_FLUSH_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + "execute() did not complete in time" + } + testedPipeline.flush(fakeRumContext) // point survives for the next flush + + // Then + assertThat(batches.flatten()).containsExactly(fakePoint) + } + + // endregion + + private fun stubUniqueReads(counter: AtomicLong) { + whenever(mockReader.read()) doAnswer { DataPoint(counter.incrementAndGet(), 0.0) } + } + + private fun stubBatchRecording(batches: MutableList>>) { + whenever(mockEventFactory.create(any(), any(), any())) doAnswer { invocation -> + batches.add(invocation.getArgument(2)) + fakeTimeseriesJson("view.cpu") + } + } + + private fun runConcurrently(task: (threadIndex: Int) -> Unit) { + val executor = Executors.newFixedThreadPool(CONCURRENT_THREADS) + val start = CountDownLatch(1) + val done = CountDownLatch(CONCURRENT_THREADS) + repeat(CONCURRENT_THREADS) { threadIndex -> + executor.execute { + start.await() + repeat(ITERATIONS_PER_THREAD) { task(threadIndex) } + done.countDown() + } + } + start.countDown() + val finished = done.await(CONCURRENT_FLUSH_TIMEOUT_MS, TimeUnit.MILLISECONDS) + executor.shutdownNow() + check(finished) { "concurrent tasks did not complete in time" } + } + // Pipeline forwards whatever the event factory returns without inspecting it, so the shape // only needs to be a distinct non-null object per name. private fun fakeTimeseriesJson(name: String): JsonObject = JsonObject().apply { addProperty("name", name) } + + private companion object { + const val CONCURRENT_FLUSH_TIMEOUT_MS = 2000L + const val CONCURRENT_THREADS = 4 + const val ITERATIONS_PER_THREAD = 200 + } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt index 91dbbd9017..52915d987c 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/csv/CsvCollector.kt @@ -16,7 +16,6 @@ import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.battery.BatteryInfo import com.datadog.android.rum.internal.domain.display.DisplayInfo import com.datadog.android.rum.internal.domain.event.RumEventSerializer -import com.datadog.android.rum.internal.domain.scope.RumViewType import com.datadog.android.rum.internal.timeseries.Buffer import com.datadog.android.rum.internal.timeseries.TimeseriesCollector import com.datadog.android.rum.internal.timeseries.factory.CpuEventFactory @@ -66,7 +65,7 @@ internal class CsvCollector( override fun onSessionStop() = Unit - override fun onViewTypeUpdate(newViewType: RumViewType) = Unit + override fun onRumContextUpdate(newRumContext: RumContext) = Unit companion object { From 2f61d4f756ff2d71153fdbcf214282a2b7d572ae Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Fri, 14 Aug 2026 16:58:07 +0100 Subject: [PATCH 28/32] RUM-17613: Stop collecting timeseries in background and flush the batch Sampling kept running while the app sat in background, and the buffered batch was only written on session stop, so points collected before a backgrounding could stay unsent for the rest of the session. The collector now suspends the sampling chain when the active view leaves the foreground and flushes the buffers at that point, attributing the batch to the last foreground context. The suspension is delayed by 200 ms to match ActivityViewTrackingStrategy.STOP_VIEW_DELAY_MS, so an Activity-to-Activity transition is not mistaken for a backgrounding. Sampling state carries a generation counter so a suspension pending on an older generation cannot stop a chain that has since been resumed. Drops TimeseriesConfiguration.collectInBackground: background suspension is now unconditional, so the flag no longer has a meaning. Ref: RUM-17613 --- detekt_custom_safe_calls_third_party.yml | 3 - .../timeseries/DefaultTimeseriesCollector.kt | 196 ++++--- .../DefaultTimeseriesCollectorFactory.kt | 1 - .../rum/timeseries/TimeseriesConfiguration.kt | 16 +- .../DefaultTimeseriesCollectorFactoryTest.kt | 23 - .../DefaultTimeseriesCollectorTest.kt | 543 ++++++++++++------ .../TimeseriesConfigurationTests.kt | 22 - 7 files changed, 493 insertions(+), 311 deletions(-) diff --git a/detekt_custom_safe_calls_third_party.yml b/detekt_custom_safe_calls_third_party.yml index ba90094994..c2c56b4737 100644 --- a/detekt_custom_safe_calls_third_party.yml +++ b/detekt_custom_safe_calls_third_party.yml @@ -259,7 +259,6 @@ datadog: - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.android.api.SdkCore?, com.datadog.android.api.SdkCore?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.android.flags.internal.repository.DefaultFlagsRepository.FlagsState?, com.datadog.android.flags.internal.repository.DefaultFlagsRepository.FlagsState?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI?, com.datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI?)" - - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.android.rum.internal.timeseries.DefaultTimeseriesCollector.State?, com.datadog.android.rum.internal.timeseries.DefaultTimeseriesCollector.State?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(com.datadog.android.trace.api.tracer.DatadogTracer?, com.datadog.android.trace.api.tracer.DatadogTracer?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(kotlin.String?, kotlin.String?)" - "java.util.concurrent.atomic.AtomicReference.compareAndSet(kotlin.collections.Set?, kotlin.collections.Set?)" @@ -272,14 +271,12 @@ datadog: - "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.flags.model.FlagsClientState?)" - "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.flags.model.ProviderContext?)" - "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.rum.internal.domain.RumContext?)" - - "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.rum.internal.timeseries.DefaultTimeseriesCollector.State?)" - "java.util.concurrent.atomic.AtomicReference.constructor(kotlin.collections.Map?)" - "java.util.concurrent.atomic.AtomicReference.constructor(kotlin.String?)" - "java.util.concurrent.atomic.AtomicReference.constructor(kotlin.collections.Set?)" - "java.util.concurrent.atomic.AtomicReference.get()" - "java.util.concurrent.atomic.AtomicReference.getAndSet(kotlin.String?)" - "java.util.concurrent.atomic.AtomicReference.getAndSet(java.util.concurrent.Future?)" - - "java.util.concurrent.atomic.AtomicReference.getAndSet(com.datadog.android.rum.internal.timeseries.DefaultTimeseriesCollector.State?)" - "java.util.concurrent.atomic.AtomicReference.set(android.app.Application.ActivityLifecycleCallbacks?)" - "java.util.concurrent.atomic.AtomicReference.set(com.datadog.android.api.FeatureEventReceiver?)" - "java.util.concurrent.atomic.AtomicReference.set(com.datadog.android.api.SdkCore?)" diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt index c6440857f1..e991999878 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt @@ -12,69 +12,37 @@ import com.datadog.android.core.internal.utils.scheduleSafe import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.scope.RumViewType import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.atomic.AtomicReference - -/** - * Per-session timeseries collector. - * - * Lifecycle invariants (enforced by [onSessionStart]/[onSessionStop] idempotency): - * - Each instance is single-use: exactly one [onSessionStart] followed by exactly one [onSessionStop]. - * - Multiple [onSessionStart] / [onSessionStop] calls are safe — duplicate calls are no-ops. - * - After [onSessionStop], the instance must not be restarted; create a new instance. - * - * Background sampling: - * - When [collectInBackground] is `false`, [onRumContextUpdate] pauses sampling on leaving foreground - * and resumes it on returning to foreground. - * - * Threading: - * - [onSessionStart] / [onSessionStop] / [onRumContextUpdate] are called from the RUM event-handler thread. - * - Sampling tasks run on a [scheduledExecutorService] shared with other RUM components - * (owned by the SDK core); the instance neither owns nor shuts it down. - * - [Pipeline] is responsible for its own thread safety via internal synchronization. - * - Duplicate-chain prevention: each sampling chain carries a generation number. - * When [startSampling] starts a new generation, any in-flight or queued ticks from the - * previous generation self-terminate on their first check. - */ + internal class DefaultTimeseriesCollector( private val internalLogger: InternalLogger, internal val pipelines: List>, - private val collectInBackground: Boolean, internal val scheduledExecutorService: ScheduledExecutorService, - // Read by the sampling ticks so every batch is attributed to the view that was active when it - // was drained, not to the one active when the collector was created. @Volatile private var rumContext: RumContext ) : TimeseriesCollector { + private val state = State() - private enum class State { IDLE, RUNNING, SUSPENDED, STOPPED } - private val state = AtomicReference(State.IDLE) + // Batches are attributed to the view they are sent from, but a flush can happen once the app + // already left the foreground, where there is no view to attribute to. Keep the last foreground + // context so those batches stay attached to the view they were sent from. + @Volatile + private var lastForegroundRumContext: RumContext = rumContext - // Incremented on every start/resume. Ticks carrying a stale generation self-terminate. - private val currentGeneration = AtomicInteger(0) + @Volatile + private var recentSuspension: ScheduledFuture<*>? = null + // region PUBLIC API @WorkerThread override fun onSessionStart() { - if (state.compareAndSet(State.IDLE, State.RUNNING)) { - startSampling() - } + state.set(isActive = rumContext.viewType.isForeground)?.let { generation -> scheduleSampling(generation) } } @WorkerThread override fun onSessionStop() { - if (state.getAndSet(State.STOPPED) != State.STOPPED) { - pipelines.forEach { pipeline -> - try { - pipeline.flush(rumContext) - } catch (@Suppress("TooGenericExceptionCaught") t: Throwable) { - internalLogger.log( - level = InternalLogger.Level.ERROR, - targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), - messageBuilder = { ERROR_FLUSH_FAILED }, - throwable = t - ) - } - } + cancelSuspension() + if (state.set(false) != null) { + flushPipelines(lastForegroundRumContext) } } @@ -82,34 +50,45 @@ internal class DefaultTimeseriesCollector( override fun onRumContextUpdate(newRumContext: RumContext) { val oldViewType = rumContext.viewType val newViewType = newRumContext.viewType + val isEnterForeground = !oldViewType.isForeground && newViewType.isForeground + val isLeaveForeground = oldViewType.isForeground && !newViewType.isForeground rumContext = newRumContext + if (newViewType.isForeground) lastForegroundRumContext = newRumContext - if (!collectInBackground) { - if (isEnterForeground && state.compareAndSet(State.SUSPENDED, State.RUNNING)) { - startSampling() - } else if (!newViewType.isForeground) { - state.compareAndSet(State.RUNNING, State.SUSPENDED) - } + if (isLeaveForeground) { + scheduleStop(state.currentGeneration) + } else if (isEnterForeground) { + scheduleSampling(generation = state.startGeneration()) } } - private fun startSampling() { - val generation = currentGeneration.incrementAndGet() - pipelines.forEach { schedulePipeline(it, generation) } + // endregion + + //region SAMPLING + + private fun scheduleSampling(generation: Int) { + cancelSuspension() + pipelines.forEach { pipeline -> scheduledExecutorService.schedulePipeline(pipeline, generation) } } - private fun schedulePipeline(pipeline: Pipeline<*>, generation: Int) { - scheduledExecutorService.scheduleSafe( - OPERATION_NAME, + private fun ScheduledExecutorService.schedulePipeline( + pipeline: Pipeline<*>, + generation: Int + ) { + scheduleSafe( + TIMESERIES_OPERATION_NAME, pipeline.intervalMs, TimeUnit.MILLISECONDS, internalLogger ) { - if (!isActive(generation)) return@scheduleSafe + if (!state.isGenerationActive(generation)) return@scheduleSafe try { - pipeline.execute(rumContext) + val currentRumContext = rumContext + if (currentRumContext.viewType.isForeground) { + pipeline.execute(currentRumContext) + } } catch (@Suppress("TooGenericExceptionCaught") t: Throwable) { internalLogger.log( level = InternalLogger.Level.ERROR, @@ -118,18 +97,62 @@ internal class DefaultTimeseriesCollector( throwable = t ) } finally { - if (isActive(generation)) schedulePipeline(pipeline, generation) + if (state.isGenerationActive(generation)) schedulePipeline(pipeline, generation) + } + } + } + + @WorkerThread + private fun flushPipelines(rumContext: RumContext) { + pipelines.forEach { pipeline -> + try { + pipeline.flush(rumContext) + } catch (@Suppress("TooGenericExceptionCaught") t: Throwable) { + internalLogger.log( + level = InternalLogger.Level.ERROR, + targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), + messageBuilder = { ERROR_FLUSH_FAILED }, + throwable = t + ) } } } - private fun isActive(generation: Int): Boolean = - state.get() == State.RUNNING && currentGeneration.get() == generation + // endregion + + // region SUSPENSION + + private fun scheduleStop(stopRequestGeneration: Int) { + recentSuspension = scheduledExecutorService.scheduleSafe( + SUSPEND_OPERATION_NAME, + SUSPEND_DELAY_MS, + TimeUnit.MILLISECONDS, + internalLogger + ) { + if (!rumContext.viewType.isForeground && state.stopGeneration(stopRequestGeneration)) { + flushPipelines(lastForegroundRumContext) + } + } + } + + private fun cancelSuspension() { + recentSuspension?.cancel(false) + recentSuspension = null + } + + // endregion internal companion object { - const val OPERATION_NAME = "Timeseries sampling" + const val TIMESERIES_OPERATION_NAME = "Timeseries sampling" + const val SUSPEND_OPERATION_NAME = "Timeseries suspend" const val ERROR_SAMPLING_FAILED = "Timeseries sampling iteration failed; rescheduling next sample." - const val ERROR_FLUSH_FAILED = "Timeseries flush on session stop failed." + const val ERROR_FLUSH_FAILED = "Timeseries flush failed." + + // Matches ActivityViewTrackingStrategy.STOP_VIEW_DELAY_MS, which guards the same race: + // an Activity-to-Activity transition leaves no active view for a moment when the tracking + // strategy stops the view on pause rather than on stop. + const val SUSPEND_DELAY_MS = 200L + val RumViewType?.isForeground: Boolean get() = when (this) { RumViewType.FOREGROUND, RumViewType.APPLICATION_LAUNCH -> true @@ -137,5 +160,46 @@ internal class DefaultTimeseriesCollector( RumViewType.NONE -> false null -> false } + + private class State { + // Written under the monitor only, but read outside of it, hence @Volatile: a stale + // generation would make the guards below reject a live sampling chain or flush. + @Volatile + var currentGeneration: Int = 0 + private set + + private var active: Boolean = false + + fun isGenerationActive(generation: Int): Boolean = synchronized(this) { + currentGeneration == generation && active + } + + /** + * Always starts a fresh generation, so that a suspension pending on the previous one + * can no longer stop the sampling chain. Returns the new generation. + */ + fun startGeneration(): Int = synchronized(this) { + active = true + ++currentGeneration + } + + /** + * Deactivates [generation] if it is still the current one. + * Returns true when this call is the one that deactivated it. + */ + fun stopGeneration(generation: Int): Boolean = synchronized(this) { + currentGeneration == generation && set(false) != null + } + + /** Returns the new generation if this call changed the state, null otherwise. */ + fun set(isActive: Boolean): Int? = synchronized(this) { + if (isActive == active) { + null + } else { + active = isActive + ++currentGeneration + } + } + } } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt index 38c3430e2a..f474627d2c 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactory.kt @@ -62,7 +62,6 @@ internal class DefaultTimeseriesCollectorFactory( return DefaultTimeseriesCollector( internalLogger = sdkCore.internalLogger, - collectInBackground = configuration.collectInBackground, scheduledExecutorService = scheduledExecutorService, rumContext = rumContext, pipelines = pipelines diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt index 4dcfc83d99..26380cedd8 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfiguration.kt @@ -13,8 +13,7 @@ import com.datadog.android.rum.ExperimentalRumApi * Use [Builder] to create an instance. */ class TimeseriesConfiguration internal constructor( - internal val enabledTypes: Set, - internal val collectInBackground: Boolean + internal val enabledTypes: Set ) { /** @@ -25,8 +24,6 @@ class TimeseriesConfiguration internal constructor( private var enabledTypes: Set = TimeseriesType.values().toSet() - private var collectInBackground: Boolean = false - /** * Restricts collection to the provided timeseries types. * @@ -39,18 +36,9 @@ class TimeseriesConfiguration internal constructor( enabledTypes = types.toSet() } - /** - * Sets whether to keep sampling timeseries when the app is in background. - * Defaults to `false`. - */ - internal fun collectInBackground(collectInBackground: Boolean): Builder = apply { - this.collectInBackground = collectInBackground - } - /** Builds a [TimeseriesConfiguration] from the current builder state. */ fun build(): TimeseriesConfiguration = TimeseriesConfiguration( - enabledTypes = enabledTypes, - collectInBackground = collectInBackground + enabledTypes = enabledTypes ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt index b120d6f420..3c1cc4e9e5 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorFactoryTest.kt @@ -34,8 +34,6 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings @@ -238,27 +236,6 @@ internal class DefaultTimeseriesCollectorFactoryTest { ).isSameAs(fakeRumContext) } - @ParameterizedTest - @ValueSource(booleans = [true, false]) - fun `M propagate collectInBackground to the collector W create()`( - fakeCollectInBackground: Boolean - ) { - // Given - val fakeConfiguration = TimeseriesConfiguration.Builder() - .collectInBackground(fakeCollectInBackground) - .build() - val testedFactory = createFactory(totalRamBytes = 1L, configuration = fakeConfiguration) - - // When - val timeseries = testedFactory.create(fakeSessionType, fakeRumContext) - - // Then - check(timeseries is DefaultTimeseriesCollector) - assertThat( - timeseries.getFieldValue("collectInBackground") - ).isEqualTo(fakeCollectInBackground) - } - @Test fun `M propagate session type and info providers to both factories W create()`( @LongForgery(min = 1L) fakeTotalRamBytes: Long diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt index c7191613c1..c38b5ca762 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt @@ -32,11 +32,14 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.doThrow @@ -45,6 +48,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.util.UUID @@ -80,9 +84,15 @@ internal class DefaultTimeseriesCollectorTest { @Mock lateinit var mockEventBatchWriter: EventBatchWriter + @Mock + lateinit var mockDatadogContext: DatadogContext + @Mock lateinit var mockExecutor: ScheduledExecutorService + @Mock + lateinit var mockScheduledFuture: ScheduledFuture<*> + @Mock lateinit var mockReaderA: DataPointsReader @@ -90,13 +100,10 @@ internal class DefaultTimeseriesCollectorTest { lateinit var mockReaderB: DataPointsReader @Mock - lateinit var mockEventFactoryA: EventFactory + lateinit var mockEventFactoryA: EventFactory @Mock - lateinit var mockEventFactoryB: EventFactory - - @Forgery - lateinit var fakeRumContext: RumContext + lateinit var mockEventFactoryB: EventFactory private lateinit var bufferA: Buffer private lateinit var bufferB: Buffer @@ -104,7 +111,7 @@ internal class DefaultTimeseriesCollectorTest { private lateinit var pipelineA: Pipeline private lateinit var pipelineB: Pipeline - @LongForgery(min = 1L, max = 60_000L) + @LongForgery(min = 1_000L, max = 60_000L) var fakeIntervalAMs: Long = 0L @LongForgery(min = 60_001L, max = 120_000L) @@ -113,46 +120,63 @@ internal class DefaultTimeseriesCollectorTest { @IntForgery(min = 2, max = 16) var fakeBufferSize: Int = 0 + @Forgery + lateinit var fakeRumContext: RumContext + private fun fakeRumContextOf(viewType: RumViewType) = fakeRumContext.copy(viewType = viewType) private fun createTimeseries( initialViewType: RumViewType, - collectInBackground: Boolean = false, pipelines: List> = listOf(pipelineA, pipelineB) ) = DefaultTimeseriesCollector( pipelines = pipelines, internalLogger = mockInternalLogger, - collectInBackground = collectInBackground, scheduledExecutorService = mockExecutor, rumContext = fakeRumContextOf(initialViewType) ) + // Single foreground pipeline wired to reader/event factory A, with a buffer the test controls. + private fun createTimeseriesWithBuffer(buffer: Buffer) = createTimeseries( + RumViewType.FOREGROUND, + listOf(Pipeline(mockSdkCore, mockReaderA, buffer, mockEventFactoryA, mockDataWriter)) + ) + @BeforeEach fun `set up`() { whenever(mockReaderA.intervalMs) doReturn fakeIntervalAMs whenever(mockReaderB.intervalMs) doReturn fakeIntervalBMs - whenever(mockExecutor.schedule(any(), any(), any())) doReturn mock>() + whenever(mockExecutor.schedule(any(), any(), any())) doReturn mockScheduledFuture whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope whenever(mockRumFeatureScope.withWriteContext(any(), any())) doAnswer { inv -> inv.getArgument<(DatadogContext, EventWriteScope) -> Unit>(inv.arguments.lastIndex) - .invoke(mock(), mockEventWriteScope) + .invoke(mockDatadogContext, mockEventWriteScope) } whenever(mockEventWriteScope.invoke(any())) doAnswer { inv -> inv.getArgument<(EventBatchWriter) -> Unit>(0).invoke(mockEventBatchWriter) } + whenever(mockEventFactoryA.eventName) doReturn "" + whenever(mockEventFactoryB.eventName) doReturn "" bufferA = Buffer(fakeBufferSize) bufferB = Buffer(fakeBufferSize) - pipelineA = createPipeline(mockReaderA, bufferA, mockEventFactoryA) - pipelineB = createPipeline(mockReaderB, bufferB, mockEventFactoryB) + pipelineA = Pipeline(mockSdkCore, mockReaderA, bufferA, mockEventFactoryA, mockDataWriter) + pipelineB = Pipeline(mockSdkCore, mockReaderB, bufferB, mockEventFactoryB, mockDataWriter) - // Seed FOREGROUND so sampling tests don't suspend on first tick testedTimeseriesCollector = createTimeseries(RumViewType.FOREGROUND) } - @Test - fun `M schedule one runnable per pipeline W onSessionStart()`() { + @ParameterizedTest + @EnumSource( + value = RumViewType::class, + names = ["FOREGROUND", "APPLICATION_LAUNCH"] + ) + fun `M schedule one runnable per pipeline W onSessionStart() { initial view is foreground }`( + initialViewType: RumViewType + ) { + // Given + testedTimeseriesCollector = createTimeseries(initialViewType) + // When testedTimeseriesCollector.onSessionStart() @@ -161,13 +185,55 @@ internal class DefaultTimeseriesCollectorTest { verify(mockExecutor).schedule(any(), eq(fakeIntervalBMs), eq(TimeUnit.MILLISECONDS)) } + @ParameterizedTest + @EnumSource( + value = RumViewType::class, + names = ["NONE", "BACKGROUND"] + ) + fun `M not schedule W onSessionStart() { initial view is not foreground }`( + initialViewType: RumViewType + ) { + // Given + testedTimeseriesCollector = createTimeseries(initialViewType) + + // When + testedTimeseriesCollector.onSessionStart() + + // Then + verify(mockExecutor, never()).schedule(any(), any(), any()) + } + @Test - fun `M not write events W onSessionStop() { never started }`() { + fun `M not schedule W onRumContextUpdate() { none to background }`() { + // Given + testedTimeseriesCollector = createTimeseries(RumViewType.NONE) + testedTimeseriesCollector.onSessionStart() + // When - testedTimeseriesCollector.onSessionStop() + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) + + // Then — neither a sampling chain nor a suspend: the app never left the foreground + verify(mockExecutor, never()).schedule(any(), any(), any()) + } + + @ParameterizedTest + @EnumSource( + value = RumViewType::class, + names = ["FOREGROUND", "APPLICATION_LAUNCH"] + ) + fun `M resume scheduling W onRumContextUpdate() { none to foreground }`( + foregroundViewType: RumViewType + ) { + // Given + testedTimeseriesCollector = createTimeseries(RumViewType.NONE) + testedTimeseriesCollector.onSessionStart() + + // When + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(foregroundViewType)) // Then - verify(mockDataWriter, never()).write(any(), any(), any()) + verify(mockExecutor).schedule(any(), eq(fakeIntervalAMs), eq(TimeUnit.MILLISECONDS)) + verify(mockExecutor).schedule(any(), eq(fakeIntervalBMs), eq(TimeUnit.MILLISECONDS)) } @Test @@ -184,7 +250,7 @@ internal class DefaultTimeseriesCollectorTest { } @Test - fun `M not write events W onSessionStart()+onSessionStop() { buffers empty }`() { + fun `M not write events W onSessionStop() { nothing sampled }`() { // Given testedTimeseriesCollector.onSessionStart() @@ -196,35 +262,36 @@ internal class DefaultTimeseriesCollectorTest { } @Test - fun `M log error and continue W onSessionStop() { event factory throws on pipelineA }`(forge: Forge) { - // Given - val fakeError = RuntimeException("serializer failure") + fun `M log error and flush remaining pipelines W onSessionStop() { pipeline flush throws }`(forge: Forge) { + // Given — drain() throws outside Pipeline's own try/catch, so the failure surfaces in + // DefaultTimeseriesCollector.flushPipelines() and must not skip the remaining pipelines. + val fakeError = RuntimeException("drain failure") val fakeJson = JsonObject().apply { addProperty("k", "v") } - whenever(mockReaderA.read()) doReturn forge.getForgery>() + val mockBuffer = mock>() + whenever(mockBuffer.drain()) doThrow fakeError whenever(mockReaderB.read()) doReturn forge.getForgery>() - whenever(mockEventFactoryA.create(any(), any(), any())) doThrow fakeError whenever(mockEventFactoryB.create(any(), any(), any())) doReturn fakeJson + val failingPipeline = + Pipeline(mockSdkCore, mockReaderA, mockBuffer, mockEventFactoryA, mockDataWriter) + testedTimeseriesCollector = createTimeseries(RumViewType.FOREGROUND, listOf(failingPipeline, pipelineB)) testedTimeseriesCollector.onSessionStart() - val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) - val runnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) - runnableA.run() - runnableB.run() + captureScheduledRunnableForInterval(fakeIntervalBMs).run() // buffers one sample for pipeline B // When testedTimeseriesCollector.onSessionStop() - // Then — error logged for pipelineA, pipelineB still flushed + // Then mockInternalLogger.verifyLog( InternalLogger.Level.ERROR, targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), - "Timeseries event creation failed", + DefaultTimeseriesCollector.ERROR_FLUSH_FAILED, fakeError ) verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) } @Test - fun `M flush partial buffer W onSessionStop() { buffer below batch size }`(forge: Forge) { + fun `M flush partial buffer W onSessionStop() { buffer below capacity }`(forge: Forge) { // Given val fakeJson = JsonObject().apply { addProperty("k", "v") } whenever(mockReaderA.read()) doReturn forge.getForgery>() @@ -237,18 +304,18 @@ internal class DefaultTimeseriesCollectorTest { testedTimeseriesCollector.onSessionStop() // Then - verify(mockEventFactoryA).create(any(), eq(fakeRumContextOf(RumViewType.FOREGROUND)), any()) + verify(mockEventFactoryA).create(any(), any(), any()) verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) } @Test - fun `M flush batch W sample tick fills buffer { single pipeline }`(forge: Forge) { + fun `M write batch W sample tick { buffer reaches capacity }`(forge: Forge) { // Given - val element = forge.getForgery>() - val dataPoints = List(fakeBufferSize) { element } + val fakePoint = forge.getForgery>() + val fakeDataPoints = List(fakeBufferSize) { fakePoint } val fakeJson = JsonObject().apply { addProperty("a", "1") } - whenever(mockReaderA.read()) doReturn element - whenever(mockEventFactoryA.create(any(), any(), eq(dataPoints))) doReturn fakeJson + whenever(mockReaderA.read()) doReturn fakePoint + whenever(mockEventFactoryA.create(any(), any(), eq(fakeDataPoints))) doReturn fakeJson testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) @@ -258,33 +325,13 @@ internal class DefaultTimeseriesCollectorTest { // Then verify(mockEventFactoryA).create( - any(), + eq(mockDatadogContext), eq(fakeRumContextOf(RumViewType.FOREGROUND)), - eq(dataPoints) + eq(fakeDataPoints) ) verify(mockDataWriter).write(any(), eq(fakeJson), eq(EventType.DEFAULT)) } - @Test - fun `M use updated context W sample tick fills buffer { foreground view changed }`(forge: Forge) { - // Given - val fakePoint = forge.getForgery>() - val fakeNextContext = fakeRumContextOf(RumViewType.FOREGROUND).copy( - viewId = forge.getForgery().toString() - ) - whenever(mockReaderA.read()) doReturn fakePoint - whenever(mockEventFactoryA.create(any(), eq(fakeNextContext), any())) doReturn JsonObject() - testedTimeseriesCollector.onSessionStart() - testedTimeseriesCollector.onRumContextUpdate(fakeNextContext) - val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) - - // When - repeat(fakeBufferSize) { runnableA.run() } - - // Then - verify(mockEventFactoryA).create(any(), eq(fakeNextContext), eq(List(fakeBufferSize) { fakePoint })) - } - @Test fun `M reschedule sample W sample tick runs`(forge: Forge) { // Given @@ -308,29 +355,13 @@ internal class DefaultTimeseriesCollectorTest { val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When - runnableA.run() + repeat(fakeBufferSize - 1) { runnableA.run() } // Then verify(mockEventFactoryA, never()).create(any(), any(), any()) verify(mockDataWriter, never()).write(any(), any(), any()) } - @Test - fun `M skip writer W sample tick { event factory returns null }`(forge: Forge) { - // Given - fill the buffer so Pipeline.drain() reaches the serializer; serializer returns null - whenever(mockReaderA.read()) doReturn forge.getForgery>() - whenever(mockEventFactoryA.create(any(), any(), any())) doReturn null - testedTimeseriesCollector.onSessionStart() - val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) - - // When - repeat(fakeBufferSize) { runnableA.run() } - - // Then - verify(mockEventFactoryA).create(any(), any(), any()) - verify(mockDataWriter, never()).write(any(), any(), any()) - } - @Test fun `M not start twice W onSessionStart() { called twice }`() { // When @@ -351,39 +382,22 @@ internal class DefaultTimeseriesCollectorTest { } @Test - fun `M not throw W onSessionStop() { called twice }`() { + fun `M flush only once W onSessionStop() { called twice }`() { // Given + val mockBuffer = mock>() + testedTimeseriesCollector = createTimeseriesWithBuffer(mockBuffer) testedTimeseriesCollector.onSessionStart() - // When + Then — must not throw - testedTimeseriesCollector.onSessionStop() + // When testedTimeseriesCollector.onSessionStop() - } - - @Test - fun `M not start after stop W onSessionStart() { called after onSessionStop() }`() { - // Given - testedTimeseriesCollector.onSessionStart() testedTimeseriesCollector.onSessionStop() - // When - testedTimeseriesCollector.onSessionStart() - - // Then — no additional schedules beyond the original start - verify(mockExecutor).schedule( - any(), - eq(fakeIntervalAMs), - eq(TimeUnit.MILLISECONDS) - ) - verify(mockExecutor).schedule( - any(), - eq(fakeIntervalBMs), - eq(TimeUnit.MILLISECONDS) - ) + // Then — the second call sees the state already STOPPED and never reaches the pipelines + verify(mockBuffer).drain() } @Test - fun `M continue scheduling W sample tick { reader throws }`() { + fun `M log error and reschedule W sample tick { reader throws }`() { // Given val fakeError = RuntimeException("reader failure") whenever(mockReaderA.read()) doThrow fakeError @@ -393,47 +407,28 @@ internal class DefaultTimeseriesCollectorTest { // When runnableA.run() - // Then — 1 from start() + 1 rescheduled via finally after exception - verify(mockExecutor, times(2)) - .schedule( - any(), - eq(fakeIntervalAMs), - eq(TimeUnit.MILLISECONDS) - ) - } - - @Test - fun `M log error W sample tick { reader throws }`() { - // Given - val fakeError = RuntimeException("reader failure") - whenever(mockReaderA.read()) doThrow fakeError - testedTimeseriesCollector.onSessionStart() - val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) - - // When - runnableA.run() - - // Then + // Then — 1 schedule from start() + 1 rescheduled via finally after the exception mockInternalLogger.verifyLog( InternalLogger.Level.ERROR, targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), DefaultTimeseriesCollector.ERROR_SAMPLING_FAILED, fakeError ) + verify(mockExecutor, times(2)) + .schedule(any(), eq(fakeIntervalAMs), eq(TimeUnit.MILLISECONDS)) } @Test fun `M log error and reschedule W sample tick { buffer drain throws }`(forge: Forge) { // Given — drain() throws outside Pipeline's own try/catch, so it must be - // caught by DefaultTimeseriesCollector' sampling try/catch instead. + // caught by DefaultTimeseriesCollector's sampling try/catch instead. val fakeError = RuntimeException("drain failure") val mockBuffer = mock>() whenever(mockBuffer.isFull()) doReturn true whenever(mockBuffer.drain()) doThrow fakeError whenever(mockReaderA.read()) doReturn forge.getForgery>() - val pipeline = createPipeline(mockReaderA, mockBuffer, mockEventFactoryA) - val timeseries = createTimeseries(RumViewType.FOREGROUND, pipelines = listOf(pipeline)) - timeseries.onSessionStart() + testedTimeseriesCollector = createTimeseriesWithBuffer(mockBuffer) + testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When @@ -452,9 +447,9 @@ internal class DefaultTimeseriesCollectorTest { @Test fun `M log error and reschedule W sample tick { event factory throws }`(forge: Forge) { - // Given — serializer throws inside Pipeline's own try/catch: Pipeline logs it itself + // Given — event factory throws inside Pipeline's own try/catch: Pipeline logs it itself // and the tick completes normally, so the sampling chain reschedules as usual. - val fakeError = RuntimeException("serializer failure") + val fakeError = RuntimeException("event factory failure") whenever(mockReaderA.read()) doReturn forge.getForgery>() whenever(mockEventFactoryA.create(any(), any(), any())) doThrow fakeError testedTimeseriesCollector.onSessionStart() @@ -464,8 +459,8 @@ internal class DefaultTimeseriesCollectorTest { repeat(fakeBufferSize) { runnableA.run() } // Then - // times(1) default on verifyLog also confirms this is the only ERROR log with that - // throwable — i.e. DefaultTimeseriesCollector' own sampling catch never fired here. + // verifyLog matches on `same(fakeError)`, so its default times(1) also proves the + // collector's own sampling catch never logged the same throwable a second time. mockInternalLogger.verifyLog( InternalLogger.Level.ERROR, targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), @@ -502,8 +497,9 @@ internal class DefaultTimeseriesCollectorTest { } @Test - fun `M not reschedule W stopped { sample tick skips try block }`() { + fun `M not sample nor reschedule W sample tick { session stopped }`(forge: Forge) { // Given + whenever(mockReaderA.read()) doReturn forge.getForgery>() testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) testedTimeseriesCollector.onSessionStop() @@ -511,7 +507,8 @@ internal class DefaultTimeseriesCollectorTest { // When runnableA.run() - // Then — no additional schedule after the one from start() + // Then — sample skipped AND no additional schedule after the one from start() + verify(mockReaderA, never()).read() verify(mockExecutor).schedule( any(), eq(fakeIntervalAMs), @@ -519,12 +516,58 @@ internal class DefaultTimeseriesCollectorTest { ) } + @ParameterizedTest + @EnumSource( + value = RumViewType::class, + names = ["FOREGROUND", "APPLICATION_LAUNCH"] + ) + fun `M neither flush nor schedule suspend W onRumContextUpdate() { stays foreground }`( + nextViewType: RumViewType, + forge: Forge + ) { + // Given + val fakeNextContext = fakeRumContextOf(nextViewType).copy(viewId = forge.getForgery().toString()) + whenever(mockReaderA.read()) doReturn forge.getForgery>() + testedTimeseriesCollector.onSessionStart() + captureScheduledRunnableForInterval(fakeIntervalAMs).run() // buffers one sample for pipeline A + + // When + testedTimeseriesCollector.onRumContextUpdate(fakeNextContext) + + // Then — no suspend is even scheduled, so nothing can flush later either + verify(mockExecutor, never()) + .schedule(any(), eq(DefaultTimeseriesCollector.SUSPEND_DELAY_MS), eq(TimeUnit.MILLISECONDS)) + verifyNoInteractions(mockEventFactoryA) + verifyNoInteractions(mockDataWriter) + } + + @Test + fun `M use updated context W sample tick fills buffer { foreground view changed }`(forge: Forge) { + // Given + val fakePoint = forge.getForgery>() + val fakeNextContext = fakeRumContextOf(RumViewType.FOREGROUND).copy( + viewId = forge.getForgery().toString() + ) + whenever(mockReaderA.read()) doReturn fakePoint + whenever(mockEventFactoryA.create(any(), eq(fakeNextContext), any())) doReturn JsonObject() + testedTimeseriesCollector.onSessionStart() + testedTimeseriesCollector.onRumContextUpdate(fakeNextContext) + val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + + // When + repeat(fakeBufferSize) { runnableA.run() } + + // Then + verify(mockEventFactoryA).create(any(), eq(fakeNextContext), eq(List(fakeBufferSize) { fakePoint })) + } + @Test - fun `M suspend chain W sample tick { background, collectInBackground = false }`(forge: Forge) { + fun `M suspend chain W sample tick { suspend fired after background }`(forge: Forge) { // Given whenever(mockReaderA.read()) doReturn forge.getForgery>() testedTimeseriesCollector.onSessionStart() testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) + runScheduledSuspend() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) // When @@ -539,105 +582,241 @@ internal class DefaultTimeseriesCollectorTest { ) } + @ParameterizedTest + @EnumSource( + value = RumViewType::class, + names = ["NONE", "BACKGROUND"] + ) + fun `M flush partial buffer with last foreground context W pending suspend fires { left foreground }`( + nextViewType: RumViewType, + forge: Forge + ) { + // Given + val fakeSample = forge.getForgery>() + val fakeJson = JsonObject().apply { addProperty("k", "v") } + whenever(mockReaderA.read()) doReturn fakeSample + whenever( + mockEventFactoryA.create( + eq(mockDatadogContext), + eq(fakeRumContextOf(RumViewType.FOREGROUND)), + eq(listOf(fakeSample)) + ) + ) doReturn fakeJson + testedTimeseriesCollector.onSessionStart() + captureScheduledRunnableForInterval(fakeIntervalAMs).run() // buffers one sample for pipeline A + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(nextViewType)) + + // When + runScheduledSuspend() + + // Then — the batch is stamped with the view the user was looking at, not the new one + verify(mockEventFactoryA).create( + eq(mockDatadogContext), + eq(fakeRumContextOf(RumViewType.FOREGROUND)), + eq(listOf(fakeSample)) + ) + verify(mockDataWriter).write(mockEventBatchWriter, fakeJson, EventType.DEFAULT) + verify(mockEventFactoryB, never()).create(any(), any(), any()) + } + + @ParameterizedTest + @EnumSource( + value = RumViewType::class, + names = ["NONE", "BACKGROUND"] + ) + fun `M flush with last foreground context W onSessionStop() { left foreground, suspend pending }`( + nextViewType: RumViewType, + forge: Forge + ) { + // Given + val fakeSample = forge.getForgery>() + whenever(mockReaderA.read()) doReturn fakeSample + whenever(mockEventFactoryA.create(any(), any(), any())) doReturn JsonObject() + testedTimeseriesCollector.onSessionStart() + captureScheduledRunnableForInterval(fakeIntervalAMs).run() // buffers one sample for pipeline A + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(nextViewType)) + + // When — the session is stopped before the pending suspend had a chance to flush + testedTimeseriesCollector.onSessionStop() + + // Then — same attribution as the suspend path, not the background context + verify(mockEventFactoryA).create( + eq(mockDatadogContext), + eq(fakeRumContextOf(RumViewType.FOREGROUND)), + eq(listOf(fakeSample)) + ) + } + @Test - fun `M sample W sample tick { background, collectInBackground = true }`(forge: Forge) { + fun `M not flush W onRumContextUpdate() { suspend not fired yet }`(forge: Forge) { // Given whenever(mockReaderA.read()) doReturn forge.getForgery>() - val bgTimeseries = createTimeseries(RumViewType.FOREGROUND, collectInBackground = true) - bgTimeseries.onSessionStart() - bgTimeseries.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) + testedTimeseriesCollector.onSessionStart() + captureScheduledRunnableForInterval(fakeIntervalAMs).run() // buffers one sample for pipeline A + + // When — leaving the foreground only schedules the suspend, it does not flush inline + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) + + // Then + verify(mockExecutor) + .schedule(any(), eq(DefaultTimeseriesCollector.SUSPEND_DELAY_MS), eq(TimeUnit.MILLISECONDS)) + verifyNoInteractions(mockEventFactoryA) + verifyNoInteractions(mockDataWriter) + } + + @ParameterizedTest + @EnumSource( + value = RumViewType::class, + names = ["NONE", "BACKGROUND"] + ) + fun `M skip sample and reschedule W sample tick { foreground exit is pending }`( + nextViewType: RumViewType + ) { + // Given + testedTimeseriesCollector.onSessionStart() val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(nextViewType)) // When runnableA.run() - // Then — sample taken even in background - verify(mockReaderA).read() + // Then + verify(mockReaderA, never()).read() + verify(mockExecutor, times(2)) + .schedule(any(), eq(fakeIntervalAMs), eq(TimeUnit.MILLISECONDS)) } @Test - fun `M resume scheduling W onRumContextUpdate() { background → foreground }`() { + fun `M keep sampling chain W stale suspend fires { foreground re-entered then left }`() { // Given testedTimeseriesCollector.onSessionStart() - // Transition to background: suspends chains + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.NONE)) + val staleSuspend = captureScheduledSuspendRunnable() + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.FOREGROUND)) + val currentRunnableA = captureLastScheduledRunnableForInterval(fakeIntervalAMs) testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) - val runnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) - runnableA.run() // State is already SUSPENDED; tick exits without reschedule - val schedulesAfterSuspend = 1 // only the one from start() - // When — app returns to foreground - testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.FOREGROUND)) + // When + staleSuspend.run() + currentRunnableA.run() - // Then — scheduling resumed (one new schedule per pipeline) - verify(mockExecutor, times(schedulesAfterSuspend + 1)) + // Then + verify(mockDataWriter, never()).write(any(), any(), any()) + verify(mockReaderA, never()).read() + verify(mockExecutor, times(3)) .schedule(any(), eq(fakeIntervalAMs), eq(TimeUnit.MILLISECONDS)) } @Test - fun `M not sample W stale tick fires after resume { old generation tick self-terminates }`(forge: Forge) { - // Regression: when pipeline B's tick (long interval) is still pending at the moment - // resumeSampling() fires, it must self-terminate rather than duplicate the chain. - // + fun `M cancel pending suspend W onRumContextUpdate() { foreground re-entered }`() { + // Given + testedTimeseriesCollector.onSessionStart() + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.NONE)) + + // When + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.FOREGROUND)) + + // Then + verify(mockScheduledFuture).cancel(false) + } + + @Test + fun `M schedule suspend once W onRumContextUpdate() { repeated non-foreground updates }`() { // Given - whenever(mockReaderB.read()) doReturn forge.getForgery>() testedTimeseriesCollector.onSessionStart() - // Pipeline A suspends the instance (short interval fires first in background) + // When + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.NONE)) testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) - val oldRunnableA = captureScheduledRunnableForInterval(fakeIntervalAMs) - val oldRunnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) - oldRunnableA.run() // State is already SUSPENDED (set by onRumContextUpdate); B's tick is still queued + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.NONE)) - // Resume creates a new generation and schedules fresh ticks for both pipelines - testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.FOREGROUND)) + // Then + verify(mockExecutor).schedule( + any(), + eq(DefaultTimeseriesCollector.SUSPEND_DELAY_MS), + eq(TimeUnit.MILLISECONDS) + ) + } + + @Test + fun `M not flush twice W pending suspend fires { session already stopped }`() { + // Given + val mockBuffer = mock>() + testedTimeseriesCollector = createTimeseriesWithBuffer(mockBuffer) + testedTimeseriesCollector.onSessionStart() + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) + testedTimeseriesCollector.onSessionStop() // drains the buffer synchronously - // When — B's old (stale) runnable fires despite the new generation - oldRunnableB.run() + // When + runScheduledSuspend() - // Then — stale tick must not sample and must not reschedule - verify(mockReaderB, never()).read() - // Total B schedules: 1 from start() + 1 from resumeSampling = 2; old tick adds nothing + // Then — the pending suspend loses the CAS against STOPPED and does nothing + verify(mockBuffer).drain() + } + + @Test + fun `M resume scheduling W onRumContextUpdate() { background to foreground }`() { + // Given — the deferred suspend fired, so the next tick dies without rescheduling + testedTimeseriesCollector.onSessionStart() + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) + runScheduledSuspend() + captureScheduledRunnableForInterval(fakeIntervalAMs).run() + verify(mockExecutor, times(1)) + .schedule(any(), eq(fakeIntervalAMs), eq(TimeUnit.MILLISECONDS)) + + // When — app returns to foreground + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.FOREGROUND)) + + // Then — scheduling resumed, one new schedule per pipeline + verify(mockExecutor, times(2)) + .schedule(any(), eq(fakeIntervalAMs), eq(TimeUnit.MILLISECONDS)) verify(mockExecutor, times(2)) .schedule(any(), eq(fakeIntervalBMs), eq(TimeUnit.MILLISECONDS)) } @Test - fun `M not sample or reschedule W stale tick fires { different generation, state is RUNNING }`() { - // A stale tick (old generation) must self-terminate via the generation check - // even when state is currently RUNNING. Ticks never write state. + fun `M not sample nor reschedule W stale tick fires { new generation is running }`() { + // Regression: a tick from the generation that was alive before the suspend must + // self-terminate on the generation check, even though the state is RUNNING again. // // Given testedTimeseriesCollector.onSessionStart() testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) - val oldRunnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) - // Resume: state = RUNNING, gen = 2; A and B each get a new schedule + runScheduledSuspend() + val staleRunnableB = captureScheduledRunnableForInterval(fakeIntervalBMs) + // Resume: state = RUNNING, generation = 2; A and B each get a fresh schedule testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.FOREGROUND)) - // When — B's stale gen-1 tick fires while state is RUNNING - oldRunnableB.run() + // When — B's stale generation-1 tick fires while state is RUNNING + staleRunnableB.run() // Then — stale tick neither samples nor produces an extra schedule verify(mockReaderB, never()).read() - // B schedules: 1 from start() + 1 from resume = 2; stale tick adds nothing + // B schedules: 1 from start() + 1 from the resume = 2; the stale tick adds nothing verify(mockExecutor, times(2)) .schedule(any(), eq(fakeIntervalBMs), eq(TimeUnit.MILLISECONDS)) } - private fun createPipeline( - reader: DataPointsReader, - buffer: Buffer, - eventFactory: EventFactory - ) = Pipeline( - sdkCore = mockSdkCore, - reader = reader, - buffer = buffer, - eventFactory = eventFactory, - dataWriter = mockDataWriter - ) + private fun runScheduledSuspend() { + captureScheduledSuspendRunnable().run() + } + + private fun captureScheduledSuspendRunnable(): Runnable { + val captor = argumentCaptor() + verify(mockExecutor, atLeastOnce()) + .schedule(captor.capture(), eq(DefaultTimeseriesCollector.SUSPEND_DELAY_MS), eq(TimeUnit.MILLISECONDS)) + return captor.lastValue + } private fun captureScheduledRunnableForInterval(intervalMs: Long): Runnable { val captor = argumentCaptor() verify(mockExecutor).schedule(captor.capture(), eq(intervalMs), eq(TimeUnit.MILLISECONDS)) return captor.firstValue } + + private fun captureLastScheduledRunnableForInterval(intervalMs: Long): Runnable { + val captor = argumentCaptor() + verify(mockExecutor, atLeastOnce()).schedule(captor.capture(), eq(intervalMs), eq(TimeUnit.MILLISECONDS)) + return captor.lastValue + } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTests.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTests.kt index 2546c4ef99..1c3575ae9d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTests.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/timeseries/TimeseriesConfigurationTests.kt @@ -11,7 +11,6 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.EnumSource -import org.junit.jupiter.params.provider.ValueSource @OptIn(ExperimentalRumApi::class) internal class TimeseriesConfigurationTests { @@ -47,25 +46,4 @@ internal class TimeseriesConfigurationTests { // Then assertThat(config.enabledTypes).isEmpty() } - - @Test - fun `M default collectInBackground to false W build() { collectInBackground not called }`() { - // When - val config = TimeseriesConfiguration.Builder().build() - - // Then - assertThat(config.collectInBackground).isFalse() - } - - @ParameterizedTest - @ValueSource(booleans = [true, false]) - fun `M store collectInBackground W collectInBackground()`(fakeCollectInBackground: Boolean) { - // When - val config = TimeseriesConfiguration.Builder() - .collectInBackground(fakeCollectInBackground) - .build() - - // Then - assertThat(config.collectInBackground).isEqualTo(fakeCollectInBackground) - } } From febc38dff08f6924ddc5017a4f5a91cf558f373d Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Tue, 18 Aug 2026 10:41:44 +0100 Subject: [PATCH 29/32] RUM-17613: Capture the flush context when leaving foreground The pending suspend read lastForegroundRumContext at fire time, so a foreground re-entry landing between stopGeneration() and the flush made it attribute the previous view's batch to the new view. Snapshot the context when the stop is scheduled instead. --- .../timeseries/DefaultTimeseriesCollector.kt | 27 ++++------- .../DefaultTimeseriesCollectorTest.kt | 45 +++++++++++++++---- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt index e991999878..03d342c99f 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollector.kt @@ -12,7 +12,6 @@ import com.datadog.android.core.internal.utils.scheduleSafe import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.scope.RumViewType import java.util.concurrent.ScheduledExecutorService -import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit internal class DefaultTimeseriesCollector( @@ -29,19 +28,15 @@ internal class DefaultTimeseriesCollector( @Volatile private var lastForegroundRumContext: RumContext = rumContext - @Volatile - private var recentSuspension: ScheduledFuture<*>? = null - // region PUBLIC API @WorkerThread override fun onSessionStart() { - state.set(isActive = rumContext.viewType.isForeground)?.let { generation -> scheduleSampling(generation) } + state.setActive(isActive = rumContext.viewType.isForeground)?.let { generation -> scheduleSampling(generation) } } @WorkerThread override fun onSessionStop() { - cancelSuspension() - if (state.set(false) != null) { + if (state.setActive(false) != null) { flushPipelines(lastForegroundRumContext) } } @@ -58,7 +53,7 @@ internal class DefaultTimeseriesCollector( if (newViewType.isForeground) lastForegroundRumContext = newRumContext if (isLeaveForeground) { - scheduleStop(state.currentGeneration) + scheduleStop(state.currentGeneration, lastForegroundRumContext) } else if (isEnterForeground) { scheduleSampling(generation = state.startGeneration()) } @@ -69,7 +64,6 @@ internal class DefaultTimeseriesCollector( //region SAMPLING private fun scheduleSampling(generation: Int) { - cancelSuspension() pipelines.forEach { pipeline -> scheduledExecutorService.schedulePipeline(pipeline, generation) } } @@ -122,24 +116,19 @@ internal class DefaultTimeseriesCollector( // region SUSPENSION - private fun scheduleStop(stopRequestGeneration: Int) { - recentSuspension = scheduledExecutorService.scheduleSafe( + private fun scheduleStop(stopRequestGeneration: Int, rumContextSnapshot: RumContext) { + scheduledExecutorService.scheduleSafe( SUSPEND_OPERATION_NAME, SUSPEND_DELAY_MS, TimeUnit.MILLISECONDS, internalLogger ) { if (!rumContext.viewType.isForeground && state.stopGeneration(stopRequestGeneration)) { - flushPipelines(lastForegroundRumContext) + flushPipelines(rumContextSnapshot) } } } - private fun cancelSuspension() { - recentSuspension?.cancel(false) - recentSuspension = null - } - // endregion internal companion object { @@ -188,11 +177,11 @@ internal class DefaultTimeseriesCollector( * Returns true when this call is the one that deactivated it. */ fun stopGeneration(generation: Int): Boolean = synchronized(this) { - currentGeneration == generation && set(false) != null + currentGeneration == generation && setActive(false) != null } /** Returns the new generation if this call changed the state, null otherwise. */ - fun set(isActive: Boolean): Int? = synchronized(this) { + fun setActive(isActive: Boolean): Int? = synchronized(this) { if (isActive == active) { null } else { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt index c38b5ca762..83c58d0e12 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/timeseries/DefaultTimeseriesCollectorTest.kt @@ -21,6 +21,7 @@ import com.datadog.android.rum.internal.timeseries.factory.EventFactory import com.datadog.android.rum.internal.timeseries.provider.DataPointsReader import com.datadog.android.rum.utils.forge.Configurator import com.datadog.android.utils.verifyLog +import com.datadog.tools.unit.setFieldValue import com.google.gson.JsonObject import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery @@ -53,7 +54,6 @@ import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.util.UUID import java.util.concurrent.ScheduledExecutorService -import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit @Extensions( @@ -90,9 +90,6 @@ internal class DefaultTimeseriesCollectorTest { @Mock lateinit var mockExecutor: ScheduledExecutorService - @Mock - lateinit var mockScheduledFuture: ScheduledFuture<*> - @Mock lateinit var mockReaderA: DataPointsReader @@ -128,11 +125,16 @@ internal class DefaultTimeseriesCollectorTest { private fun createTimeseries( initialViewType: RumViewType, pipelines: List> = listOf(pipelineA, pipelineB) + ) = createTimeseries(fakeRumContextOf(initialViewType), pipelines) + + private fun createTimeseries( + initialRumContext: RumContext, + pipelines: List> = listOf(pipelineA, pipelineB) ) = DefaultTimeseriesCollector( pipelines = pipelines, internalLogger = mockInternalLogger, scheduledExecutorService = mockExecutor, - rumContext = fakeRumContextOf(initialViewType) + rumContext = initialRumContext ) // Single foreground pipeline wired to reader/event factory A, with a buffer the test controls. @@ -145,7 +147,6 @@ internal class DefaultTimeseriesCollectorTest { fun `set up`() { whenever(mockReaderA.intervalMs) doReturn fakeIntervalAMs whenever(mockReaderB.intervalMs) doReturn fakeIntervalBMs - whenever(mockExecutor.schedule(any(), any(), any())) doReturn mockScheduledFuture whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope whenever(mockRumFeatureScope.withWriteContext(any(), any())) doAnswer { inv -> @@ -619,6 +620,28 @@ internal class DefaultTimeseriesCollectorTest { verify(mockEventFactoryB, never()).create(any(), any(), any()) } + @Test + fun `M flush with the context captured on leaving foreground W suspend fires { newer view }`( + forge: Forge + ) { + // Given + val fakeForegroundContext = fakeRumContextOf(RumViewType.FOREGROUND) + val mockPipeline = mock>() + val testedCollector = createTimeseries(fakeForegroundContext, listOf(mockPipeline)) + testedCollector.onSessionStart() + + // When + testedCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.BACKGROUND)) + testedCollector.setFieldValue( + "lastForegroundRumContext", + fakeForegroundContext.copy(viewId = forge.getForgery().toString()) + ) + runScheduledSuspend() + + // Then + verify(mockPipeline).flush(fakeForegroundContext) + } + @ParameterizedTest @EnumSource( value = RumViewType::class, @@ -708,16 +731,20 @@ internal class DefaultTimeseriesCollectorTest { } @Test - fun `M cancel pending suspend W onRumContextUpdate() { foreground re-entered }`() { + fun `M not flush W stale suspend fires { foreground re-entered }`() { // Given + val mockBuffer = mock>() + testedTimeseriesCollector = createTimeseriesWithBuffer(mockBuffer) testedTimeseriesCollector.onSessionStart() testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.NONE)) + val staleSuspend = captureScheduledSuspendRunnable() + testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.FOREGROUND)) // When - testedTimeseriesCollector.onRumContextUpdate(fakeRumContextOf(RumViewType.FOREGROUND)) + staleSuspend.run() // Then - verify(mockScheduledFuture).cancel(false) + verify(mockBuffer, never()).drain() } @Test From 77f6e7ee458a7957b7d13bbb66eb7c686e51ca6b Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Fri, 14 Aug 2026 16:00:17 +0100 Subject: [PATCH 30/32] RUM-17613: Add integration tests for timeseries collection Add a playground activity and an instrumented test that assert CPU and memory timeseries are collected, flushed on backgrounding and resumed on return to foreground. --- .../rum/TimeseriesCollectionTest.kt | 176 ++++++++++++++++++ .../sdk/rules/MockServerActivityTestRule.kt | 3 +- .../integration/src/main/AndroidManifest.xml | 3 + .../TimeseriesTrackingPlaygroundActivity.kt | 52 ++++++ 4 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/rum/TimeseriesCollectionTest.kt create mode 100644 instrumented/integration/src/main/kotlin/com/datadog/android/sdk/integration/rum/TimeseriesTrackingPlaygroundActivity.kt diff --git a/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/rum/TimeseriesCollectionTest.kt b/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/rum/TimeseriesCollectionTest.kt new file mode 100644 index 0000000000..928faadfb9 --- /dev/null +++ b/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/rum/TimeseriesCollectionTest.kt @@ -0,0 +1,176 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sdk.integration.rum + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.platform.app.InstrumentationRegistry +import com.datadog.android.privacy.TrackingConsent +import com.datadog.android.rum.model.TimeseriesCpuEvent +import com.datadog.android.rum.model.TimeseriesMemoryEvent +import com.datadog.android.sdk.integration.RuntimeConfig +import com.datadog.android.sdk.integration.rum.TimeseriesTrackingPlaygroundActivity.Companion.SAMPLE_INTERVAL_MS +import com.datadog.android.sdk.rules.HandledRequest +import com.datadog.android.sdk.rules.RumMockServerActivityTestRule +import com.datadog.tools.unit.ConditionWatcher +import com.google.gson.JsonObject +import org.assertj.core.api.Assertions.assertThat +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@LargeTest +internal class TimeseriesCollectionTest { + + @get:Rule + val mockServerRule = RumMockServerActivityTestRule( + TimeseriesTrackingPlaygroundActivity::class.java, + keepRequests = true, + trackingConsent = TrackingConsent.GRANTED + ) + + @Test + fun verifyCpuAndMemoryTimeseriesAreCollected() { + // Given + Thread.sleep(FOREGROUND_COLLECTION_DURATION_MS) + + // When + moveActivityToBackground() + val events: List = waitForTimeseriesEvents(expectedCount = TIMESERIES_PIPELINE_COUNT) + + // Then + assertTrue(events.size >= TIMESERIES_PIPELINE_COUNT) + events.assertForegroundDataPointCounts(EXPECTED_FOREGROUND_MEMORY_DATA_POINTS) + } + + @Test + fun verifyCollectionFlushesPausesAndResumesAcrossBackgroundTransition() { + // Given + Thread.sleep(FOREGROUND_COLLECTION_DURATION_MS) + + // When + moveActivityToBackground() + val firstForegroundEvents = waitForTimeseriesEvents(expectedCount = TIMESERIES_PIPELINE_COUNT) + Thread.sleep(BACKGROUND_OBSERVATION_DURATION_MS) + val backgroundEvents = timeseriesEvents() + moveActivityToForeground() + Thread.sleep(FOREGROUND_COLLECTION_DURATION_MS) + moveActivityToBackground() + val secondForegroundEvents = waitForTimeseriesEvents(expectedCount = TIMESERIES_PIPELINE_COUNT * 2) + + // Then + assertEquals(TIMESERIES_PIPELINE_COUNT, firstForegroundEvents.size) + firstForegroundEvents.assertForegroundDataPointCounts(EXPECTED_FOREGROUND_MEMORY_DATA_POINTS) + assertThat(firstForegroundEvents.size).isEqualTo(backgroundEvents.size) + assertThat(secondForegroundEvents.size).isEqualTo(TIMESERIES_PIPELINE_COUNT * 2) + secondForegroundEvents.assertForegroundDataPointCounts(EXPECTED_FOREGROUND_MEMORY_DATA_POINTS) + } + + private fun moveActivityToBackground() { + val instrumentation = InstrumentationRegistry.getInstrumentation() + instrumentation.runOnMainSync { + instrumentation.callActivityOnPause(mockServerRule.activity) + instrumentation.callActivityOnStop(mockServerRule.activity) + } + instrumentation.waitForIdleSync() + } + + private fun moveActivityToForeground() { + val instrumentation = InstrumentationRegistry.getInstrumentation() + instrumentation.runOnMainSync { + instrumentation.callActivityOnStart(mockServerRule.activity) + instrumentation.callActivityOnResume(mockServerRule.activity) + } + instrumentation.waitForIdleSync() + } + + private fun waitForTimeseriesEvents(expectedCount: Int): List { + var events = emptyList() + ConditionWatcher { + events = timeseriesEvents() + assertTrue(events.size >= expectedCount) + true + }.doWait(timeoutMs = RumTest.FINAL_WAIT_MS) + return events + } + + private fun timeseriesEvents(): List = mockServerRule + .getRequests(RuntimeConfig.rumEndpointUrl) + .rumEvents() + .filter { it.get(TYPE_KEY)?.asString == TIMESERIES_TYPE } + + private fun List.rumEvents(): List = flatMap { request -> + request.textBody?.let(::rumPayloadToJsonList).orEmpty() + } + + companion object { + private const val CPU_TIMESERIES_NAME = "cpu" + private const val MEMORY_TIMESERIES_NAME = "memory" + private const val TIMESERIES_TYPE = "timeseries" + private const val TYPE_KEY = "type" + private const val TIMESERIES_KEY = "timeseries" + private const val NAME_KEY = "name" + + // One independent pipeline for each timeseries source: CPU and memory. + private const val TIMESERIES_PIPELINE_COUNT = 2 + + // Backgrounding defers the flush twice: ActivityViewTrackingStrategy waits before stopping + // the view, then the collector waits before suspending sampling and flushing the buffers. + private const val ACTIVITY_STOP_DELAY_MS = 200L + private const val TIMESERIES_SUSPEND_DELAY_MS = 200L + private const val FLUSH_DELAY_MS = ACTIVITY_STOP_DELAY_MS + TIMESERIES_SUSPEND_DELAY_MS + + // Sized so the flush lands in the middle of a sampling interval, leaving half an interval + // of room on either side before the expected data point count changes. + private const val FOREGROUND_COLLECTION_DURATION_MS = + SAMPLE_INTERVAL_MS * 3 + SAMPLE_INTERVAL_MS / 2 - FLUSH_DELAY_MS + private const val UPLOAD_GRACE_PERIOD_MS = 500L + private const val BACKGROUND_OBSERVATION_DURATION_MS = + SAMPLE_INTERVAL_MS + UPLOAD_GRACE_PERIOD_MS + + private const val EXPECTED_FOREGROUND_MEMORY_DATA_POINTS = + ((FOREGROUND_COLLECTION_DURATION_MS + FLUSH_DELAY_MS) / SAMPLE_INTERVAL_MS).toInt() + + // Sampling shares a single executor with the other vitals on a device the test does not + // own, so a stalled tick costs a data point and a late flush adds one. Keeps the lower + // bound at two points, so the CPU event — which trails memory by one — always exists. + private const val DATA_POINTS_TOLERANCE = 1 + + private val JsonObject.timeseriesName: String? + get() = getAsJsonObject(TIMESERIES_KEY).get(NAME_KEY).asString + + private fun List.filterByName(name: String): List = filter { it.timeseriesName == name } + + private fun List.cpuEvents(): List = + filterByName(CPU_TIMESERIES_NAME).map(TimeseriesCpuEvent::fromJsonObject) + + private fun List.memoryEvents(): List = + filterByName(MEMORY_TIMESERIES_NAME).map(TimeseriesMemoryEvent::fromJsonObject) + + /** + * Asserts the data point count of every foreground batch: memory within the tolerance of + * the collection window, CPU exactly one point behind memory, because the first CPU read + * only establishes the delta baseline and yields no data point. + */ + private fun List.assertForegroundDataPointCounts( + expected: Int, + tolerance: Int = DATA_POINTS_TOLERANCE + ) { + val memoryDataPointCounts = memoryEvents().map { it.timeseries.data.timestamps.size } + val cpuDataPointCounts = cpuEvents().map { it.timeseries.data.timestamps.size } + + memoryDataPointCounts.forEach { count -> + assertThat(count).isBetween(expected - tolerance, expected + tolerance) + } + assertThat(cpuDataPointCounts) + .containsExactlyElementsOf(memoryDataPointCounts.map { it - 1 }) + } + } +} diff --git a/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/rules/MockServerActivityTestRule.kt b/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/rules/MockServerActivityTestRule.kt index 1c5393f0a5..8389680c76 100644 --- a/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/rules/MockServerActivityTestRule.kt +++ b/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/rules/MockServerActivityTestRule.kt @@ -26,6 +26,7 @@ import okhttp3.mockwebserver.MockWebServer import org.junit.runner.Description import org.junit.runners.model.Statement import java.io.File +import java.util.concurrent.CopyOnWriteArrayList internal open class MockServerActivityTestRule( val activityClass: Class, @@ -35,7 +36,7 @@ internal open class MockServerActivityTestRule( private val mockWebServer: MockWebServer = MockWebServer() - private val requests = mutableListOf() + private val requests = CopyOnWriteArrayList() val forge = Forge() diff --git a/instrumented/integration/src/main/AndroidManifest.xml b/instrumented/integration/src/main/AndroidManifest.xml index 2386dcf50c..ef023fc0d1 100644 --- a/instrumented/integration/src/main/AndroidManifest.xml +++ b/instrumented/integration/src/main/AndroidManifest.xml @@ -29,6 +29,9 @@ android:name=".rum.ActivityTrackingPlaygroundActivity" android:label="@string/rum_activity_view_tracking_end_to_end" android:windowSoftInputMode="adjustResize"/> + Date: Wed, 19 Aug 2026 11:25:19 +0100 Subject: [PATCH 31/32] RUM-18101: Supporting timeseries apiUsage telemetry. --- .../assertj/InternalApiUsageEventAssert.kt | 6 + .../assertj/InternalTimeseriesEventAssert.kt | 37 ++++++ dd-sdk-android-internal/api/apiSurface | 2 + .../api/dd-sdk-android-internal.api | 6 + .../telemetry/InternalTelemetryEvent.kt | 4 + ...InternalTelemetryApiUsageForgeryFactory.kt | 3 + features/dd-sdk-android-rum/api/apiSurface | 6 + .../api/dd-sdk-android-rum.api | 14 +++ .../usage/mobile-features-schema.json | 11 ++ .../kotlin/com/datadog/android/rum/Rum.kt | 12 ++ .../internal/TelemetryEventHandler.kt | 3 + .../kotlin/com/datadog/android/rum/RumTest.kt | 108 ++++++++++++++++-- .../assertj/TelemetryUsageEventAssert.kt | 5 + .../integration/src/main/AndroidManifest.xml | 2 +- .../TimeseriesTrackingPlaygroundActivity.kt | 4 +- .../src/main/res/values/strings.xml | 1 + 16 files changed, 212 insertions(+), 12 deletions(-) create mode 100644 dd-sdk-android-core/src/testFixtures/kotlin/com/datadog/android/utils/assertj/InternalTimeseriesEventAssert.kt diff --git a/dd-sdk-android-core/src/testFixtures/kotlin/com/datadog/android/utils/assertj/InternalApiUsageEventAssert.kt b/dd-sdk-android-core/src/testFixtures/kotlin/com/datadog/android/utils/assertj/InternalApiUsageEventAssert.kt index 8abbfa9c5f..0b6b9e7d00 100644 --- a/dd-sdk-android-core/src/testFixtures/kotlin/com/datadog/android/utils/assertj/InternalApiUsageEventAssert.kt +++ b/dd-sdk-android-core/src/testFixtures/kotlin/com/datadog/android/utils/assertj/InternalApiUsageEventAssert.kt @@ -35,6 +35,12 @@ class InternalApiUsageEventAssert(actual: InternalTelemetryEvent.ApiUsage) : .isEqualTo(expected as InternalTelemetryEvent.ApiUsage.TrackWebView) } + is InternalTelemetryEvent.ApiUsage.Timeseries -> { + InternalTimeseriesEventAssert + .assertThat(actual as InternalTelemetryEvent.ApiUsage.Timeseries) + .isEqualTo(expected as InternalTelemetryEvent.ApiUsage.Timeseries) + } + is InternalTelemetryEvent.ApiUsage.NetworkInstrumentation -> { InternalApiUsageNetworkInstrumentationEventAssert .assertThat(actual as InternalTelemetryEvent.ApiUsage.NetworkInstrumentation) diff --git a/dd-sdk-android-core/src/testFixtures/kotlin/com/datadog/android/utils/assertj/InternalTimeseriesEventAssert.kt b/dd-sdk-android-core/src/testFixtures/kotlin/com/datadog/android/utils/assertj/InternalTimeseriesEventAssert.kt new file mode 100644 index 0000000000..af16a19dad --- /dev/null +++ b/dd-sdk-android-core/src/testFixtures/kotlin/com/datadog/android/utils/assertj/InternalTimeseriesEventAssert.kt @@ -0,0 +1,37 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.utils.assertj + +import com.datadog.android.internal.telemetry.InternalTelemetryEvent +import org.assertj.core.api.AbstractAssert +import org.assertj.core.api.Assertions + +private typealias TimeseriesApiUsage = InternalTelemetryEvent.ApiUsage.Timeseries + +internal class InternalTimeseriesEventAssert(actual: TimeseriesApiUsage) : + AbstractAssert( + actual, + InternalTimeseriesEventAssert::class.java + ) { + + fun isEqualTo(expected: TimeseriesApiUsage) { + hasAdditionalProperties(expected.additionalProperties) + } + + private fun hasAdditionalProperties(expected: Map) = apply { + Assertions.assertThat(actual.additionalProperties) + .overridingErrorMessage( + "Expected Timeseries event to have" + + " additionalProperties $expected but was ${actual.additionalProperties}" + ) + .isEqualTo(expected) + } + + companion object { + fun assertThat(actual: TimeseriesApiUsage) = InternalTimeseriesEventAssert(actual) + } +} diff --git a/dd-sdk-android-internal/api/apiSurface b/dd-sdk-android-internal/api/apiSurface index 43a3cd6aa2..97eba0c7a9 100644 --- a/dd-sdk-android-internal/api/apiSurface +++ b/dd-sdk-android-internal/api/apiSurface @@ -247,6 +247,8 @@ sealed class com.datadog.android.internal.telemetry.InternalTelemetryEvent - CRONET - OKHTTP - LEGACY_OKHTTP + class Timeseries : ApiUsage + constructor(MutableMap = mutableMapOf()) object InterceptorInstantiated : InternalTelemetryEvent class ResourceHeadersTrackingConfigured : InternalTelemetryEvent constructor(Mode) diff --git a/dd-sdk-android-internal/api/dd-sdk-android-internal.api b/dd-sdk-android-internal/api/dd-sdk-android-internal.api index 6621a0b49a..4237a2e82e 100644 --- a/dd-sdk-android-internal/api/dd-sdk-android-internal.api +++ b/dd-sdk-android-internal/api/dd-sdk-android-internal.api @@ -543,6 +543,12 @@ public final class com/datadog/android/internal/telemetry/InternalTelemetryEvent public static fun values ()[Lcom/datadog/android/internal/telemetry/InternalTelemetryEvent$ApiUsage$NetworkInstrumentation$LibraryType; } +public final class com/datadog/android/internal/telemetry/InternalTelemetryEvent$ApiUsage$Timeseries : com/datadog/android/internal/telemetry/InternalTelemetryEvent$ApiUsage { + public fun ()V + public fun (Ljava/util/Map;)V + public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + public final class com/datadog/android/internal/telemetry/InternalTelemetryEvent$ApiUsage$TrackWebView : com/datadog/android/internal/telemetry/InternalTelemetryEvent$ApiUsage { public fun ()V public fun (Ljava/util/Map;)V diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/InternalTelemetryEvent.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/InternalTelemetryEvent.kt index 1fd9f4fb75..02c0caf1d9 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/InternalTelemetryEvent.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/InternalTelemetryEvent.kt @@ -82,6 +82,10 @@ sealed class InternalTelemetryEvent { LEGACY_OKHTTP } } + + class Timeseries( + additionalProperties: MutableMap = mutableMapOf() + ) : ApiUsage(additionalProperties) } object InterceptorInstantiated : InternalTelemetryEvent() diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryApiUsageForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryApiUsageForgeryFactory.kt index 43b3768042..0de1fffb2d 100644 --- a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryApiUsageForgeryFactory.kt +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryApiUsageForgeryFactory.kt @@ -35,6 +35,9 @@ class InternalTelemetryApiUsageForgeryFactory : ForgeryFactory ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$Timeseries; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$Timeseries; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$Timeseries$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$Timeseries; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$Timeseries; +} + public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TrackWebView : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TrackWebView$Companion; public fun ()V diff --git a/features/dd-sdk-android-rum/src/main/json/telemetry/usage/mobile-features-schema.json b/features/dd-sdk-android-rum/src/main/json/telemetry/usage/mobile-features-schema.json index 815f714977..b2ffd57832 100644 --- a/features/dd-sdk-android-rum/src/main/json/telemetry/usage/mobile-features-schema.json +++ b/features/dd-sdk-android-rum/src/main/json/telemetry/usage/mobile-features-schema.json @@ -39,6 +39,17 @@ } } }, + { + "required": ["feature"], + "title": "Timeseries", + "properties": { + "feature": { + "type": "string", + "description": "Timeseries tracking enabled", + "const": "timeseries" + } + } + }, { "required": ["feature", "type"], "title": "AndroidNetworkInstrumentation", diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt index 80ffa3b0aa..7fcf71aea6 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt @@ -18,8 +18,10 @@ import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.sampling.DeterministicSampler import com.datadog.android.core.sampling.RateBasedSampler import com.datadog.android.internal.sampling.SessionSamplingIdProvider +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.internal.RumAnonymousIdentifierManager import com.datadog.android.rum.internal.RumFeature +import com.datadog.android.rum.internal.RumFeature.Configuration import com.datadog.android.rum.internal.domain.scope.RumVitalAppLaunchEventHelper import com.datadog.android.rum.internal.metric.SessionEndedMetricDispatcher import com.datadog.android.rum.internal.monitor.DatadogRumMonitor @@ -106,6 +108,12 @@ object Rum { // and before it is registered, but with current code (internal RUM scopes using the // `GlobalRumMonitor`) it is impossible to break cycle dependency. rumMonitor.start() + + // Reported here and not in RumFeature.onInitialize: telemetry events are dropped while the + // global RUM monitor is not registered yet (see RumFeature.handleTelemetryEvent). + if (rumConfiguration.featureConfiguration.isTimeseriesConfigured()) { + sdkCore.internalLogger.logApiUsage { InternalTelemetryEvent.ApiUsage.Timeseries() } + } } // region private @@ -198,5 +206,9 @@ object Rum { internal const val RUM_FEATURE_ALREADY_ENABLED = "RUM Feature is already enabled in this SDK core, ignoring the call to enable it." + private fun Configuration.isTimeseriesConfigured(): Boolean { + return timeseriesConfiguration?.enabledTypes?.isNotEmpty() == true + } + // endregion } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt index f60919cfdb..b351b4810c 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt @@ -446,6 +446,9 @@ internal class TelemetryEventHandler( is InternalTelemetryEvent.ApiUsage.TrackWebView -> { TelemetryUsageEvent.Usage.TrackWebView() } + is InternalTelemetryEvent.ApiUsage.Timeseries -> { + TelemetryUsageEvent.Usage.Timeseries() + } is InternalTelemetryEvent.ApiUsage.NetworkInstrumentation -> { TelemetryUsageEvent.Usage.AndroidNetworkInstrumentation( type = when (event.type) { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumTest.kt index 1232c02422..8d4afa0a0f 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumTest.kt @@ -14,18 +14,23 @@ import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.sampling.DeterministicSampler import com.datadog.android.core.sampling.RateBasedSampler +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.internal.RumFeature import com.datadog.android.rum.internal.monitor.DatadogRumMonitor import com.datadog.android.rum.internal.monitor.NoOpAdvancedRumMonitor import com.datadog.android.rum.internal.net.RumRequestFactory +import com.datadog.android.rum.timeseries.TimeseriesConfiguration +import com.datadog.android.rum.timeseries.TimeseriesType import com.datadog.android.rum.tracking.NoOpTrackingStrategy import com.datadog.android.rum.tracking.NoOpViewTrackingStrategy import com.datadog.android.rum.utils.config.MainLooperTestConfiguration import com.datadog.android.rum.utils.forge.Configurator +import com.datadog.android.utils.verifyApiUsage import com.datadog.android.utils.verifyLog import com.datadog.tools.unit.annotations.TestConfigurationsProvider import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration +import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -55,14 +60,18 @@ import org.mockito.quality.Strictness ) @MockitoSettings(strictness = Strictness.LENIENT) @ForgeConfiguration(Configurator::class) +@OptIn(ExperimentalRumApi::class) internal class RumTest { @Mock lateinit var mockSdkCore: InternalSdkCore + @Mock + lateinit var mockInternalLogger: InternalLogger + @BeforeEach fun `set up`() { - whenever(mockSdkCore.internalLogger) doReturn mock() + whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger whenever(mockSdkCore.timeProvider) doReturn mock() whenever(mockSdkCore.firstPartyHostResolver) doReturn mock() whenever(mockSdkCore.createSingleThreadExecutorService(any())) doReturn mock() @@ -183,12 +192,81 @@ internal class RumTest { assertThat(monitor.appPackageName).isEqualTo(fakePackageName) } + @Test + fun `M send timeseries api usage W enable() { timeseries collection enabled }`( + @StringForgery fakePackageName: String, + @Forgery fakeRumConfiguration: RumConfiguration + ) { + // Given + stubFeatureInitialization(fakePackageName) + + // When + Rum.enable(fakeRumConfiguration.withTimeseries(TimeseriesConfiguration.DEFAULT), mockSdkCore) + + // Then + mockInternalLogger.verifyApiUsage( + apiUsage = InternalTelemetryEvent.ApiUsage.Timeseries(), + samplingRate = API_USAGE_SAMPLING_RATE + ) + } + + @Test + fun `M send timeseries api usage W enable() { timeseries collection subset }`( + @StringForgery fakePackageName: String, + @Forgery fakeRumConfiguration: RumConfiguration, + forge: Forge + ) { + // Given + stubFeatureInitialization(fakePackageName) + val fakeType = forge.aValueFrom(TimeseriesType::class.java) + val timeseriesConfiguration = TimeseriesConfiguration.Builder().collectOnly(fakeType).build() + + // When + Rum.enable(fakeRumConfiguration.withTimeseries(timeseriesConfiguration), mockSdkCore) + + // Then + mockInternalLogger.verifyApiUsage( + apiUsage = InternalTelemetryEvent.ApiUsage.Timeseries(), + samplingRate = API_USAGE_SAMPLING_RATE + ) + } + + @Test + fun `M not send timeseries api usage W enable() { timeseries collection disabled }`( + @StringForgery fakePackageName: String, + @Forgery fakeRumConfiguration: RumConfiguration + ) { + // Given + stubFeatureInitialization(fakePackageName) + val fakeTimeseriesConfiguration = TimeseriesConfiguration.Builder().collectOnly().build() + + // When + Rum.enable(fakeRumConfiguration.withTimeseries(fakeTimeseriesConfiguration), mockSdkCore) + + // Then + verify(mockInternalLogger, never()).logApiUsage(any(), any()) + } + + @Test + fun `M not send timeseries api usage W enable() { no timeseries configuration }`( + @StringForgery fakePackageName: String, + @Forgery fakeRumConfiguration: RumConfiguration + ) { + // Given + stubFeatureInitialization(fakePackageName) + + // When + Rum.enable(fakeRumConfiguration.withTimeseries(null), mockSdkCore) + + // Then + verify(mockInternalLogger, never()).logApiUsage(any(), any()) + } + @Test fun `M register nothing W enable() { SDK instance doesn't implement InternalSdkCore }`( @Forgery fakeRumConfiguration: RumConfiguration ) { // Given - val mockInternalLogger = mock() val wrongSdkCore = mock() whenever(wrongSdkCore.internalLogger) doReturn mockInternalLogger @@ -210,10 +288,6 @@ internal class RumTest { @Forgery fakeRumConfiguration: RumConfiguration, @StringForgery(regex = "\\s*") fakeApplicationId: String ) { - // Given - val mockInternalLogger = mock() - whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger - // When Rum.enable( rumConfiguration = fakeRumConfiguration.copy(applicationId = fakeApplicationId), @@ -235,8 +309,6 @@ internal class RumTest { @Forgery fakeRumConfiguration: RumConfiguration ) { // Given - val mockInternalLogger = mock() - whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mock() // When @@ -251,7 +323,27 @@ internal class RumTest { verify(mockSdkCore, never()).registerFeature(any()) } + private fun stubFeatureInitialization(packageName: String) { + whenever(mockSdkCore.registerFeature(any())) doAnswer { + val mockApplication = mock { application -> + whenever(application.packageName) doReturn packageName + whenever(application.resources) doReturn mock() + whenever(application.contentResolver) doReturn mock() + whenever(application.resources.configuration) doReturn mock() + } + whenever(mockApplication.applicationContext) doReturn mockApplication + + it.getArgument(0).onInitialize(appContext = mockApplication) + } + } + + private fun RumConfiguration.withTimeseries(configuration: TimeseriesConfiguration?) = copy( + featureConfiguration = featureConfiguration.copy(timeseriesConfiguration = configuration) + ) + companion object { + private const val API_USAGE_SAMPLING_RATE = 15f + private val mainLooper = MainLooperTestConfiguration() @TestConfigurationsProvider diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryUsageEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryUsageEventAssert.kt index 1bad1f04c7..38950c8fda 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryUsageEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryUsageEventAssert.kt @@ -181,6 +181,11 @@ internal class TelemetryUsageEventAssert(actual: TelemetryUsageEvent) : .isInstanceOf(TelemetryUsageEvent.Usage.TrackWebView::class.java) } + is InternalTelemetryEvent.ApiUsage.Timeseries -> { + assertThat(actual.telemetry.usage) + .isInstanceOf(TelemetryUsageEvent.Usage.Timeseries::class.java) + } + is InternalTelemetryEvent.ApiUsage.NetworkInstrumentation -> { val actualUsage = actual.telemetry.usage as TelemetryUsageEvent.Usage.AndroidNetworkInstrumentation diff --git a/instrumented/integration/src/main/AndroidManifest.xml b/instrumented/integration/src/main/AndroidManifest.xml index ef023fc0d1..63ab3ac188 100644 --- a/instrumented/integration/src/main/AndroidManifest.xml +++ b/instrumented/integration/src/main/AndroidManifest.xml @@ -31,7 +31,7 @@ android:windowSoftInputMode="adjustResize"/> + android:label="@string/rum_activity_timeseries_end_to_end" /> Button1 Rum Gestures (End to End) Rum Activity View (End to End) + RUM timeseries end-to-end Telemetry (End to End) Session Replay (End to End) Session Replay Text Fields (End to End) From a0dee4eee1e2cea1ed58f2dd2344f47752211dd2 Mon Sep 17 00:00:00 2001 From: Timur Valeev Date: Mon, 24 Aug 2026 11:11:43 +0100 Subject: [PATCH 32/32] Fix merge conflicts --- .../CrossFileOneOfPrimitiveAForgeryFactory.kt | 24 ++ .../CrossFileOneOfPrimitiveBForgeryFactory.kt | 24 ++ .../OneOfPrimitiveRefForgeryFactory.kt | 29 +++ .../example/model/CrossFileOneOfPrimitiveA.kt | 208 +++++++++++++++++ .../example/model/CrossFileOneOfPrimitiveB.kt | 208 +++++++++++++++++ .../com/example/model/OneOfPrimitiveRef.kt | 216 ++++++++++++++++++ .../input/cross_file_one_of_primitive.json | 15 ++ .../input/cross_file_one_of_primitive_a.json | 9 + .../input/cross_file_one_of_primitive_b.json | 9 + .../resources/input/one_of_primitive_ref.json | 26 +++ 10 files changed, 768 insertions(+) create mode 100644 build-logic/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveAForgeryFactory.kt create mode 100644 build-logic/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveBForgeryFactory.kt create mode 100644 build-logic/src/test/kotlin/com/example/forgery/OneOfPrimitiveRefForgeryFactory.kt create mode 100644 build-logic/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveA.kt create mode 100644 build-logic/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveB.kt create mode 100644 build-logic/src/test/kotlin/com/example/model/OneOfPrimitiveRef.kt create mode 100644 build-logic/src/test/resources/input/cross_file_one_of_primitive.json create mode 100644 build-logic/src/test/resources/input/cross_file_one_of_primitive_a.json create mode 100644 build-logic/src/test/resources/input/cross_file_one_of_primitive_b.json create mode 100644 build-logic/src/test/resources/input/one_of_primitive_ref.json diff --git a/build-logic/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveAForgeryFactory.kt b/build-logic/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveAForgeryFactory.kt new file mode 100644 index 0000000000..068d3de755 --- /dev/null +++ b/build-logic/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveAForgeryFactory.kt @@ -0,0 +1,24 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.example.forgery + +import com.example.model.CrossFileOneOfPrimitiveA +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +internal class CrossFileOneOfPrimitiveAForgeryFactory : ForgeryFactory { + override fun getForgery(forge: Forge): CrossFileOneOfPrimitiveA { + return CrossFileOneOfPrimitiveA( + path = forge.anElementFrom( + listOf( + CrossFileOneOfPrimitiveA.Path.String(forge.aString()), + CrossFileOneOfPrimitiveA.Path.Long(forge.aLong()) + ) + ) + ) + } +} diff --git a/build-logic/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveBForgeryFactory.kt b/build-logic/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveBForgeryFactory.kt new file mode 100644 index 0000000000..eea6fbefe6 --- /dev/null +++ b/build-logic/src/test/kotlin/com/example/forgery/CrossFileOneOfPrimitiveBForgeryFactory.kt @@ -0,0 +1,24 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.example.forgery + +import com.example.model.CrossFileOneOfPrimitiveB +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +internal class CrossFileOneOfPrimitiveBForgeryFactory : ForgeryFactory { + override fun getForgery(forge: Forge): CrossFileOneOfPrimitiveB { + return CrossFileOneOfPrimitiveB( + path = forge.anElementFrom( + listOf( + CrossFileOneOfPrimitiveB.Path.String(forge.aString()), + CrossFileOneOfPrimitiveB.Path.Long(forge.aLong()) + ) + ) + ) + } +} diff --git a/build-logic/src/test/kotlin/com/example/forgery/OneOfPrimitiveRefForgeryFactory.kt b/build-logic/src/test/kotlin/com/example/forgery/OneOfPrimitiveRefForgeryFactory.kt new file mode 100644 index 0000000000..000a5e8575 --- /dev/null +++ b/build-logic/src/test/kotlin/com/example/forgery/OneOfPrimitiveRefForgeryFactory.kt @@ -0,0 +1,29 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.example.forgery + +import com.example.model.OneOfPrimitiveRef +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +internal class OneOfPrimitiveRefForgeryFactory : ForgeryFactory { + override fun getForgery(forge: Forge): OneOfPrimitiveRef { + return OneOfPrimitiveRef( + from = forge.aPath(), + to = forge.aNullable { aPath() } + ) + } + + private fun Forge.aPath(): OneOfPrimitiveRef.Path { + return anElementFrom( + listOf( + OneOfPrimitiveRef.Path.String(aString()), + OneOfPrimitiveRef.Path.Long(aLong()) + ) + ) + } +} diff --git a/build-logic/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveA.kt b/build-logic/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveA.kt new file mode 100644 index 0000000000..cd669c459c --- /dev/null +++ b/build-logic/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveA.kt @@ -0,0 +1,208 @@ +package com.example.model + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonParser +import com.google.gson.JsonPrimitive +import java.lang.IllegalStateException +import java.lang.NullPointerException +import java.lang.NumberFormatException +import java.lang.UnsupportedOperationException +import kotlin.String +import kotlin.jvm.JvmStatic +import kotlin.jvm.Throws + +/** + * @param path This is a definition of a path + */ +public data class CrossFileOneOfPrimitiveA( + public val path: Path, +) { + public fun toJson(): JsonElement { + val json = JsonObject() + json.add("path", path.toJson()) + return json + } + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: String): CrossFileOneOfPrimitiveA { + try { + val jsonObject = JsonParser.parseString(jsonString).asJsonObject + return fromJsonObject(jsonObject) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveA", + e + ) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonObject(jsonObject: JsonObject): CrossFileOneOfPrimitiveA { + try { + val path = jsonObject.get("path").let { + Path.fromJsonElement(it) + } + return CrossFileOneOfPrimitiveA(path) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveA", + e + ) + } catch (e: NumberFormatException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveA", + e + ) + } catch (e: NullPointerException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveA", + e + ) + } + } + } + + /** + * This is a definition of a path + */ + public sealed class Path { + public abstract fun toJson(): JsonElement + + /** + * string element + */ + public data class String( + public val item: kotlin.String, + ) : Path() { + override fun toJson(): JsonElement = JsonPrimitive(item) + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): String { + val jsonElement = JsonParser.parseString(jsonString) + try { + return fromJsonPrimitive(jsonElement.asJsonPrimitive) + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type String", e) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonPrimitive(jsonPrimitive: JsonPrimitive): String { + try { + if (jsonPrimitive.isString) { + return String(jsonPrimitive.asString) + } else { + throw JsonParseException("Can't convert jsonPrimitive to String") + } + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type String", e) + } catch (e: UnsupportedOperationException) { + throw JsonParseException("Unable to parse json into type String", e) + } + } + } + } + + /** + * integer element + */ + public data class Long( + public val item: kotlin.Long, + ) : Path() { + override fun toJson(): JsonElement = JsonPrimitive(item) + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): Long { + val jsonElement = JsonParser.parseString(jsonString) + try { + return fromJsonPrimitive(jsonElement.asJsonPrimitive) + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type Long", e) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonPrimitive(jsonPrimitive: JsonPrimitive): Long { + try { + if (jsonPrimitive.isNumber) { + return Long(jsonPrimitive.asLong) + } else { + throw JsonParseException("Can't convert jsonPrimitive to Long") + } + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type Long", e) + } catch (e: NumberFormatException) { + throw JsonParseException("Unable to parse json into type Long", e) + } catch (e: UnsupportedOperationException) { + throw JsonParseException("Unable to parse json into type Long", e) + } + } + } + } + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): Path { + try { + val jsonElement = JsonParser.parseString(jsonString) + return fromJsonElement(jsonElement) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into one of type Path", + e + ) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonElement(jsonElement: JsonElement): Path { + val errors = mutableListOf() + val asString = try { + if (jsonElement is JsonPrimitive) { + String.fromJsonPrimitive(jsonElement) + } else { + throw JsonParseException("Unable to parse json into type " + + "kotlin.String") + } + } catch (e: JsonParseException) { + errors.add(e) + null + } + val asLong = try { + if (jsonElement is JsonPrimitive) { + Long.fromJsonPrimitive(jsonElement) + } else { + throw JsonParseException("Unable to parse json into type " + + "kotlin.Long") + } + } catch (e: JsonParseException) { + errors.add(e) + null + } + val result = arrayOf( + asString, + asLong, + ).firstOrNull { it != null } + if (result == null) { + val message = "Unable to parse json into one of type \n" + "Path\n" + + errors.joinToString("\n") { it.message.toString() } + throw JsonParseException(message) + } + return result + } + } + } +} diff --git a/build-logic/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveB.kt b/build-logic/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveB.kt new file mode 100644 index 0000000000..650f48256f --- /dev/null +++ b/build-logic/src/test/kotlin/com/example/model/CrossFileOneOfPrimitiveB.kt @@ -0,0 +1,208 @@ +package com.example.model + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonParser +import com.google.gson.JsonPrimitive +import java.lang.IllegalStateException +import java.lang.NullPointerException +import java.lang.NumberFormatException +import java.lang.UnsupportedOperationException +import kotlin.String +import kotlin.jvm.JvmStatic +import kotlin.jvm.Throws + +/** + * @param path This is a definition of a path + */ +public data class CrossFileOneOfPrimitiveB( + public val path: Path, +) { + public fun toJson(): JsonElement { + val json = JsonObject() + json.add("path", path.toJson()) + return json + } + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: String): CrossFileOneOfPrimitiveB { + try { + val jsonObject = JsonParser.parseString(jsonString).asJsonObject + return fromJsonObject(jsonObject) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveB", + e + ) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonObject(jsonObject: JsonObject): CrossFileOneOfPrimitiveB { + try { + val path = jsonObject.get("path").let { + Path.fromJsonElement(it) + } + return CrossFileOneOfPrimitiveB(path) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveB", + e + ) + } catch (e: NumberFormatException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveB", + e + ) + } catch (e: NullPointerException) { + throw JsonParseException( + "Unable to parse json into type CrossFileOneOfPrimitiveB", + e + ) + } + } + } + + /** + * This is a definition of a path + */ + public sealed class Path { + public abstract fun toJson(): JsonElement + + /** + * string element + */ + public data class String( + public val item: kotlin.String, + ) : Path() { + override fun toJson(): JsonElement = JsonPrimitive(item) + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): String { + val jsonElement = JsonParser.parseString(jsonString) + try { + return fromJsonPrimitive(jsonElement.asJsonPrimitive) + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type String", e) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonPrimitive(jsonPrimitive: JsonPrimitive): String { + try { + if (jsonPrimitive.isString) { + return String(jsonPrimitive.asString) + } else { + throw JsonParseException("Can't convert jsonPrimitive to String") + } + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type String", e) + } catch (e: UnsupportedOperationException) { + throw JsonParseException("Unable to parse json into type String", e) + } + } + } + } + + /** + * integer element + */ + public data class Long( + public val item: kotlin.Long, + ) : Path() { + override fun toJson(): JsonElement = JsonPrimitive(item) + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): Long { + val jsonElement = JsonParser.parseString(jsonString) + try { + return fromJsonPrimitive(jsonElement.asJsonPrimitive) + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type Long", e) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonPrimitive(jsonPrimitive: JsonPrimitive): Long { + try { + if (jsonPrimitive.isNumber) { + return Long(jsonPrimitive.asLong) + } else { + throw JsonParseException("Can't convert jsonPrimitive to Long") + } + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type Long", e) + } catch (e: NumberFormatException) { + throw JsonParseException("Unable to parse json into type Long", e) + } catch (e: UnsupportedOperationException) { + throw JsonParseException("Unable to parse json into type Long", e) + } + } + } + } + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): Path { + try { + val jsonElement = JsonParser.parseString(jsonString) + return fromJsonElement(jsonElement) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into one of type Path", + e + ) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonElement(jsonElement: JsonElement): Path { + val errors = mutableListOf() + val asString = try { + if (jsonElement is JsonPrimitive) { + String.fromJsonPrimitive(jsonElement) + } else { + throw JsonParseException("Unable to parse json into type " + + "kotlin.String") + } + } catch (e: JsonParseException) { + errors.add(e) + null + } + val asLong = try { + if (jsonElement is JsonPrimitive) { + Long.fromJsonPrimitive(jsonElement) + } else { + throw JsonParseException("Unable to parse json into type " + + "kotlin.Long") + } + } catch (e: JsonParseException) { + errors.add(e) + null + } + val result = arrayOf( + asString, + asLong, + ).firstOrNull { it != null } + if (result == null) { + val message = "Unable to parse json into one of type \n" + "Path\n" + + errors.joinToString("\n") { it.message.toString() } + throw JsonParseException(message) + } + return result + } + } + } +} diff --git a/build-logic/src/test/kotlin/com/example/model/OneOfPrimitiveRef.kt b/build-logic/src/test/kotlin/com/example/model/OneOfPrimitiveRef.kt new file mode 100644 index 0000000000..c3b8b3754d --- /dev/null +++ b/build-logic/src/test/kotlin/com/example/model/OneOfPrimitiveRef.kt @@ -0,0 +1,216 @@ +package com.example.model + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonParser +import com.google.gson.JsonPrimitive +import java.lang.IllegalStateException +import java.lang.NullPointerException +import java.lang.NumberFormatException +import java.lang.UnsupportedOperationException +import kotlin.String +import kotlin.jvm.JvmStatic +import kotlin.jvm.Throws + +/** + * @param from This is a definition of a path + * @param to This is a definition of a path + */ +public data class OneOfPrimitiveRef( + public val from: Path, + public val to: Path? = null, +) { + public fun toJson(): JsonElement { + val json = JsonObject() + json.add("from", from.toJson()) + to?.let { toNonNull -> + json.add("to", toNonNull.toJson()) + } + return json + } + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: String): OneOfPrimitiveRef { + try { + val jsonObject = JsonParser.parseString(jsonString).asJsonObject + return fromJsonObject(jsonObject) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into type OneOfPrimitiveRef", + e + ) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonObject(jsonObject: JsonObject): OneOfPrimitiveRef { + try { + val from = jsonObject.get("from").let { + Path.fromJsonElement(it) + } + val to = jsonObject.get("to")?.let { + Path.fromJsonElement(it) + } + return OneOfPrimitiveRef(from, to) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into type OneOfPrimitiveRef", + e + ) + } catch (e: NumberFormatException) { + throw JsonParseException( + "Unable to parse json into type OneOfPrimitiveRef", + e + ) + } catch (e: NullPointerException) { + throw JsonParseException( + "Unable to parse json into type OneOfPrimitiveRef", + e + ) + } + } + } + + /** + * This is a definition of a path + */ + public sealed class Path { + public abstract fun toJson(): JsonElement + + /** + * string element + */ + public data class String( + public val item: kotlin.String, + ) : Path() { + override fun toJson(): JsonElement = JsonPrimitive(item) + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): String { + val jsonElement = JsonParser.parseString(jsonString) + try { + return fromJsonPrimitive(jsonElement.asJsonPrimitive) + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type String", e) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonPrimitive(jsonPrimitive: JsonPrimitive): String { + try { + if (jsonPrimitive.isString) { + return String(jsonPrimitive.asString) + } else { + throw JsonParseException("Can't convert jsonPrimitive to String") + } + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type String", e) + } catch (e: UnsupportedOperationException) { + throw JsonParseException("Unable to parse json into type String", e) + } + } + } + } + + /** + * integer element + */ + public data class Long( + public val item: kotlin.Long, + ) : Path() { + override fun toJson(): JsonElement = JsonPrimitive(item) + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): Long { + val jsonElement = JsonParser.parseString(jsonString) + try { + return fromJsonPrimitive(jsonElement.asJsonPrimitive) + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type Long", e) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonPrimitive(jsonPrimitive: JsonPrimitive): Long { + try { + if (jsonPrimitive.isNumber) { + return Long(jsonPrimitive.asLong) + } else { + throw JsonParseException("Can't convert jsonPrimitive to Long") + } + } catch (e: IllegalStateException) { + throw JsonParseException("Unable to parse json into type Long", e) + } catch (e: NumberFormatException) { + throw JsonParseException("Unable to parse json into type Long", e) + } catch (e: UnsupportedOperationException) { + throw JsonParseException("Unable to parse json into type Long", e) + } + } + } + } + + public companion object { + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJson(jsonString: kotlin.String): Path { + try { + val jsonElement = JsonParser.parseString(jsonString) + return fromJsonElement(jsonElement) + } catch (e: IllegalStateException) { + throw JsonParseException( + "Unable to parse json into one of type Path", + e + ) + } + } + + @JvmStatic + @Throws(JsonParseException::class) + public fun fromJsonElement(jsonElement: JsonElement): Path { + val errors = mutableListOf() + val asString = try { + if (jsonElement is JsonPrimitive) { + String.fromJsonPrimitive(jsonElement) + } else { + throw JsonParseException("Unable to parse json into type " + + "kotlin.String") + } + } catch (e: JsonParseException) { + errors.add(e) + null + } + val asLong = try { + if (jsonElement is JsonPrimitive) { + Long.fromJsonPrimitive(jsonElement) + } else { + throw JsonParseException("Unable to parse json into type " + + "kotlin.Long") + } + } catch (e: JsonParseException) { + errors.add(e) + null + } + val result = arrayOf( + asString, + asLong, + ).firstOrNull { it != null } + if (result == null) { + val message = "Unable to parse json into one of type \n" + "Path\n" + + errors.joinToString("\n") { it.message.toString() } + throw JsonParseException(message) + } + return result + } + } + } +} diff --git a/build-logic/src/test/resources/input/cross_file_one_of_primitive.json b/build-logic/src/test/resources/input/cross_file_one_of_primitive.json new file mode 100644 index 0000000000..7744920ad8 --- /dev/null +++ b/build-logic/src/test/resources/input/cross_file_one_of_primitive.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Path", + "description": "This is a definition of a path", + "oneOf": [ + { + "type": "string", + "description": "string element" + }, + { + "type": "integer", + "description": "integer element" + } + ] +} diff --git a/build-logic/src/test/resources/input/cross_file_one_of_primitive_a.json b/build-logic/src/test/resources/input/cross_file_one_of_primitive_a.json new file mode 100644 index 0000000000..d1765a0547 --- /dev/null +++ b/build-logic/src/test/resources/input/cross_file_one_of_primitive_a.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CrossFileOneOfPrimitiveA", + "type": "object", + "required": ["path"], + "properties": { + "path": { "$ref": "cross_file_one_of_primitive.json" } + } +} diff --git a/build-logic/src/test/resources/input/cross_file_one_of_primitive_b.json b/build-logic/src/test/resources/input/cross_file_one_of_primitive_b.json new file mode 100644 index 0000000000..13f6141fe8 --- /dev/null +++ b/build-logic/src/test/resources/input/cross_file_one_of_primitive_b.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CrossFileOneOfPrimitiveB", + "type": "object", + "required": ["path"], + "properties": { + "path": { "$ref": "cross_file_one_of_primitive.json" } + } +} diff --git a/build-logic/src/test/resources/input/one_of_primitive_ref.json b/build-logic/src/test/resources/input/one_of_primitive_ref.json new file mode 100644 index 0000000000..839a44364e --- /dev/null +++ b/build-logic/src/test/resources/input/one_of_primitive_ref.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OneOfPrimitiveRef", + "type": "object", + "properties": { + "from": { "$ref": "#/definitions/path" }, + "to": { "$ref": "#/definitions/path" } + }, + "required": ["from"], + "definitions": { + "path": { + "title": "Path", + "description": "This is a definition of a path", + "oneOf": [ + { + "type": "string", + "description": "string element" + }, + { + "type": "integer", + "description": "integer element" + } + ] + } + } +}