diff --git a/tools/intellij.tools.ide.metrics.collector/src/com/intellij/tools/ide/metrics/collector/telemetry/OpentelemetrySpanJsonParser.kt b/tools/intellij.tools.ide.metrics.collector/src/com/intellij/tools/ide/metrics/collector/telemetry/OpentelemetrySpanJsonParser.kt index 97b87b214ae5..1cbd0dbad349 100644 --- a/tools/intellij.tools.ide.metrics.collector/src/com/intellij/tools/ide/metrics/collector/telemetry/OpentelemetrySpanJsonParser.kt +++ b/tools/intellij.tools.ide.metrics.collector/src/com/intellij/tools/ide/metrics/collector/telemetry/OpentelemetrySpanJsonParser.kt @@ -80,7 +80,7 @@ private data class OpentelemetryJsonData( open class OpentelemetrySpanJsonParser(private val spanFilter: SpanFilter) { fun getSpanElements(file: Path, spanElementFilter: (SpanElement) -> Boolean = { true }): Set { - var jsonData = getSpans(file, jsonSerializerNanoseconds) + val jsonData = getSpans(file, jsonSerializerNanoseconds) val spans = jsonData.data.single().spans val index = getParentToSpanMap(spans) @@ -90,6 +90,7 @@ open class OpentelemetrySpanJsonParser(private val spanFilter: SpanFilter) { result.add(span) processChild(result, span, index) } + OpenTelemetryDeserializerCache.clearCaches() return result } diff --git a/tools/intellij.tools.ide.metrics.collector/src/com/intellij/tools/ide/metrics/collector/telemetry/SpanElement.kt b/tools/intellij.tools.ide.metrics.collector/src/com/intellij/tools/ide/metrics/collector/telemetry/SpanElement.kt index 90170e3a0a43..e5c08ee42e29 100644 --- a/tools/intellij.tools.ide.metrics.collector/src/com/intellij/tools/ide/metrics/collector/telemetry/SpanElement.kt +++ b/tools/intellij.tools.ide.metrics.collector/src/com/intellij/tools/ide/metrics/collector/telemetry/SpanElement.kt @@ -2,10 +2,8 @@ package com.intellij.tools.ide.metrics.collector.telemetry -import kotlinx.serialization.Contextual -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable +import kotlinx.serialization.* +import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder @@ -52,7 +50,6 @@ internal fun toSpanElement(span: SpanData): SpanElement { ) } -private val cache = ConcurrentHashMap() /** * OT has a very verbose format with a lot of string duplication. @@ -69,7 +66,71 @@ private class CachedStringSerializer : KSerializer { override fun deserialize(decoder: Decoder): String { val deserialized = delegate.deserialize(decoder) - return cache.computeIfAbsent(deserialized) { it } + return OpenTelemetryDeserializerCache.stringCache.getOrPut(deserialized) { deserialized } + } +} + +private object CachedTagListSerializer : KSerializer> by createCachedListSerializer( + listCache = OpenTelemetryDeserializerCache.tagListCache, + elementCache = OpenTelemetryDeserializerCache.tagCache +) + +private object CachedReferencesListSerializer : KSerializer> by createCachedListSerializer( + listCache = OpenTelemetryDeserializerCache.referencesListCache, + elementCache = OpenTelemetryDeserializerCache.referencesCache +) + +private inline fun createCachedListSerializer( + listCache: MutableMap, List>, + elementCache: MutableMap +): KSerializer> { + return object : KSerializer> { + private val elementSerializer = CachedElementSerializer(serializer(), elementCache) + private val delegate = ListSerializer(elementSerializer) + + override val descriptor: SerialDescriptor = delegate.descriptor + + override fun serialize(encoder: Encoder, value: List) { + delegate.serialize(encoder, value) + } + + override fun deserialize(decoder: Decoder): List { + val deserialized = delegate.deserialize(decoder) + return listCache.getOrPut(deserialized) { deserialized } + } + } +} + +private class CachedElementSerializer( + private val serializer: KSerializer, + private val cache: MutableMap +) : KSerializer { + + override val descriptor: SerialDescriptor = serializer.descriptor + + override fun serialize(encoder: Encoder, value: T) { + serializer.serialize(encoder, value) + } + + override fun deserialize(decoder: Decoder): T { + val deserialized = serializer.deserialize(decoder) + return cache.getOrPut(deserialized) { deserialized } + } +} + +internal object OpenTelemetryDeserializerCache { + val stringCache = ConcurrentHashMap() + val tagListCache = ConcurrentHashMap, List>() + val tagCache = ConcurrentHashMap() + val referencesListCache = ConcurrentHashMap, List>() + val referencesCache = ConcurrentHashMap() + + fun clearCaches() { + stringCache.clear() + tagListCache.clear() + referencesListCache.clear() + tagCache.clear() + referencesCache.clear() } } @@ -85,8 +146,8 @@ data class SpanData( @JsonNames("startTime") @Contextual val startTimeNano: Instant, - @JvmField val references: List = emptyList(), - @JvmField val tags: List = emptyList(), + @JvmField @Serializable(with = CachedReferencesListSerializer::class) val references: List = emptyList(), + @JvmField @Serializable(with = CachedTagListSerializer::class) val tags: List = emptyList(), ) @Serializable