Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -56,7 +56,6 @@ import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata
import software.aws.toolkits.telemetry.CodewhispererCompletionType
import software.aws.toolkits.telemetry.CodewhispererSuggestionState
import java.time.Instant
import java.util.concurrent.TimeUnit
import kotlin.reflect.KProperty0
import kotlin.reflect.jvm.isAccessible

Expand Down Expand Up @@ -114,7 +113,8 @@ interface CodeWhispererClientAdaptor : Disposable {
requestId: String,
language: CodeWhispererProgrammingLanguage,
customizationArn: String,
modificationPercentage: Double,
acceptedCharacterCount: Int,
unmodifiedAcceptedTokenCount: Int,
): SendTelemetryEventResponse

fun sendCodeScanTelemetry(
Expand Down Expand Up @@ -305,13 +305,14 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
val programmingLanguage = fileContext.programmingLanguage
var e2eLatency = requestContext.latencyContext.getCodeWhispererEndToEndLatency()

// When we send a userTriggerDecision of Empty or Discard, we set the time users see the first
// suggestion to be now.
if (e2eLatency < 0) {
e2eLatency = TimeUnit.NANOSECONDS.toMillis(
System.nanoTime() - requestContext.latencyContext.codewhispererEndToEndStart
).toDouble()
// When we send a userTriggerDecision for neither Accept nor Reject, service side should not use this value
// and client side will set this value to 0.0.
if (suggestionState != CodewhispererSuggestionState.Accept &&
suggestionState != CodewhispererSuggestionState.Reject
) {
e2eLatency = 0.0
}

return bearerClient().sendTelemetryEvent { requestBuilder ->
requestBuilder.telemetryEvent { telemetryEventBuilder ->
telemetryEventBuilder.userTriggerDecisionEvent {
Expand All @@ -321,6 +322,9 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
it.sessionId(responseContext.sessionId)
it.recommendationLatencyMilliseconds(e2eLatency)
it.triggerToResponseLatencyMilliseconds(requestContext.latencyContext.paginationFirstCompletionTime)
it.perceivedLatencyMilliseconds(
requestContext.latencyContext.getPerceivedLatency(requestContext.triggerTypeInfo.triggerType)
)
it.suggestionState(suggestionState.toCodeWhispererSdkType())
it.timestamp(Instant.now())
it.suggestionReferenceCount(suggestionReferenceCount)
Expand Down Expand Up @@ -360,7 +364,8 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
requestId: String,
language: CodeWhispererProgrammingLanguage,
customizationArn: String,
modificationPercentage: Double,
acceptedCharacterCount: Int,
unmodifiedAcceptedTokenCount: Int,
): SendTelemetryEventResponse = bearerClient().sendTelemetryEvent { requestBuilder ->
requestBuilder.telemetryEvent { telemetryEventBuilder ->
telemetryEventBuilder.userModificationEvent {
Expand All @@ -370,8 +375,11 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
languageBuilder.languageName(language.toCodeWhispererRuntimeLanguage().languageId)
}
it.customizationArn(customizationArn)
it.modificationPercentage(modificationPercentage)
// deprecated field, service side should not use this % anymore
it.modificationPercentage(0.0)
it.timestamp(Instant.now())
it.acceptedCharacterCount(acceptedCharacterCount)
it.unmodifiedAcceptedCharacterCount(unmodifiedAcceptedTokenCount)
}
}
requestBuilder.optOutPreference(getTelemetryOptOutPreference())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import software.amazon.awssdk.services.codewhispererruntime.model.GenerateComple
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext
import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutoTriggerService
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutomatedTriggerType
import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContext
import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext
Expand Down Expand Up @@ -198,6 +199,18 @@ data class LatencyContext(
fun getCodeWhispererPreprocessingLatency() = TimeUnit.NANOSECONDS.toMillis(
codewhispererPreprocessingEnd - codewhispererPreprocessingStart
).toDouble()

// For auto-trigger it's from the time when last char typed
// for manual-trigger it's from the time when last trigger action happened(alt + c)
fun getPerceivedLatency(triggerType: CodewhispererTriggerType) =
if (triggerType == CodewhispererTriggerType.OnDemand) {
getCodeWhispererEndToEndLatency()
} else {
(
TimeUnit.NANOSECONDS.toMillis(codewhispererEndToEndEnd) -
CodeWhispererAutoTriggerService.getInstance().timeAtLastCharTyped.toEpochMilli()
).toDouble()
}
}

data class TryExampleRowContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa

private var lastInvocationTime: Instant? = null
private var lastInvocationLineNum: Int? = null
var timeAtLastCharTyped: Instant = Instant.now()

init {
scheduleReset()
Expand All @@ -54,6 +55,7 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa
// a util wrapper
fun tryInvokeAutoTrigger(editor: Editor, triggerType: CodeWhispererAutomatedTriggerType): Job? {
// only needed for Classifier group, thus calculate it lazily
timeAtLastCharTyped = Instant.now()
val classifierResult: ClassifierResult by lazy { shouldTriggerClassifier(editor, triggerType.telemetryType) }
val language = runReadAction {
FileDocumentManager.getInstance().getFile(editor.document)?.programmingLanguage()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
featureConfigs[it.feature()] = FeatureContext(it.feature(), it.variation(), it.value())
}

// Only apply new auto-trigger UX to BID users
val isNewAutoTriggerUX = getNewAutoTriggerUX()
if (isNewAutoTriggerUX) {
calculateIfIamIdentityCenterConnection(project) {
featureConfigs.remove(NEW_AUTO_TRIGGER_UX)
}
}

val customizationArnOverride = featureConfigs[CUSTOMIZATION_ARN_OVERRIDE_NAME]?.value?.stringValue()
if (customizationArnOverride != null) {
// Double check if server-side wrongly returns a customizationArn to BID users
Expand Down Expand Up @@ -84,20 +92,24 @@

fun getCustomizationArnOverride(): String = getFeatureValueForKey(CUSTOMIZATION_ARN_OVERRIDE_NAME).stringValue()

fun getNewAutoTriggerUX(): Boolean = getFeatureValueForKey(NEW_AUTO_TRIGGER_UX).boolValue()

Check notice on line 95 in plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererFeatureConfigService.kt

View workflow job for this annotation

GitHub Actions / qodana

Class member can have 'private' visibility

Function 'getNewAutoTriggerUX' could be private

// Get the feature value for the given key.
// In case of a misconfiguration, it will return a default feature value of Boolean true.
// In case of a misconfiguration, it will return a default feature value of Boolean false.
private fun getFeatureValueForKey(name: String): FeatureValue =
featureConfigs[name]?.value ?: FEATURE_DEFINITIONS[name]?.value
?: FeatureValue.builder().boolValue(true).build()
?: FeatureValue.builder().boolValue(false).build()

companion object {
fun getInstance(): CodeWhispererFeatureConfigService = service()
private const val TEST_FEATURE_NAME = "testFeature"
private const val DATA_COLLECTION_FEATURE = "IDEProjectContextDataCollection"
const val CUSTOMIZATION_ARN_OVERRIDE_NAME = "customizationArnOverride"
private const val NEW_AUTO_TRIGGER_UX = "newAutoTriggerUX"
Copy link
Contributor

@Will-ShaoHua Will-ShaoHua Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could make these constants sealed data object

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like

sealed interface Feature {
     val  id: String
     
     sealed data object Feature1 { id: 'feat1'}

     sealed data object Feature2 { id: 'feat2' }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can do this later

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

private val LOG = getLogger<CodeWhispererFeatureConfigService>()

// TODO: add real feature later
// Also serve as default values in case server-side config isn't there yet
internal val FEATURE_DEFINITIONS = mapOf(
TEST_FEATURE_NAME to FeatureContext(
TEST_FEATURE_NAME,
Expand All @@ -109,7 +121,12 @@
CUSTOMIZATION_ARN_OVERRIDE_NAME,
"customizationARN",
FeatureValue.builder().stringValue("").build()
)
),
NEW_AUTO_TRIGGER_UX to FeatureContext(
NEW_AUTO_TRIGGER_UX,
"CONTROL",
FeatureValue.builder().boolValue(false).build()
),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
val sessionId = response.sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0]
if (requestCount == 1) {
requestContext.latencyContext.codewhispererPostprocessingStart = System.nanoTime()
requestContext.latencyContext.paginationFirstCompletionTime = latency
requestContext.latencyContext.paginationFirstCompletionTime =
(endTime - requestContext.latencyContext.codewhispererEndToEndStart).toDouble()
requestContext.latencyContext.firstRequestId = requestId
CodeWhispererInvocationStatus.getInstance().setInvocationSessionId(sessionId)
}
Expand Down
Loading
Loading