From 23abef9f9bbe132e4ec83ae27bf1ced5160fa6da Mon Sep 17 00:00:00 2001 From: xiajon <108155953+xiajon@users.noreply.github.com> Date: Fri, 6 Jun 2025 09:13:53 -0700 Subject: [PATCH] Refactors the MetricsManager to improve reliability and monitoring control for metric delivery. --- .../chat/sdk/repository/MetricsManager.kt | 101 ++++++++++++------ .../chat/sdk/repository/MetricsManagerTest.kt | 31 +++++- 2 files changed, 94 insertions(+), 38 deletions(-) diff --git a/chat-sdk/src/main/java/com/amazon/connect/chat/sdk/repository/MetricsManager.kt b/chat-sdk/src/main/java/com/amazon/connect/chat/sdk/repository/MetricsManager.kt index 3889bcc..f212a9d 100644 --- a/chat-sdk/src/main/java/com/amazon/connect/chat/sdk/repository/MetricsManager.kt +++ b/chat-sdk/src/main/java/com/amazon/connect/chat/sdk/repository/MetricsManager.kt @@ -20,52 +20,86 @@ class MetricsManager @Inject constructor( private var metricList: MutableList = mutableListOf() private var isMonitoring: Boolean = false private var timer: Timer? = null - private var shouldRetry: Boolean = true + private var retryCount: Int = 0 private var _isCsmDisabled: Boolean = false + private val maxRetries = 3 + private val monitoringPeriod = 10000L // 10 seconds + private val initialDelay = 1000L // 1 second + + companion object { + private const val METRIC_NAMESPACE = "chat-widget" + } init { if (!_isCsmDisabled) { - monitorAndSendMetrics() + startMonitoring() } } fun configure(config: GlobalConfig) { + val wasDisabled = _isCsmDisabled _isCsmDisabled = config.disableCsm + + if (wasDisabled && !_isCsmDisabled) { + startMonitoring() + } else if (!wasDisabled && _isCsmDisabled) { + stopMonitoring() + } } @Synchronized - private fun monitorAndSendMetrics() { + private fun startMonitoring() { if (isMonitoring) return isMonitoring = true + retryCount = 0 - timer = timer(initialDelay = 10000, period = 10000) { + timer = timer(initialDelay = initialDelay, period = monitoringPeriod) { if (metricList.isNotEmpty()) { - val metricRequestBody = createMetricRequestBody() - apiClient.sendMetrics(metricRequestBody) { response -> - if (response != null && response.isSuccessful) { - metricList = mutableListOf() - isMonitoring = false - timer?.cancel() - } else { - // We should retry once after 10s delay, otherwise we will send the missed - // payload with the next batch of metrics - if (shouldRetry) { - shouldRetry = false - } else { - isMonitoring = false - shouldRetry = true - timer?.cancel() - } - } - } + sendMetrics() } } } - private fun createMetricRequestBody(): MetricRequestBody { + private fun stopMonitoring() { + timer?.cancel() + timer = null + isMonitoring = false + } + + private fun sendMetrics() { + val currentMetrics = synchronized(metricList) { + val metrics = metricList.toList() + metricList.clear() + metrics + } + + if (currentMetrics.isEmpty()) return + + val metricRequestBody = createMetricRequestBody(currentMetrics) + apiClient.sendMetrics(metricRequestBody) { response -> + if (response != null && response.isSuccessful) { + retryCount = 0 + } else { + handleMetricSendFailure(currentMetrics) + } + } + } + + private fun handleMetricSendFailure(failedMetrics: List) { + if (retryCount < maxRetries) { + retryCount++ + synchronized(metricList) { + metricList.addAll(0, failedMetrics) + } + } else { + retryCount = 0 + } + } + + private fun createMetricRequestBody(metrics: List): MetricRequestBody { return MetricRequestBody( - metricNamespace = "chat-widget", - metricList = metricList + metricNamespace = METRIC_NAMESPACE, + metricList = metrics ) } @@ -79,27 +113,26 @@ class MetricsManager @Inject constructor( } fun addCountMetric(metricName: MetricName) { + if (_isCsmDisabled) return + val currentTime = MetricsUtils.getCurrentMetricTimestamp() val countMetricDimensions = getCountMetricDimensions() val countMetric = Metric( dimensions = countMetricDimensions, metricName = metricName.name, - namespace = "chat-widget", + namespace = METRIC_NAMESPACE, optionalDimensions = emptyList(), timestamp = currentTime, unit = "Count", value = 1 ) - addMetric(countMetric) - } - - private fun addMetric(metric: Metric) { - if (_isCsmDisabled) { - return + synchronized(metricList) { + metricList.add(countMetric) } - metricList.add(0, metric) - monitorAndSendMetrics() + if (!isMonitoring) { + startMonitoring() + } } } diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/repository/MetricsManagerTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/repository/MetricsManagerTest.kt index 3bdce0b..f701885 100644 --- a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/repository/MetricsManagerTest.kt +++ b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/repository/MetricsManagerTest.kt @@ -76,7 +76,14 @@ class MetricsManagerTest { // Add a metric to the list metricsManager.addCountMetric(metricName) - val requestBody = invokePrivateMethod(metricsManager, "createMetricRequestBody") as MetricRequestBody + // Get the metrics list to pass to createMetricRequestBody + val metricList = getPrivateField(metricsManager, "metricList") as MutableList + + val requestBody = invokePrivateMethod( + metricsManager, + "createMetricRequestBody", + metricList.toList() + ) as MetricRequestBody assert(requestBody.metricNamespace == "chat-widget") assert(requestBody.metricList.size == 1) @@ -97,8 +104,24 @@ class MetricsManagerTest { } private fun invokePrivateMethod(obj: Any, methodName: String, vararg args: Any?): Any? { - val method = obj.javaClass.getDeclaredMethod(methodName) - method.isAccessible = true - return method.invoke(obj, *args) + val matchingMethods = obj.javaClass.declaredMethods.filter { it.name == methodName } + + if (matchingMethods.isEmpty()) { + throw NoSuchMethodException("$methodName not found in ${obj.javaClass.name}") + } + + val methodsWithMatchingParamCount = matchingMethods.filter { it.parameterCount == args.size } + + for (method in methodsWithMatchingParamCount) { + try { + method.isAccessible = true + return method.invoke(obj, *args) + } catch (e: IllegalArgumentException) { + // Continue to next method + } + } + + throw NoSuchMethodException("No suitable method $methodName found for given arguments") } + }