This is an automated email from the ASF dual-hosted git repository. hanahmily pushed a commit to branch 6.0.0/dev in repository https://gitbox.apache.org/repos/asf/incubator-skywalking-ui.git
commit 44c7474a01edda4730ef576bf9ba10a89a2f29aa Author: Gao Hongtao <[email protected]> AuthorDate: Thu Oct 11 21:37:31 2018 +0800 Refactor Endpoint dependencies chart with new V6 api --- .roadhogrc.mock.js | 3 +- mock/topology.js | 50 +++++++++++++++++++ src/components/Charts/EndpointDeps/index.js | 74 +++++++++++++++++++++++++++++ src/components/Charts/index.js | 3 ++ src/models/endpoint.js | 60 +++++++++++++++++++++++ src/routes/Endpoint/Endpoint.js | 69 +++++++++++---------------- 6 files changed, 218 insertions(+), 41 deletions(-) diff --git a/.roadhogrc.mock.js b/.roadhogrc.mock.js index 628b6dd..d255f73 100644 --- a/.roadhogrc.mock.js +++ b/.roadhogrc.mock.js @@ -1,6 +1,6 @@ import fs from 'fs'; import { delay } from 'roadhog-api-doc'; -import { getGlobalTopology, getServiceTopology } from './mock/topology'; +import { getGlobalTopology, getServiceTopology, getEndpointTopology } from './mock/topology'; import { Alarms, AlarmTrend } from './mock/alarm'; import { TraceBrief, Trace } from './mock/trace' import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools'; @@ -19,6 +19,7 @@ const resolvers = { getAllEndpointTopN, getGlobalTopology, getServiceTopology, + getEndpointTopology, searchEndpoint, getEndpointTopN, getServiceInstanceTopN, diff --git a/mock/topology.js b/mock/topology.js index 62c4fbd..5861e93 100644 --- a/mock/topology.js +++ b/mock/topology.js @@ -69,6 +69,56 @@ export default { calls, }; }, + getEndpointTopology: () => { + const upNodes = mockjs.mock({ + 'nodes|1-5': [ + { + 'id|+1': 100, + name: '@url', + 'type|1': ['DUBBO', 'USER', 'SPRINGMVC'], + isReal: true, + }, + ], + }); + const centerNodes = mockjs.mock({ + nodes: [ + { + 'id|+1': 10, + name: '@url', + 'type|1': ['DUBBO', 'tomcat', 'SPRINGMVC'], + isReal: true, + }, + ], + }); + const downNodes = mockjs.mock({ + 'nodes|2-5': [ + { + 'id|+1': 200, + name: '@url', + 'type|1': ['Oracle', 'MYSQL', 'REDIS'], + isReal: false, + }, + ], + }); + downNodes.nodes.push({ id: -111 }); + const nodes = upNodes.nodes.concat(centerNodes.nodes, downNodes.nodes); + const calls = upNodes.nodes.map(node => (mockjs.mock({ + source: node.id, + target: 10, + 'callType|1': ['rpc', 'http', 'dubbo'], + 'cpm|0-1000': 1, + }))).concat(downNodes.nodes.map(node => (mockjs.mock({ + source: 10, + target: node.id, + 'callType|1': ['rpc', 'http', 'dubbo'], + 'cpm|0-2000': 1, + })))); + calls.push({ source: '-175', target: 10, callType: 'GRPC', cpm: 0 }); + return { + nodes, + calls, + }; + }, getGlobalTopology: () => { const application = mockjs.mock({ 'nodes|2-3': [ diff --git a/src/components/Charts/EndpointDeps/index.js b/src/components/Charts/EndpointDeps/index.js new file mode 100644 index 0000000..8cc478a --- /dev/null +++ b/src/components/Charts/EndpointDeps/index.js @@ -0,0 +1,74 @@ +/** + * 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 React, { PureComponent } from 'react'; +import { Sankey } from '..'; + +class EndpointDeps extends PureComponent { + + componentDidUpdate({ deps: preDeps }) { + const { deps } = this.props; + if (deps === preDeps) { + return; + } + const { onLoadMetrics } = this.props; + onLoadMetrics(deps); + } + + edgeWith = (edge) => { + const { metrics: { cpm: { values } } } = this.props; + if (values.length < 1) { + return 1; + } + const v = values.find(_ => _.id === edge.id); + if (!v) { + return 1; + } + return v.value; + } + + render() { + const { deps: { nodes, calls } } = this.props; + if (nodes.length < 2) { + return <span style={{ display: 'none' }} />; + } + const nodesMap = new Map(); + nodes.forEach((_, i) => { + nodesMap.set(`${_.id}`, i); + }); + const nData = { + nodes, + edges: calls + .filter(_ => nodesMap.has(`${_.source}`) && nodesMap.has(`${_.target}`)) + .map(_ => + ({ ..._, value: (this.edgeWith(_) < 1 ? 1000 : this.edgeWith(_)), source: nodesMap.get(`${_.source}`), target: nodesMap.get(`${_.target}`) })), + }; + return ( + <Sankey + data={nData} + edgeTooltip={['target*source*value', (target, source, value) => { + return { + name: `${source.name} to ${target.name} </span>`, + value: `${value} cpm`, + }; + }]} + edgeColor="#bbb" + />); + } +} + +export default EndpointDeps; \ No newline at end of file diff --git a/src/components/Charts/index.js b/src/components/Charts/index.js index fb5ec1f..959788d 100644 --- a/src/components/Charts/index.js +++ b/src/components/Charts/index.js @@ -28,6 +28,7 @@ import Field from './Field'; import StackBar from './StackBar'; import Sankey from './Sankey'; import HeatMap from './HeatMap'; +import EndpointDeps from './EndpointDeps'; const yuan = val => `¥ ${numeral(val).format('0,0')}`; @@ -44,6 +45,7 @@ const Charts = { StackBar, Sankey, HeatMap, + EndpointDeps, }; export { @@ -60,4 +62,5 @@ export { StackBar, Sankey, HeatMap, + EndpointDeps, }; diff --git a/src/models/endpoint.js b/src/models/endpoint.js index 27135dd..c4b0dda 100644 --- a/src/models/endpoint.js +++ b/src/models/endpoint.js @@ -105,6 +105,21 @@ const dataQuery = ` value } } + getEndpointTopology(endpointId: $endpointId, duration: $duration) { + nodes { + id + name + type + isReal + } + calls { + id + source + target + callType + detectPoint + } + } } `; @@ -146,6 +161,29 @@ const spanQuery = `query Spans($traceId: ID!) { } }`; +const metricQuery = ` + query TopologyMetric($duration: Duration!, $idsS: [ID!]!, $idsC: [ID!]!) { + cpmS: getValues(metric: { + name: "endpoint_relation_server_cpm" + ids: $idsS + }, duration: $duration) { + values { + id + value + } + } + cpmC: getValues(metric: { + name: "endpoint_relation_client_cpm" + ids: $idsC + }, duration: $duration) { + values { + id + value + } + } + } +`; + export default base({ namespace: 'endpoint', state: { @@ -162,6 +200,11 @@ export default base({ nodes: [], calls: [], }, + metrics: { + cpm: { + values: [], + }, + }, queryBasicTraces: { traces: [], total: 0, @@ -193,6 +236,23 @@ export default base({ traceId: payload.variables.traceId, }); }, + *fetchMetrics({ payload }, { call, put }) { + const response = yield call(exec, { query: metricQuery, variables: payload.variables }); + if (!response.data) { + return; + } + const { cpmS, cpmC } = response.data; + yield put({ + type: 'saveData', + payload: { + metrics: { + cpm: { + values: cpmS.values.concat(cpmC.values), + }, + }, + }, + }); + }, }, reducers: { saveSpans(state, { payload, traceId }) { diff --git a/src/routes/Endpoint/Endpoint.js b/src/routes/Endpoint/Endpoint.js index a16f36f..7de5125 100644 --- a/src/routes/Endpoint/Endpoint.js +++ b/src/routes/Endpoint/Endpoint.js @@ -20,7 +20,7 @@ import React, { PureComponent } from 'react'; import { connect } from 'dva'; import { Row, Col, Form, Button, Icon, Select } from 'antd'; import { - ChartCard, MiniArea, MiniBar, Sankey, Line, + ChartCard, MiniArea, MiniBar, Sankey, Line, EndpointDeps, } from 'components/Charts'; import { axisY, axisMY } from '../../utils/time'; import { avgTS } from '../../utils/utils'; @@ -137,11 +137,24 @@ export default class Endpoint extends PureComponent { } handleGoBack = () => { - this.props.dispatch({ + const { dispatch } = this.props; + dispatch({ type: 'endpoint/hideTimeline', }); } + handleLoadMetrics = ({ calls }) => { + const { dispatch, globalVariables: { duration } } = this.props; + dispatch({ + type: 'endpoint/fetchMetrics', + payload: { variables: { + idsS: calls.filter(_ => _.detectPoint === 'SERVER').map(_ => _.id), + idsC: calls.filter(_ => _.detectPoint === 'CLIENT').map(_ => _.id), + duration, + }}, + }); + } + edgeWith = edge => edge.cpm; renderPanel = () => { @@ -212,6 +225,20 @@ export default class Endpoint extends PureComponent { <Row gutter={8}> <Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: 8 }}> <ChartCard + title="Dependency Map" + contentHeight={200} + > + <EndpointDeps + deps={data.getEndpointTopology} + metrics={data.metrics} + onLoadMetrics={this.handleLoadMetrics} + /> + </ChartCard> + </Col> + </Row> + <Row gutter={8}> + <Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: 8 }}> + <ChartCard title="Top 20 Slow Traces" > <TraceList @@ -222,48 +249,10 @@ export default class Endpoint extends PureComponent { </ChartCard> </Col> </Row> - {this.renderSankey(getEndpointTopology)} </Panel> ); } - renderSankey = (data) => { - if (data.nodes.length < 2) { - return <span style={{ display: 'none' }} />; - } - const nodesMap = new Map(); - data.nodes.forEach((_, i) => { - nodesMap.set(`${_.id}`, i); - }); - const nData = { - nodes: data.nodes, - edges: data.calls - .filter(_ => nodesMap.has(`${_.source}`) && nodesMap.has(`${_.target}`)) - .map(_ => - ({ ..._, value: (this.edgeWith(_) < 1 ? 1000 : this.edgeWith(_)), source: nodesMap.get(`${_.source}`), target: nodesMap.get(`${_.target}`) })), - }; - return ( - <Row gutter={8}> - <Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: 8 }}> - <ChartCard - title="Dependency Map" - contentHeight={200} - > - <Sankey - data={nData} - edgeTooltip={['target*source*cpm', (target, source, cpm) => { - return { - name: `${source.name} to ${target.name} </span>`, - value: `${cpm} cpm`, - }; - }]} - edgeColor="#bbb" - /> - </ChartCard> - </Col> - </Row>); - } - render() { const { form, endpoint } = this.props; const { getFieldDecorator } = form;
