diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/LoadModuleCompletion.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/LoadModuleCompletion.kt new file mode 100644 index 00000000000..24d15d1fcab --- /dev/null +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/LoadModuleCompletion.kt @@ -0,0 +1,53 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.amazonq + +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.intellij.util.Alarm +import software.aws.toolkits.telemetry.MetricResult +import software.aws.toolkits.telemetry.Telemetry + +@Service(Service.Level.PROJECT) +class LoadModuleCompletion(project: Project?) : Disposable{ + + private val alarm = Alarm(Alarm.ThreadToUse.POOLED_THREAD, this) + private var initializeLoad = true + + fun start(moduleName: String) { + alarm.addRequest( + { + if(!initializeLoad) { + emitMetric(moduleName) + + } + initializeLoad = false + + + }, + 10000 + ) + } + + private fun emitMetric(moduleName: String) { + Telemetry.toolkit.didLoadModule.use { + it.module(moduleName) + it.result(MetricResult.Failed) + } + initializeLoad = true + } + + fun resetTimer() { + alarm.cancelAllRequests() + + } + + companion object { + fun getInstance(project: Project?) = project?.service() + } + + override fun dispose() {} +} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt index bc594209fba..e6ff7dbf8e7 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt @@ -38,6 +38,8 @@ import software.aws.toolkits.jetbrains.utils.isQConnected import software.aws.toolkits.jetbrains.utils.isQExpired import software.aws.toolkits.jetbrains.utils.isQWebviewsAvailable import software.aws.toolkits.telemetry.FeatureId +import software.aws.toolkits.telemetry.MetricResult +import software.aws.toolkits.telemetry.Telemetry import software.aws.toolkits.telemetry.UiTelemetry import software.aws.toolkits.telemetry.WebviewTelemetry import java.awt.event.ActionListener @@ -150,6 +152,11 @@ class QWebviewBrowser(val project: Project, private val parentDisposable: Dispos project, reAuth = isQExpired(project) ) + Telemetry.toolkit.didLoadModule.use { + it.module("Login") + it.result(MetricResult.Succeeded) + } + LoadModuleCompletion.getInstance(project)?.resetTimer() } is BrowserMessage.SelectConnection -> { diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt index 3435075caaf..cb72b6916c0 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt @@ -25,6 +25,7 @@ import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenPr import software.aws.toolkits.jetbrains.core.notifications.NotificationPanel import software.aws.toolkits.jetbrains.core.notifications.ProcessNotificationsBase import software.aws.toolkits.jetbrains.core.webview.BrowserState +import software.aws.toolkits.jetbrains.services.amazonq.LoadModuleCompletion import software.aws.toolkits.jetbrains.services.amazonq.QWebviewPanel import software.aws.toolkits.jetbrains.services.amazonq.RefreshQChatPanelButtonPressedListener import software.aws.toolkits.jetbrains.services.amazonq.gettingstarted.openMeetQPage @@ -33,6 +34,7 @@ import software.aws.toolkits.jetbrains.utils.isQExpired import software.aws.toolkits.jetbrains.utils.isQWebviewsAvailable import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.FeatureId +import software.aws.toolkits.telemetry.Telemetry import java.awt.event.ComponentAdapter import java.awt.event.ComponentEvent @@ -107,9 +109,12 @@ class AmazonQToolWindowFactory : ToolWindowFactory, DumbAware { project: Project, qPanel: Wrapper, ) { + Telemetry.toolkit.didLoadModule.startSpan() val component = if (isQConnected(project) && !isQExpired(project)) { + LoadModuleCompletion.getInstance(project)?.start("Chat") AmazonQToolWindow.getInstance(project).component } else { + LoadModuleCompletion.getInstance(project)?.start("Login") QWebviewPanel.getInstance(project).browser?.prepareBrowser(BrowserState(FeatureId.AmazonQ)) QWebviewPanel.getInstance(project).component } @@ -149,10 +154,12 @@ class AmazonQToolWindowFactory : ToolWindowFactory, DumbAware { openMeetQPage(project) } + LoadModuleCompletion.getInstance(project)?.start("Login") QWebviewPanel.getInstance(project).browser?.prepareBrowser(BrowserState(FeatureId.AmazonQ)) // isQConnected alone is not robust and there is race condition (read/update connection states) val component = if (isNewConnectionForQ || (isQConnected(project) && !isQExpired(project))) { + LoadModuleCompletion.getInstance(project)?.start("Chat") LOG.debug { "returning Q-chat window; isQConnection=$isNewConnectionForQ; hasPinnedConnection=$isNewConnectionForQ" } AmazonQToolWindow.getInstance(project).component } else { diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt index ffddd69b70b..22498c9485c 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.cef.browser.CefBrowser +import software.aws.toolkits.jetbrains.services.amazonq.LoadModuleCompletion import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageSerializer import software.aws.toolkits.jetbrains.services.amazonq.util.command @@ -24,6 +25,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.util.tabType import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.AmazonQTheme import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.ThemeBrowserAdapter import software.aws.toolkits.jetbrains.settings.MeetQSettings +import software.aws.toolkits.telemetry.MetricResult import software.aws.toolkits.telemetry.Telemetry import java.util.function.Function @@ -47,6 +49,14 @@ class BrowserConnector( RunOnceUtil.runOnceForApp("AmazonQ-UI-Ready") { MeetQSettings.getInstance().reinvent2024OnboardingCount += 1 } + + Telemetry.toolkit.didLoadModule.use { + // the Duration is usually 0 because it takes a few nanoseconds to load the module + // so when it's translated to millis it is returned as 0 + it.module("Chat") + it.result(MetricResult.Succeeded) + } + LoadModuleCompletion.getInstance(null)?.resetTimer() } "disclaimer-acknowledged" -> { @@ -70,6 +80,7 @@ class BrowserConnector( Telemetry.toolkit.willOpenModule.use { it.module(module.asText()) it.source(trigger.asText()) + it.result(MetricResult.Succeeded) } } }