Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 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
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class FeatureDevClient(
requestBuilder.userContext(featureDevUserContext)
}

fun sendFeatureDevMetricData(operationName: String, result: String): SendTelemetryEventResponse =
fun sendFeatureDevMetricData(operationName: String, result: String, log: String?): SendTelemetryEventResponse =
bearerClient().sendTelemetryEvent { requestBuilder ->
requestBuilder.telemetryEvent { telemetryEventBuilder ->
telemetryEventBuilder.metricData {
Expand All @@ -100,16 +100,29 @@ class FeatureDevClient(
.timestamp(Instant.now())
.product("FeatureDev")
.dimensions(
listOf(
Dimension.builder()
.name("operationName")
.value(operationName)
.build(),
Dimension.builder()
.name("result")
.value(result)
.build()
)
buildList {
add(
Dimension.builder()
.name("operationName")
.value(operationName)
.build()
)
add(
Dimension.builder()
.name("result")
.value(result)
.build()
)
if (log != null) {
// The log dimension will be emitted only to CloudWatch Logs
add(
Dimension.builder()
.name("log")
.value(log)
.build()
)
}
}
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@
package software.aws.toolkits.jetbrains.services.amazonqFeatureDev.controller

import com.intellij.notification.NotificationAction
import software.aws.toolkits.jetbrains.services.amazonq.RepoSizeLimitError
import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CODE_GENERATION_RETRY_LIMIT
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CodeGenerationException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CodeIterationLimitException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ContentLengthException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ConversationIdNotFoundException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.EmptyPatchException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ExportParseException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.GuardrailsException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.MetricDataOperationName
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.MetricDataResult
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.MonthlyConversationLimitError
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.NoChangeRequiredException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.PromptRefusalException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ThrottlingException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.UploadCodeException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.UploadURLExpired
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ZipFileCorruptedException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.FeatureDevMessageType
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.FollowUp
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.FollowUpStatusType
Expand All @@ -33,6 +43,8 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.InsertAct
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.getFollowUpOptions
import software.aws.toolkits.jetbrains.utils.notifyInfo
import software.aws.toolkits.resources.message
import java.io.PrintWriter
import java.io.StringWriter

suspend fun FeatureDevController.onCodeGeneration(
session: Session,
Expand Down Expand Up @@ -70,8 +82,9 @@ suspend fun FeatureDevController.onCodeGeneration(
messenger.sendUpdatePlaceholder(tabId = tabId, newPlaceholder = message("amazonqFeatureDev.placeholder.generating_code"))

session.sendMetricDataTelemetry(
MetricDataOperationName.StartCodeGeneration,
MetricDataResult.Success
operationName = MetricDataOperationName.StartCodeGeneration,
result = MetricDataResult.Success,
log = null
)

session.send(message) // Trigger code generation
Expand Down Expand Up @@ -153,26 +166,28 @@ suspend fun FeatureDevController.onCodeGeneration(
messenger.sendSystemPrompt(tabId = tabId, followUp = getFollowUpOptions(session.sessionState.phase, InsertAction.ALL))
messenger.sendUpdatePlaceholder(tabId = tabId, newPlaceholder = message("amazonqFeatureDev.placeholder.after_code_generation"))
} catch (err: Exception) {
when (err) {
is GuardrailsException, is NoChangeRequiredException, is PromptRefusalException, is ThrottlingException -> {
session.sendMetricDataTelemetry(
MetricDataOperationName.EndCodeGeneration,
MetricDataResult.Error
)
val result: MetricDataResult = when (err) {
is GuardrailsException, is NoChangeRequiredException, is PromptRefusalException, is ThrottlingException,
is ContentLengthException, is MonthlyConversationLimitError, is CodeIterationLimitException,
is RepoSizeLimitError, is UploadURLExpired,
-> {
MetricDataResult.Error
}

is EmptyPatchException -> {
session.sendMetricDataTelemetry(
MetricDataOperationName.EndCodeGeneration,
MetricDataResult.LlmFailure
)
MetricDataResult.LlmFailure
}

else -> {
session.sendMetricDataTelemetry(
MetricDataOperationName.EndCodeGeneration,
MetricDataResult.Fault
)
MetricDataResult.Fault
}
}

session.sendMetricDataTelemetry(
operationName = MetricDataOperationName.EndCodeGeneration,
result = result,
log = "stack trace: " + getStackTraceForError(err)
)
throw err
} finally {
if (session.sessionState.token
Expand All @@ -195,8 +210,9 @@ suspend fun FeatureDevController.onCodeGeneration(
}

session.sendMetricDataTelemetry(
MetricDataOperationName.EndCodeGeneration,
MetricDataResult.Success
operationName = MetricDataOperationName.EndCodeGeneration,
result = MetricDataResult.Success,
log = null
)
}

Expand Down Expand Up @@ -272,3 +288,60 @@ private fun FeatureDevController.openChatNotificationAction() =
) {
toolWindow?.show()
}

// Should include error messages only for safe exceptions
// i.e. exceptions with deterministic error messages and do not include sensitive data
private fun getStackTraceForError(error: Throwable): String {
val recursionLimit = 3
val writer = StringWriter()
val printer = PrintWriter(writer)
val seenExceptions = mutableSetOf<Throwable>()

fun printExceptionDetails(throwable: Throwable, depth: Int, prefix: String = "") {
if (depth >= recursionLimit || throwable in seenExceptions) {
return
}
seenExceptions.add(throwable)

when (throwable) {
is NoChangeRequiredException,
is EmptyPatchException,
is ContentLengthException,
is ZipFileCorruptedException,
is UploadURLExpired,
is CodeIterationLimitException,
is GuardrailsException,
is PromptRefusalException,
is ThrottlingException,
is ExportParseException,
is CodeGenerationException,
is UploadCodeException,
is ConversationIdNotFoundException,
is RepoSizeLimitError,
-> {
printer.println("$prefix${throwable.javaClass.name}: ${throwable.message}")
}
else -> {
// No message included
printer.println("$prefix${throwable.javaClass.name}")
}
}

throwable.stackTrace.forEach { element ->
printer.println("$prefix\tat $element")
}

throwable.cause?.let { cause ->
printer.println("$prefix\tCaused by: ")
printExceptionDetails(cause, depth + 1, "$prefix\t")
}

throwable.suppressed.forEach { suppressed ->
printer.println("$prefix\tSuppressed: ")
printExceptionDetails(suppressed, depth + 1, "$prefix\t")
}
}

printExceptionDetails(error, 0)
return writer.toString()
}
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@ class Session(val tabID: String, val project: Project) {
this._codeResultMessageId = null
}

fun sendMetricDataTelemetry(operationName: MetricDataOperationName, result: MetricDataResult) {
featureDevService.sendFeatureDevMetricData(operationName.toString(), result.toString())
fun sendMetricDataTelemetry(operationName: MetricDataOperationName, result: MetricDataResult, log: String?) {
featureDevService.sendFeatureDevMetricData(operationName.toString(), result.toString(), log)
}

suspend fun send(msg: String): Interaction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,13 @@ class FeatureDevService(val proxyClient: FeatureDevClient, val project: Project)
}
}

fun sendFeatureDevMetricData(operationName: String, result: String) {
fun sendFeatureDevMetricData(operationName: String, result: String, log: String?) {
val sendFeatureDevTelemetryEventResponse: SendTelemetryEventResponse
try {
sendFeatureDevTelemetryEventResponse = proxyClient.sendFeatureDevMetricData(operationName, result)
sendFeatureDevTelemetryEventResponse = proxyClient.sendFeatureDevMetricData(operationName, result, log)
val requestId = sendFeatureDevTelemetryEventResponse.responseMetadata().requestId()
logger.debug {
"$FEATURE_NAME: succesfully sent feature dev metric data: OperationName: $operationName Result: $result RequestId: $requestId"
"$FEATURE_NAME: succesfully sent feature dev metric data: OperationName: $operationName Result: $result Log: $log RequestId: $requestId"
}
} catch (e: Exception) {
logger.warn(e) { "$FEATURE_NAME: failed to send feature dev metric data" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import org.junit.Rule
import org.junit.Test
import org.junit.jupiter.api.assertThrows
import org.mockito.kotlin.any
import org.mockito.kotlin.argThat
import org.mockito.kotlin.doNothing
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.inOrder
import org.mockito.kotlin.mock
import org.mockito.kotlin.reset
Expand All @@ -34,18 +36,26 @@ import org.mockito.kotlin.times
import org.mockito.kotlin.whenever
import software.aws.toolkits.jetbrains.common.util.selectFolder
import software.aws.toolkits.jetbrains.services.amazonq.FeatureDevSessionContext
import software.aws.toolkits.jetbrains.services.amazonq.RepoSizeLimitError
import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController
import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthNeededStates
import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CodeIterationLimitException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ContentLengthException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.EmptyPatchException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ExportParseException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FeatureDevTestBase
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.GuardrailsException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.MetricDataOperationName
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.MetricDataResult
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.MonthlyConversationLimitError
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.NoChangeRequiredException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.PromptRefusalException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ThrottlingException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.UploadCodeException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.UploadURLExpired
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ZipFileCorruptedException
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.clients.FeatureDevClient
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.FeatureDevMessageType
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.FollowUp
Expand Down Expand Up @@ -74,6 +84,7 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.FeatureDe
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.InsertAction
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.getFollowUpOptions
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.uploadArtifactToS3
import software.aws.toolkits.resources.AwsCoreBundle
import software.aws.toolkits.resources.message
import software.aws.toolkits.telemetry.AmazonqTelemetry
import org.mockito.kotlin.verify as mockitoVerify
Expand Down Expand Up @@ -578,12 +589,13 @@ class FeatureDevControllerTest : FeatureDevTestBase() {

mockInOrder.verify(mockSession).sendMetricDataTelemetry(
MetricDataOperationName.StartCodeGeneration,
MetricDataResult.Success

MetricDataResult.Success,
null
)
mockInOrder.verify(mockSession).sendMetricDataTelemetry(
MetricDataOperationName.EndCodeGeneration,
MetricDataResult.Success
MetricDataResult.Success,
null
)
}

Expand All @@ -596,25 +608,57 @@ class FeatureDevControllerTest : FeatureDevTestBase() {

val testCases = listOf(
ErrorTestCase(
EmptyPatchException("EmptyPatchException", "Empty patch"),
EmptyPatchException("EmptyPatchException", null),
MetricDataResult.LlmFailure
),
ErrorTestCase(
GuardrailsException(operation = "GenerateCode", desc = "Failed guardrails"),
GuardrailsException(operation = "GenerateCode", desc = null),
MetricDataResult.Error
),
ErrorTestCase(
PromptRefusalException(operation = "GenerateCode", desc = null),
MetricDataResult.Error
),
ErrorTestCase(
NoChangeRequiredException(operation = "GenerateCode", desc = null),
MetricDataResult.Error
),
ErrorTestCase(
ThrottlingException(operation = "GenerateCode", desc = null),
MetricDataResult.Error
),
ErrorTestCase(
ContentLengthException(operation = "GenerateCode", desc = null),
MetricDataResult.Error
),
ErrorTestCase(
PromptRefusalException(operation = "GenerateCode", desc = "Prompt refused"),
MonthlyConversationLimitError(message = "", operation = "GenerateCode", desc = null),
MetricDataResult.Error
),
ErrorTestCase(
NoChangeRequiredException(operation = "GenerateCode", desc = "No changes needed"),
CodeIterationLimitException(operation = "GenerateCode", desc = null),
MetricDataResult.Error
),
ErrorTestCase(
ThrottlingException(operation = "GenerateCode", desc = "Request throttled"),
RepoSizeLimitError(AwsCoreBundle.message("amazonqFeatureDev.content_length.error_text")),
MetricDataResult.Error
),
ErrorTestCase(
UploadURLExpired(operation = "GenerateCode", desc = null),
MetricDataResult.Error
),
ErrorTestCase(
ZipFileCorruptedException(operation = "GenerateCode", desc = null),
MetricDataResult.Fault
),
ErrorTestCase(
ExportParseException(operation = "GenerateCode", desc = null),
MetricDataResult.Fault
),
ErrorTestCase(
UploadCodeException(operation = "GenerateCode", desc = null),
MetricDataResult.Fault
),
ErrorTestCase(
RuntimeException("Unknown error"),
MetricDataResult.Fault
Expand Down Expand Up @@ -646,12 +690,13 @@ class FeatureDevControllerTest : FeatureDevTestBase() {

mockInOrder.verify(mockSession).sendMetricDataTelemetry(
MetricDataOperationName.StartCodeGeneration,
MetricDataResult.Success

MetricDataResult.Success,
null
)
mockInOrder.verify(mockSession).sendMetricDataTelemetry(
MetricDataOperationName.EndCodeGeneration,
expectedResult
eq(MetricDataOperationName.EndCodeGeneration),
eq(expectedResult),
argThat { this.startsWith("stack trace: " + error::class.qualifiedName) }
)
}
}
Expand Down
Loading