diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fa5ba47d6..d894e20a00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ - `globalHubMode` used to only be a param on `Sentry.init`. To make it easier to be used in e.g. Desktop environments, we now additionally added it as an option on SentryOptions that can also be set via `sentry.properties`. - If both the param on `Sentry.init` and the option are set, the option will win. By default the option is set to `null` meaning whatever is passed to `Sentry.init` takes effect. - Lazy uuid generation for SentryId and SpanId ([#3770](https://github.com/getsentry/sentry-java/pull/3770)) +- Faster generation of Sentry and Span IDs ([#3818](https://github.com/getsentry/sentry-java/pull/3818)) + - Uses faster implementation to convert UUID to SentryID String + - Uses faster Random implementation to generate UUIDs - Use a separate `Random` instance per thread to improve SDK performance ([#3835](https://github.com/getsentry/sentry-java/pull/3835)) - Android 15: Add support for 16KB page sizes ([#3851](https://github.com/getsentry/sentry-java/pull/3851)) - See https://developer.android.com/guide/practices/page-sizes for more details diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java index f10dbef109..c1ae1237e2 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java @@ -12,6 +12,7 @@ import io.sentry.MemoryCollectionData; import io.sentry.PerformanceCollectionData; import io.sentry.SentryLevel; +import io.sentry.SentryUUID; import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; import io.sentry.profilemeasurements.ProfileMeasurement; import io.sentry.profilemeasurements.ProfileMeasurementValue; @@ -23,7 +24,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.UUID; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; @@ -129,7 +129,7 @@ public AndroidProfiler( } // We create a file with a uuid name, so no need to check if it already exists - traceFile = new File(traceFilesDir, UUID.randomUUID() + ".trace"); + traceFile = new File(traceFilesDir, SentryUUID.generateSentryId() + ".trace"); measurementsMap.clear(); screenFrameRateMeasurements.clear(); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java b/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java index 4c9b5ddbc2..ba08e71342 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java @@ -2,6 +2,7 @@ import android.content.Context; import io.sentry.ISentryLifecycleToken; +import io.sentry.SentryUUID; import io.sentry.util.AutoClosableReentrantLock; import java.io.File; import java.io.FileOutputStream; @@ -9,7 +10,6 @@ import java.io.OutputStream; import java.io.RandomAccessFile; import java.nio.charset.Charset; -import java.util.UUID; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @@ -65,7 +65,7 @@ public static String id(final @NotNull Context context) throws RuntimeException static @NotNull String writeInstallationFile(final @NotNull File installation) throws IOException { try (final OutputStream out = new FileOutputStream(installation)) { - final String id = UUID.randomUUID().toString(); + final String id = SentryUUID.generateSentryId(); out.write(id.getBytes(UTF_8)); out.flush(); return id; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java index 25ff5da2bd..ef509228dc 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java @@ -16,6 +16,7 @@ import io.sentry.ILogger; import io.sentry.SentryLevel; import io.sentry.SentryOptions; +import io.sentry.SentryUUID; import io.sentry.android.core.BuildInfoProvider; import io.sentry.android.core.ContextUtils; import io.sentry.util.Objects; @@ -23,7 +24,6 @@ import java.lang.reflect.Field; import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; @@ -262,7 +262,7 @@ public void onActivityDestroyed(@NotNull Activity activity) {} if (!isAvailable) { return null; } - final String uid = UUID.randomUUID().toString(); + final String uid = SentryUUID.generateSentryId(); listenerMap.put(uid, listener); trackCurrentWindow(); return uid; diff --git a/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts b/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts index cebf744a24..da7add25cc 100644 --- a/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts +++ b/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts @@ -17,7 +17,7 @@ android { defaultConfig { applicationId = "io.sentry.uitest.android.critical" - minSdk = Config.Android.minSdkVersionCompose + minSdk = Config.Android.minSdkVersion targetSdk = Config.Android.targetSdkVersion versionCode = 1 versionName = "1.0" diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 1bbe484f7b..cd641fe935 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -3265,6 +3265,11 @@ public final class io/sentry/SentryTracer : io/sentry/ITransaction { public fun updateEndDate (Lio/sentry/SentryDate;)Z } +public final class io/sentry/SentryUUID { + public static fun generateSentryId ()Ljava/lang/String; + public static fun generateSpanId ()Ljava/lang/String; +} + public final class io/sentry/SentryWrapper { public fun ()V public static fun wrapCallable (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Callable; @@ -6198,6 +6203,20 @@ public final class io/sentry/util/TracingUtils$TracingHeaders { public fun getSentryTraceHeader ()Lio/sentry/SentryTraceHeader; } +public final class io/sentry/util/UUIDGenerator { + public fun ()V + public static fun randomHalfLengthUUID ()J + public static fun randomUUID ()Ljava/util/UUID; +} + +public final class io/sentry/util/UUIDStringUtils { + public fun ()V + public static fun toSentryIdString (JJ)Ljava/lang/String; + public static fun toSentryIdString (Ljava/util/UUID;)Ljava/lang/String; + public static fun toSentrySpanIdString (J)Ljava/lang/String; + public static fun toSentrySpanIdString (Ljava/util/UUID;)Ljava/lang/String; +} + public final class io/sentry/util/UrlUtils { public static final field SENSITIVE_DATA_SUBSTITUTE Ljava/lang/String; public fun ()V diff --git a/sentry/src/main/java/io/sentry/ProfilingTraceData.java b/sentry/src/main/java/io/sentry/ProfilingTraceData.java index 3998735917..44d74a3eb7 100644 --- a/sentry/src/main/java/io/sentry/ProfilingTraceData.java +++ b/sentry/src/main/java/io/sentry/ProfilingTraceData.java @@ -10,7 +10,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import org.jetbrains.annotations.ApiStatus; @@ -154,7 +153,7 @@ public ProfilingTraceData( // Stacktrace context this.transactionId = transactionId; this.traceId = traceId; - this.profileId = UUID.randomUUID().toString(); + this.profileId = SentryUUID.generateSentryId(); this.environment = environment != null ? environment : DEFAULT_ENVIRONMENT; this.truncationReason = truncationReason; if (!isTruncationReasonValid()) { diff --git a/sentry/src/main/java/io/sentry/SentryUUID.java b/sentry/src/main/java/io/sentry/SentryUUID.java new file mode 100644 index 0000000000..14050fd86a --- /dev/null +++ b/sentry/src/main/java/io/sentry/SentryUUID.java @@ -0,0 +1,23 @@ +package io.sentry; + +import io.sentry.util.UUIDGenerator; +import io.sentry.util.UUIDStringUtils; + +/** + * SentryUUID is a utility class for generating Sentry-specific ID Strings. It provides methods for + * generating Sentry IDs and Sentry Span IDs. + */ +public final class SentryUUID { + + private SentryUUID() { + // A private constructor prevents callers from accidentally instantiating FastUUID objects + } + + public static String generateSentryId() { + return UUIDStringUtils.toSentryIdString(UUIDGenerator.randomUUID()); + } + + public static String generateSpanId() { + return UUIDStringUtils.toSentrySpanIdString(UUIDGenerator.randomHalfLengthUUID()); + } +} diff --git a/sentry/src/main/java/io/sentry/SpanId.java b/sentry/src/main/java/io/sentry/SpanId.java index 7857919e02..fcc7f3a4f3 100644 --- a/sentry/src/main/java/io/sentry/SpanId.java +++ b/sentry/src/main/java/io/sentry/SpanId.java @@ -3,14 +3,13 @@ import static io.sentry.util.StringUtils.PROPER_NIL_UUID; import io.sentry.util.LazyEvaluator; -import io.sentry.util.StringUtils; import java.io.IOException; import java.util.Objects; -import java.util.UUID; import org.jetbrains.annotations.NotNull; public final class SpanId implements JsonSerializable { - public static final SpanId EMPTY_ID = new SpanId(PROPER_NIL_UUID); + public static final SpanId EMPTY_ID = + new SpanId(PROPER_NIL_UUID.replace("-", "").substring(0, 16)); private final @NotNull LazyEvaluator lazyValue; @@ -20,12 +19,7 @@ public SpanId(final @NotNull String value) { } public SpanId() { - this.lazyValue = - new LazyEvaluator<>( - () -> - StringUtils.normalizeUUID(UUID.randomUUID().toString()) - .replace("-", "") - .substring(0, 16)); + this.lazyValue = new LazyEvaluator<>(SentryUUID::generateSpanId); } @Override diff --git a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java index 0255d05a18..8117002daf 100644 --- a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java +++ b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java @@ -16,6 +16,7 @@ import io.sentry.SentryItemType; import io.sentry.SentryLevel; import io.sentry.SentryOptions; +import io.sentry.SentryUUID; import io.sentry.Session; import io.sentry.UncaughtExceptionHandlerIntegration; import io.sentry.hints.AbnormalExit; @@ -45,7 +46,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.UUID; import java.util.WeakHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -368,7 +368,7 @@ public void discard(final @NotNull SentryEnvelope envelope) { if (fileNameMap.containsKey(envelope)) { fileName = fileNameMap.get(envelope); } else { - fileName = UUID.randomUUID() + SUFFIX_ENVELOPE_FILE; + fileName = SentryUUID.generateSentryId() + SUFFIX_ENVELOPE_FILE; fileNameMap.put(envelope, fileName); } diff --git a/sentry/src/main/java/io/sentry/protocol/SentryId.java b/sentry/src/main/java/io/sentry/protocol/SentryId.java index bf1bde0c4d..a5bd7980c3 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryId.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryId.java @@ -5,8 +5,10 @@ import io.sentry.JsonSerializable; import io.sentry.ObjectReader; import io.sentry.ObjectWriter; +import io.sentry.SentryUUID; import io.sentry.util.LazyEvaluator; import io.sentry.util.StringUtils; +import io.sentry.util.UUIDStringUtils; import java.io.IOException; import java.util.UUID; import org.jetbrains.annotations.NotNull; @@ -14,7 +16,8 @@ public final class SentryId implements JsonSerializable { - public static final SentryId EMPTY_ID = new SentryId(new UUID(0, 0)); + public static final SentryId EMPTY_ID = + new SentryId(StringUtils.PROPER_NIL_UUID.replace("-", "")); private final @NotNull LazyEvaluator lazyStringValue; @@ -24,9 +27,10 @@ public SentryId() { public SentryId(@Nullable UUID uuid) { if (uuid != null) { - this.lazyStringValue = new LazyEvaluator<>(() -> normalize(uuid.toString())); + this.lazyStringValue = + new LazyEvaluator<>(() -> normalize(UUIDStringUtils.toSentryIdString(uuid))); } else { - this.lazyStringValue = new LazyEvaluator<>(() -> normalize(UUID.randomUUID().toString())); + this.lazyStringValue = new LazyEvaluator<>(SentryUUID::generateSentryId); } } @@ -38,7 +42,11 @@ public SentryId(final @NotNull String sentryIdString) { + "or 36 characters long (completed UUID). Received: " + sentryIdString); } - this.lazyStringValue = new LazyEvaluator<>(() -> normalize(normalized)); + if (normalized.length() == 36) { + this.lazyStringValue = new LazyEvaluator<>(() -> normalize(normalized)); + } else { + this.lazyStringValue = new LazyEvaluator<>(() -> normalized); + } } @Override diff --git a/sentry/src/main/java/io/sentry/util/UUIDGenerator.java b/sentry/src/main/java/io/sentry/util/UUIDGenerator.java new file mode 100644 index 0000000000..cbe90f96d8 --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/UUIDGenerator.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package io.sentry.util; + +import java.util.UUID; + +/** + * Utility class for generating UUIDs and half-length (1 long) UUIDs. Adapted from `java.util.UUID` + * to use a faster random number generator. + */ +public final class UUIDGenerator { + + @SuppressWarnings("NarrowingCompoundAssignment") + public static long randomHalfLengthUUID() { + Random random = SentryRandom.current(); + byte[] randomBytes = new byte[8]; + random.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + + long msb = 0; + + for (int i = 0; i < 8; i++) msb = (msb << 8) | (randomBytes[i] & 0xff); + + return msb; + } + + @SuppressWarnings("NarrowingCompoundAssignment") + public static UUID randomUUID() { + Random random = SentryRandom.current(); + byte[] randomBytes = new byte[16]; + random.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= (byte) 0x80; /* set to IETF variant */ + + long msb = 0; + long lsb = 0; + + for (int i = 0; i < 8; i++) msb = (msb << 8) | (randomBytes[i] & 0xff); + + for (int i = 8; i < 16; i++) lsb = (lsb << 8) | (randomBytes[i] & 0xff); + + return new UUID(msb, lsb); + } +} diff --git a/sentry/src/main/java/io/sentry/util/UUIDStringUtils.java b/sentry/src/main/java/io/sentry/util/UUIDStringUtils.java new file mode 100644 index 0000000000..ffff5a8155 --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/UUIDStringUtils.java @@ -0,0 +1,141 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Jon Chambers + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.sentry.util; + +import java.util.Arrays; +import java.util.UUID; + +/** + * Utility class to convert UUIDs and longs to Sentry ID Strings + * + *

Adapted from Jon Chambers' work here. + */ +public final class UUIDStringUtils { + + private static final int SENTRY_UUID_STRING_LENGTH = 32; + private static final int SENTRY_SPAN_UUID_STRING_LENGTH = 16; + + private static final char[] HEX_DIGITS = + new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + private static final long[] HEX_VALUES = new long[128]; + + static { + Arrays.fill(HEX_VALUES, -1); + + HEX_VALUES['0'] = 0x0; + HEX_VALUES['1'] = 0x1; + HEX_VALUES['2'] = 0x2; + HEX_VALUES['3'] = 0x3; + HEX_VALUES['4'] = 0x4; + HEX_VALUES['5'] = 0x5; + HEX_VALUES['6'] = 0x6; + HEX_VALUES['7'] = 0x7; + HEX_VALUES['8'] = 0x8; + HEX_VALUES['9'] = 0x9; + + HEX_VALUES['a'] = 0xa; + HEX_VALUES['b'] = 0xb; + HEX_VALUES['c'] = 0xc; + HEX_VALUES['d'] = 0xd; + HEX_VALUES['e'] = 0xe; + HEX_VALUES['f'] = 0xf; + + HEX_VALUES['A'] = 0xa; + HEX_VALUES['B'] = 0xb; + HEX_VALUES['C'] = 0xc; + HEX_VALUES['D'] = 0xd; + HEX_VALUES['E'] = 0xe; + HEX_VALUES['F'] = 0xf; + } + + public static String toSentryIdString(final UUID uuid) { + + final long mostSignificantBits = uuid.getMostSignificantBits(); + final long leastSignificantBits = uuid.getLeastSignificantBits(); + + return toSentryIdString(mostSignificantBits, leastSignificantBits); + } + + public static String toSentryIdString(long mostSignificantBits, long leastSignificantBits) { + final char[] uuidChars = new char[SENTRY_UUID_STRING_LENGTH]; + + fillMostSignificantBits(uuidChars, mostSignificantBits); + + uuidChars[16] = HEX_DIGITS[(int) ((leastSignificantBits & 0xf000000000000000L) >>> 60)]; + uuidChars[17] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0f00000000000000L) >>> 56)]; + uuidChars[18] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00f0000000000000L) >>> 52)]; + uuidChars[19] = HEX_DIGITS[(int) ((leastSignificantBits & 0x000f000000000000L) >>> 48)]; + uuidChars[20] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0000f00000000000L) >>> 44)]; + uuidChars[21] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00000f0000000000L) >>> 40)]; + uuidChars[22] = HEX_DIGITS[(int) ((leastSignificantBits & 0x000000f000000000L) >>> 36)]; + uuidChars[23] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0000000f00000000L) >>> 32)]; + uuidChars[24] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00000000f0000000L) >>> 28)]; + uuidChars[25] = HEX_DIGITS[(int) ((leastSignificantBits & 0x000000000f000000L) >>> 24)]; + uuidChars[26] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0000000000f00000L) >>> 20)]; + uuidChars[27] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00000000000f0000L) >>> 16)]; + uuidChars[28] = HEX_DIGITS[(int) ((leastSignificantBits & 0x000000000000f000L) >>> 12)]; + uuidChars[29] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0000000000000f00L) >>> 8)]; + uuidChars[30] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00000000000000f0L) >>> 4)]; + uuidChars[31] = HEX_DIGITS[(int) (leastSignificantBits & 0x000000000000000fL)]; + + return new String(uuidChars); + } + + public static String toSentrySpanIdString(final UUID uuid) { + + final long mostSignificantBits = uuid.getMostSignificantBits(); + return toSentrySpanIdString(mostSignificantBits); + } + + public static String toSentrySpanIdString(long mostSignificantBits) { + final char[] uuidChars = new char[SENTRY_SPAN_UUID_STRING_LENGTH]; + + fillMostSignificantBits(uuidChars, mostSignificantBits); + + return new String(uuidChars); + } + + private static void fillMostSignificantBits( + final char[] uuidChars, final long mostSignificantBits) { + uuidChars[0] = HEX_DIGITS[(int) ((mostSignificantBits & 0xf000000000000000L) >>> 60)]; + uuidChars[1] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0f00000000000000L) >>> 56)]; + uuidChars[2] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00f0000000000000L) >>> 52)]; + uuidChars[3] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000f000000000000L) >>> 48)]; + uuidChars[4] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000f00000000000L) >>> 44)]; + uuidChars[5] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000f0000000000L) >>> 40)]; + uuidChars[6] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000f000000000L) >>> 36)]; + uuidChars[7] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000000f00000000L) >>> 32)]; + uuidChars[8] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000000f0000000L) >>> 28)]; + uuidChars[9] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000000f000000L) >>> 24)]; + uuidChars[10] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000000000f00000L) >>> 20)]; + uuidChars[11] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000000000f0000L) >>> 16)]; + uuidChars[12] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000000000f000L) >>> 12)]; + uuidChars[13] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000000000000f00L) >>> 8)]; + uuidChars[14] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000000000000f0L) >>> 4)]; + uuidChars[15] = HEX_DIGITS[(int) (mostSignificantBits & 0x000000000000000fL)]; + } +} diff --git a/sentry/src/test/java/io/sentry/SentryUUIDTest.kt b/sentry/src/test/java/io/sentry/SentryUUIDTest.kt new file mode 100644 index 0000000000..cdb8b92684 --- /dev/null +++ b/sentry/src/test/java/io/sentry/SentryUUIDTest.kt @@ -0,0 +1,19 @@ +package io.sentry + +import junit.framework.TestCase.assertEquals +import kotlin.test.Test + +class SentryUUIDTest { + + @Test + fun `generated SentryID is 32 characters long`() { + val sentryId = SentryUUID.generateSentryId() + assertEquals(32, sentryId.length) + } + + @Test + fun `generated SpanID is 16 characters long`() { + val sentryId = SentryUUID.generateSpanId() + assertEquals(16, sentryId.length) + } +} diff --git a/sentry/src/test/java/io/sentry/UUIDStringUtilsTest.kt b/sentry/src/test/java/io/sentry/UUIDStringUtilsTest.kt new file mode 100644 index 0000000000..c6a1901d93 --- /dev/null +++ b/sentry/src/test/java/io/sentry/UUIDStringUtilsTest.kt @@ -0,0 +1,23 @@ +package io.sentry + +import io.sentry.util.UUIDStringUtils +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertEquals + +class UUIDStringUtilsTest { + + @Test + fun `UUID toString matches UUIDStringUtils to String`() { + val uuid = UUID.randomUUID() + val sentryIdString = uuid.toString().replace("-", "") + assertEquals(sentryIdString, UUIDStringUtils.toSentryIdString(uuid)) + } + + @Test + fun `UUID toString matches UUIDStringUtils to String for SpanId`() { + val uuid = UUID.randomUUID() + val sentryIdString = uuid.toString().replace("-", "").substring(0, 16) + assertEquals(sentryIdString, UUIDStringUtils.toSentrySpanIdString(uuid)) + } +} diff --git a/sentry/src/test/java/io/sentry/protocol/SentryIdTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryIdTest.kt index f26f591156..a517185e1b 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryIdTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryIdTest.kt @@ -1,5 +1,6 @@ package io.sentry.protocol +import io.sentry.SentryUUID import io.sentry.util.StringUtils import org.mockito.Mockito import org.mockito.kotlin.any @@ -17,38 +18,71 @@ class SentryIdTest { assertEquals("00000000000000000000000000000000", id.toString()) } + @Test + fun `dashes are stripped if initialized with 36char uuid string`() { + val uuidString = UUID.randomUUID().toString() + val id = SentryId(uuidString) + assertEquals(uuidString.replace("-", ""), id.toString()) + } + @Test fun `UUID is not generated on initialization`() { - val uuid = UUID.randomUUID() - Mockito.mockStatic(UUID::class.java).use { utils -> - utils.`when` { UUID.randomUUID() }.thenReturn(uuid) + val uuid = SentryUUID.generateSentryId() + Mockito.mockStatic(SentryUUID::class.java).use { utils -> + utils.`when` { SentryUUID.generateSentryId() }.thenReturn(uuid) val ignored = SentryId() - utils.verify({ UUID.randomUUID() }, never()) + utils.verify({ SentryUUID.generateSentryId() }, never()) } } @Test fun `UUID is generated only once`() { - val uuid = UUID.randomUUID() - Mockito.mockStatic(UUID::class.java).use { utils -> - utils.`when` { UUID.randomUUID() }.thenReturn(uuid) + val uuid = SentryUUID.generateSentryId() + Mockito.mockStatic(SentryUUID::class.java).use { utils -> + utils.`when` { SentryUUID.generateSentryId() }.thenReturn(uuid) val sentryId = SentryId() val uuid1 = sentryId.toString() val uuid2 = sentryId.toString() assertEquals(uuid1, uuid2) - utils.verify({ UUID.randomUUID() }, times(1)) + utils.verify({ SentryUUID.generateSentryId() }, times(1)) } } @Test - fun `normalizeUUID is only called once`() { + fun `normalizeUUID is never called when using empty constructor`() { Mockito.mockStatic(StringUtils::class.java).use { utils -> utils.`when` { StringUtils.normalizeUUID(any()) }.thenReturn("00000000000000000000000000000000") val sentryId = SentryId() val uuid1 = sentryId.toString() val uuid2 = sentryId.toString() + assertEquals(uuid1, uuid2) + utils.verify({ StringUtils.normalizeUUID(any()) }, times(0)) + } + } + + @Test + fun `normalizeUUID is only called once when String is passed to constructor`() { + Mockito.mockStatic(StringUtils::class.java).use { utils -> + utils.`when` { StringUtils.normalizeUUID(any()) }.thenReturn("00000000000000000000000000000000") + val sentryId = SentryId("00000000000000000000000000000000") + val uuid1 = sentryId.toString() + val uuid2 = sentryId.toString() + + assertEquals(uuid1, uuid2) + utils.verify({ StringUtils.normalizeUUID(any()) }, times(1)) + } + } + + @Test + fun `normalizeUUID is only called once when UUID is passed to constructor`() { + Mockito.mockStatic(StringUtils::class.java).use { utils -> + utils.`when` { StringUtils.normalizeUUID(any()) }.thenReturn("00000000000000000000000000000000") + val sentryId = SentryId(UUID.randomUUID()) + val uuid1 = sentryId.toString() + val uuid2 = sentryId.toString() + assertEquals(uuid1, uuid2) utils.verify({ StringUtils.normalizeUUID(any()) }, times(1)) } diff --git a/sentry/src/test/java/io/sentry/protocol/SpanIdTest.kt b/sentry/src/test/java/io/sentry/protocol/SpanIdTest.kt index 071d402e2f..f21a9d5930 100644 --- a/sentry/src/test/java/io/sentry/protocol/SpanIdTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SpanIdTest.kt @@ -1,51 +1,36 @@ package io.sentry.protocol +import io.sentry.SentryUUID import io.sentry.SpanId -import io.sentry.util.StringUtils import org.mockito.Mockito -import org.mockito.kotlin.any import org.mockito.kotlin.never import org.mockito.kotlin.times -import java.util.* import kotlin.test.Test import kotlin.test.assertEquals class SpanIdTest { @Test - fun `UUID is not generated on initialization`() { - val uuid = UUID.randomUUID() - Mockito.mockStatic(UUID::class.java).use { utils -> - utils.`when` { UUID.randomUUID() }.thenReturn(uuid) + fun `ID is not generated on initialization`() { + val uuid = SentryUUID.generateSpanId() + Mockito.mockStatic(SentryUUID::class.java).use { utils -> + utils.`when` { SentryUUID.generateSpanId() }.thenReturn(uuid) val ignored = SpanId() - utils.verify({ UUID.randomUUID() }, never()) + utils.verify({ SentryUUID.generateSpanId() }, never()) } } @Test - fun `UUID is generated only once`() { - val uuid = UUID.randomUUID() - Mockito.mockStatic(java.util.UUID::class.java).use { utils -> - utils.`when` { UUID.randomUUID() }.thenReturn(uuid) + fun `ID is generated only once`() { + val uuid = SentryUUID.generateSpanId() + Mockito.mockStatic(SentryUUID::class.java).use { utils -> + utils.`when` { SentryUUID.generateSpanId() }.thenReturn(uuid) val spanId = SpanId() val uuid1 = spanId.toString() val uuid2 = spanId.toString() assertEquals(uuid1, uuid2) - utils.verify({ UUID.randomUUID() }, times(1)) - } - } - - @Test - fun `normalizeUUID is only called once`() { - Mockito.mockStatic(StringUtils::class.java).use { utils -> - utils.`when` { StringUtils.normalizeUUID(any()) }.thenReturn("00000000000000000000000000000000") - val spanId = SpanId() - val uuid1 = spanId.toString() - val uuid2 = spanId.toString() - - assertEquals(uuid1, uuid2) - utils.verify({ StringUtils.normalizeUUID(any()) }, times(1)) + utils.verify({ SentryUUID.generateSpanId() }, times(1)) } } }