Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ This project hosts the following modules:
- `features/dd-sdk-android-trace-otel`: an extension of Trace library to integrate with OpenTelemetry;
- `features/dd-sdk-android-webview`: a library to forward logs and RUM events captured in a webview to be linked with the mobile session;
- `integrations/***`: a set of libraries integrating Datadog products in third party libraries:
- `integrations/dd-sdk-android-apollo`: a lightweight library providing a bridge integration between Datadog SDK and [Apollo Kotlin](https://github.com/apollographql/apollo-kotlin)
- `integrations/dd-sdk-android-coil`: a lightweight library providing a bridge integration between Datadog SDK and [Coil](https://coil-kt.github.io/coil/);
- `integrations/dd-sdk-android-compose`: a lightweight library providing a bridge integration between Datadog SDK and [Jetpack Compose](https://developer.android.com/jetpack/compose);
- `integrations/dd-sdk-android-fresco`: a lightweight library providing a bridge integration between Datadog SDK and [Fresco](https://frescolib.org/);
Expand Down
2 changes: 2 additions & 0 deletions LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import,androidx.versionedparcelable,Apache-2.0,Copyright 2018 The Android Open S
import,androidx.viewpager,Apache-2.0,Copyright 2018 The Android Open Source Project
import,androidx.work,Apache-2.0,Copyright 2018 The Android Open Source Project
import,com.android.tools,Apache-2.0,Copyright 2018 The Android Open Source Project
import,com.apollographql.apollo,MIT,"Copyright (c) 2016-2024 Apollo Graph, Inc. (Formerly Meteor Development Group, Inc.)"
import,com.benasher44,MIT,"Copyright (c) 2019 Ben Asher"
import,com.github.bumptech.glide,"BSD 3-Clause","Copyright 2014 Google, Inc. All rights reserved, Copyright (c) 2013. Bump Technologies Inc. All Rights Reserved."
import,com.facebook.fresco,MIT,"Copyright (c) Facebook, Inc. and its affiliates"
import,com.github.spotbugs,"GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1","Copyright (C) 1991, 1999 Free Software Foundation, Inc."
Expand Down
17 changes: 17 additions & 0 deletions ci/pipelines/default-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,23 @@ publish:release-webview:

# region Publish integrations/*

publish:release-apollo:
tags: [ "arch:amd64" ]
only:
- tags
- develop
image: $CI_IMAGE_DOCKER
stage: publish
timeout: 30m
script:
- !reference [.snippets, set-publishing-credentials]
- ./gradlew :integrations:dd-sdk-android-apollo:publishToSonatype closeSonatypeStagingRepository --stacktrace --no-daemon
artifacts:
when: on_success
expire_in: 7 days
paths:
- integrations/dd-sdk-android-apollo/verification-metadata.xml

publish:release-coil:
tags: [ "arch:amd64" ]
only:
Expand Down
6 changes: 6 additions & 0 deletions dd-sdk-android-internal/api/apiSurface
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ class com.datadog.android.internal.concurrent.CompletableFuture<T: Any>
var value: T
fun isComplete(): Boolean
fun complete(T)
enum com.datadog.android.internal.network.GraphQLHeaders
constructor(String)
- DD_GRAPHQL_NAME_HEADER
- DD_GRAPHQL_VARIABLES_HEADER
- DD_GRAPHQL_TYPE_HEADER
- DD_GRAPHQL_PAYLOAD_HEADER
interface com.datadog.android.internal.profiler.BenchmarkCounter
fun add(Long, Map<String, String>)
interface com.datadog.android.internal.profiler.BenchmarkMeter
Expand Down
10 changes: 10 additions & 0 deletions dd-sdk-android-internal/api/dd-sdk-android-internal.api
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ public final class com/datadog/android/internal/concurrent/CompletableFuture {
public final fun isComplete ()Z
}

public final class com/datadog/android/internal/network/GraphQLHeaders : java/lang/Enum {
public static final field DD_GRAPHQL_NAME_HEADER Lcom/datadog/android/internal/network/GraphQLHeaders;
public static final field DD_GRAPHQL_PAYLOAD_HEADER Lcom/datadog/android/internal/network/GraphQLHeaders;
public static final field DD_GRAPHQL_TYPE_HEADER Lcom/datadog/android/internal/network/GraphQLHeaders;
public static final field DD_GRAPHQL_VARIABLES_HEADER Lcom/datadog/android/internal/network/GraphQLHeaders;
public final fun getHeaderValue ()Ljava/lang/String;
public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/internal/network/GraphQLHeaders;
public static fun values ()[Lcom/datadog/android/internal/network/GraphQLHeaders;
}

public abstract interface class com/datadog/android/internal/profiler/BenchmarkCounter {
public abstract fun add (JLjava/util/Map;)V
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.internal.network

/**
* Headers used internally for intercepting GraphQL requests.
* @param headerValue name of the header.
*/
enum class GraphQLHeaders(val headerValue: String) {
DD_GRAPHQL_NAME_HEADER("_dd-custom-header-graph-ql-operation-name"),
DD_GRAPHQL_VARIABLES_HEADER("_dd-custom-header-graph-ql-variables"),
DD_GRAPHQL_TYPE_HEADER("_dd-custom-header-graph-ql-operation_type"),
DD_GRAPHQL_PAYLOAD_HEADER("_dd-custom-header-graph-ql-payload")
}
10 changes: 10 additions & 0 deletions detekt_custom_safe_calls.yml
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,16 @@ datadog:
# region Google Material
- "com.google.android.material.tabs.TabLayout.TabView.getChildAt(kotlin.Int)"
# endregion
# region Apollo Kotlin
- "com.apollographql.apollo.api.json.buildJsonString(kotlin.String?, kotlin.Function1)"
- "com.apollographql.apollo.api.ApolloRequest.newBuilder()"
- "com.apollographql.apollo.api.ApolloRequest.Builder.addHttpHeader(kotlin.String, kotlin.String)"
- "com.apollographql.apollo.api.ApolloRequest.Builder.build()"
- "com.apollographql.apollo.api.Operation.composeJsonRequest(com.apollographql.apollo.api.json.JsonWriter, com.apollographql.apollo.api.CustomScalarAdapters)"
- "com.apollographql.apollo.api.Operation.document()"
- "com.apollographql.apollo.api.Operation.name()"
- "com.apollographql.apollo.interceptor.ApolloInterceptorChain.proceed(com.apollographql.apollo.api.ApolloRequest)"
# endregion
# region Glide
- "com.bumptech.glide.GlideBuilder.setDiskCacheExecutor(com.bumptech.glide.load.engine.executor.GlideExecutor?)"
- "com.bumptech.glide.GlideBuilder.setSourceExecutor(com.bumptech.glide.load.engine.executor.GlideExecutor?)"
Expand Down
3 changes: 3 additions & 0 deletions detekt_custom_unsafe_calls.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ datadog:
- "androidx.work.WorkManager.enqueueUniqueWork(kotlin.String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest):java.util.concurrent.RejectedExecutionException,java.lang.NullPointerException"
- "androidx.work.Data.Builder.build():java.lang.IllegalStateException"
# endregion
# region Apollo Kotlin
- "com.apollographql.apollo.api.Operation.variablesJson(com.apollographql.apollo.api.CustomScalarAdapters):java.io.IOException"
# endregion
# region Java File
- "java.io.ByteArrayOutputStream.write(kotlin.ByteArray, kotlin.Int, kotlin.Int):java.lang.IndexOutOfBoundsException"
- "java.io.File.canRead():java.lang.SecurityException"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,22 +107,22 @@ object RumAttributes {
const val RESOURCE_TIMINGS: String = "_dd.resource_timings"

/**
* GraphQL operation type coming from cross-platform SDK (String).
* GraphQL operation type (String).
*/
const val GRAPHQL_OPERATION_TYPE: String = "_dd.graphql.operation_type"

/**
* GraphQL operation name coming from cross-platform SDK (String).
* GraphQL operation name (String).
*/
const val GRAPHQL_OPERATION_NAME: String = "_dd.graphql.operation_name"

/**
* JSON representation of GraphQL payload coming from cross-platform SDK (String).
* JSON representation of GraphQL payload (String).
*/
const val GRAPHQL_PAYLOAD: String = "_dd.graphql.payload"

/**
* JSON representation of GraphQL variables type coming from cross-platform SDK (String).
* JSON representation of GraphQL variables (String).
*/
const val GRAPHQL_VARIABLES: String = "_dd.graphql.variables"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,20 @@ internal class RumResourceScope(
val finalTiming = timing ?: extractResourceTiming(
resourceAttributes.remove(RumAttributes.RESOURCE_TIMINGS) as? Map<String, Any?>
)

val graphqlOperationName = resourceAttributes.remove(RumAttributes.GRAPHQL_OPERATION_NAME) as? String
val graphqlOperationType = resourceAttributes.remove(RumAttributes.GRAPHQL_OPERATION_TYPE) as? String
val graphqlVariables = resourceAttributes.remove(RumAttributes.GRAPHQL_VARIABLES) as? String

// The decision whether to send payloads is determined by a DatadogApolloInterceptor parameter
val rawPayload = resourceAttributes.remove(RumAttributes.GRAPHQL_PAYLOAD) as? String
val graphqlPayload = rawPayload?.let { truncateGraphQLPayload(it) }

val graphql = resolveGraphQLAttributes(
resourceAttributes.remove(RumAttributes.GRAPHQL_OPERATION_TYPE) as? String?,
resourceAttributes.remove(RumAttributes.GRAPHQL_OPERATION_NAME) as? String?,
resourceAttributes.remove(RumAttributes.GRAPHQL_PAYLOAD) as? String?,
resourceAttributes.remove(RumAttributes.GRAPHQL_VARIABLES) as? String?
operationType = graphqlOperationType,
operationName = graphqlOperationName,
variables = graphqlVariables,
payload = graphqlPayload
)

sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer) {
Expand Down Expand Up @@ -533,24 +542,58 @@ internal class RumResourceScope(
private fun resolveGraphQLAttributes(
operationType: String?,
operationName: String?,
payload: String?,
variables: String?
variables: String?,
payload: String?
): ResourceEvent.Graphql? {
operationType?.toOperationType(sdkCore.internalLogger)?.let {
return ResourceEvent.Graphql(
it,
operationName,
payload,
variables
operationType = it,
operationName = operationName,
variables = variables,
payload = payload
)
}

return null
}

private fun truncateGraphQLPayload(payload: String): String {
val payloadBytes = payload.toByteArray(Charsets.UTF_8)

return if (payloadBytes.size <= MAX_GRAPHQL_PAYLOAD_SIZE_BYTES) {
payload
} else {
// We know the string is too long, so work backwards from the end
// to find where to cut without breaking UTF-8 characters
val excessBytes = payloadBytes.size - MAX_GRAPHQL_PAYLOAD_SIZE_BYTES

// Start from the end and work backwards, checking only the characters
// that might be affected by the truncation
var bytesToRemove = 0
var charactersToRemove = 0

for (i in payload.length - 1 downTo 0) {
@Suppress("UnsafeThirdPartyFunctionCall") // indexOutOfBounds cant be thrown here
val charByteSize = payload.substring(i, i + 1).toByteArray(Charsets.UTF_8).size

bytesToRemove += charByteSize
charactersToRemove++

// Once we've removed enough bytes to fit within the limit, stop
if (bytesToRemove >= excessBytes) {
break
}
}

@Suppress("UnsafeThirdPartyFunctionCall") // indexOutOfBounds cant be thrown here
payload.substring(0, payload.length - charactersToRemove)
}
}
Comment on lines +560 to +591
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a simpler and more efficient way to do this (in the end, manipulating something over 30Kb in the memory may have performance impact):

  • we can avoid redundant val payloadBytes = payload.toByteArray(Charsets.UTF_8) allocation by checking payload.length first: since strings are stored as UTF-16 in runtime and UTF-16 is two 16-bit code units max for code point, if payload.length == codepoint number it means that if payload.length is less than 7500, it is definitely below 30Kb limit (7500*4). If it is more, than we need to have a more precise look by getting exact bytes.
  • we can truncate easier: you can take advantage from the fact that in UTF-8 continuation byte is always starting from 10 https://en.wikipedia.org/wiki/UTF-8#Description, so we just need to slice (MAX_BYTES - something) where something is 0 if payloadBytes[MAX_BYTES] starts from 0 or some number which can be found by going from the tail till the closest byte starting from 11.

Not sure about the performance impact, but it seems it will at least have less allocations.


// endregion

companion object {
internal const val MAX_GRAPHQL_PAYLOAD_SIZE_BYTES = 30 * 1024

internal const val NEGATIVE_DURATION_WARNING_MESSAGE = "The computed duration for your " +
"resource: %s was 0 or negative. In order to keep the resource event" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,15 @@ internal class ResourceEventAssert(actual: ResourceEvent) :
return this
}

fun hasNoGraphql(): ResourceEventAssert {
assertThat(actual.resource.graphql)
.overridingErrorMessage(
"Expected event data to have no resource.graphql but was ${actual.resource.graphql}"
)
.isNull()
return this
}

companion object {

internal const val TIMESTAMP_THRESHOLD_MS = 50L
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,6 @@ internal class RumApplicationScopeAttributePropagationTest {
accessibilitySnapshotManager = mockAccessibilitySnapshotManager,
batteryInfoProvider = mockBatteryInfoProvider,
displayInfoProvider = mockDisplayInfoProvider

)
}

Expand Down
Loading
Loading