From 5cc9884ee25f2de2f3f715035971018ae163181f Mon Sep 17 00:00:00 2001 From: Qiuxia Fan Date: Mon, 28 Feb 2022 15:53:14 +0800 Subject: [PATCH] feat: add profile --- src/graphql/fragments/profile.ts | 124 ++++++++++++ src/graphql/query/profile.ts | 39 ++++ src/store/modules/dashboard.ts | 2 +- src/store/modules/profile.ts | 185 ++++++++++++++++++ src/types/profile.d.ts | 60 ++++++ src/views/dashboard/controls/Profile.vue | 86 ++++++++ src/views/dashboard/controls/index.ts | 3 +- src/views/dashboard/data.ts | 1 + src/views/dashboard/panel/Tool.vue | 3 + .../dashboard/related/profile/Header.vue | 63 ++++++ 10 files changed, 564 insertions(+), 2 deletions(-) create mode 100644 src/graphql/fragments/profile.ts create mode 100644 src/graphql/query/profile.ts create mode 100644 src/store/modules/profile.ts create mode 100644 src/types/profile.d.ts create mode 100644 src/views/dashboard/controls/Profile.vue create mode 100644 src/views/dashboard/related/profile/Header.vue diff --git a/src/graphql/fragments/profile.ts b/src/graphql/fragments/profile.ts new file mode 100644 index 00000000..c1053083 --- /dev/null +++ b/src/graphql/fragments/profile.ts @@ -0,0 +1,124 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://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, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const ProfileSegment = { + variable: "$segmentId: String", + query: ` + segment: getProfiledSegment(segmentId: $segmentId) { + spans { + spanId + parentSpanId + serviceCode + startTime + endTime + endpointName + type + peer + component + isError + layer + tags { + key value + } + logs { + time + data { + key + value + } + } + } + } + `, +}; + +export const CreateProfileTask = { + variable: "$creationRequest: ProfileTaskCreationRequest", + query: ` + task: createProfileTask(creationRequest: $creationRequest) { + id + errorReason + } + `, +}; + +export const GetProfileTaskList = { + variable: "$endpointName: String, $serviceId: ID", + query: ` + taskList: getProfileTaskList(endpointName: $endpointName, serviceId: $serviceId) { + serviceId + endpointName + startTime + duration + minDurationThreshold + dumpPeriod + maxSamplingCount + id + logs { + id + instanceId + instanceName + operationType + operationTime + } + } + `, +}; +export const GetProfileTaskSegmentList = { + variable: "$taskID: String", + query: ` + segmentList: getProfileTaskSegmentList(taskID: $taskID) { + segmentId + endpointNames + start + duration + traceIds + isError + } + `, +}; + +export const GetProfileAnalyze = { + variable: "$segmentId: String!, $timeRanges: [ProfileAnalyzeTimeRange!]!", + query: ` + analyze: getProfileAnalyze(segmentId: $segmentId, timeRanges: $timeRanges) { + tip + trees { + elements { + id + parentId + codeSignature + duration + durationChildExcluded + count + } + } + } + `, +}; +export const GetProfileTaskLogs = { + variable: "$taskID: String", + query: ` + taskLogs: getProfileTaskLogs(taskID: $taskID) { + id + instanceId + instanceName + operationTime + operationType + } + `, +}; diff --git a/src/graphql/query/profile.ts b/src/graphql/query/profile.ts new file mode 100644 index 00000000..3534140e --- /dev/null +++ b/src/graphql/query/profile.ts @@ -0,0 +1,39 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://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, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ProfileSegment, + CreateProfileTask, + GetProfileTaskList, + GetProfileTaskSegmentList, + GetProfileAnalyze, + GetProfileTaskLogs, +} from "../fragments/profile"; + +export const queryProfileSegment = `query queryProfileSegment(${ProfileSegment.variable}) {${ProfileSegment.query}}`; + +export const saveProfileTask = `mutation createProfileTask(${CreateProfileTask.variable}) {${CreateProfileTask.query}}`; + +export const getProfileTaskList = `query getProfileTaskList(${GetProfileTaskList.variable}) { + ${GetProfileTaskList.query}}`; + +export const getProfileTaskSegmentList = `query getProfileTaskSegmentList(${GetProfileTaskSegmentList.variable}) { + ${GetProfileTaskSegmentList.query}}`; + +export const getProfileAnalyze = `query getProfileAnalyze(${GetProfileAnalyze.variable}) {${GetProfileAnalyze.query}}`; + +export const getProfileTaskLogs = `query profileTaskLogs(${GetProfileTaskLogs.variable}) {${GetProfileTaskLogs.query}}`; diff --git a/src/store/modules/dashboard.ts b/src/store/modules/dashboard.ts index 27b2d702..8c60f27f 100644 --- a/src/store/modules/dashboard.ts +++ b/src/store/modules/dashboard.ts @@ -95,7 +95,7 @@ export const dashboardStore = defineStore({ showDepth: true, }; } - if (type === "Trace") { + if (type === "Trace" || type === "Profile") { newItem.h = 24; } this.layout = this.layout.map((d: LayoutConfig) => { diff --git a/src/store/modules/profile.ts b/src/store/modules/profile.ts new file mode 100644 index 00000000..5a3db670 --- /dev/null +++ b/src/store/modules/profile.ts @@ -0,0 +1,185 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://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, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { defineStore } from "pinia"; +import { Duration } from "@/types/app"; +import { Service } from "@/types/selector"; +import { + TaskListItem, + SegmentSpan, + ProfileAnalyzationTrees, + TaskLog, +} from "@/types/profile"; +import { Trace } from "@/types/trace"; +import { store } from "@/store"; +import graphql from "@/graphql"; +import { AxiosResponse } from "axios"; +import { useAppStoreWithOut } from "@/store/modules/app"; + +interface ProfileState { + services: Service[]; + durationTime: Duration; + condition: { serviceId: string; endpointName: string }; + taskList: TaskListItem[]; + segmentList: Trace[]; + currentSegment: Nullable; + segmentSpans: SegmentSpan[]; + currentSpan: Nullable; + analyzeTrees: ProfileAnalyzationTrees; + taskLogs: TaskLog[]; +} + +export const traceStore = defineStore({ + id: "profile", + state: (): ProfileState => ({ + services: [{ value: "0", label: "All" }], + durationTime: useAppStoreWithOut().durationTime, + condition: { serviceId: "", endpointName: "" }, + taskList: [], + segmentList: [], + currentSegment: null, + segmentSpans: [], + currentSpan: null, + analyzeTrees: [], + taskLogs: [], + }), + actions: { + setConditions(data: { serviceId?: string; endpointName?: string }) { + this.condition = { + ...this.condition, + ...data, + }; + }, + async getServices(layer: string) { + const res: AxiosResponse = await graphql.query("queryServices").params({ + layer, + }); + if (res.data.errors) { + return res.data; + } + this.services = [ + { value: "0", label: "All" }, + ...res.data.data.services, + ] || [{ value: "0", label: "All" }]; + return res.data; + }, + async getTaskList() { + const res: AxiosResponse = await graphql + .query("getProfileTaskList") + .params(this.condition); + + if (res.data.errors) { + return res.data; + } + const list = res.data.data.taskList; + this.taskList = list; + if (!list.length) { + return res.data; + } + }, + async getSegmentList(params: { taskID: string }) { + const res: AxiosResponse = await graphql + .query("getProfileTaskSegmentList") + .params(params); + + if (res.data.errors) { + this.segmentList = []; + return res.data; + } + const { segmentList } = res.data.data; + + this.segmentList = segmentList; + + if (segmentList[0]) { + this.currentSegment = segmentList[0]; + } else { + this.currentSegment = null; + } + return res.data; + }, + async getSegmentSpans(params: { segmentId: string }) { + const res: AxiosResponse = await graphql + .query("queryProfileSegment") + .params(params); + if (res.data.errors) { + this.segmentSpans = []; + return res.data; + } + const { segment } = res.data.data; + if (!segment) { + this.segmentSpans = []; + return res.data; + } + this.segmentSpans = segment.spans; + if (!(segment.spans && segment.spans.length)) { + return res.data; + } + const index = segment.spans.length - 1 || 0; + this.currentSpan = segment.spans[index]; + return res.data; + }, + async getProfileAnalyze(params: { + segmentId: string; + timeRanges: Array<{ start: number; end: number }>; + }) { + const res: AxiosResponse = await graphql + .query("getProfileAnalyze") + .params(params); + + if (res.data.errors) { + this.segmentSpans = []; + return res.data; + } + const { analyze, tip } = res.data.data; + if (tip) { + return res.data; + } + + if (!analyze) { + this.analyzeTrees = []; + return res.data; + } + this.analyzeTrees = analyze.trees; + return res.data; + }, + async createTask(param: any) { + const res: AxiosResponse = await graphql + .query("saveProfileTask") + .params({ param }); + + if (res.data.errors) { + return res.data; + } + this.getTaskList(); + return res.data; + }, + async getTaskLogs(param: { taskID: string }) { + const res: AxiosResponse = await graphql + .query("getProfileTaskLogs") + .params(param); + + if (res.data.errors) { + return res.data; + } + this.taskLogs = res.data.data.taskLogs; + return res.data; + }, + }, +}); + +export function useProfileStore(): any { + return traceStore(store); +} diff --git a/src/types/profile.d.ts b/src/types/profile.d.ts new file mode 100644 index 00000000..7d89fc99 --- /dev/null +++ b/src/types/profile.d.ts @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://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, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +type ProfileStackElement = { + id: string; + parentId: string; + codeSignature: string; + duration: number; + durationChildExcluded: number; + count: number; +}; +export type ProfileAnalyzationTrees = { elements: ProfileStackElement[] }[]; +export interface TaskLog { + id: string; + instanceId: string; + instanceName: string; + operationTime: number; + operationType: string; +} +export interface TaskListItem { + id: string; + serviceId: string; + serviceName: string; + endpointName: string; + startTime: number; + duration: number; + minDurationThreshold: number; + dumpPeriod: number; + maxSamplingCount: number; + logs: TaskLog[]; +} +export interface SegmentSpan { + spanId: string; + parentSpanId: string; + serviceCode: string; + serviceInstanceName: string; + startTime: number; + endTime: number; + endpointName: string; + type: string; + peer: string; + component: string; + isError: boolean; + layer: string; + tags: any[]; + logs: any[]; +} diff --git a/src/views/dashboard/controls/Profile.vue b/src/views/dashboard/controls/Profile.vue new file mode 100644 index 00000000..4f2fbbd5 --- /dev/null +++ b/src/views/dashboard/controls/Profile.vue @@ -0,0 +1,86 @@ + + + + diff --git a/src/views/dashboard/controls/index.ts b/src/views/dashboard/controls/index.ts index d4f2656c..4f2ee20e 100644 --- a/src/views/dashboard/controls/index.ts +++ b/src/views/dashboard/controls/index.ts @@ -18,5 +18,6 @@ import Topology from "./Topology.vue"; import Tab from "./Tab.vue"; import Widget from "./Widget.vue"; import Trace from "./Trace.vue"; +import Profile from "./Profile.vue"; -export default { Tab, Widget, Trace, Topology }; +export default { Tab, Widget, Trace, Topology, Profile }; diff --git a/src/views/dashboard/data.ts b/src/views/dashboard/data.ts index 97a079e3..9880fa95 100644 --- a/src/views/dashboard/data.ts +++ b/src/views/dashboard/data.ts @@ -167,6 +167,7 @@ export const ToolIcons = [ { name: "all_inbox", content: "Add Tab", id: "addTab" }, { name: "device_hub", content: "Add Topology", id: "topology" }, { name: "merge", content: "Add Trace", id: "trace" }, + { name: "timeline", content: "Add Profile", id: "profile" }, // { name: "save_alt", content: "Export", id: "export" }, // { name: "folder_open", content: "Import", id: "import" }, // { name: "settings", content: "Settings", id: "settings" }, diff --git a/src/views/dashboard/panel/Tool.vue b/src/views/dashboard/panel/Tool.vue index 287ca3d0..844eb4d7 100644 --- a/src/views/dashboard/panel/Tool.vue +++ b/src/views/dashboard/panel/Tool.vue @@ -295,6 +295,9 @@ function clickIcons(t: { id: string; content: string; name: string }) { case "trace": dashboardStore.addControl("Trace"); break; + case "profile": + dashboardStore.addControl("Profile"); + break; case "topology": dashboardStore.addControl("Topology"); break; diff --git a/src/views/dashboard/related/profile/Header.vue b/src/views/dashboard/related/profile/Header.vue new file mode 100644 index 00000000..aca7b2b1 --- /dev/null +++ b/src/views/dashboard/related/profile/Header.vue @@ -0,0 +1,63 @@ + + +