merge main

This commit is contained in:
Brandon Fergerson 2022-04-30 09:39:38 +02:00
commit 887a4de628
46 changed files with 31229 additions and 431 deletions

29868
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,12 +13,14 @@
"@types/jquery": "^3.5.14", "@types/jquery": "^3.5.14",
"axios": "^0.24.0", "axios": "^0.24.0",
"d3": "^7.3.0", "d3": "^7.3.0",
"d3-flame-graph": "^4.1.3",
"d3-tip": "^0.9.1", "d3-tip": "^0.9.1",
"echarts": "^5.2.2", "echarts": "^5.2.2",
"element-plus": "^2.0.2", "element-plus": "^2.0.2",
"jquery": "^3.6.0", "jquery": "^3.6.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"pinia": "^2.0.5", "pinia": "^2.0.5",
"vis-timeline": "^7.5.1",
"vue": "^3.0.0", "vue": "^3.0.0",
"vue-grid-layout": "^3.0.0-beta1", "vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.9", "vue-i18n": "^9.1.9",

16
src/assets/icons/arrow-down.svg Executable file
View File

@ -0,0 +1,16 @@
<!-- 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. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path id="a" d="m10.472352 7.28232367c.3431062-.36783247.904419-.36783247 1.2592596-.00644059.3578153.36442148.3578153.95850784.0002156 1.28561559l-3.10532264 3.16826253c-.17025689.1734002-.39845625.2702388-.62654793.2702388-.24380864 0-.45151514-.0919745-.62697852-.2706782l-3.09835734-3.16693764c-.36405333-.352236-.36405333-.94614513-.01248284-1.28566765.34310619-.36783247.90441901-.36783247 1.25901327-.0066912l2.48658215 2.52737493z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,17 @@
<!-- 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. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M17.016 17.016v-4.031h-2.016v4.031h2.016zM12.984 17.016v-10.031h-1.969v10.031h1.969zM9 17.016v-7.031h-2.016v7.031h2.016zM18.984 3q0.797 0 1.406 0.609t0.609 1.406v13.969q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797 0-1.406-0.609t-0.609-1.406v-13.969q0-0.797 0.609-1.406t1.406-0.609h13.969z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

15
src/assets/icons/view.svg Normal file
View File

@ -0,0 +1,15 @@
<!-- 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. -->
<svg t="1650287922642" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3055" width="48" height="48"><path d="M277.333333 325.333333m5.333334 0l325.333333 0q5.333333 0 5.333333 5.333334l0 64q0 5.333333-5.333333 5.333333l-325.333333 0q-5.333333 0-5.333334-5.333333l0-64q0-5.333333 5.333334-5.333334Z" p-id="3056" fill="#707070"></path><path d="M277.333333 474.666667m5.333334 0l325.333333 0q5.333333 0 5.333333 5.333333l0 64q0 5.333333-5.333333 5.333333l-325.333333 0q-5.333333 0-5.333334-5.333333l0-64q0-5.333333 5.333334-5.333333Z" p-id="3057" fill="#707070"></path><path d="M277.333333 624m5.333334 0l247.36 0q5.333333 0 5.333333 5.333333l0 64q0 5.333333-5.333333 5.333334l-247.36 0q-5.333333 0-5.333334-5.333334l0-64q0-5.333333 5.333334-5.333333Z" p-id="3058" fill="#707070"></path><path d="M565.333333 842.666667H186.666667a5.333333 5.333333 0 0 1-5.333334-5.333334V186.666667a5.333333 5.333333 0 0 1 5.333334-5.333334h522.666666v346.24a5.333333 5.333333 0 0 0 5.333334 5.333334h64a5.333333 5.333333 0 0 0 5.333333-5.333334V106.666667H112a5.333333 5.333333 0 0 0-5.333333 5.333333v800a5.333333 5.333333 0 0 0 5.333333 5.333333h453.333333a5.333333 5.333333 0 0 0 5.333334-5.333333v-64a5.333333 5.333333 0 0 0-5.333334-5.333333z" p-id="3059" fill="#707070"></path><path d="M868.426667 723.786667a144.64 144.64 0 1 0-144.64 144.64 144.64 144.64 0 0 0 144.64-144.64z m-144.64 69.973333a69.973333 69.973333 0 1 1 69.973333-69.973333 70.026667 70.026667 0 0 1-69.973333 69.973333z" p-id="3060" fill="#707070"></path><path d="M811.758007 864.533065m3.771237-3.771236l45.254834-45.254834q3.771236-3.771236 7.542472 0l45.254834 45.254834q3.771236 3.771236 0 7.542472l-45.254834 45.254834q-3.771236 3.771236-7.542472 0l-45.254834-45.254834q-3.771236-3.771236 0-7.542472Z" p-id="3061" fill="#707070"></path></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -32,7 +32,7 @@ interface Option {
const emit = defineEmits(["change"]); const emit = defineEmits(["change"]);
const props = defineProps({ const props = defineProps({
options: { options: {
type: Array as PropType<(Option & { disabled: boolean })[]>, type: Array as PropType<Option[]>,
default: () => [], default: () => [],
}, },
value: { value: {
@ -44,7 +44,7 @@ const props = defineProps({
const selected = ref<string>(props.value); const selected = ref<string>(props.value);
function checked(opt: string) { function checked(opt: unknown) {
emit("change", opt); emit("change", opt);
} }
</script> </script>

View File

@ -45,7 +45,7 @@ import { Option } from "@/types/app";
const emit = defineEmits(["change"]); const emit = defineEmits(["change"]);
const props = defineProps({ const props = defineProps({
options: { options: {
type: Array as PropType<(Option & { disabled: boolean })[]>, type: Array as PropType<Option[]>,
default: () => [], default: () => [],
}, },
value: { value: {

View File

@ -58,7 +58,7 @@ const props = defineProps({
}, },
size: { type: null, default: "default" }, size: { type: null, default: "default" },
placeholder: { placeholder: {
type: [String, Number] as PropType<string | number>, type: [String, undefined] as PropType<string>,
default: "Select a option", default: "Select a option",
}, },
borderRadius: { type: Number, default: 3 }, borderRadius: { type: Number, default: 3 },

View File

@ -14,13 +14,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import type { App } from "vue";
import Icon from "./Icon.vue"; import Icon from "./Icon.vue";
import TimePicker from "./TimePicker.vue"; import TimePicker from "./TimePicker.vue";
import Selector from "./Selector.vue"; import Selector from "./Selector.vue";
import Graph from "./Graph.vue"; import Graph from "./Graph.vue";
import Radio from "./Radio.vue"; import Radio from "./Radio.vue";
import SelectSingle from "./SelectSingle.vue"; import SelectSingle from "./SelectSingle.vue";
import type { App } from "vue";
import VueGridLayout from "vue-grid-layout"; import VueGridLayout from "vue-grid-layout";
const components: { [key: string]: any } = { const components: { [key: string]: any } = {

View File

@ -0,0 +1,93 @@
/**
* 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 queryCreateTaskData = {
variable: "$serviceId: ID!",
query: `
createTaskData: queryPrepareCreateEBPFProfilingTaskData(serviceId: $serviceId) {
couldProfiling
processLabels
}`,
};
export const createEBPFTask = {
variable: "$request: EBPFProfilingTaskFixedTimeCreationRequest!",
query: `
createTaskData: createEBPFProfilingFixedTimeTask(request: $request) {
status
errorReason
id
}`,
};
export const queryEBPFTasks = {
variable: "$serviceId: ID!",
query: `
queryEBPFTasks: queryEBPFProfilingTasks(serviceId: $serviceId) {
taskId
serviceName
serviceId
processLabels
taskStartTime
triggerType
fixedTriggerDuration
targetType
createTime
}`,
};
export const queryEBPFSchedules = {
variable: "$taskId: ID!, $duration: Duration!",
query: `
eBPFSchedules: queryEBPFProfilingSchedules(taskId: $taskId, duration: $duration) {
scheduleId
taskId
process {
id
name
serviceId
serviceName
instanceId
instanceName
layer
agentId
detectType
attributes {
name
value
}
labels
}
startTime
endTime
}`,
};
export const analysisEBPFResult = {
variable:
"$scheduleIdList: [ID!]!, $timeRanges: [EBPFProfilingAnalyzeTimeRange!]!",
query: `
analysisEBPFResult: analysisEBPFProfilingResult(scheduleIdList: $scheduleIdList, timeRanges: $timeRanges) {
tip
trees {
elements {
id
parentId
symbol
stackType
dumpCount
}
}
}`,
};

View File

@ -25,6 +25,7 @@ import * as log from "./query/log";
import * as profile from "./query/profile"; import * as profile from "./query/profile";
import * as alarm from "./query/alarm"; import * as alarm from "./query/alarm";
import * as event from "./query/event"; import * as event from "./query/event";
import * as ebpf from "./query/ebpf";
const query: { [key: string]: string } = { const query: { [key: string]: string } = {
...app, ...app,
@ -36,6 +37,7 @@ const query: { [key: string]: string } = {
...profile, ...profile,
...alarm, ...alarm,
...event, ...event,
...ebpf,
}; };
class Graphql { class Graphql {
private queryData = ""; private queryData = "";

34
src/graphql/query/ebpf.ts Normal file
View File

@ -0,0 +1,34 @@
/**
* 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 {
queryCreateTaskData,
createEBPFTask,
queryEBPFTasks,
queryEBPFSchedules,
analysisEBPFResult,
} from "../fragments/ebpf";
export const getCreateTaskData = `query queryCreateTaskData(${queryCreateTaskData.variable}) {${queryCreateTaskData.query}}`;
export const saveEBPFTask = `mutation createEBPFTask(${createEBPFTask.variable}) {${createEBPFTask.query}}`;
export const getEBPFTasks = `query queryEBPFTasks(${queryEBPFTasks.variable}) {${queryEBPFTasks.query}}`;
export const getEBPFSchedules = `query queryEBPFSchedules(${queryEBPFSchedules.variable}) {${queryEBPFSchedules.query}}`;
export const getEBPFResult = `query analysisEBPFResult(${analysisEBPFResult.variable}) {${analysisEBPFResult.query}}`;

View File

@ -131,6 +131,11 @@ const msg = {
metricLabel: "Metric Label", metricLabel: "Metric Label",
showUnit: "Show Unit", showUnit: "Show Unit",
noGraph: "No Graph", noGraph: "No Graph",
taskId: "Task ID",
triggerType: "Trigger Type",
targetType: "Target Type",
ebpfTip: "Don't have process could profiling",
processSelect: "Click to select processes",
hourTip: "Select Hour", hourTip: "Select Hour",
minuteTip: "Select Minute", minuteTip: "Select Minute",
secondTip: "Select Second", secondTip: "Select Second",
@ -237,6 +242,10 @@ const msg = {
defaultDepth: "Default Depth", defaultDepth: "Default Depth",
traceTagsTip: `Only tags defined in the core/default/searchableTracesTags are searchable. traceTagsTip: `Only tags defined in the core/default/searchableTracesTags are searchable.
Check more details on the Configuration Vocabulary page`, Check more details on the Configuration Vocabulary page`,
logTagsTip: `Only tags defined in the core/default/searchableLogsTags are searchable.
Check more details on the Configuration Vocabulary page`,
alarmTagsTip: `Only tags defined in the core/default/searchableAlarmTags are searchable.
Check more details on the Configuration Vocabulary page`,
tagsLink: "Configuration Vocabulary page", tagsLink: "Configuration Vocabulary page",
addTag: "Please input a tag", addTag: "Please input a tag",
log: "Log", log: "Log",

View File

@ -129,6 +129,11 @@ const msg = {
metricLabel: "指标标签", metricLabel: "指标标签",
showUnit: "显示单位", showUnit: "显示单位",
noGraph: "无图表", noGraph: "无图表",
taskId: "任务ID",
triggerType: "触发类型",
targetType: "目标类型",
processSelect: "点击选择进程",
ebpfTip: "没有进程可以分析",
hourTip: "选择小时", hourTip: "选择小时",
minuteTip: "选择分钟", minuteTip: "选择分钟",
secondTip: "选择秒数", secondTip: "选择秒数",
@ -238,6 +243,10 @@ const msg = {
defaultDepth: "默认深度", defaultDepth: "默认深度",
traceTagsTip: traceTagsTip:
"只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。", "只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
logTagsTip:
"只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
alarmTagsTip:
"只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
tagsLink: "配置词汇页", tagsLink: "配置词汇页",
addTag: "请添加标签", addTag: "请添加标签",
logCategory: "日志类别", logCategory: "日志类别",

View File

@ -20,7 +20,7 @@ import router from "./router";
import { store } from "./store"; import { store } from "./store";
import components from "@/components"; import components from "@/components";
import i18n from "./locales"; import i18n from "./locales";
import "./styles/index.scss"; import "./styles/index.ts";
const app = createApp(App); const app = createApp(App);

View File

@ -88,12 +88,9 @@ export const appStore = defineStore({
this.duration.start.getMonth()); this.duration.start.getMonth());
break; break;
} }
const utcArr = this.utc.split(":");
const utcHour = isNaN(Number(utcArr[0])) ? 0 : Number(utcArr[0]);
const utcMin = isNaN(Number(utcArr[1])) ? 0 : Number(utcArr[1]);
const utcSpace = const utcSpace =
(utcHour + new Date().getTimezoneOffset() / 60) * 3600000 + (this.utcHour + new Date().getTimezoneOffset() / 60) * 3600000 +
utcMin * 60000; this.utcMin * 60000;
const startUnix: number = this.duration.start.getTime(); const startUnix: number = this.duration.start.getTime();
const endUnix: number = this.duration.end.getTime(); const endUnix: number = this.duration.end.getTime();
const timeIntervals: string[] = []; const timeIntervals: string[] = [];
@ -155,6 +152,10 @@ export const appStore = defineStore({
} }
this.utc = res.data.data.getTimeInfo.timezone / 100 + ":0"; this.utc = res.data.data.getTimeInfo.timezone / 100 + ":0";
const utcArr = this.utc.split(":");
this.utcHour = isNaN(Number(utcArr[0])) ? 0 : Number(utcArr[0]);
this.utcMin = isNaN(Number(utcArr[1])) ? 0 : Number(utcArr[1]);
return res.data; return res.data;
}, },
async fetchVersion(): Promise<void> { async fetchVersion(): Promise<void> {

View File

@ -119,7 +119,12 @@ export const dashboardStore = defineStore({
: 3, : 3,
}; };
} }
if (type === "Trace" || type === "Profile" || type === "Log") { if (
type === "Trace" ||
type === "Profile" ||
type === "Log" ||
type === "Ebpf"
) {
newItem.h = 36; newItem.h = 36;
} }
if (type === "Text") { if (type === "Text") {

153
src/store/modules/ebpf.ts Normal file
View File

@ -0,0 +1,153 @@
/**
* 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, Option } from "@/types/app";
import {
EBPFTaskCreationRequest,
EBPFProfilingSchedule,
EBPFTaskList,
AnalyzationTrees,
} from "@/types/ebpf";
import { Trace, Span } from "@/types/trace";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
interface EbpfStore {
durationTime: Duration;
taskList: EBPFTaskList[];
eBPFSchedules: EBPFProfilingSchedule[];
currentSchedule: EBPFProfilingSchedule | Record<string, never>;
analyzeTrees: AnalyzationTrees[];
labels: Option[];
couldProfiling: boolean;
tip: string;
}
export const ebpfStore = defineStore({
id: "eBPF",
state: (): EbpfStore => ({
durationTime: useAppStoreWithOut().durationTime,
taskList: [],
eBPFSchedules: [],
currentSchedule: {},
analyzeTrees: [],
labels: [{ value: "", label: "" }],
couldProfiling: false,
tip: "",
}),
actions: {
setCurrentSpan(span: Span) {
this.currentSpan = span;
},
setCurrentSchedule(s: Trace) {
this.currentSchedule = s;
},
async getCreateTaskData(serviceId: string) {
const res: AxiosResponse = await graphql
.query("getCreateTaskData")
.params({ serviceId });
if (res.data.errors) {
return res.data;
}
const json = res.data.data.createTaskData;
this.couldProfiling = json.couldProfiling || false;
this.labels = json.processLabels.map((d: string) => {
return { label: d, value: d };
});
return res.data;
},
async createTask(param: EBPFTaskCreationRequest) {
const res: AxiosResponse = await graphql
.query("saveEBPFTask")
.params({ request: param });
if (res.data.errors) {
return res.data;
}
this.getTaskList(param.serviceId);
return res.data;
},
async getTaskList(serviceId: string) {
const res: AxiosResponse = await graphql
.query("getEBPFTasks")
.params({ serviceId });
this.tip = "";
if (res.data.errors) {
return res.data;
}
this.taskList = res.data.data.queryEBPFTasks.reverse() || [];
if (!this.taskList.length) {
return res.data;
}
this.getEBPFSchedules({ taskId: this.taskList[0].taskId });
return res.data;
},
async getEBPFSchedules(params: { taskId: string; duration?: Duration }) {
const duration = useAppStoreWithOut().durationTime;
const res: AxiosResponse = await graphql
.query("getEBPFSchedules")
.params({ ...params, duration });
if (res.data.errors) {
this.eBPFSchedules = [];
return res.data;
}
this.tip = "";
const { eBPFSchedules } = res.data.data;
this.eBPFSchedules = eBPFSchedules;
if (!eBPFSchedules.length) {
this.eBPFSchedules = [];
}
this.analyzeTrees = [];
return res.data;
},
async getEBPFAnalyze(params: {
scheduleIdList: string[];
timeRanges: Array<{ start: number; end: number }>;
}) {
const res: AxiosResponse = await graphql
.query("getEBPFResult")
.params(params);
if (res.data.errors) {
this.analyzeTrees = [];
return res.data;
}
const { analysisEBPFResult } = res.data.data;
this.tip = analysisEBPFResult.tip;
if (!analysisEBPFResult) {
this.analyzeTrees = [];
return res.data;
}
if (analysisEBPFResult.tip) {
this.analyzeTrees = [];
return res.data;
}
this.analyzeTrees = analysisEBPFResult.trees[0].elements;
return res.data;
},
},
});
export function useEbpfStore(): any {
return ebpfStore(store);
}

View File

@ -16,7 +16,7 @@
*/ */
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { Duration } from "@/types/app"; import { Duration } from "@/types/app";
import { Service } from "@/types/selector"; import { Endpoint } from "@/types/selector";
import { import {
TaskListItem, TaskListItem,
SegmentSpan, SegmentSpan,
@ -31,7 +31,8 @@ import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
interface ProfileState { interface ProfileState {
services: Service[]; endpoints: Endpoint[];
taskEndpoints: Endpoint[];
durationTime: Duration; durationTime: Duration;
condition: { serviceId: string; endpointName: string }; condition: { serviceId: string; endpointName: string };
taskList: TaskListItem[]; taskList: TaskListItem[];
@ -47,7 +48,8 @@ interface ProfileState {
export const profileStore = defineStore({ export const profileStore = defineStore({
id: "profile", id: "profile",
state: (): ProfileState => ({ state: (): ProfileState => ({
services: [{ value: "0", label: "All" }], endpoints: [{ value: "", label: "All" }],
taskEndpoints: [{ value: "", label: "All" }],
durationTime: useAppStoreWithOut().durationTime, durationTime: useAppStoreWithOut().durationTime,
condition: { serviceId: "", endpointName: "" }, condition: { serviceId: "", endpointName: "" },
taskList: [], taskList: [],
@ -75,14 +77,28 @@ export const profileStore = defineStore({
setHighlightTop() { setHighlightTop() {
this.highlightTop = !this.highlightTop; this.highlightTop = !this.highlightTop;
}, },
async getServices(layer: string) { async getEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({ const res: AxiosResponse = await graphql.query("queryEndpoints").params({
layer, serviceId,
duration: this.durationTime,
keyword: keyword || "",
}); });
if (res.data.errors) { if (res.data.errors) {
return res.data; return res.data;
} }
this.services = res.data.data.services; this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods];
return res.data;
},
async getTaskEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: this.durationTime,
keyword: keyword || "",
});
if (res.data.errors) {
return res.data;
}
this.taskEndpoints = [{ value: "", label: "All" }, ...res.data.data.pods];
return res.data; return res.data;
}, },
async getTaskList() { async getTaskList() {

View File

@ -410,6 +410,9 @@ export const topologyStore = defineStore({
const idsC = this.calls const idsC = this.calls
.filter((i: Call) => i.detectPoints.includes("CLIENT")) .filter((i: Call) => i.detectPoints.includes("CLIENT"))
.map((b: Call) => b.id); .map((b: Call) => b.id);
if (!idsC.length) {
return;
}
const param = await useQueryTopologyMetrics(linkClientMetrics, idsC); const param = await useQueryTopologyMetrics(linkClientMetrics, idsC);
const res = await this.getCallClientMetrics(param); const res = await this.getCallClientMetrics(param);
@ -425,6 +428,9 @@ export const topologyStore = defineStore({
const idsS = this.calls const idsS = this.calls
.filter((i: Call) => i.detectPoints.includes("SERVER")) .filter((i: Call) => i.detectPoints.includes("SERVER"))
.map((b: Call) => b.id); .map((b: Call) => b.id);
if (!idsS.length) {
return;
}
const param = await useQueryTopologyMetrics(linkServerMetrics, idsS); const param = await useQueryTopologyMetrics(linkServerMetrics, idsS);
const res = await this.getCallServerMetrics(param); const res = await this.getCallServerMetrics(param);
@ -438,6 +444,9 @@ export const topologyStore = defineStore({
return; return;
} }
const ids = this.nodes.map((d: Node) => d.id); const ids = this.nodes.map((d: Node) => d.id);
if (!ids.length) {
return;
}
const param = await useQueryTopologyMetrics(nodeMetrics, ids); const param = await useQueryTopologyMetrics(nodeMetrics, ids);
const res = await this.getNodeMetricValue(param); const res = await this.getNodeMetricValue(param);

View File

@ -95,10 +95,7 @@ export const traceStore = defineStore({
if (res.data.errors) { if (res.data.errors) {
return res.data; return res.data;
} }
this.instances = [ this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods];
{ value: "0", label: "All" },
...res.data.data.pods,
] || [{ value: " 0", label: "All" }];
return res.data; return res.data;
}, },
async getEndpoints(id: string, keyword?: string) { async getEndpoints(id: string, keyword?: string) {
@ -113,10 +110,7 @@ export const traceStore = defineStore({
if (res.data.errors) { if (res.data.errors) {
return res.data; return res.data;
} }
this.endpoints = [ this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods];
{ value: "0", label: "All" },
...res.data.data.pods,
] || [{ value: "0", label: "All" }];
return res.data; return res.data;
}, },
async getTraces() { async getTraces() {

View File

@ -14,9 +14,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
@import "./grid.scss"; import "element-plus/es/components/message/style/css";
@import "./lib.scss"; import "element-plus/es/components/message-box/style/css";
@import "./reset.scss"; import "element-plus/es/components/notification/style/css";
import "./grid.scss";
import "./lib.scss";
import "./reset.scss";
//@import "./spp-light.scss"; //@import "./spp-light.scss";
//@import "./spp-dark.scss"; //@import "./spp-dark.scss";
@import "./jetbrains-dark.scss"; @import "./jetbrains-dark.scss";

76
src/types/ebpf.d.ts vendored Normal file
View File

@ -0,0 +1,76 @@
/**
* 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 interface EBPFTaskCreationRequest {
serviceId: string;
processLabels: string[];
startTime: number;
duration: number;
targetType: string;
}
export interface EBPFTaskList {
taskId: string;
serviceName: string;
serviceId: string;
processLabels: string[];
taskStartTime: number;
fixedTriggerDuration: number;
targetType: string;
createTime: number;
triggerType: string;
}
export interface EBPFProfilingSchedule {
scheduleId: string;
taskId: string;
process: Process;
endTime: number;
startTime: number;
}
export type Process = {
id: string;
name: string;
serviceId: string;
serviceName: string;
instanceId: string;
instanceName: string;
layer: string;
agentId: string;
detectType: string;
attributes: { name: string; value: string };
labels: string[];
};
export type StackElement = {
id: string;
originId: string;
name: string;
parentId: string;
symbol: string;
dumpCount: number;
stackType: string;
value: number;
children?: StackElement[];
};
export type AnalyzationTrees = {
id: string;
parentId: string;
symbol: string;
dumpCount: number;
stackType: string;
};

View File

@ -84,9 +84,8 @@ const state = reactive<{ timer: ReturnType<typeof setInterval> | null }>({
const lang = ref<string>(locale.value || "en"); const lang = ref<string>(locale.value || "en");
const autoTime = ref<number>(6); const autoTime = ref<number>(6);
const auto = ref<boolean>(appStore.autoRefresh || false); const auto = ref<boolean>(appStore.autoRefresh || false);
const utcArr = appStore.utc.split(":"); const utcHour = ref<number>(appStore.utcHour);
const utcHour = ref<number>(isNaN(Number(utcArr[0])) ? 0 : Number(utcArr[0])); const utcMin = ref<number>(appStore.utcMin);
const utcMin = ref<number>(isNaN(Number(utcArr[1])) ? 0 : Number(utcArr[1]));
appStore.setPageTitle("Setting"); appStore.setPageTitle("Setting");
const handleReload = () => { const handleReload = () => {

View File

@ -39,7 +39,17 @@ limitations under the License. -->
> >
{{ t("tagsLink") }} {{ t("tagsLink") }}
</a> </a>
<el-tooltip :content="t('traceTagsTip')"> <el-tooltip
:content="
t(
type === 'LOG'
? 'logTagsTip'
: type === 'TRACE'
? 'traceTagsTip'
: 'alarmTagsTip'
)
"
>
<span> <span>
<Icon class="icon-help mr-5" iconName="help" size="middle" /> <Icon class="icon-help mr-5" iconName="help" size="middle" />
</span> </span>

View File

@ -44,7 +44,7 @@ limitations under the License. -->
:style="{ fontSize: '13px', width: '100%' }" :style="{ fontSize: '13px', width: '100%' }"
v-loading="loading" v-loading="loading"
ref="multipleTableRef" ref="multipleTableRef"
:default-sort="{ prop: 'name' }" :default-sort="{ prop: 'name', order: 'ascending' }"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
height="637px" height="637px"
size="small" size="small"

View File

@ -0,0 +1,95 @@
<!-- 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. -->
<template>
<div class="profile-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
</span>
</template>
<div class="tools" @click="removeWidget">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<Header />
<Content />
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/ebpf/Header.vue";
import Content from "../related/ebpf/Content.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.profile-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.trace {
width: 100%;
overflow: auto;
}
</style>

View File

@ -132,7 +132,7 @@ limitations under the License. -->
:key="item.i" :key="item.i"
@click="clickTabGrid($event, item)" @click="clickTabGrid($event, item)"
:class="{ active: activeTabWidget === item.i }" :class="{ active: activeTabWidget === item.i }"
drag-ignore-from="svg.d3-trace-tree, .dragger, .micro-topo-chart" :drag-ignore-from="dragIgnoreFrom"
> >
<component <component
:is="item.type" :is="item.type"
@ -165,6 +165,8 @@ import Trace from "./Trace.vue";
import Profile from "./Profile.vue"; import Profile from "./Profile.vue";
import Log from "./Log.vue"; import Log from "./Log.vue";
import Text from "./Text.vue"; import Text from "./Text.vue";
import Ebpf from "./Ebpf.vue";
import { dragIgnoreFrom } from "../data";
const props = { const props = {
data: { data: {
@ -175,7 +177,7 @@ const props = {
}; };
export default defineComponent({ export default defineComponent({
name: "Tab", name: "Tab",
components: { Topology, Widget, Trace, Profile, Log, Text }, components: { Topology, Widget, Trace, Profile, Log, Text, Ebpf },
props, props,
setup(props) { setup(props) {
const { t } = useI18n(); const { t } = useI18n();
@ -327,17 +329,6 @@ export default defineComponent({
} }
function initScrollWatcher() { function initScrollWatcher() {
// tabObserveContainer?.value?.addEventListener("scroll", (e: Event) => {
// const isBottom =
// tabObserveContainer?.value?.offsetHeight +
// tabObserveContainer?.value?.scrollTop +
// 70 >
// tabObserveContainer?.value?.scrollHeight;
// if (isBottom) {
// tabObserveContainer?.value.scroll(0, 0);
// }
// });
tabObserveContainer?.value?.addEventListener("wheel", (e: WheelEvent) => { tabObserveContainer?.value?.addEventListener("wheel", (e: WheelEvent) => {
if (isScrolling.value === false) { if (isScrolling.value === false) {
isScrolling.value = true; isScrolling.value = true;
@ -394,6 +385,7 @@ export default defineComponent({
canEditTabName, canEditTabName,
showTools, showTools,
t, t,
dragIgnoreFrom,
}; };
}, },
}); });

View File

@ -21,5 +21,6 @@ import Trace from "./Trace.vue";
import Profile from "./Profile.vue"; import Profile from "./Profile.vue";
import Log from "./Log.vue"; import Log from "./Log.vue";
import Text from "./Text.vue"; import Text from "./Text.vue";
import Ebpf from "./Ebpf.vue";
export default { Tab, Widget, Trace, Topology, Profile, Log, Text }; export default { Tab, Widget, Trace, Topology, Profile, Log, Text, Ebpf };

View File

@ -14,6 +14,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
export const dragIgnoreFrom =
"svg.d3-trace-tree, .dragger, .micro-topo-chart, .schedules";
export const PodsChartTypes = ["EndpointList", "InstanceList"]; export const PodsChartTypes = ["EndpointList", "InstanceList"];
@ -181,7 +183,8 @@ export const ServiceTools = [
{ name: "library_books", content: "Text", id: "addText" }, { name: "library_books", content: "Text", id: "addText" },
{ name: "device_hub", content: "Topology", id: "addTopology" }, { name: "device_hub", content: "Topology", id: "addTopology" },
{ name: "merge", content: "Trace", id: "addTrace" }, { name: "merge", content: "Trace", id: "addTrace" },
{ name: "timeline", content: "Profile", id: "addProfile" }, { name: "timeline", content: "Trace Profiling", id: "addProfile" },
{ name: "insert_chart", content: "eBPF Profiling", id: "addEbpf" },
{ name: "assignment", content: "Log", id: "addLog" }, { name: "assignment", content: "Log", id: "addLog" },
]; ];
export const InstanceTools = [ export const InstanceTools = [

View File

@ -33,7 +33,7 @@ limitations under the License. -->
:key="item.i" :key="item.i"
@click="clickGrid(item)" @click="clickGrid(item)"
:class="{ active: dashboardStore.activedGridItem === item.i }" :class="{ active: dashboardStore.activedGridItem === item.i }"
drag-ignore-from="svg.d3-trace-tree, .dragger, .micro-topo-chart" :drag-ignore-from="dragIgnoreFrom"
> >
<component :is="item.type" :data="item" /> <component :is="item.type" :data="item" />
</grid-item> </grid-item>
@ -47,6 +47,7 @@ import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { LayoutConfig } from "@/types/dashboard"; import { LayoutConfig } from "@/types/dashboard";
import controls from "../controls/index"; import controls from "../controls/index";
import { dragIgnoreFrom } from "../data";
import FullVue from "@/components/FullVue.vue"; import FullVue from "@/components/FullVue.vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
@ -81,6 +82,7 @@ export default defineComponent({
dashboardStore, dashboardStore,
clickGrid, clickGrid,
t, t,
dragIgnoreFrom,
}; };
}, },
}); });

View File

@ -265,7 +265,7 @@ async function setSourceSelector() {
let currentPod; let currentPod;
if (states.currentPod) { if (states.currentPod) {
currentPod = selectorStore.pods.find( currentPod = selectorStore.pods.find(
(d: { id: string }) => d.label === states.currentPod (d: { label: string }) => d.label === states.currentPod
); );
} else { } else {
currentPod = selectorStore.pods.find((d: { id: string }) => d.id === pod); currentPod = selectorStore.pods.find((d: { id: string }) => d.id === pod);
@ -290,10 +290,10 @@ async function setDestSelector() {
return; return;
} }
const destPod = params.destPodId || selectorStore.destPods[0].id; const destPod = params.destPodId || selectorStore.destPods[0].id;
let currentDestPod = ""; let currentDestPod = { label: "" };
if (states.currentDestPod) { if (states.currentDestPod) {
currentDestPod = selectorStore.pods.find( currentDestPod = selectorStore.pods.find(
(d: { id: string }) => d.label === states.currentDestPod (d: { label: string }) => d.label === states.currentDestPod
); );
} else { } else {
currentDestPod = selectorStore.destPods.find( currentDestPod = selectorStore.destPods.find(
@ -324,19 +324,23 @@ async function getServices() {
let s; let s;
if (states.currentService) { if (states.currentService) {
s = (selectorStore.services || []).find( s = (selectorStore.services || []).find(
(d) => d.label === states.currentService (d: { label: string }) => d.label === states.currentService
); );
} else { } else {
s = (selectorStore.services || []).find((d, index) => index === 0); s = (selectorStore.services || []).find(
(d: unknown, index: number) => index === 0
);
} }
selectorStore.setCurrentService(s || null); selectorStore.setCurrentService(s || null);
let d; let d;
if (states.currentService) { if (states.currentService) {
d = (selectorStore.services || []).find( d = (selectorStore.services || []).find(
(d) => d.label === states.currentDestService (d: { label: string }) => d.label === states.currentDestService
); );
} else { } else {
d = (selectorStore.services || []).find((d, index) => index === 1); d = (selectorStore.services || []).find(
(d: unknown, index: number) => index === 1
);
} }
selectorStore.setCurrentDestService(d || null); selectorStore.setCurrentDestService(d || null);
if (!selectorStore.currentService) { if (!selectorStore.currentService) {
@ -447,6 +451,9 @@ function setTabControls(id: string) {
case "addProfile": case "addProfile":
dashboardStore.addTabControls("Profile"); dashboardStore.addTabControls("Profile");
break; break;
case "addEbpf":
dashboardStore.addTabControls("Ebpf");
break;
case "addTopology": case "addTopology":
dashboardStore.addTabControls("Topology"); dashboardStore.addTabControls("Topology");
break; break;
@ -473,6 +480,9 @@ function setControls(id: string) {
case "addProfile": case "addProfile":
dashboardStore.addControl("Profile"); dashboardStore.addControl("Profile");
break; break;
case "addEbpf":
dashboardStore.addControl("Ebpf");
break;
case "addLog": case "addLog":
dashboardStore.addControl("Log"); dashboardStore.addControl("Log");
break; break;
@ -500,9 +510,13 @@ async function fetchPods(
if (setPod) { if (setPod) {
let p; let p;
if (states.currentPod) { if (states.currentPod) {
p = selectorStore.pods.find((d) => d.label === states.currentPod); p = selectorStore.pods.find(
(d: { label: unknown }) => d.label === states.currentPod
);
} else { } else {
p = selectorStore.pods.find((d, index) => index === 0); p = selectorStore.pods.find(
(d: unknown, index: number) => index === 0
);
} }
selectorStore.setCurrentPod(p || null); selectorStore.setCurrentPod(p || null);
states.currentPod = selectorStore.currentPod.label; states.currentPod = selectorStore.currentPod.label;
@ -513,9 +527,13 @@ async function fetchPods(
if (setPod) { if (setPod) {
let p; let p;
if (states.currentPod) { if (states.currentPod) {
p = selectorStore.pods.find((d) => d.label === states.currentPod); p = selectorStore.pods.find(
(d: { label: string }) => d.label === states.currentPod
);
} else { } else {
p = selectorStore.pods.find((d, index) => index === 0); p = selectorStore.pods.find(
(d: { label: string }, index: number) => index === 0
);
} }
selectorStore.setCurrentPod(p || null); selectorStore.setCurrentPod(p || null);
states.currentPod = selectorStore.currentPod.label; states.currentPod = selectorStore.currentPod.label;
@ -530,9 +548,13 @@ async function fetchPods(
if (setPod) { if (setPod) {
let p; let p;
if (states.currentDestPod) { if (states.currentDestPod) {
p = selectorStore.pods.find((d) => d.label === states.currentDestPod); p = selectorStore.pods.find(
(d: { label: string }) => d.label === states.currentDestPod
);
} else { } else {
p = selectorStore.pods.find((d, index) => index === 0); p = selectorStore.pods.find(
(d: { label: string }, index: number) => index === 0
);
} }
selectorStore.setCurrentDestPod(p || null); selectorStore.setCurrentDestPod(p || null);
states.currentDestPod = selectorStore.currentDestPod.label; states.currentDestPod = selectorStore.currentDestPod.label;
@ -546,9 +568,13 @@ async function fetchPods(
if (setPod) { if (setPod) {
let p; let p;
if (states.currentDestPod) { if (states.currentDestPod) {
p = selectorStore.pods.find((d) => d.label === states.currentDestPod); p = selectorStore.pods.find(
(d: { label: string }) => d.label === states.currentDestPod
);
} else { } else {
p = selectorStore.pods.find((d, index) => index === 0); p = selectorStore.pods.find(
(d: { label: string }, index: number) => index === 0
);
} }
selectorStore.setCurrentDestPod(p || null); selectorStore.setCurrentDestPod(p || null);
states.currentDestPod = selectorStore.currentDestPod.label; states.currentDestPod = selectorStore.currentDestPod.label;

View File

@ -0,0 +1,50 @@
<!-- 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. -->
<template>
<div class="flex-h content">
<TaskList />
<div class="vis-graph ml-5">
<div class="item">
<EBPFSchedules />
</div>
<div class="item">
<EBPFStack />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import TaskList from "./components/TaskList.vue";
import EBPFSchedules from "./components/EBPFSchedules.vue";
import EBPFStack from "./components/EBPFStack.vue";
</script>
<style lang="scss" scoped>
.content {
height: calc(100% - 30px);
width: 100%;
}
.vis-graph {
height: 100%;
width: calc(100% - 300px);
}
.item {
width: 100%;
overflow: auto;
height: calc(50% - 10px);
padding-bottom: 10px;
}
</style>

View File

@ -0,0 +1,104 @@
<!-- 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. -->
<template>
<div class="flex-h header">
<div class="title">eBPF Profiling</div>
<el-button type="primary" size="small" @click="createTask">
{{ t("newTask") }}
</el-button>
</div>
<el-dialog
v-model="newTask"
:destroy-on-close="true"
fullscreen
@closed="newTask = false"
>
<NewTask @close="newTask = false" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useEbpfStore } from "@/store/modules/ebpf";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import NewTask from "./components/NewTask.vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType } from "../../data";
const ebpfStore = useEbpfStore();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const { t } = useI18n();
const newTask = ref<boolean>(false);
searchTasks();
async function searchTasks() {
const serviceId =
(selectorStore.currentService && selectorStore.currentService.id) || "";
const res = await ebpfStore.getTaskList(serviceId);
if (res.errors) {
ElMessage.error(res.errors);
}
}
async function createTask() {
if (!selectorStore.currentService) {
return;
}
newTask.value = true;
ebpfStore.getCreateTaskData(selectorStore.currentService.id);
}
watch(
() => selectorStore.currentService,
() => {
searchTasks();
}
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
searchTasks();
}
}
);
</script>
<style lang="scss" scoped>
.header {
padding: 5px 20px 5px 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
justify-content: space-between;
}
.name {
width: 270px;
}
.new-btn {
float: right;
}
.title {
font-weight: bold;
line-height: 24px;
}
</style>

View File

@ -0,0 +1,244 @@
<!-- 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. -->
<template>
<div class="filters">
<Selector
:value="selectedLabels"
:options="labels"
size="small"
placeholder="Please select labels"
@change="changeLabels"
class="inputs mr-10"
:multiple="true"
/>
<el-popover placement="bottom" :width="680" trigger="click">
<template #reference>
<el-button type="primary" size="small">
{{ t("processSelect") }}
</el-button>
</template>
<el-input
v-model="searchText"
placeholder="Please input name"
class="input-with-search"
size="small"
@change="searchProcesses"
>
<template #append>
<el-button size="small">
<Icon size="sm" iconName="search" />
</el-button>
</template>
</el-input>
<el-table
:data="currentProcesses"
ref="multipleTableRef"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column
v-for="(h, index) of TableHeader"
:property="h.property"
:label="h.label"
:key="index"
width="150"
/>
<el-table-column width="300" label="Attributes">
<template #default="scope">
{{ scope.row.attributes.map((d: {name: string, value: string}) => `${d.name}=${d.value}`).join("; ") }}
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
background
small
layout="prev, pager, next"
:page-size="pageSize"
:total="processes.length"
@current-change="changePage"
@prev-click="changePage"
@next-click="changePage"
/>
</el-popover>
<el-button type="primary" size="small" @click="analyzeEBPF">
{{ t("analyze") }}
</el-button>
</div>
<div ref="timeline" class="schedules"></div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import { Option } from "@/types/app";
import { TableHeader } from "./data";
import { useEbpfStore } from "@/store/modules/ebpf";
import { EBPFProfilingSchedule, Process } from "@/types/ebpf";
import { DataSet, Timeline } from "vis-timeline/standalone";
import "vis-timeline/styles/vis-timeline-graph2d.css";
import { ElMessage, ElTable } from "element-plus";
const { t } = useI18n();
const ebpfStore = useEbpfStore();
const pageSize = 5;
/*global Nullable */
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const selectedProcesses = ref<string[]>([]);
const timeline = ref<Nullable<HTMLDivElement>>(null);
const visGraph = ref<Nullable<any>>(null);
const labels = ref<Option[]>([{ label: "All", value: "0" }]);
const processes = ref<Process[]>([]);
const currentProcesses = ref<Process[]>([]);
const selectedLabels = ref<string[]>(["0"]);
const searchText = ref<string>("");
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
new Date(dayjs(date).format(pattern));
function changeLabels(opt: any[]) {
const arr = opt.map((d) => d.value);
selectedLabels.value = arr;
}
const handleSelectionChange = (arr: Process[]) => {
selectedProcesses.value = arr.map((d: Process) => d.id);
};
async function analyzeEBPF() {
let arr: string[] = selectedLabels.value;
if (selectedLabels.value.includes("0")) {
arr = labels.value.map((d: Option) => d.value);
}
const ranges: { start: number; end: number }[] = [];
const scheduleIdList = ebpfStore.eBPFSchedules.flatMap(
(d: EBPFProfilingSchedule) => {
const l = d.process.labels.find((d: string) => arr.includes(d));
const i = selectedProcesses.value.includes(d.process.id);
if (l || i) {
ranges.push({
start: d.startTime,
end: d.endTime,
});
return d.scheduleId;
}
}
);
let timeRanges: { start: number; end: number }[] = [];
for (const r of ranges) {
if (timeRanges.length) {
for (const t of timeRanges) {
if (r.start > t.start && r.start < t.end) {
if (r.end > t.end) {
t.end = r.end;
}
}
}
} else {
timeRanges.push(r);
}
}
const res = await ebpfStore.getEBPFAnalyze({
scheduleIdList,
timeRanges,
});
if (res.data.errors) {
ElMessage.error(res.data.errors);
return;
}
}
function visTimeline() {
if (visGraph.value) {
visGraph.value.destroy();
}
labels.value = [{ label: "All", value: "0" }];
selectedLabels.value = ["0"];
processes.value = [];
const schedules = ebpfStore.eBPFSchedules.map(
(d: EBPFProfilingSchedule, index: number) => {
for (const l of d.process.labels) {
labels.value.push({ label: l, value: l });
}
processes.value.push(d.process);
return {
id: index + 1,
content: d.process.name,
start: dateFormat(d.startTime),
end: dateFormat(d.endTime),
};
}
);
searchProcesses();
if (!timeline.value) {
return;
}
const h = timeline.value.getBoundingClientRect().height;
const items: any = new DataSet(schedules);
const options = {
height: h,
width: "100%",
locale: "en",
};
visGraph.value = new Timeline(timeline.value, items, options);
}
function changePage(pageIndex: number) {
searchProcesses(pageIndex);
}
function searchProcesses(pageIndex?: any) {
const arr = processes.value.filter(
(d: { name: string; instanceName: string }) =>
d.name.includes(searchText.value) ||
d.instanceName.includes(searchText.value)
);
currentProcesses.value = arr.splice(
(pageIndex - 1 || 0) * pageSize,
pageSize * (pageIndex || 1)
);
}
watch(
() => ebpfStore.eBPFSchedules,
() => {
visTimeline();
}
);
</script>
<style lang="scss" scoped>
.filters {
margin: 5px 0;
}
.schedules {
width: calc(100% - 5px);
margin: 0 5px 5px 0;
height: calc(100% - 60px);
min-height: 150px;
}
.inputs {
width: 300px;
}
.input-with-search {
width: 650px;
margin-bottom: 5px;
}
.pagination {
margin-top: 10px;
}
</style>

View File

@ -0,0 +1,148 @@
<!-- 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. -->
<template>
<div id="graph-stack" ref="graph">
<span class="tip" v-show="ebpfStore.tip">{{ ebpfStore.tip }}</span>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import * as d3 from "d3";
import d3tip from "d3-tip";
import { flamegraph } from "d3-flame-graph";
import { useEbpfStore } from "@/store/modules/ebpf";
import { StackElement } from "@/types/ebpf";
import "d3-flame-graph/dist/d3-flamegraph.css";
/*global Nullable*/
const ebpfStore = useEbpfStore();
const stackTree = ref<Nullable<StackElement>>(null);
const graph = ref<Nullable<HTMLDivElement>>(null);
const flameChart = ref<any>(null);
function drawGraph() {
if (flameChart.value) {
flameChart.value.destroy();
}
if (!ebpfStore.analyzeTrees.length) {
return (stackTree.value = null);
}
stackTree.value = processTree(ebpfStore.analyzeTrees);
const w = (graph.value && graph.value.getBoundingClientRect().width) || 10;
flameChart.value = flamegraph()
.width(w - 15)
.cellHeight(18)
.transitionDuration(750)
.minFrameSize(5)
.transitionEase(d3.easeCubic as any)
.sort(true)
.title("")
.selfValue(false)
.setColorMapper((d, originalColor) =>
d.highlight ? "#6aff8f" : originalColor
);
const tip = (d3tip as any)()
.attr("class", "d3-tip")
.direction("w")
.html(
(d: { data: StackElement }) =>
`<div class="mb-5">Symbol: ${d.data.name}</div><div class="mb-5">Dump Count: ${d.data.dumpCount}</div>`
);
flameChart.value.tooltip(tip);
d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value);
}
function processTree(arr: StackElement[]) {
const copyArr = JSON.parse(JSON.stringify(arr));
const obj: any = {};
let res = null;
let min = 1;
let max = 1;
for (const item of copyArr) {
item.originId = item.id;
item.name = item.symbol;
delete item.id;
obj[item.originId] = item;
if (item.dumpCount > max) {
max = item.dumpCount;
}
if (item.dumpCount < min) {
min = item.dumpCount;
}
}
const scale = d3.scaleLinear().domain([min, max]).range([1, 200]);
for (const item of copyArr) {
if (item.parentId === "0") {
const val = Number(scale(item.dumpCount).toFixed(4));
res = item;
res.value = val;
}
for (const key in obj) {
if (item.originId === obj[key].parentId) {
const val = Number(scale(obj[key].dumpCount).toFixed(4));
obj[key].value = val;
if (item.children) {
item.children.push(obj[key]);
} else {
item.children = [obj[key]];
}
}
}
}
treeForeach([res], (node: StackElement) => {
if (node.children) {
let val = 0;
for (const child of node.children) {
val = child.value + val;
}
node.value = node.value < val ? val : node.value;
}
});
return res;
}
function treeForeach(tree: StackElement[], func: (node: StackElement) => void) {
for (const data of tree) {
data.children && treeForeach(data.children, func);
func(data);
}
return tree;
}
watch(
() => ebpfStore.analyzeTrees,
() => {
drawGraph();
}
);
</script>
<style>
#graph-stack {
width: 100%;
height: 100%;
cursor: pointer;
}
.tip {
display: inline-block;
width: 100%;
text-align: center;
color: red;
margin-top: 20px;
}
</style>

View File

@ -0,0 +1,165 @@
<!-- 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. -->
<template>
<div class="ebpf-task" v-if="eBPFStore.couldProfiling">
<div>
<div class="label">{{ t("labels") }}</div>
<Selector
class="profile-input"
size="small"
:value="labels"
:options="eBPFStore.labels"
placeholder="Select labels"
:multiple="true"
@change="changeLabel"
/>
</div>
<div>
<div class="label">{{ t("targetType") }}</div>
<Selector
class="profile-input"
size="small"
:value="type"
:options="TargetTypes"
placeholder="Select a type"
:isRemote="true"
@change="changeType"
/>
</div>
<div>
<div class="label">{{ t("monitorTime") }}</div>
<div>
<Radio
:value="monitorTime"
:options="InitTaskField.monitorTimeEn"
@change="changeMonitorTime"
/>
<span class="date">
<TimePicker
:value="time"
position="bottom"
format="YYYY-MM-DD HH:mm:ss"
@input="changeTimeRange"
/>
</span>
</div>
</div>
<div>
<div class="label">{{ t("monitorDuration") }}</div>
<el-input
class="profile-input"
v-model="monitorDuration"
size="small"
placeholder="none"
type="number"
:min="1"
:max="60"
/>
Min
</div>
<div>
<el-button @click="createTask" type="primary" class="create-task-btn">
{{ t("createTask") }}
</el-button>
</div>
</div>
<div v-else>{{ t("ebpfTip") }}</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useEbpfStore } from "@/store/modules/ebpf";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage } from "element-plus";
import { InitTaskField, TargetTypes } from "./data";
/* global defineEmits */
const emits = defineEmits(["close"]);
const eBPFStore = useEbpfStore();
const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const labels = ref<string[]>([]);
const type = ref<string>(TargetTypes[0].value);
const monitorTime = ref<string>(InitTaskField.monitorTimeEn[0].value);
const monitorDuration = ref<number>(10);
const time = ref<Date>(appStore.durationRow.start);
function changeMonitorTime(opt: string) {
monitorTime.value = opt;
}
function changeLabel(opt: any[]) {
labels.value = opt.map((d) => d.value);
}
function changeType(opt: any[]) {
type.value = opt[0].value;
}
async function createTask() {
if (!labels.value.length) {
ElMessage.warning("no labels");
return;
}
const date = monitorTime.value === "0" ? new Date() : time.value;
const params = {
serviceId: selectorStore.currentService.id,
processLabels: labels.value,
startTime: date.getTime(),
duration: monitorDuration.value * 60,
targetType: "ON_CPU",
};
const res = await eBPFStore.createTask(params);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
if (!res.data.createTaskData.status) {
ElMessage.error(res.data.createTaskData.errorReason);
return;
}
ElMessage.success("Task created successfully");
emits("close");
}
function changeTimeRange(val: Date) {
time.value = val;
}
</script>
<style lang="scss" scoped>
.ebpf-task {
margin: 0 auto;
width: 400px;
}
.date {
font-size: 12px;
}
.label {
margin-top: 10px;
font-size: 14px;
}
.profile-input {
width: 300px;
}
.create-task-btn {
width: 300px;
margin-top: 50px;
}
</style>

View File

@ -0,0 +1,195 @@
<!-- 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. -->
<template>
<div class="profile-task-list flex-v">
<div class="profile-task-wrapper flex-v">
<div class="profile-t-tool flex-h">{{ t("taskList") }}</div>
<div class="profile-t-wrapper">
<div class="no-data" v-show="!ebpfStore.taskList.length">
{{ t("noData") }}
</div>
<table class="profile-t">
<tr
class="profile-tr cp"
v-for="(i, index) in ebpfStore.taskList"
@click="changeTask(i)"
:key="index"
>
<td
class="profile-td"
:class="{
selected: selectedTask.taskId === i.taskId,
}"
>
<div class="ell">
<span>{{ i.processLabels.join(" ") }}</span>
<a class="profile-btn r" @click="viewDetail = true">
<Icon iconName="view" size="middle" />
</a>
</div>
<div class="grey ell sm">
<span class="mr-10 sm">{{ dateFormat(i.taskStartTime) }}</span>
<span class="mr-10 sm">
{{
dateFormat(i.taskStartTime + i.fixedTriggerDuration * 1000)
}}
</span>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>
<el-dialog
v-model="viewDetail"
:destroy-on-close="true"
fullscreen
@closed="viewDetail = false"
>
<div class="profile-detail flex-v">
<div>
<h5 class="mb-10">{{ t("task") }}.</h5>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("taskId") }}:</span>
<span class="g-sm-8 wba">
{{ selectedTask.taskId }}
</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("service") }}:</span>
<span class="g-sm-8 wba">{{ selectedTask.serviceName }}</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("labels") }}:</span>
<span class="g-sm-8 wba">{{ selectedTask.processLabels }}</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("monitorTime") }}:</span>
<span class="g-sm-8 wba">
{{ dateFormat(selectedTask.taskStartTime) }}
</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("monitorDuration") }}:</span>
<span class="g-sm-8 wba">
{{ selectedTask.fixedTriggerDuration / 60 }} min
</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("triggerType") }}:</span>
<span class="g-sm-8 wba">{{ selectedTask.triggerType }}</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("targetType") }}:</span>
<span class="g-sm-8 wba">{{ selectedTask.targetType }}</span>
</div>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import { useEbpfStore } from "@/store/modules/ebpf";
import { EBPFTaskList } from "@/types/ebpf";
import { ElMessage } from "element-plus";
const { t } = useI18n();
const ebpfStore = useEbpfStore();
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
const selectedTask = ref<EBPFTaskList | Record<string, never>>({});
const viewDetail = ref<boolean>(false);
async function changeTask(item: EBPFTaskList) {
selectedTask.value = item;
const res = await ebpfStore.getEBPFSchedules({
taskId: item.taskId,
});
if (res.errors) {
ElMessage.error(res.errors);
}
}
watch(
() => ebpfStore.taskList,
() => {
selectedTask.value = ebpfStore.taskList[0] || {};
}
);
</script>
<style lang="scss" scoped>
.profile-task-list {
width: 300px;
height: calc(100% - 10px);
overflow: auto;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.item span {
height: 21px;
}
.profile-td {
padding: 5px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
&.selected {
background-color: #ededed;
}
}
.no-data {
text-align: center;
margin-top: 10px;
}
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
border: none;
}
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
}
.profile-t-tool {
padding: 5px 10px;
font-weight: bold;
border-right: 1px solid rgba(0, 0, 0, 0.07);
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
}
.profile-btn {
color: #3d444f;
padding: 1px 3px;
border-radius: 2px;
font-size: 12px;
float: right;
}
</style>

View File

@ -0,0 +1,51 @@
/**
* 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 ProfileMode: any[] = [
{ label: "Include Children", value: "include" },
{ label: "Exclude Children", value: "exclude" },
];
export const NewTaskField = {
service: { key: "", label: "None" },
monitorTime: { key: "0", label: "monitor now" },
monitorDuration: { key: 5, label: "5 min" },
minThreshold: 0,
dumpPeriod: { key: 10, label: "10ms" },
endpointName: "",
maxSamplingCount: { key: 5, label: "5" },
};
export const TargetTypes = [{ label: "ON_CPU", value: "ON_CPU" }];
export const InitTaskField = {
monitorTimeEn: [
{ value: "0", label: "monitor now" },
{ value: "1", label: "set start time" },
],
monitorTimeCn: [
{ value: "0", label: "此刻" },
{ value: "1", label: "设置时间" },
],
monitorDuration: [
{ value: "5", label: "5 min" },
{ value: "10", label: "10 min" },
{ value: "15", label: "15 min" },
],
};
export const TableHeader = [
{ property: "name", label: "Name" },
{ property: "instanceName", label: "Instance Name" },
];

View File

@ -14,19 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="flex-h header"> <div class="flex-h header">
<!-- <div class="mr-10" v-if="dashboardStore.entity==='All'">
<span class="grey mr-5">{{ t("service") }}:</span>
<Selector
size="small"
:value="service.value"
:options="profileStore.services"
placeholder="Select a service"
@change="changeService"
/>
</div> -->
<div class="mr-10"> <div class="mr-10">
<span class="grey mr-5">{{ t("endpointName") }}:</span> <span class="grey mr-5">{{ t("endpointName") }}:</span>
<el-input v-model="endpointName" class="name" size="small" /> <Selector
class="name"
size="small"
:value="endpointName"
:options="profileStore.endpoints"
placeholder="Select a endpoint"
:isRemote="true"
@change="changeEndpoint"
@query="searchEndpoints"
/>
</div> </div>
<el-button <el-button
class="search-btn" class="search-btn"
@ -65,27 +64,30 @@ const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const { t } = useI18n(); const { t } = useI18n();
// const service = ref<any>({});
const endpointName = ref<string>(""); const endpointName = ref<string>("");
const newTask = ref<boolean>(false); const newTask = ref<boolean>(false);
searchTasks(); searchTasks();
// getServices(); searchEndpoints("");
// async function getServices() { async function searchEndpoints(keyword: string) {
// const res = await profileStore.getServices(dashboardStore.layerId); if (!selectorStore.currentService) {
return;
}
const service = selectorStore.currentService.id;
const res = await profileStore.getEndpoints(service, keyword);
// if (res.errors) { if (res.errors) {
// ElMessage.error(res.errors); ElMessage.error(res.errors);
// return; return;
// } }
// service.value = profileStore.services[0]; endpointName.value = profileStore.endpoints[0].value;
// searchTasks(); }
// }
function changeEndpoint(opt: any[]) {
endpointName.value = opt[0].value;
}
// function changeService(opt: any[]) {
// service.value = opt[0];
// }
async function searchTasks() { async function searchTasks() {
profileStore.setConditions({ profileStore.setConditions({
serviceId: serviceId:

View File

@ -17,7 +17,16 @@ limitations under the License. -->
<div class="profile-task"> <div class="profile-task">
<div> <div>
<div class="label">{{ t("endpointName") }}</div> <div class="label">{{ t("endpointName") }}</div>
<el-input v-model="endpointName" class="profile-input" size="small" /> <Selector
class="profile-input"
size="small"
:value="endpointName"
:options="profileStore.endpoints"
placeholder="Select a endpoint"
:isRemote="true"
@change="changeEndpoint"
@query="searchEndpoints"
/>
</div> </div>
<div> <div>
<div class="label">{{ t("monitorTime") }}</div> <div class="label">{{ t("monitorTime") }}</div>
@ -105,6 +114,20 @@ const minThreshold = ref<number>(0);
const dumpPeriod = ref<string>(InitTaskField.dumpPeriod[0].value); const dumpPeriod = ref<string>(InitTaskField.dumpPeriod[0].value);
const maxSamplingCount = ref<string>(InitTaskField.maxSamplingCount[0].value); const maxSamplingCount = ref<string>(InitTaskField.maxSamplingCount[0].value);
async function searchEndpoints(keyword: string) {
if (!selectorStore.currentService) {
return;
}
const service = selectorStore.currentService.id;
const res = await profileStore.getEndpoints(service, keyword);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
endpointName.value = profileStore.taskEndpoints[0].value;
}
function changeMonitorTime(opt: string) { function changeMonitorTime(opt: string) {
monitorTime.value = opt; monitorTime.value = opt;
} }
@ -121,10 +144,13 @@ function changeMaxSamplingCount(opt: any[]) {
maxSamplingCount.value = opt[0].value; maxSamplingCount.value = opt[0].value;
} }
function changeEndpoint(opt: any[]) {
endpointName.value = opt[0].value;
}
async function createTask() { async function createTask() {
emits("close"); emits("close");
const date = const date = monitorTime.value === "0" ? new Date() : time.value;
monitorTime.value === "0" ? appStore.durationRow.start : time.value;
const params = { const params = {
serviceId: selectorStore.currentService.id, serviceId: selectorStore.currentService.id,
endpointName: endpointName.value, endpointName: endpointName.value,
@ -153,7 +179,7 @@ function changeTimeRange(val: Date) {
<style lang="scss" scoped> <style lang="scss" scoped>
.profile-task { .profile-task {
margin: 0 auto; margin: 0 auto;
width: 350px; width: 400px;
} }
.date { .date {

View File

@ -36,7 +36,7 @@ limitations under the License. -->
<div class="ell"> <div class="ell">
<span>{{ i.endpointName }}</span> <span>{{ i.endpointName }}</span>
<a class="profile-btn r" @click="viewTask($event, i)"> <a class="profile-btn r" @click="viewTask($event, i)">
<Icon iconName="library_books" size="middle" /> <Icon iconName="view" size="middle" />
</a> </a>
</div> </div>
<div class="grey ell sm"> <div class="grey ell sm">

View File

@ -24,7 +24,12 @@ limitations under the License. -->
/> />
<span class="vm">{{ traceStore.currentTrace.endpointNames[0] }}</span> <span class="vm">{{ traceStore.currentTrace.endpointNames[0] }}</span>
<div class="trace-log-btn"> <div class="trace-log-btn">
<el-button class="mr-10" type="primary" @click="searchTraceLogs"> <el-button
size="small"
class="mr-10"
type="primary"
@click="searchTraceLogs"
>
{{ t("viewLogs") }} {{ t("viewLogs") }}
</el-button> </el-button>
</div> </div>
@ -89,6 +94,7 @@ limitations under the License. -->
<div> <div>
<el-button <el-button
class="grey" class="grey"
size="small"
:class="{ ghost: displayMode !== 'List' }" :class="{ ghost: displayMode !== 'List' }"
@click="displayMode = 'List'" @click="displayMode = 'List'"
> >
@ -97,6 +103,7 @@ limitations under the License. -->
</el-button> </el-button>
<el-button <el-button
class="grey" class="grey"
size="small"
:class="{ ghost: displayMode !== 'Tree' }" :class="{ ghost: displayMode !== 'Tree' }"
@click="displayMode = 'Tree'" @click="displayMode = 'Tree'"
> >
@ -105,6 +112,7 @@ limitations under the License. -->
</el-button> </el-button>
<el-button <el-button
class="grey" class="grey"
size="small"
:class="{ ghost: displayMode !== 'Table' }" :class="{ ghost: displayMode !== 'Table' }"
@click="displayMode = 'Table'" @click="displayMode = 'Table'"
> >
@ -113,6 +121,7 @@ limitations under the License. -->
</el-button> </el-button>
<el-button <el-button
class="grey" class="grey"
size="small"
:class="{ ghost: displayMode !== 'Statistics' }" :class="{ ghost: displayMode !== 'Statistics' }"
@click="displayMode = 'Statistics'" @click="displayMode = 'Statistics'"
> >
@ -191,7 +200,7 @@ export default defineComponent({
const res = await traceStore.getSpanLogs({ const res = await traceStore.getSpanLogs({
condition: { condition: {
relatedTrace: { relatedTrace: {
traceId: traceId.value || traceStore.currentTrace.traceIds[0], traceId: traceId.value || traceStore.currentTrace.traceIds[0].value,
}, },
paging: { pageNum: pageNum.value, pageSize, needTotal: true }, paging: { pageNum: pageNum.value, pageSize, needTotal: true },
}, },
@ -230,7 +239,9 @@ export default defineComponent({
} }
.trace-chart { .trace-chart {
height: 100%; height: calc(100% - 100px);
overflow: auto;
padding-bottom: 20px;
} }
.trace-detail-wrapper { .trace-detail-wrapper {

View File

@ -22,11 +22,11 @@ limitations under the License. -->
<Icon iconName="issue-open-m" class="mr-5" size="sm" /> <Icon iconName="issue-open-m" class="mr-5" size="sm" />
<span>{{ i }}</span> <span>{{ i }}</span>
</span> </span>
<el-button class="btn" type="primary" @click="downloadTrace"> <el-button class="btn" size="small" type="primary" @click="downloadTrace">
{{ t("exportImage") }} {{ t("exportImage") }}
</el-button> </el-button>
</div> </div>
<div> <div class="list">
<Graph :data="data" :traceId="traceId" type="List" /> <Graph :data="data" :traceId="traceId" type="List" />
</div> </div>
</div> </div>
@ -103,4 +103,8 @@ function downloadTrace() {
.btn { .btn {
float: right; float: right;
} }
.list {
height: calc(100% - 150px);
}
</style> </style>

View File

@ -40,8 +40,8 @@ export default class ListGraph {
constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) { constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) {
this.handleSelectSpan = handleSelectSpan; this.handleSelectSpan = handleSelectSpan;
this.el = el; this.el = el;
this.width = el.clientWidth - 10; this.width = el.getBoundingClientRect().width - 10;
this.height = el.clientHeight; this.height = el.getBoundingClientRect().height - 10;
this.svg = d3 this.svg = d3
.select(this.el) .select(this.el)
.append("svg") .append("svg")
@ -306,8 +306,8 @@ export default class ListGraph {
if (!this.el) { if (!this.el) {
return; return;
} }
this.width = this.el.clientWidth - 20; this.width = this.el.getBoundingClientRect().width - 20;
this.height = this.el.clientHeight; this.height = this.el.getBoundingClientRect().height - 10;
this.svg.attr("width", this.width).attr("height", this.height); this.svg.attr("width", this.width).attr("height", this.height);
this.svg.select("g").attr("transform", () => `translate(160, 0)`); this.svg.select("g").attr("transform", () => `translate(160, 0)`);
const transform = d3.zoomTransform(this.svg).translate(0, 0); const transform = d3.zoomTransform(this.svg).translate(0, 0);