Skip to content

Commit

Permalink
feat: add rpc timing event track (#3743)
Browse files Browse the repository at this point in the history
* feat: add rpc timing event track

* feat: can configure measure report

* chore: revert typings

* feat: adjust config
  • Loading branch information
bytemain authored Jun 6, 2024
1 parent 76ff557 commit aeaa7be
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 9 deletions.
53 changes: 51 additions & 2 deletions packages/connection/src/common/rpc-service/center.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { Deferred, DisposableStore, IDisposable, randomString } from '@opensumi/ide-core-common';
import {
Deferred,
DisposableStore,
IDisposable,
IReporterService,
IReporterTimer,
REPORT_NAME,
randomString,
} from '@opensumi/ide-core-common';
import { addElement } from '@opensumi/ide-utils/lib/arrays';

import { METHOD_NOT_REGISTERED } from '../constants';
Expand All @@ -12,6 +20,8 @@ import { ProtocolRegistry, ServiceRegistry } from './registry';

import type { MessageConnection } from '@opensumi/vscode-jsonrpc';

const kDefaultMinimumReportThresholdTime = 200;

export class RPCServiceCenter implements IDisposable {
private _disposables = new DisposableStore();

Expand All @@ -30,6 +40,16 @@ export class RPCServiceCenter implements IDisposable {
this.logger = logger || console;
}

private _reporterService: IReporterService | undefined;
private _reportThreshold: number = kDefaultMinimumReportThresholdTime;
setReporter(
reporterService: IReporterService,
minimumReportThresholdTime: number = kDefaultMinimumReportThresholdTime,
) {
this._reporterService = reporterService;
this._reportThreshold = minimumReportThresholdTime;
}

registerService(serviceName: string, type: ServiceType): void {
if (type === ServiceType.Service) {
if (this.bench) {
Expand Down Expand Up @@ -98,8 +118,13 @@ export class RPCServiceCenter implements IDisposable {

async broadcast(serviceName: string, _name: string, ...args: any[]): Promise<any> {
await this.ready();

const name = getMethodName(serviceName, _name);

let timer: IReporterTimer | undefined;
if (this._reporterService) {
timer = this._reporterService.time(REPORT_NAME.RPC_TIMMING_MEASURE);
}

const broadcastResult = await Promise.all(this.proxies.map((proxy) => proxy.invoke(name, ...args)));

const doubtfulResult = [] as any[];
Expand All @@ -117,9 +142,33 @@ export class RPCServiceCenter implements IDisposable {
}

if (result.length === 0) {
if (timer) {
timer.timeEnd(
name,
{
success: false,
},
{
minimumReportThresholdTime: this._reportThreshold,
},
);
}

throw new Error(`broadcast rpc \`${name}\` error: no remote service can handle this call`);
}

if (timer) {
timer.timeEnd(
name,
{
success: true,
},
{
minimumReportThresholdTime: this._reportThreshold,
},
);
}

// FIXME: this is an unreasonable design, if remote service only returned doubtful result, we will return an empty array.
// but actually we should throw an error to tell user that no remote service can handle this call.
// or just return `undefined`.
Expand Down
7 changes: 7 additions & 0 deletions packages/core-browser/src/bootstrap/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { BackService } from '@opensumi/ide-core-common/lib/module';

import { ClientAppStateService } from '../application';
import { AppConfig } from '../react-providers/config-provider';

import { ModuleConstructor } from './app.interface';

Expand All @@ -25,6 +26,7 @@ export async function createConnectionService(
channelHandler: WSChannelHandler,
options: ISumiConnectionOptions = {},
) {
const appConfig = injector.get(AppConfig) as AppConfig;
const reporterService: IReporterService = injector.get(IReporterService);
channelHandler.setReporter(reporterService);

Expand Down Expand Up @@ -69,6 +71,11 @@ export async function createConnectionService(

const clientCenter = new RPCServiceCenter();
clientCenter.setSumiConnection(channel.createSumiConnection(options));

if (appConfig?.measure?.connection) {
clientCenter.setReporter(reporterService, appConfig.measure.connection.minimumReportThresholdTime);
}

initConnectionService(injector, modules, clientCenter);

return channel;
Expand Down
20 changes: 19 additions & 1 deletion packages/core-browser/src/react-providers/config-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const AppConfig = Symbol('AppConfig');
export interface AppConfig {
/**
* APP的名称
* 默认值为 `ClientApp.DEFAULT_APPLICATION_NAME` 即 `OPENSUMI`
* 默认值为 `ClientApp.DEFAULT_APPLICATION_NAME` 即 `OpenSumi`
*/
appName?: string;
/**
Expand Down Expand Up @@ -284,6 +284,10 @@ export interface AppConfig {
* 支持的通信协议类型
*/
connectionProtocols?: string[];
/**
* 埋点上报的配置
*/
measure?: IMeasureConfig;
/**
* 是否启用 Diff 协议文件自动恢复
*/
Expand All @@ -294,6 +298,20 @@ export interface ICollaborationClientOpts {
port?: number;
}

export interface IMeasureConfig {
/**
* 是否开启连接性能监控
*/
connection?: IConnectionMeasureConfig;
}

export interface IConnectionMeasureConfig {
/**
* 最低上报阈值时间,单位 ms
*/
minimumReportThresholdTime?: number;
}

export const ConfigContext = React.createContext<AppConfig>({
workspaceDir: '',
injector: null as any,
Expand Down
15 changes: 13 additions & 2 deletions packages/core-common/src/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
IReporter,
IReporterService,
IReporterTimer,
IReporterTimerEndOptions,
PerformanceData,
PointData,
REPORT_NAME,
Expand All @@ -18,8 +19,18 @@ class ReporterTimer implements IReporterTimer {
this.now = Date.now();
}

timeEnd(msg?: string, extra?: any) {
const duration = Date.now() - this.now;
getElapsedTime() {
return Date.now() - this.now;
}

timeEnd(msg?: string, extra?: any, options?: IReporterTimerEndOptions) {
const duration = this.getElapsedTime();

if (options?.minimumReportThresholdTime && duration < options.minimumReportThresholdTime) {
// 不满足最小时间要求,不上报
return duration;
}

this.reporter.performance(this.name, {
duration,
metadata: this.metadata,
Expand Down
11 changes: 10 additions & 1 deletion packages/core-common/src/types/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export enum REPORT_NAME {
TERMINAL_MEASURE = 'terminalMeasure',
SEARCH_MEASURE = 'searchMeasure',
QUICK_OPEN_MEASURE = 'quickOpenMeasure',
RPC_TIMMING_MEASURE = 'rpcTimingMeasure',
}

export enum REPORT_HOST {
Expand Down Expand Up @@ -71,8 +72,16 @@ export interface PerformanceData extends PointData {

export const IReporterService = Symbol('IReporterService');

export interface IReporterTimerEndOptions {
/**
* 上报的最小时间阈值,单位毫秒
* 经过的时间要大于这个值才会上报
*/
minimumReportThresholdTime?: number;
}

export interface IReporterTimer {
timeEnd(msg?: string, extra?: any): number;
timeEnd(msg?: string, extra?: any, options?: IReporterTimerEndOptions): number;
}

export interface IReporterService {
Expand Down
5 changes: 5 additions & 0 deletions packages/startup/entry/web/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ renderApp(
modules: [DESIGN_MENUBAR_CONTAINER_VIEW_ID],
},
},
measure: {
connection: {
minimumReportThresholdTime: 400,
},
},
},
}),
);
2 changes: 1 addition & 1 deletion packages/startup/entry/web/render-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export async function renderApp(opts: IClientAppOpts) {
// eslint-disable-next-line no-console
console.timeEnd('Render');
};
registerLocalStorageProvider(GeneralSettingsId.Theme, opts.workspaceDir || '', 'prefix1');
registerLocalStorageProvider(GeneralSettingsId.Theme, opts.workspaceDir || '', 'sumi-dev');

const app = new ClientApp(opts);

Expand Down
4 changes: 2 additions & 2 deletions packages/utils/src/functional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ export function diffSets<T>(before: Set<T>, after: Set<T>): { removed: T[]; adde
}

export function findFirstTruthy<T>(...sources: Array<T | (() => T)>): T | undefined {
for (let i = 0; i < sources.length - 1; i++) {
for (let i = 0; i <= sources.length - 1; i++) {
const result = check(sources[i]);
if (result) {
return result;
}
}

return check(sources[sources.length - 1]);
return undefined;

function check(value: T | (() => T)): T | undefined {
if (typeof value === 'function') {
Expand Down

0 comments on commit aeaa7be

Please sign in to comment.