Skip to content

Improvements in telemetry client #358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 28, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ build/
/nbcode/l10n/locale_zh_CN/release/
/nbcode/l10n/locale_ja/nbproject/private/
/nbcode/l10n/locale_zh_CN/nbproject/private/
telemetryConfig.json
2 changes: 1 addition & 1 deletion vscode/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"preLaunchTask": "${defaultBuildTask}",
"env": {
"nbcode_userdir": "global",
"oracle.oracle-java.enable.debug-logs": "true"
"oracle_oracleJava_enable_debugLogs": "true"
}
},
{
Expand Down
112 changes: 80 additions & 32 deletions vscode/esbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ const scriptConfig = {
};

const watchConfig = {
watch: {
onRebuild(error, result) {
console.log("[watch] build started");
if (error) {
error.errors.forEach(error =>
console.error(`> ${error.location.file}:${error.location.line}:${error.location.column}: error: ${error.text}`)
);
} else {
console.log("[watch] build finished");
}
},
watch: {
onRebuild(error, result) {
console.log("[watch] build started");
if (error) {
error.errors.forEach(error =>
console.error(`> ${error.location.file}:${error.location.line}:${error.location.column}: error: ${error.text}`)
);
} else {
console.log("[watch] build finished");
}
},
};
},
};

const NON_NPM_ARTIFACTORY = new RegExp(
String.raw`"resolved"\s*:\s*"http[s]*://(?!registry.npmjs.org)[^"]+"`,
Expand All @@ -43,26 +43,74 @@ const checkAritfactoryUrl = () => {
}
}

(async () => {
const args = process.argv.slice(2);
try {
if (args.includes("--watch")) {
// Build and watch source code
console.log("[watch] build started");
await build({
...scriptConfig,
...watchConfig,
const createTelemetryConfig = () => {
const defaultConfig = {
telemetryRetryConfig: {
maxRetries: 6,
baseCapacity: 256,
baseTimer: 5000,
maxDelayMs: 100000,
backoffFactor: 2,
jitterFactor: 0.25
},
telemetryApi: {
baseUrl: null,
baseEndpoint: "/vscode/java/sendTelemetry",
version: "/v1"
}
}

const envConfig = Object.freeze({
telemetryRetryConfig: {
maxRetries: process.env.TELEMETRY_MAX_RETRIES,
baseCapacity: process.env.TELEMETRY_BASE_CAPACITY,
baseTimer: process.env.TELEMETRY_BASE_TIMER,
maxDelayMs: process.env.TELEMETRY_MAX_DELAY,
backoffFactor: process.env.TELEMETRY_BACKOFF_FACTOR,
jitterFactor: process.env.TELEMETRY_JITTER_FACTOR
},
telemetryApi: {
baseUrl: process.env.TELEMETRY_API_BASE_URL,
baseEndpoint: process.env.TELEMETRY_API_ENDPOINT,
version: process.env.TELEMETRY_API_VERSION
}
});

Object.entries(defaultConfig).forEach(([parent, configs]) => {
if (parent in envConfig) {
Object.entries(configs).forEach(([key, _]) => {
if (envConfig[parent]?.[key]) {
defaultConfig[parent][key] = envConfig[parent][key];
}
});
console.log("[watch] build finished");
} else if(args.includes("--artifactory-check")){
checkAritfactoryUrl();
} else {
// Build source code
await build(scriptConfig);
console.log("build complete");
}
} catch (err) {
process.stderr.write(err.message);
process.exit(1);
});

fs.writeFileSync("telemetryConfig.json", JSON.stringify(defaultConfig, null, 4));
console.log("Telemetry config generated successfully.");
}

(async () => {
const args = process.argv.slice(2);
try {
if (args.includes("--watch")) {
// Build and watch source code
console.log("[watch] build started");
await build({
...scriptConfig,
...watchConfig,
});
console.log("[watch] build finished");
} else if (args.includes("--artifactory-check")) {
checkAritfactoryUrl();
} else {
// Build source code
createTelemetryConfig();
await build(scriptConfig);
console.log("build complete");
}
})();
} catch (err) {
process.stderr.write(err.message);
process.exit(1);
}
})();
2 changes: 1 addition & 1 deletion vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -804,4 +804,4 @@
"jsonc-parser": "3.3.1",
"vscode-languageclient": "^9.0.1"
}
}
}
2 changes: 1 addition & 1 deletion vscode/src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class ExtensionLogger {

constructor(channelName: string) {
this.outChannel = window.createOutputChannel(channelName);
this.isDebugLogEnabled = process.env['oracle.oracle-java.enable.debug-logs'] === "true";
this.isDebugLogEnabled = process.env['oracle_oracleJava_enable_debugLogs'] === "true";
}

public log(message: string): void {
Expand Down
2 changes: 1 addition & 1 deletion vscode/src/lsp/nbLanguageClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class NbLanguageClient extends LanguageClient {
'showHtmlPageSupport': true,
'wantsJavaSupport': true,
'wantsGroovySupport': false,
'wantsTelemetryEnabled': Telemetry.isTelemetryFeatureAvailable,
'wantsTelemetryEnabled': Telemetry.getIsTelemetryFeatureAvailable(),
'commandPrefix': extConstants.COMMAND_PREFIX,
'configurationPrefix': `${extConstants.COMMAND_PREFIX}.`,
'altConfigurationPrefix': `${extConstants.COMMAND_PREFIX}.`
Expand Down
70 changes: 55 additions & 15 deletions vscode/src/telemetry/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0
https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -14,18 +14,58 @@
limitations under the License.
*/
import { RetryConfig, TelemetryApi } from "./types";
import * as path from 'path';
import * as fs from 'fs';
import { LOGGER } from "../logger";

export const TELEMETRY_RETRY_CONFIG: RetryConfig = Object.freeze({
maxRetries: 6,
baseCapacity: 256,
baseTimer: 5 * 1000,
maxDelayMs: 100 * 1000,
backoffFactor: 2,
jitterFactor: 0.25
});

export const TELEMETRY_API: TelemetryApi = Object.freeze({
baseUrl: null,
baseEndpoint: "/vscode/java/sendTelemetry",
version: "/v1"
});
export class TelemetryConfiguration {
private static CONFIG_FILE_PATH = path.resolve(__dirname, "..", "..", "telemetryConfig.json");

private static instance: TelemetryConfiguration;
private retryConfig!: RetryConfig;
private apiConfig!: TelemetryApi;

public constructor() {
this.initialize();
}

public static getInstance(): TelemetryConfiguration {
if (!TelemetryConfiguration.instance) {
TelemetryConfiguration.instance = new TelemetryConfiguration();
}
return TelemetryConfiguration.instance;
}

private initialize(): void {
try {
const config = JSON.parse(fs.readFileSync(TelemetryConfiguration.CONFIG_FILE_PATH).toString());

this.retryConfig = Object.freeze({
maxRetries: config.telemetryRetryConfig.maxRetries,
baseCapacity: config.telemetryRetryConfig.baseCapacity,
baseTimer: config.telemetryRetryConfig.baseTimer,
maxDelayMs: config.telemetryRetryConfig.maxDelayMs,
backoffFactor: config.telemetryRetryConfig.backoffFactor,
jitterFactor: config.telemetryRetryConfig.jitterFactor
});

this.apiConfig = Object.freeze({
baseUrl: config.telemetryApi.baseUrl,
baseEndpoint: config.telemetryApi.baseEndpoint,
version: config.telemetryApi.version
});
} catch (error: any) {
LOGGER.error("Error occurred while setting up telemetry config");
LOGGER.error(error.message);
}
}

public getRetryConfig(): Readonly<RetryConfig> {
return this.retryConfig;
}

public getApiConfig(): Readonly<TelemetryApi> {
return this.apiConfig;
}

}
36 changes: 24 additions & 12 deletions vscode/src/telemetry/impl/postTelemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
import { integer } from "vscode-languageclient";
import { LOGGER } from "../../logger";
import { TELEMETRY_API } from "../config";
import { TelemetryConfiguration } from "../config";
import { BaseEvent } from "../events/baseEvent";

interface TelemetryEventResponse {
Expand All @@ -28,9 +29,11 @@ export interface TelemetryPostResponse {
};

export class PostTelemetry {
private TELEMETRY_API = TelemetryConfiguration.getInstance().getApiConfig();

public post = async (events: BaseEvent<any>[]): Promise<TelemetryPostResponse> => {
try {
if (TELEMETRY_API.baseUrl == null) {
if (this.TELEMETRY_API.baseUrl == null) {
return {
success: [],
failures: []
Expand All @@ -47,7 +50,7 @@ export class PostTelemetry {
};

private addBaseEndpoint = (endpoint: string) => {
return `${TELEMETRY_API.baseUrl}${TELEMETRY_API.baseEndpoint}${TELEMETRY_API.version}${endpoint}`;
return `${this.TELEMETRY_API.baseUrl}${this.TELEMETRY_API.baseEndpoint}${this.TELEMETRY_API.version}${endpoint}`;
}

private postEvent = (event: BaseEvent<any>): Promise<Response> => {
Expand All @@ -57,25 +60,34 @@ export class PostTelemetry {

return fetch(serverEndpoint, {
method: "POST",
body: JSON.stringify(payload)
body: JSON.stringify(payload),
redirect: "follow",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
});
}

private parseTelemetryResponse = (events: BaseEvent<any>[], eventResponses: PromiseSettledResult<Response>[]): TelemetryPostResponse => {
let success: TelemetryEventResponse[] = [], failures: TelemetryEventResponse[] = [];
eventResponses.forEach((eventResponse, index) => {
const event = events[index];
let list: TelemetryEventResponse[] = success;
let statusCode: integer = 0;
if (eventResponse.status === "rejected") {
failures.push({
event,
statusCode: -1
});
list = failures;
statusCode = -1;
} else {
success.push({
statusCode: eventResponse.value.status,
event
});
statusCode = eventResponse.value.status;
if (statusCode <= 0 || statusCode >= 400) {
list = failures;
}
}
list.push({
event,
statusCode
});
});

return {
Expand Down
27 changes: 14 additions & 13 deletions vscode/src/telemetry/impl/telemetryRetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@
*/

import { LOGGER } from "../../logger";
import { TELEMETRY_RETRY_CONFIG } from "../config";
import { TelemetryConfiguration } from "../config";
import { BaseEvent } from "../events/baseEvent";
import { TelemetryPostResponse } from "./postTelemetry";

export class TelemetryRetry {
private timePeriod: number = TELEMETRY_RETRY_CONFIG.baseTimer;
private TELEMETRY_RETRY_CONFIG = TelemetryConfiguration.getInstance().getRetryConfig();
private timePeriod: number = this.TELEMETRY_RETRY_CONFIG?.baseTimer;
private timeout?: NodeJS.Timeout | null;
private numOfAttemptsWhenTimerHits: number = 1;
private queueCapacity: number = TELEMETRY_RETRY_CONFIG.baseCapacity;
private queueCapacity: number = this.TELEMETRY_RETRY_CONFIG?.baseCapacity;
private numOfAttemptsWhenQueueIsFull: number = 1;
private triggeredDueToQueueOverflow: boolean = false;
private callbackHandler?: () => {};
Expand All @@ -45,12 +46,12 @@ export class TelemetryRetry {

private resetTimerParameters = () => {
this.numOfAttemptsWhenTimerHits = 1;
this.timePeriod = TELEMETRY_RETRY_CONFIG.baseTimer;
this.timePeriod = this.TELEMETRY_RETRY_CONFIG.baseTimer;
this.clearTimer();
}

private increaseTimePeriod = (): void => {
if (this.numOfAttemptsWhenTimerHits <= TELEMETRY_RETRY_CONFIG.maxRetries) {
if (this.numOfAttemptsWhenTimerHits <= this.TELEMETRY_RETRY_CONFIG.maxRetries) {
this.timePeriod = this.calculateDelay();
this.numOfAttemptsWhenTimerHits++;
return;
Expand All @@ -66,26 +67,26 @@ export class TelemetryRetry {
}

private calculateDelay = (): number => {
const baseDelay = TELEMETRY_RETRY_CONFIG.baseTimer *
Math.pow(TELEMETRY_RETRY_CONFIG.backoffFactor, this.numOfAttemptsWhenTimerHits);
const baseDelay = this.TELEMETRY_RETRY_CONFIG.baseTimer *
Math.pow(this.TELEMETRY_RETRY_CONFIG.backoffFactor, this.numOfAttemptsWhenTimerHits);

const cappedDelay = Math.min(baseDelay, TELEMETRY_RETRY_CONFIG.maxDelayMs);
const cappedDelay = Math.min(baseDelay, this.TELEMETRY_RETRY_CONFIG.maxDelayMs);

const jitterMultiplier = 1 + (Math.random() * 2 - 1) * TELEMETRY_RETRY_CONFIG.jitterFactor;
const jitterMultiplier = 1 + (Math.random() * 2 - 1) * this.TELEMETRY_RETRY_CONFIG.jitterFactor;

return Math.floor(cappedDelay * jitterMultiplier);
};

private increaseQueueCapacity = (): void => {
if (this.numOfAttemptsWhenQueueIsFull < TELEMETRY_RETRY_CONFIG.maxRetries) {
this.queueCapacity = TELEMETRY_RETRY_CONFIG.baseCapacity *
Math.pow(TELEMETRY_RETRY_CONFIG.backoffFactor, this.numOfAttemptsWhenQueueIsFull);
if (this.numOfAttemptsWhenQueueIsFull < this.TELEMETRY_RETRY_CONFIG.maxRetries) {
this.queueCapacity = this.TELEMETRY_RETRY_CONFIG.baseCapacity *
Math.pow(this.TELEMETRY_RETRY_CONFIG.backoffFactor, this.numOfAttemptsWhenQueueIsFull);
}
throw new Error("Number of retries exceeded");
}

private resetQueueCapacity = (): void => {
this.queueCapacity = TELEMETRY_RETRY_CONFIG.baseCapacity;
this.queueCapacity = this.TELEMETRY_RETRY_CONFIG.baseCapacity;
this.numOfAttemptsWhenQueueIsFull = 1;
this.triggeredDueToQueueOverflow = false;
}
Expand Down
Loading
Loading