feat: enhance source processor

This commit is contained in:
Qiuxia Fan 2022-01-21 17:59:18 +08:00
parent 1a57665012
commit 367cc03f0a
13 changed files with 184 additions and 95 deletions

View File

@ -90,6 +90,7 @@ export const querySampledRecords = {
variable: ["$condition: TopNCondition!, $duration: Duration!"], variable: ["$condition: TopNCondition!, $duration: Duration!"],
query: ` query: `
readSampledRecords: readSampledRecords(condition: $condition, duration: $duration) { readSampledRecords: readSampledRecords(condition: $condition, duration: $duration) {
id
name name
value value
refId refId

View File

@ -18,7 +18,8 @@ export const Services = {
variable: "$layer: String!", variable: "$layer: String!",
query: ` query: `
services: listServices(layer: $layer) { services: listServices(layer: $layer) {
value: id id
value: name
label: name label: name
group group
layers layers

View File

@ -14,6 +14,14 @@
* 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 enum MetricQueryTypes {
ReadMetricsValue = "readMetricsValue",
ReadMetricsValues = "readMetricsValues",
SortMetrics = "sortMetrics",
ReadLabeledMetricsValues = "readLabeledMetricsValues",
READHEATMAP = "readHeatMap",
ReadSampledRecords = "readSampledRecords",
}
export enum sizeEnum { export enum sizeEnum {
XS = "XS", XS = "XS",
SM = "SM", SM = "SM",
@ -77,3 +85,11 @@ export const RespFields: any = {
refId refId
}`, }`,
}; };
export enum CalculationType {
Plus = "+",
Minus = "-",
Multiplication = "*",
Division = "/",
"Convert Unix Timestamp(milliseconds)" = "milliseconds",
"Convert Unix Timestamp(seconds)" = "seconds",
}

View File

@ -14,21 +14,21 @@
* 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 { Duration } from "@/types/app"; import { RespFields, MetricQueryTypes, CalculationType } from "./data";
import { RespFields } from "./data";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
export function useQueryProcessor( export function useQueryProcessor(config: any) {
config: any,
selectorStore: any,
dashboardStore: any,
durationTime: Duration
) {
if (!(config.metrics && config.metrics.length)) { if (!(config.metrics && config.metrics.length)) {
return; return;
} }
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
const conditions: { [key: string]: unknown } = { const conditions: { [key: string]: unknown } = {
duration: durationTime, duration: appStore.durationTime,
}; };
const variables: string[] = [`$duration: Duration!`]; const variables: string[] = [`$duration: Duration!`];
const { currentPod, currentService, currentDestPod, currentDestService } = const { currentPod, currentService, currentDestPod, currentDestService } =
@ -40,31 +40,42 @@ export function useQueryProcessor(
"EndpointRelation", "EndpointRelation",
].includes(entity); ].includes(entity);
const fragment = config.metrics.map((name: string, index: number) => { const fragment = config.metrics.map((name: string, index: number) => {
const metricTypes = config.metricTypes[index] || ""; const metricType = config.metricTypes[index] || "";
// const labels = config.metricType === 'LABELED_VALUE' ? labelsIndex : undefined; const labels = ["0", "1", "2", "3", "4"];
if (["readSampledRecords", "sortMetrics"].includes(metricTypes)) { if (
[
MetricQueryTypes.ReadSampledRecords,
MetricQueryTypes.SortMetrics,
].includes(metricType)
) {
variables.push(`$condition${index}: TopNCondition!`); variables.push(`$condition${index}: TopNCondition!`);
conditions[`condition${index}`] = { conditions[`condition${index}`] = {
name, name,
parentService: entity === "All" ? null : currentService, parentService: ["Service", "All"].includes(entity)
? null
: currentService,
normal: normal, normal: normal,
scope: entity, scope: entity,
topN: Number(config.standard.maxItemNum || 10), topN: 10,
order: config.standard.sortOrder || "DES", order: "DES",
}; };
} else { } else {
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
variables.push(`$labels${index}: [String!]!`);
conditions[`labels${index}`] = labels;
}
variables.push(`$condition${index}: MetricsCondition!`); variables.push(`$condition${index}: MetricsCondition!`);
conditions[`condition${index}`] = { conditions[`condition${index}`] = {
name, name,
entity: { entity: {
scope: entity, scope: entity,
serviceName: entity === "All" ? undefined : currentService, serviceName: entity === "All" ? undefined : currentService,
normal: true, normal: entity === "All" ? undefined : normal,
serviceInstanceName: entity.includes("ServiceInstance") serviceInstanceName: entity.includes("ServiceInstance")
? currentPod ? currentPod
: undefined, : undefined,
endpointName: entity.includes("Endpoint") ? currentPod : undefined, endpointName: entity.includes("Endpoint") ? currentPod : undefined,
destNormal: entity === "All" ? undefined : destNormal, destNormal: entity === "All" ? undefined : undefined,
destServiceName: isRelation ? currentDestService : undefined, destServiceName: isRelation ? currentDestService : undefined,
destServiceInstanceName: destServiceInstanceName:
entity === "ServiceInstanceRelation" ? currentDestPod : undefined, entity === "ServiceInstanceRelation" ? currentDestPod : undefined,
@ -73,8 +84,11 @@ export function useQueryProcessor(
}, },
}; };
} }
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
return `${name}${index}: ${metricTypes}(condition: $condition${index}, duration: $duration)${RespFields[metricTypes]}`; return `${name}${index}: ${metricType}(condition: $condition${index}, labels: $labels${index}, duration: $duration)${RespFields[metricType]}`;
} else {
return `${name}${index}: ${metricType}(condition: $condition${index}, duration: $duration)${RespFields[metricType]}`;
}
}); });
const queryStr = `query queryData(${variables}) {${fragment}}`; const queryStr = `query queryData(${variables}) {${fragment}}`;
return { return {
@ -84,20 +98,72 @@ export function useQueryProcessor(
} }
export function useSourceProcessor( export function useSourceProcessor(
resp: { errors: string; data: { [key: string]: any } }, resp: { errors: string; data: { [key: string]: any } },
config: { metrics: string[] } config: { metrics: string[]; metricTypes: string[] }
) { ) {
const source: { [key: string]: unknown } = {};
if (resp.errors) { if (resp.errors) {
ElMessage.error(resp.errors); ElMessage.error(resp.errors);
return {}; return {};
} }
const source: { [key: string]: unknown } = {};
const keys = Object.keys(resp.data); const keys = Object.keys(resp.data);
keys.forEach((key: string, index) => {
config.metricTypes.forEach((type: string, index) => {
const m = config.metrics[index]; const m = config.metrics[index];
source[m] = resp.data[key].values.values.map(
(d: { value: number }) => d.value if (type === MetricQueryTypes.ReadMetricsValues) {
); source[m] = resp.data[keys[index]].values.values.map(
(d: { value: number }) => d.value
);
}
if (type === MetricQueryTypes.ReadLabeledMetricsValues) {
const resVal = Object.values(resp.data)[0] || [];
const labelsIdx = ["0", "1", "2", "3", "4"];
const labels = ["P50", "P75", "P90", "P95", "P99"];
for (const item of resVal) {
const values = item.values.values.map(
(d: { value: number }) => d.value
);
const indexNum = labelsIdx.findIndex((d: string) => d === item.label);
if (labels[indexNum] && indexNum > -1) {
source[labels[indexNum]] = values;
} else {
source[item.label] = values;
}
}
}
if (type === MetricQueryTypes.ReadMetricsValue) {
source[m] = Object.values(resp.data)[0];
}
if (
type === MetricQueryTypes.SortMetrics ||
type === MetricQueryTypes.ReadSampledRecords
) {
source[m] = Object.values(resp.data)[0] || [];
}
}); });
return source; return source;
} }
function aggregation(json: {
data: number;
type: string;
aggregationNum: number;
}) {
if (isNaN(json.aggregationNum)) {
return json.data;
}
if (json.type === CalculationType.Plus) {
return json.data + json.aggregationNum;
}
if (json.type === CalculationType.Minus) {
return json.data - json.aggregationNum;
}
if (json.type === CalculationType.Multiplication) {
return json.data * json.aggregationNum;
}
if (json.type === CalculationType.Division) {
return json.data / json.aggregationNum;
}
return json.data;
}

View File

@ -35,7 +35,7 @@ export const ConfigData: any = {
w: 8, w: 8,
h: 12, h: 12,
i: "0", i: "0",
metrics: ["service_resp_time", "service_cpm"], metrics: ["service_resp_time", "service_apdex"],
metricTypes: ["readMetricsValues", "readMetricsValues"], metricTypes: ["readMetricsValues", "readMetricsValues"],
type: "Widget", type: "Widget",
widget: { widget: {

View File

@ -23,7 +23,7 @@ limitations under the License. -->
:destroy-on-close="true" :destroy-on-close="true"
@closed="dashboardStore.setConfigPanel(false)" @closed="dashboardStore.setConfigPanel(false)"
> >
<widget-config /> <config-edit />
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
@ -32,7 +32,7 @@ import { useI18n } from "vue-i18n";
import GridLayout from "./panel/Layout.vue"; import GridLayout from "./panel/Layout.vue";
// import { LayoutConfig } from "@/types/dashboard"; // import { LayoutConfig } from "@/types/dashboard";
import Tool from "./panel/Tool.vue"; import Tool from "./panel/Tool.vue";
import WidgetConfig from "./configuration/ConfigEdit.vue"; import ConfigEdit from "./configuration/ConfigEdit.vue";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n(); const { t } = useI18n();

View File

@ -69,6 +69,7 @@ import { useDashboardStore } from "@/store/modules/dashboard";
import { MetricTypes, TableChartTypes, MetricCatalog } from "../data"; import { MetricTypes, TableChartTypes, MetricCatalog } from "../data";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import Icon from "@/components/Icon.vue"; import Icon from "@/components/Icon.vue";
import { useQueryProcessor, useSourceProcessor } from "@/hooks/useProcessor";
/*global defineProps, defineEmits */ /*global defineProps, defineEmits */
const props = defineProps({ const props = defineProps({
@ -122,8 +123,9 @@ async function setMetricType() {
for (const metric of metrics) { for (const metric of metrics) {
states.metricTypeList.push(MetricTypes[metric.type]); states.metricTypeList.push(MetricTypes[metric.type]);
} }
if (states.metrics && states.metrics[0]) {
queryMetrics(); queryMetrics();
}
} }
function changeMetrics(index: number, arr: (Option & { type: string })[]) { function changeMetrics(index: number, arr: (Option & { type: string })[]) {
@ -135,6 +137,7 @@ function changeMetrics(index: number, arr: (Option & { type: string })[]) {
} }
states.metrics[index] = arr[0].value; states.metrics[index] = arr[0].value;
const typeOfMetrics = arr[0].type; const typeOfMetrics = arr[0].type;
states.metricTypeList[index] = MetricTypes[typeOfMetrics]; states.metricTypeList[index] = MetricTypes[typeOfMetrics];
states.metricTypes[index] = MetricTypes[typeOfMetrics][0].value; states.metricTypes[index] = MetricTypes[typeOfMetrics][0].value;
emit("apply", { metricTypes: states.metricTypes, metrics: states.metrics }); emit("apply", { metricTypes: states.metricTypes, metrics: states.metrics });
@ -164,26 +167,21 @@ function changeMetricType(index: number, opt: Option[]) {
queryMetrics(); queryMetrics();
} }
async function queryMetrics() { async function queryMetrics() {
const json = await dashboardStore.fetchMetricValue( const params = useQueryProcessor(states);
dashboardStore.selectedGrid if (!params) {
); emit("update", {});
if (!json) {
return; return;
} }
const json = await dashboardStore.fetchMetricValue(params);
if (json.errors) { if (json.errors) {
ElMessage.error(json.errors); ElMessage.error(json.errors);
return; return;
} }
const metricVal = json.data.readMetricsValues.values.values.map( const source = useSourceProcessor(json, states);
(d: { value: number }) => d.value emit("update", source);
);
const m = states.metrics[0];
if (!m) {
return;
}
emit("update", { [m]: metricVal });
} }
function changeDashboard(item: Option[]) { function changeDashboard(item: Option[]) {
states.graph.dashboardName = item[0].value; states.graph.dashboardName = item[0].value;
} }
@ -204,7 +202,6 @@ watch(
() => props.graph, () => props.graph,
(data: any) => { (data: any) => {
states.isTable = TableChartTypes.includes(data.type); states.isTable = TableChartTypes.includes(data.type);
console.log(data);
} }
); );
</script> </script>

View File

@ -20,9 +20,9 @@ limitations under the License. -->
v-model="fontSize" v-model="fontSize"
show-input show-input
input-size="small" input-size="small"
:min="0.1" :min="10"
:max="1" :max="20"
:step="0.1" :step="1"
@change="updateConfig({ fontSize })" @change="updateConfig({ fontSize })"
/> />
</div> </div>

View File

@ -87,14 +87,11 @@ export default defineComponent({
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
queryMetrics(); if (props.data.metrics && props.data.metrics[0]) {
queryMetrics();
}
async function queryMetrics() { async function queryMetrics() {
const params = useQueryProcessor( const params = await useQueryProcessor(props.data);
props.data,
selectorStore,
dashboardStore,
appStore.durationTime
);
if (!params) { if (!params) {
state.source = {}; state.source = {};
return; return;
@ -102,6 +99,9 @@ export default defineComponent({
loading.value = true; loading.value = true;
const json = await dashboardStore.fetchMetricValue(params); const json = await dashboardStore.fetchMetricValue(params);
loading.value = false; loading.value = false;
if (!json) {
return;
}
state.source = useSourceProcessor(json, props.data); state.source = useSourceProcessor(json, props.data);
} }

View File

@ -78,14 +78,6 @@ export const DefaultGraphConfig: { [key: string]: any } = {
}, },
}; };
export enum MetricQueryTypes {
ReadMetricsValue = "readMetricsValue",
ReadMetricsValues = "readMetricsValues",
SortMetrics = "sortMetrics",
ReadLabeledMetricsValues = "readLabeledMetricsValues",
READHEATMAP = "readHeatMap",
ReadSampledRecords = "readSampledRecords",
}
export enum MetricsType { export enum MetricsType {
UNKNOWN = "UNKNOWN", UNKNOWN = "UNKNOWN",
REGULAR_VALUE = "REGULAR_VALUE", REGULAR_VALUE = "REGULAR_VALUE",

View File

@ -16,11 +16,11 @@ limitations under the License. -->
<template> <template>
<div class="chart-card" :style="{ fontSize: `${config.fontSize}px` }"> <div class="chart-card" :style="{ fontSize: `${config.fontSize}px` }">
{{ {{
typeof data[key] === "string" typeof singleVal === "string"
? data[key] ? singleVal
: isNaN(data[key]) : isNaN(singleVal)
? null ? null
: data[key].toFixed(2) : singleVal.toFixed(2)
}} }}
<span v-show="config.showUint">{{ standard.unit }}</span> <span v-show="config.showUint">{{ standard.unit }}</span>
</div> </div>
@ -45,6 +45,7 @@ const props = defineProps({
}, },
}); });
const key = computed(() => Object.keys(props.data)[0]); const key = computed(() => Object.keys(props.data)[0]);
const singleVal = computed(() => props.data[key.value]);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chart-card { .chart-card {

View File

@ -14,23 +14,25 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="chart-slow-i" v-for="(i, index) in datas" :key="index"> <div class="top-list">
<Icon <div class="chart-slow-i" v-for="(i, index) in datas" :key="index">
iconName="review-list" <Icon
size="sm" iconName="review-list"
@click="handleClick((i.traceIds && i.traceIds[0]) || i.name)" size="sm"
/> @click="handleClick((i.traceIds && i.traceIds[0]) || i.name)"
<div class="mb-5 ell"> />
<span class="calls sm mr-10">{{ i.value }}</span> <div class="mb-5 ell">
<span class="cp link-hover"> <span class="calls sm mr-10">{{ i.value }}</span>
{{ i.name + getTraceId(i) }} <span class="cp link-hover">
</span> {{ i.name + getTraceId(i) }}
</span>
</div>
<el-progress
:stroke-width="10"
:percentage="(i.value / maxValue) * 100"
color="#bf99f8"
/>
</div> </div>
<el-progress
:stroke-width="10"
:percentage="(i.value / maxValue) * 100"
color="#bf99f8"
/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -40,32 +42,34 @@ import copy from "@/utils/copy";
/*global defineProps */ /*global defineProps */
const props = defineProps({ const props = defineProps({
data: { data: {
type: Array as PropType< type: Object as PropType<{
{ name: string; value: number; traceIds: string[] }[] [key: string]: { name: string; value: number; traceIds: string[] }[];
>, }>,
default: () => [], default: () => ({}),
}, },
config: { config: {
type: Object as PropType<{ sortOrder: string }>, type: Object as PropType<{ sortOrder: string }>,
default: () => ({}), default: () => ({}),
}, },
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
}); });
const key = computed(() => Object.keys(props.data)[0]);
const maxValue = computed(() => { const maxValue = computed(() => {
if (!props.data.length) { if (!props.data[key.value].length) {
return 0; return 0;
} }
const temp: number[] = props.data.map((i: any) => i.value); const temp: number[] = props.data[key.value].map((i: any) => i.value);
return Math.max.apply(null, temp); return Math.max.apply(null, temp);
}); });
const getTraceId = (i: { [key: string]: (number | string)[] }): string => { const getTraceId = (i: { [key: string]: (number | string)[] }): string => {
return i.traceIds && i.traceIds[0] ? ` - ${i.traceIds[0]}` : ""; return i.traceIds && i.traceIds[0] ? ` - ${i.traceIds[0]}` : "";
}; };
const datas: any = () => { const datas = computed(() => {
if (!props.data.length) { if (!props.data[key.value].length) {
return []; return [];
} }
const { sortOrder } = props.config; const { sortOrder } = props.config;
const val: any = props.data; const val: any = props.data[key.value];
switch (sortOrder) { switch (sortOrder) {
case "DES": case "DES":
@ -75,17 +79,25 @@ const datas: any = () => {
val.sort((a: any, b: any) => a.value - b.value); val.sort((a: any, b: any) => a.value - b.value);
break; break;
default: default:
val.sort((a: any, b: any) => b.value - a.value);
break; break;
} }
return val; return val;
}; });
function handleClick(i: string) { function handleClick(i: string) {
copy(i); copy(i);
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.top-list {
height: 100%;
overflow: auto;
padding: 10px;
}
.progress-bar { .progress-bar {
font-size: 14px; font-size: 12px;
color: #333; color: #333;
} }

View File

@ -108,7 +108,10 @@ const states = reactive<{
}); });
dashboardStore.setLayer(states.layerId); dashboardStore.setLayer(states.layerId);
dashboardStore.setEntity(states.entity); dashboardStore.setEntity(states.entity);
onBeforeMount(async () => {
getServices();
async function getServices() {
if (!states.layerId) { if (!states.layerId) {
return; return;
} }
@ -118,7 +121,7 @@ onBeforeMount(async () => {
return; return;
} }
fetchPods(states.entity); fetchPods(states.entity);
}); }
async function changeService(service: Option[]) { async function changeService(service: Option[]) {
if (service[0]) { if (service[0]) {