Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -6,7 +6,13 @@
import assert from 'assert'
import * as sinon from 'sinon'
import * as vscode from 'vscode'
import { CodeWhispererCodeCoverageTracker, vsCodeState, TelemetryHelper, AuthUtil } from 'aws-core-vscode/codewhisperer'
import {
CodeWhispererCodeCoverageTracker,
vsCodeState,
TelemetryHelper,
AuthUtil,
getUnmodifiedAcceptedTokens,
} from 'aws-core-vscode/codewhisperer'
import { createMockDocument, createMockTextEditor, resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test'
import { globals } from 'aws-core-vscode/shared'
import { assertTelemetryCurried } from 'aws-core-vscode/test'
Expand Down Expand Up @@ -150,14 +156,13 @@ describe('codewhispererCodecoverageTracker', function () {
})

it('Should return correct unmodified accepted tokens count', function () {
const tracker = CodeWhispererCodeCoverageTracker.getTracker(language)
assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('foo', 'fou'), 2)
assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('foo', 'f11111oo'), 3)
assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('foo', 'fo'), 2)
assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('helloworld', 'HelloWorld'), 8)
assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('helloworld', 'World'), 4)
assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('CodeWhisperer', 'CODE'), 1)
assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('CodeWhisperer', 'CodeWhispererGood'), 13)
assert.strictEqual(getUnmodifiedAcceptedTokens('foo', 'fou'), 2)
assert.strictEqual(getUnmodifiedAcceptedTokens('foo', 'f11111oo'), 3)
assert.strictEqual(getUnmodifiedAcceptedTokens('foo', 'fo'), 2)
assert.strictEqual(getUnmodifiedAcceptedTokens('helloworld', 'HelloWorld'), 8)
assert.strictEqual(getUnmodifiedAcceptedTokens('helloworld', 'World'), 4)
assert.strictEqual(getUnmodifiedAcceptedTokens('CodeWhisperer', 'CODE'), 1)
assert.strictEqual(getUnmodifiedAcceptedTokens('CodeWhisperer', 'CodeWhispererGood'), 13)
})
})

Expand Down
18 changes: 15 additions & 3 deletions packages/core/src/codewhisperer/client/user-service-2.json
Original file line number Diff line number Diff line change
Expand Up @@ -2197,14 +2197,24 @@
},
"UserModificationEvent": {
"type": "structure",
"required": ["sessionId", "requestId", "programmingLanguage", "modificationPercentage", "timestamp"],
"required": [
"sessionId",
"requestId",
"programmingLanguage",
"modificationPercentage",
"timestamp",
"acceptedCharacterCount",
"unmodifiedAcceptedCharacterCount"
],
"members": {
"sessionId": { "shape": "UUID" },
"requestId": { "shape": "UUID" },
"programmingLanguage": { "shape": "ProgrammingLanguage" },
"modificationPercentage": { "shape": "Double" },
"customizationArn": { "shape": "CustomizationArn" },
"timestamp": { "shape": "Timestamp" }
"timestamp": { "shape": "Timestamp" },
"acceptedCharacterCount": { "shape": "PrimitiveInteger" },
"unmodifiedAcceptedCharacterCount": { "shape": "PrimitiveInteger" }
}
},
"UserTriggerDecisionEvent": {
Expand All @@ -2230,7 +2240,9 @@
"triggerToResponseLatencyMilliseconds": { "shape": "Double" },
"suggestionReferenceCount": { "shape": "PrimitiveInteger" },
"generatedLine": { "shape": "PrimitiveInteger" },
"numberOfRecommendations": { "shape": "PrimitiveInteger" }
"numberOfRecommendations": { "shape": "PrimitiveInteger" },
"perceivedLatencyMilliseconds": { "shape": "Double" },
"acceptedCharacterCount": { "shape": "PrimitiveInteger" }
}
},
"ValidationException": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ export class InlineCompletionService {

await this.setState('loading')

TelemetryHelper.instance.setInvocationStartTime(performance.now())
RecommendationHandler.instance.checkAndResetCancellationTokens()
RecommendationHandler.instance.documentUri = editor.document.uri
let response: GetRecommendationsResponse = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ export class RecommendationHandler {
sessionId = resp?.$response?.httpResponse?.headers['x-amzn-sessionid']
TelemetryHelper.instance.setFirstResponseRequestId(requestId)
if (page === 0) {
TelemetryHelper.instance.setTimeToFirstRecommendation(performance.now())
session.setTimeToFirstRecommendation(performance.now())
}
if (nextToken === '') {
TelemetryHelper.instance.setAllPaginationEndTime()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { getLogger } from '../../shared/logger/logger'
import * as CodeWhispererConstants from '../models/constants'
import globals from '../../shared/extensionGlobals'
import { vsCodeState } from '../models/model'
import { distance } from 'fastest-levenshtein'
import { CodewhispererLanguage, telemetry } from '../../shared/telemetry/telemetry'
import { runtimeLanguageContext } from '../util/runtimeLanguageContext'
import { TelemetryHelper } from '../util/telemetryHelper'
import { AuthUtil } from '../util/authUtil'
import { getSelectedCustomization } from '../util/customizationUtil'
import { codeWhispererClient as client } from '../client/codewhisperer'
import { isAwsError } from '../../shared/errors'
import { getUnmodifiedAcceptedTokens } from '../util/commonUtil'

interface CodeWhispererToken {
range: vscode.Range
Expand Down Expand Up @@ -86,18 +86,10 @@ export class CodeWhispererCodeCoverageTracker {
for (let i = 0; i < this._acceptedTokens[filename].length; i++) {
const oldText = this._acceptedTokens[filename][i].text
const newText = editor.document.getText(this._acceptedTokens[filename][i].range)
this._acceptedTokens[filename][i].accepted = this.getUnmodifiedAcceptedTokens(oldText, newText)
this._acceptedTokens[filename][i].accepted = getUnmodifiedAcceptedTokens(oldText, newText)
}
}
}
// With edit distance, complicate usermodification can be considered as simple edit(add, delete, replace),
// and thus the unmodified part of recommendation length can be deducted/approximated
// ex. (modified > original): originalRecom: foo -> modifiedRecom: fobarbarbaro, distance = 9, delta = 12 - 9 = 3
// ex. (modified == original): originalRecom: helloworld -> modifiedRecom: HelloWorld, distance = 2, delta = 10 - 2 = 8
// ex. (modified < original): originalRecom: CodeWhisperer -> modifiedRecom: CODE, distance = 12, delta = 13 - 12 = 1
public getUnmodifiedAcceptedTokens(origin: string, after: string) {
return Math.max(origin.length, after.length) - distance(origin, after)
}

public emitCodeWhispererCodeContribution() {
let totalTokens = 0
Expand Down
48 changes: 40 additions & 8 deletions packages/core/src/codewhisperer/tracker/codewhispererTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { codeWhispererClient } from '../client/codewhisperer'
import { logSendTelemetryEventFailure } from '../../codewhispererChat/controllers/chat/telemetryHelper'
import { Timeout } from '../../shared/utilities/timeoutUtils'
import { getSelectedCustomization } from '../util/customizationUtil'
import { undefinedIfEmpty } from '../../shared'
import { isAwsError, undefinedIfEmpty } from '../../shared'
import { getUnmodifiedAcceptedTokens } from '../util/commonUtil'

/**
* This singleton class is mainly used for calculating the percentage of user modification.
Expand Down Expand Up @@ -89,19 +90,20 @@ export class CodeWhispererTracker {

public async emitTelemetryOnSuggestion(suggestion: AcceptedSuggestionEntry | InsertedCode) {
let percentage = 1.0
let currString = ''
const customizationArn = undefinedIfEmpty(getSelectedCustomization().arn)

try {
if (suggestion.fileUrl?.scheme !== '') {
const document = await vscode.workspace.openTextDocument(suggestion.fileUrl)
if (document) {
const currString = document.getText(
new vscode.Range(suggestion.startPosition, suggestion.endPosition)
)
currString = document.getText(new vscode.Range(suggestion.startPosition, suggestion.endPosition))
percentage = this.checkDiff(currString, suggestion.originalString)
}
}
} catch (e) {
getLogger().verbose(`Exception Thrown from CodeWhispererTracker: ${e}`)
return
} finally {
if ('conversationID' in suggestion) {
const event: AmazonqModifyCode = {
Expand All @@ -120,7 +122,7 @@ export class CodeWhispererTracker {
conversationId: event.cwsprChatConversationId,
messageId: event.cwsprChatMessageId,
modificationPercentage: event.cwsprChatModificationPercentage,
customizationArn: undefinedIfEmpty(getSelectedCustomization().arn),
customizationArn: customizationArn,
},
},
})
Expand All @@ -139,9 +141,39 @@ export class CodeWhispererTracker {
codewhispererCharactersAccepted: suggestion.originalString.length,
codewhispererCharactersModified: 0, // TODO: currently we don't have an accurate number for this field with existing implementation
})
// TODO:
// Temperary comment out user modification event, need further discussion on how to calculate this metric
// TelemetryHelper.instance.sendUserModificationEvent(suggestion, percentage)

codeWhispererClient
.sendTelemetryEvent({
telemetryEvent: {
userModificationEvent: {
sessionId: suggestion.sessionId,
requestId: suggestion.requestId,
programmingLanguage: { languageName: suggestion.language },
// deprecated % value and should not be used by service side
modificationPercentage: percentage,
customizationArn: customizationArn,
timestamp: new Date(),
acceptedCharacterCount: suggestion.originalString.length,
unmodifiedAcceptedCharacterCount: getUnmodifiedAcceptedTokens(
suggestion.originalString,
currString
),
},
},
})
.then()
.catch((error) => {
let requestId: string | undefined
if (isAwsError(error)) {
requestId = error.requestId
}

getLogger().debug(
`Failed to send UserModificationEvent to CodeWhisperer, requestId: ${requestId ?? ''}, message: ${
error.message
}`
)
})
}
}
}
Expand Down
18 changes: 17 additions & 1 deletion packages/core/src/codewhisperer/util/codeWhispererSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '../../shared/telemetry/telemetry.gen'
import { GenerateRecommendationsRequest, ListRecommendationsRequest, Recommendation } from '../client/codewhisperer'
import { Position } from 'vscode'
import { CodeWhispererSupplementalContext } from '../models/model'
import { CodeWhispererSupplementalContext, vsCodeState } from '../models/model'

class CodeWhispererSession {
static #instance: CodeWhispererSession
Expand Down Expand Up @@ -41,6 +41,8 @@ class CodeWhispererSession {
fetchCredentialStartTime = 0
sdkApiCallStartTime = 0
invokeSuggestionStartTime = 0
timeToFirstRecommendation = 0
firstSuggestionShowTime = 0

public static get instance() {
return (this.#instance ??= new CodeWhispererSession())
Expand All @@ -58,6 +60,12 @@ class CodeWhispererSession {
}
}

setTimeToFirstRecommendation(timeToFirstRecommendation: number) {
if (this.invokeSuggestionStartTime) {
this.timeToFirstRecommendation = timeToFirstRecommendation - this.invokeSuggestionStartTime
}
}

setSuggestionState(index: number, value: string) {
this.suggestionStates.set(index, value)
}
Expand All @@ -75,6 +83,14 @@ class CodeWhispererSession {
return this.completionTypes.get(index) || 'Line'
}

getPerceivedLatency(triggerType: CodewhispererTriggerType) {
if (triggerType === 'OnDemand') {
return this.timeToFirstRecommendation
} else {
return session.firstSuggestionShowTime - vsCodeState.lastUserModificationTime
}
}

reset() {
this.sessionId = ''
this.requestContext = { request: {} as any, supplementalMetadata: {} as any }
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/codewhisperer/util/commonUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import * as vscode from 'vscode'
import * as semver from 'semver'
import { distance } from 'fastest-levenshtein'
import { isCloud9 } from '../../shared/extensionUtilities'
import { getInlineSuggestEnabled } from '../../shared/utilities/editorUtilities'
import {
Expand Down Expand Up @@ -76,3 +77,12 @@ export function checkLeftContextKeywordsForJson(fileName: string, leftFileConten
}
return false
}

// With edit distance, complicate usermodification can be considered as simple edit(add, delete, replace),
// and thus the unmodified part of recommendation length can be deducted/approximated
// ex. (modified > original): originalRecom: foo -> modifiedRecom: fobarbarbaro, distance = 9, delta = 12 - 9 = 3
// ex. (modified == original): originalRecom: helloworld -> modifiedRecom: HelloWorld, distance = 2, delta = 10 - 2 = 8
// ex. (modified < original): originalRecom: CodeWhisperer -> modifiedRecom: CODE, distance = 12, delta = 13 - 12 = 1
export function getUnmodifiedAcceptedTokens(origin: string, after: string) {
return Math.max(origin.length, after.length) - distance(origin, after)
}
Loading
Loading