You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ha...@apache.org on 2018/09/26 03:54:57 UTC

[incubator-skywalking-ui] branch 6.0.0/dev updated (dec04d1 -> 35510a4)

This is an automated email from the ASF dual-hosted git repository.

hanahmily pushed a change to branch 6.0.0/dev
in repository https://gitbox.apache.org/repos/asf/incubator-skywalking-ui.git.


    from dec04d1  Refactor topoloy node style
     new bcbef4d  Add metircs query
     new 35510a4  Add the style of latency and cpm to the lines of Topology page.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .roadhogrc.mock.js                     |   3 +-
 mock/aggregation.js                    |  14 +---
 mock/metric.js                         |   2 +-
 mock/topology.js                       | 134 ++-------------------------------
 package-lock.json                      |  66 ++++++++--------
 query-protocol                         |   2 +-
 src/components/Topology/AppTopology.js | 127 +++++++++++++++++++++++++------
 src/components/Topology/Base.js        | 108 ++++++++++++++++++++------
 src/models/topology.js                 | 101 ++++++++++++++++++++++++-
 src/routes/Topology/Topology.js        |  68 +++++++++++------
 10 files changed, 380 insertions(+), 245 deletions(-)


[incubator-skywalking-ui] 02/02: Add the style of latency and cpm to the lines of Topology page.

Posted by ha...@apache.org.
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 35510a49147935a69732ed3ef69f0a528c13af80
Author: Gao Hongtao <ha...@gmail.com>
AuthorDate: Wed Sep 26 10:42:06 2018 +0800

    Add the style of latency and cpm to the lines of Topology page.
---
 mock/topology.js                       |  16 ++---
 src/components/Topology/AppTopology.js | 111 +++++++++++++++++++++++++++------
 src/components/Topology/Base.js        |  80 +++++++++++++++++-------
 src/models/topology.js                 |  82 +++++++++++++++++++++---
 src/routes/Topology/Topology.js        |  49 ++++++++-------
 5 files changed, 258 insertions(+), 80 deletions(-)

diff --git a/mock/topology.js b/mock/topology.js
index 7d87cf1..62c4fbd 100644
--- a/mock/topology.js
+++ b/mock/topology.js
@@ -104,49 +104,49 @@ export default {
     const userConnectApplication = mockjs.mock({
       calls: [
         {
-          id: 1,
+          id: 11,
           source: 1,
           target: 2,
           'callType|1': ['rpc', 'http', 'dubbo'],
         },
         {
-          id: 2,
+          id: 12,
           source: 2,
           target: 3,
           'callType|1': ['rpc', 'http', 'dubbo'],
         },
         {
-          id: 3,
+          id: 13,
           source: 3,
           target: 2,
           'callType|1': ['rpc', 'http', 'dubbo'],
         },
         {
-          id: 4,
+          id: 14,
           source: 2,
           target: 200,
           'callType|1': ['rpc', 'http', 'dubbo'],
         },
         {
-          id: 5,
+          id: 15,
           source: 2,
           target: 201,
           'callType|1': ['rpc', 'http', 'dubbo'],
         },
         {
-          id: 1,
+          id: 16,
           source: 3,
           target: 202,
           'callType|1': ['rpc', 'http', 'dubbo'],
         },
         {
-          id: 6,
+          id: 17,
           source: 3,
           target: 203,
           'callType|1': ['rpc', 'http', 'dubbo'],
         },
         {
-          id: 7,
+          id: 18,
           source: 3,
           target: 204,
           'callType|1': ['rpc', 'http', 'dubbo'],
diff --git a/src/components/Topology/AppTopology.js b/src/components/Topology/AppTopology.js
index 873fdd7..e583e91 100644
--- a/src/components/Topology/AppTopology.js
+++ b/src/components/Topology/AppTopology.js
@@ -20,12 +20,8 @@ import cytoscape from 'cytoscape';
 import * as d3 from 'd3';
 import Base from './Base';
 
-const conf = {
-  nodeSize: {
-    min: 60,
-    max: 120,
-  },
-};
+const latencyColorRange = ['#40a9ff', '#d4b106', '#cf1322'];
+
 export default class AppTopology extends Base {
   setUp = (elements) => {
     const { nodes } = elements;
@@ -37,28 +33,32 @@ export default class AppTopology extends Base {
   }
 
   supplyUserNode = (edges) => {
-    let i = 0;
     const nodes = [];
-    const time = new Date().getTime();
     return {
       nodes,
       edges: edges.map((_) => {
         if (_.data.source !== '1') {
-          return _;
+          return {
+            data: {
+              ..._.data,
+              dataId: _.data.id,
+            },
+          };
         }
-        i += 1;
-        const newId = `USER-${time}-${i}`;
+        const newId = `USER-${_.data.target}`;
         nodes.push({
           data: {
             id: newId,
             name: 'User',
             type: 'USER',
+            isReal: false,
           },
         });
         return {
           data: {
             ..._.data,
             source: newId,
+            dataId: _.data.id,
             id: `${newId}-${_.data.target}`,
           },
         };
@@ -81,6 +81,11 @@ export default class AppTopology extends Base {
   }
 
   updateMetrics = (cy, data) => {
+    this.updateNodeMetrics(cy, data);
+    this.updateEdgeMetrics(cy, data);
+  }
+
+  updateNodeMetrics = (cy, data) => {
     const { sla: { values: slaValues } } = data;
     const layer = cy.cyCanvas();
     const canvas = layer.getCanvas();
@@ -96,7 +101,7 @@ export default class AppTopology extends Base {
       cy.nodes('node[?isReal]').forEach( (node) => {
         const pos = node.position();
         layer.setTransform(ctx);
-        const colors = ["#f5222d", "#1890ff"];
+        const colors = ["#cf1322", "#40a9ff"];
         const nodeId = node.id();
         const nodeSla = slaValues.find(_ => _.id === nodeId);
         let sla = 100;
@@ -126,6 +131,77 @@ export default class AppTopology extends Base {
     });
   }
 
+  updateEdgeMetrics = (cy, data) => {
+    const { cpm, latency } = data;
+    if (!cpm) {
+      return;
+    }
+    const { latencyRange } = this.props;
+    const range = [0, ...latencyRange];
+    const colorRange = range.map((_, i) => {
+      const begin = _;
+      let end = 99999;
+      if (range.length > i + 1) {
+        end = range[i + 1];
+      }
+      return {
+        range: [begin, end],
+        color: latencyColorRange[i],
+      }
+    })
+    const cpmFunc = this.mapFunc(cpm.values);
+    cy.style().selector('edge')
+    .css({
+      width: ele => cpmFunc(ele.data('dataId'), 3, 12),
+      'line-color': ele => this.lineColor(latency.values, ele.data('dataId'), colorRange),
+      'target-arrow-color': ele => this.lineColor(latency.values, ele.data('dataId'), colorRange),
+      'curve-style': 'bezier',
+      'control-point-step-size': 100,
+      'target-arrow-shape': 'triangle',
+      'arrow-scale': 1.2,
+      'opacity': 0.666,
+      'text-wrap': 'wrap',
+      'text-rotation': 'autorotate',
+    })
+    .update();
+  }
+
+  mapFunc = (values) => {
+    if (values.length < 1) {
+      return (id, rLimit) => {
+        return rLimit;
+      }; 
+    }
+    const valueData = values.map(_ => _.value);
+    const max = Math.max(...valueData);
+    const min = Math.min(...valueData);
+    const range = max - min;
+    return (id, lLimit, rLimit) => {
+      if (!id) {
+        return lLimit;
+      }
+      const value = values.find(_ => _.id === id);
+      let v = min;
+      if (value) {
+        v = value.value;
+      }
+      const r = Math.round((v - min) * (rLimit - lLimit) / range + lLimit);
+      if (r < lLimit) {
+        return lLimit;
+      }
+      return r;
+    };
+  }
+
+  lineColor = (values, id, colorRange) => {
+    const value = values.find(_ => _.id === id);
+    if (!value) {
+      return '#40a9ff';
+    }
+    const range = colorRange.find(_ => value.value >= _.range[0] && value.value < _.range[1]);
+    return range ? range.color : '#40a9ff';
+  }
+
   getStyle = () => {
     return cytoscape.stylesheet()
       .selector('node[?isReal]')
@@ -146,7 +222,9 @@ export default class AppTopology extends Base {
       })
       .selector(':selected')
       .css({
-        'border-width': 4,
+        width: 67,
+        height: 67,
+        'border-width': 13,
       })
       .selector('.faded')
       .css({
@@ -175,14 +253,9 @@ export default class AppTopology extends Base {
         'curve-style': 'bezier',
         'control-point-step-size': 100,
         'target-arrow-shape': 'triangle',
-        'arrow-scale': 1.7,
-        'target-arrow-color': '#40a9ff',
-        'line-color': 'mapData(101, 0, 100, #40a9ff, #ffa39e)',
+        'arrow-scale': 1.2,
         'opacity': 0.666,
-        width: 3,
-        label: ele => `${ele.data('callType')}`,
         'text-wrap': 'wrap',
-        color: 'rgb(110, 112, 116)',
         'text-rotation': 'autorotate',
       });
   }
diff --git a/src/components/Topology/Base.js b/src/components/Topology/Base.js
index 82b50c7..e6631fa 100644
--- a/src/components/Topology/Base.js
+++ b/src/components/Topology/Base.js
@@ -41,9 +41,10 @@ export default class Base extends Component {
   }
 
   componentDidMount() {
-    const { elements, layout = config.layout, onLoadMetircs, metrics } = this.props;
+    const { elements, layout = config.layout, metrics } = this.props;
     this.layout = layout;
     this.metrics = metrics;
+    this.elements = elements;
     let nextElements = this.transform(elements);
     if (this.setUp) {
       nextElements = this.setUp(nextElements);
@@ -61,9 +62,6 @@ export default class Base extends Component {
     if (this.bindEvent) {
       this.bindEvent(this.cy);
     }
-    if (onLoadMetircs) {
-      onLoadMetircs(nextElements.nodes.filter(_ => _.data.id.indexOf('USER') < 0).map(_ => _.data.id));
-    }
   }
 
   componentWillReceiveProps(nextProps) {
@@ -91,21 +89,55 @@ export default class Base extends Component {
     return diff.left.length < 1 && diff.right.length < 1;
   }
 
+  loadMetrics = (elementes) => {
+    const { onLoadMetircs } = this.props;
+    if (onLoadMetircs) {
+      onLoadMetircs(
+        elementes.nodes.filter(_ => _.data.id.indexOf('USER') < 0).map(_ => _.data.id),
+        elementes.edges.filter(_ => _.data.detectPoint === 'SERVER').map(_ => _.data.id),
+        elementes.edges.filter(_ => _.data.detectPoint === 'CLIENT').map(_ => _.data.id),
+      );
+    }
+  }
+
+  transform = (elements) => {
+    if (!elements) {
+      return [];
+    }
+    const { nodes, calls } = elements;
+    return {
+      nodes: nodes.map(node => ({ data: node })),
+      edges: calls.filter(call => (nodes.findIndex(node => node.id === call.source) > -1
+        && nodes.findIndex(node => node.id === call.target) > -1))
+        .map(call => ({ data: { ...call } })),
+    };
+  }
+
   updateTopology(nextProps) {
-    if (nextProps.elements === this.elements && nextProps.layout === this.layout) {
+    const { elements, layout: nextLayout, appRegExps } = nextProps;
+    let thisElements = this.elements;
+    let nextElements = elements;
+    const filteredElements = this.filter(elements, appRegExps);
+    if (filteredElements) {
+      thisElements = this.filteredElements;
+      nextElements = filteredElements;
+      this.filteredElements = filteredElements;
+    }
+    if (thisElements === nextElements && nextLayout === this.layout) {
       return;
     }
-    const { elements, layout: nextLayout, onLoadMetircs } = nextProps;
-    const nodes = this.cy.nodes();
-    let nextElements = this.transform(elements);
+    this.elements = elements;
+    nextElements = this.transform(nextElements);
     if (this.setUp) {
       nextElements = this.setUp(nextElements);
     }
-    this.cy.json({ elements: nextElements, style: this.getStyle() });
+    const nodes = this.cy.nodes();
+    this.cy.json({ elements: nextElements });
     
     if (this.bindEvent) {
       this.bindEvent(this.cy);
     }
+    this.loadMetrics(nextElements);
     if (nextLayout === this.layout && this.isSame(nodes, this.cy.nodes())) {
       return;
     }
@@ -115,32 +147,36 @@ export default class Base extends Component {
       this.cy.minZoom(this.cy.zoom() - 0.3);
     });
     layout.run();
-    if (onLoadMetircs) {
-      onLoadMetircs(nextElements.nodes.filter(_ => _.data.id.indexOf('USER') < 0).map(_ => _.data.id));
-    }
   }
 
   updateMetric(nextProps) {
-    if (nextProps.metrics === this.metrics) {
+    if (nextProps.metrics === this.metrics && nextProps.latencyRange === this.latencyRange) {
       return;
     }
     this.metrics = nextProps.metrics;
+    this.latencyRange = nextProps.latencyRange;
     if (this.updateMetrics) {
       this.updateMetrics(this.cy, this.metrics);
     }
   }
 
-  transform(elements) {
-    if (!elements) {
-      return [];
+  filter(elements, appRegExps) {
+    if (!appRegExps) {
+      this.appRegExps = appRegExps;
+      return elements;
     }
-    this.elements = elements;
-    const { nodes, calls } = elements;
+    if (this.elements === elements && this.appRegExps === appRegExps) {
+      return this.filteredElements;
+    }
+    this.appRegExps = appRegExps;
+    const nn = elements.nodes.filter(_ => appRegExps
+      .findIndex(r => _.name.match(r)) > -1);
+    const cc = elements.calls.filter(_ => nn
+      .findIndex(n => n.id === _.source || n.id === _.target) > -1);
     return {
-      nodes: nodes.map(node => ({ data: node })),
-      edges: calls.filter(call => (nodes.findIndex(node => node.id === call.source) > -1
-        && nodes.findIndex(node => node.id === call.target) > -1))
-        .map(call => ({ data: { ...call, id: `${call.source}-${call.target}` } })),
+      nodes: elements.nodes.filter(_ => cc
+        .findIndex(c => c.source === _.id || c.target === _.id) > -1),
+      calls: cc,
     };
   }
 
diff --git a/src/models/topology.js b/src/models/topology.js
index 5f2d853..1b34e44 100644
--- a/src/models/topology.js
+++ b/src/models/topology.js
@@ -19,8 +19,8 @@
 import { base } from '../utils/models';
 import { exec } from '../services/graphql';
 
-const nodeMetricQuery = `
-  query NodeMetric($duration: Duration!, $ids: [ID!]!) {
+const metricQuery = `
+  query TopologyMetric($duration: Duration!, $ids: [ID!]!,  $idsS: [ID!]!, $idsC: [ID!]!) {
     sla: getValues(metric: {
       name: "service_relation_server_call_sla"
       ids: $ids
@@ -30,6 +30,42 @@ const nodeMetricQuery = `
         value
       }
     }
+    cpmS: getValues(metric: {
+      name: "service_relation_server_cpm"
+      ids: $idsS
+    }, duration: $duration) {
+      values {
+        id
+        value
+      }
+    }
+    latencyS: getValues(metric: {
+      name: "service_relation_client_resp_time"
+      ids: $idsS
+    }, duration: $duration) {
+      values {
+        id
+        value
+      }
+    }
+    cpmC: getValues(metric: {
+      name: "service_relation_client_cpm"
+      ids: $idsC
+    }, duration: $duration) {
+      values {
+        id
+        value
+      }
+    }
+    latencyC: getValues(metric: {
+      name: "service_relation_client_resp_time"
+      ids: $idsC
+    }, duration: $duration) {
+      values {
+        id
+        value
+      }
+    }
   }
 `;
 
@@ -40,10 +76,21 @@ export default base({
       nodes: [],
       calls: [],
     },
-    sla: {
-      values: [],
+    metrics: {
+      sla: {
+        values: [],
+      },
+      cpm: {
+        values: [],
+      },
+      latency: {
+        values: [],
+      },
     },
   },
+  varState: {
+    latencyRange: [100, 500],
+  },
   dataQuery: `
     query Topology($duration: Duration!) {
       getGlobalTopology(duration: $duration) {
@@ -64,14 +111,25 @@ export default base({
     }
   `,
   effects: {
-    *fetchNodeMetrics({ payload }, { call, put }) {
-      const response = yield call(exec, { query: nodeMetricQuery, variables: payload.variables });
+    *fetchMetrics({ payload }, { call, put }) {
+      const response = yield call(exec, { query: metricQuery, variables: payload.variables });
       if (!response.data) {
         return;
       }
+      const { sla, cpmS, cpmC, latencyS, latencyC } = response.data;
       yield put({
         type: 'saveData',
-        payload: response.data,
+        payload: {
+          metrics: {
+            sla,
+            cpm: {
+              values: cpmS.values.concat(cpmC.values),
+            },
+            latency: {
+              values: latencyS.values.concat(latencyC.values),
+            },
+          },
+        },
       });
     },
   },
@@ -102,5 +160,15 @@ export default base({
         },
       };
     },
+    setLatencyStyleRange(preState, { payload: { latencyRange } }) {
+      const { variables } = preState;
+      return {
+        ...preState,
+        variables: {
+          ...variables,
+          latencyRange,
+        },
+      };
+    },
   },
 });
diff --git a/src/routes/Topology/Topology.js b/src/routes/Topology/Topology.js
index e811c11..4dbe867 100644
--- a/src/routes/Topology/Topology.js
+++ b/src/routes/Topology/Topology.js
@@ -18,7 +18,7 @@
 
 import React, { PureComponent } from 'react';
 import { connect } from 'dva';
-import { Row, Col, Card, Icon, Radio, Avatar, Select } from 'antd';
+import { Row, Col, Card, Icon, Radio, Avatar, Select, Input } from 'antd';
 import { ChartCard } from '../../components/Charts';
 import { AppTopology } from '../../components/Topology';
 import { Panel } from '../../components/Page';
@@ -89,13 +89,15 @@ export default class Topology extends PureComponent {
     });
   }
 
-  handleLoadMetrics = (nodeIds) => {
+  handleLoadMetrics = (ids, idsS, idsC) => {
     const { dispatch, globalVariables: { duration } } = this.props;
     dispatch({
-      type: 'topology/fetchNodeMetrics',
+      type: 'topology/fetchMetrics',
       payload: { variables: {
         duration,
-        ids: nodeIds,
+        ids,
+        idsS,
+        idsC,
       }},
     });
   }
@@ -115,6 +117,16 @@ export default class Topology extends PureComponent {
     }
   }
 
+  handleChangeLatencyStyle = (e) => {
+    const { value } = e.target;
+    const latencyRange = value.split(',').map(_ => parseInt(_.trim(), 10));
+    const { dispatch } = this.props;
+    dispatch({
+      type: 'topology/setLatencyStyleRange',
+      payload: { latencyRange },
+    });
+  }
+
   handleFilterApplication = (aa) => {
     const { dispatch } = this.props;
     dispatch({
@@ -123,22 +135,6 @@ export default class Topology extends PureComponent {
     });
   }
 
-  filter = () => {
-    const { topology: { variables: { appRegExps }, data: { getGlobalTopology } } } = this.props;
-    if (!appRegExps) {
-      return getGlobalTopology;
-    }
-    const nn = getGlobalTopology.nodes.filter(_ => appRegExps
-      .findIndex(r => _.name.match(r)) > -1);
-    const cc = getGlobalTopology.calls.filter(_ => nn
-      .findIndex(n => n.id === _.source || n.id === _.target) > -1);
-    return {
-      nodes: getGlobalTopology.nodes.filter(_ => cc
-        .findIndex(c => c.source === _.id || c.target === _.id) > -1),
-      calls: cc,
-    };
-  }
-
   renderActions = () => {
     const { data: { appInfo } } = this.props.topology;
     return [
@@ -172,9 +168,9 @@ export default class Topology extends PureComponent {
   }
 
   render() {
-    const { data, variables: { appFilters = [] } } = this.props.topology;
-    const { layout = 0 } = data;
-    const topologData = this.filter();
+    const { data, variables: { appRegExps, appFilters = [], latencyRange } } = this.props.topology;
+    const { metrics, layout = 0 } = data;
+    const { getGlobalTopology: topologData } = data;
     return (
       <Panel globalVariables={this.props.globalVariables} onChange={this.handleChange}>
         <Row gutter={8}>
@@ -195,10 +191,12 @@ export default class Topology extends PureComponent {
                 <AppTopology
                   height={this.props.graphHeight}
                   elements={topologData}
-                  metrics={data}
+                  metrics={metrics}
                   onSelectedApplication={this.handleSelectedApplication}
                   onLoadMetircs={this.handleLoadMetrics}
                   layout={layouts[layout]}
+                  latencyRange={latencyRange}
+                  appRegExps={appRegExps}
                 />
               ) : null}
             </ChartCard>
@@ -226,6 +224,9 @@ export default class Topology extends PureComponent {
                   {data.getGlobalTopology.nodes.filter(_ => _.sla)
                     .map(_ => <Option key={_.name}>{_.name}</Option>)}
                 </Select>
+                <h4>Latency coloring thresholds</h4>
+                <Input style={{ width: '100%', marginBottom: 20 }} onChange={this.handleChangeLatencyStyle} value={latencyRange.join(',')} />
+                <h4>Overview</h4>
                 <DescriptionList layout="vertical">
                   <Description term="Total">{topologData.nodes.length}</Description>
                   {this.renderNodeType(topologData)}


[incubator-skywalking-ui] 01/02: Add metircs query

Posted by ha...@apache.org.
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 bcbef4dc3aeffe345e0fa5677b0b01bc72346246
Author: Gao Hongtao <ha...@gmail.com>
AuthorDate: Tue Sep 25 10:56:52 2018 +0800

    Add metircs query
---
 .roadhogrc.mock.js                     |   3 +-
 mock/aggregation.js                    |  14 +---
 mock/metric.js                         |   2 +-
 mock/topology.js                       | 134 ++-------------------------------
 package-lock.json                      |  66 ++++++++--------
 query-protocol                         |   2 +-
 src/components/Topology/AppTopology.js |  16 +++-
 src/components/Topology/Base.js        |  64 +++++++++++-----
 src/models/topology.js                 |  33 +++++++-
 src/routes/Topology/Topology.js        |  27 +++++--
 10 files changed, 159 insertions(+), 202 deletions(-)

diff --git a/.roadhogrc.mock.js b/.roadhogrc.mock.js
index c415afe..38ff59e 100644
--- a/.roadhogrc.mock.js
+++ b/.roadhogrc.mock.js
@@ -7,13 +7,12 @@ import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
 import { graphql } from 'graphql';
 import { ClusterBrief, getServiceInstances, getAllServices, searchEndpoint } from './mock/metadata';
 import { IntValues, Thermodynamic } from './mock/metric';
-import { getTopN } from './mock/aggregation';
+import {  } from './mock/aggregation';
 
 const noMock = process.env.NO_MOCK === 'true';
 
 const resolvers = {
   Query: {
-    getTopN,
     getAllServices,
     getServiceInstances,
     getGlobalTopology,
diff --git a/mock/aggregation.js b/mock/aggregation.js
index 3047ce3..b45c112 100644
--- a/mock/aggregation.js
+++ b/mock/aggregation.js
@@ -18,15 +18,7 @@
 import mockjs from 'mockjs';
 
 export default {
-  getTopN: (obj, args, context, info) => {
-    let array = null;
-    if (args.condition.filterScope === 'ENDPOINT') {
-      array = mockjs.mock({ 'array|10': [{ 'id|+1': 1, name: '@url', 'value|200-1000': 1 }]});
-    } else if (args.condition.filterScope === 'SERVICE') {
-      array = mockjs.mock({ 'array|10': [{ 'id|+1': 1, name: '@name', 'value|100-10000': 1 }]});
-    } else if (args.condition.filterScope === 'SERVICE_INSTANCE') {
-      array = mockjs.mock({ 'array|10': [{ 'id|+1': 1, name: '@name', 'value|100-10000': 1 }]});
-    }
-    return array.array;
-  },
+  getServiceTopN: () => mockjs.mock({ 'array|10': [{ 'id|+1': 1, name: '@name', 'value|100-10000': 1 }]}),
+  getAllServiceInstanceTopN: () => mockjs.mock({ 'array|10': [{ 'id|+1': 1, name: '@name', 'value|100-10000': 1 }]}),
+  getAllEndpointTopN: () => mockjs.mock({ 'array|10': [{ 'id|+1': 1, name: '@url', 'value|200-1000': 1 }]}),
 };
diff --git a/mock/metric.js b/mock/metric.js
index 410d006..203d525 100644
--- a/mock/metric.js
+++ b/mock/metric.js
@@ -34,7 +34,7 @@ export default {
     {
       'values|60': [{
         'id|+1': 1,
-        value: '@natural(0, 1000)',
+        value: '@natural(0, 100)',
       }],
     }
   )),
diff --git a/mock/topology.js b/mock/topology.js
index 7a82c64..7d87cf1 100644
--- a/mock/topology.js
+++ b/mock/topology.js
@@ -104,52 +104,52 @@ export default {
     const userConnectApplication = mockjs.mock({
       calls: [
         {
+          id: 1,
           source: 1,
           target: 2,
           'callType|1': ['rpc', 'http', 'dubbo'],
-          'cpm|100-2000': 1,
         },
         {
+          id: 2,
           source: 2,
           target: 3,
           'callType|1': ['rpc', 'http', 'dubbo'],
-          'cpm|100-2000': 1,
         },
         {
+          id: 3,
           source: 3,
           target: 2,
           'callType|1': ['rpc', 'http', 'dubbo'],
-          'cpm|100-2000': 1,
         },
         {
+          id: 4,
           source: 2,
           target: 200,
           'callType|1': ['rpc', 'http', 'dubbo'],
-          'cpm|100-2000': 1,
         },
         {
+          id: 5,
           source: 2,
           target: 201,
           'callType|1': ['rpc', 'http', 'dubbo'],
-          'cpm|100-2000': 1,
         },
         {
+          id: 1,
           source: 3,
           target: 202,
           'callType|1': ['rpc', 'http', 'dubbo'],
-          'cpm|100-2000': 1,
         },
         {
+          id: 6,
           source: 3,
           target: 203,
           'callType|1': ['rpc', 'http', 'dubbo'],
-          'cpm|100-2000': 1,
         },
         {
+          id: 7,
           source: 3,
           target: 204,
           'callType|1': ['rpc', 'http', 'dubbo'],
-          'cpm|100-2000': 1,
         },
       ],
     });
@@ -158,122 +158,4 @@ export default {
       calls: userConnectApplication.calls,
     };
   },
-  getTopology(req, res) {
-    res.json(mockjs.mock(
-      {
-        data: {
-          getClusterTopology: () => {
-            const application = mockjs.mock({
-              'nodes|2-3': [
-                {
-                  'id|+1': 1,
-                  name: '@name',
-                  'type|1': ['DUBBO', 'tomcat', 'SPRINGMVC'],
-                  'cpm|10-2000': 1,
-                  'sla|1-100.1-2': 1,
-                  'apdex|0.2': 1,
-                  'avgResponseTime|500-1000': 1,
-                  'isAlarm|1': true,
-                  'numOfServer|1-100': 1,
-                  'numOfServerAlarm|1-100': 1,
-                  'numOfServiceAlarm|1-100': 1,
-                },
-              ],
-            });
-            const users = mockjs.mock({
-              nodes: [
-                {
-                  id: 100,
-                  name: 'User',
-                  type: 'USER',
-                },
-              ],
-            });
-            const resources = mockjs.mock({
-              'nodes|5': [
-                {
-                  'id|+1': 200,
-                  name: '@name',
-                  'type|1': ['Oracle', 'MYSQL', 'REDIS'],
-                },
-              ],
-            });
-            const nodes = users.nodes.concat(application.nodes, resources.nodes);
-            const userConnectApplication = mockjs.mock({
-              calls: [
-                {
-                  source: 100,
-                  target: 1,
-                  'isAlert|1': true,
-                  'callType|1': ['rpc', 'http', 'dubbo'],
-                  'cpm|100-2000': 1,
-                  'avgResponseTime|500-5000': 1,
-                },
-                {
-                  source: 1,
-                  target: 2,
-                  'isAlert|1': true,
-                  'callType|1': ['rpc', 'http', 'dubbo'],
-                  'cpm|100-2000': 1,
-                  'avgResponseTime|500-5000': 1,
-                },
-                {
-                  source: 2,
-                  target: 1,
-                  'isAlert|1': true,
-                  'callType|1': ['rpc', 'http', 'dubbo'],
-                  'cpm|100-2000': 1,
-                  'avgResponseTime|500-5000': 1,
-                },
-                {
-                  source: 1,
-                  target: 200,
-                  'isAlert|1': true,
-                  'callType|1': ['rpc', 'http', 'dubbo'],
-                  'cpm|100-2000': 1,
-                  'avgResponseTime|500-5000': 1,
-                },
-                {
-                  source: 1,
-                  target: 201,
-                  'isAlert|1': true,
-                  'callType|1': ['rpc', 'http', 'dubbo'],
-                  'cpm|100-2000': 1,
-                  'avgResponseTime|500-5000': 1,
-                },
-                {
-                  source: 2,
-                  target: 202,
-                  'isAlert|1': true,
-                  'callType|1': ['rpc', 'http', 'dubbo'],
-                  'cpm|100-2000': 1,
-                  'avgResponseTime|500-5000': 1,
-                },
-                {
-                  source: 2,
-                  target: 203,
-                  'isAlert|1': true,
-                  'callType|1': ['rpc', 'http', 'dubbo'],
-                  'cpm|100-2000': 1,
-                  'avgResponseTime|500-5000': 1,
-                },
-                {
-                  source: 2,
-                  target: 204,
-                  'isAlert|1': true,
-                  'callType|1': ['rpc', 'http', 'dubbo'],
-                  'cpm|100-2000': 1,
-                  'avgResponseTime|500-5000': 1,
-                },
-              ],
-            });
-            return {
-              nodes,
-              calls: userConnectApplication.calls,
-            };
-          },
-        },
-      }
-    ));
-  },
 };
diff --git a/package-lock.json b/package-lock.json
index 047caed..28c7387 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3422,7 +3422,7 @@
     },
     "acorn-dynamic-import": {
       "version": "2.0.2",
-      "resolved": "http://registry.npm.taobao.org/acorn-dynamic-import/download/acorn-dynamic-import-2.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz",
       "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=",
       "dev": true,
       "requires": {
@@ -3431,7 +3431,7 @@
       "dependencies": {
         "acorn": {
           "version": "4.0.13",
-          "resolved": "http://registry.npm.taobao.org/acorn/download/acorn-4.0.13.tgz",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
           "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=",
           "dev": true
         }
@@ -3578,7 +3578,7 @@
         },
         "ansi-regex": {
           "version": "3.0.0",
-          "resolved": "http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-3.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
           "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
           "dev": true
         },
@@ -3818,7 +3818,7 @@
           "dependencies": {
             "strip-ansi": {
               "version": "4.0.0",
-              "resolved": "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-4.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
               "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
               "dev": true,
               "requires": {
@@ -3965,7 +3965,7 @@
     },
     "ansi-styles": {
       "version": "2.2.1",
-      "resolved": "http://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
       "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
     },
     "ant-design-palettes": {
@@ -4689,13 +4689,13 @@
     },
     "babel-plugin-syntax-decorators": {
       "version": "6.13.0",
-      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz",
+      "resolved": "http://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz",
       "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=",
       "dev": true
     },
     "babel-plugin-syntax-object-rest-spread": {
       "version": "6.13.0",
-      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
+      "resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
       "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=",
       "dev": true
     },
@@ -5402,7 +5402,7 @@
     },
     "buffer": {
       "version": "4.9.1",
-      "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+      "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
       "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
       "dev": true,
       "requires": {
@@ -5638,7 +5638,7 @@
     },
     "chalk": {
       "version": "1.1.3",
-      "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz",
+      "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
       "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
       "requires": {
         "ansi-styles": "^2.2.1",
@@ -5925,7 +5925,7 @@
       "dependencies": {
         "is-fullwidth-code-point": {
           "version": "1.0.0",
-          "resolved": "http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
           "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
           "dev": true,
           "requires": {
@@ -5940,7 +5940,7 @@
         },
         "string-width": {
           "version": "1.0.2",
-          "resolved": "http://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
           "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
           "dev": true,
           "requires": {
@@ -10409,7 +10409,7 @@
       "dependencies": {
         "minimist": {
           "version": "1.1.3",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
           "integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=",
           "dev": true
         }
@@ -10417,7 +10417,7 @@
     },
     "got": {
       "version": "6.7.1",
-      "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
+      "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz",
       "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
       "dev": true,
       "requires": {
@@ -11442,7 +11442,7 @@
     },
     "is-fullwidth-code-point": {
       "version": "2.0.0",
-      "resolved": "http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-2.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
       "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
       "dev": true
     },
@@ -14788,7 +14788,7 @@
     },
     "minimist": {
       "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+      "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
       "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
     },
     "minimist-options": {
@@ -14842,7 +14842,7 @@
     },
     "mkdirp": {
       "version": "0.5.1",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
       "requires": {
         "minimist": "0.0.8"
@@ -14850,7 +14850,7 @@
       "dependencies": {
         "minimist": {
           "version": "0.0.8",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
           "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
         }
       }
@@ -15422,7 +15422,7 @@
       "dependencies": {
         "minimist": {
           "version": "0.0.10",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
           "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
           "dev": true
         }
@@ -18699,7 +18699,7 @@
       "dependencies": {
         "ansi-regex": {
           "version": "3.0.0",
-          "resolved": "http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-3.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
           "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
           "dev": true
         },
@@ -18785,7 +18785,7 @@
             },
             "strip-ansi": {
               "version": "4.0.0",
-              "resolved": "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-4.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
               "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
               "dev": true,
               "requires": {
@@ -20846,13 +20846,13 @@
       "dependencies": {
         "ansi-regex": {
           "version": "3.0.0",
-          "resolved": "http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-3.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
           "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
           "dev": true
         },
         "strip-ansi": {
           "version": "4.0.0",
-          "resolved": "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-4.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
           "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
           "dev": true,
           "requires": {
@@ -21532,7 +21532,7 @@
     },
     "supports-color": {
       "version": "2.0.0",
-      "resolved": "http://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
       "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
     },
     "svg-tags": {
@@ -22392,7 +22392,7 @@
     },
     "uglifyjs-webpack-plugin": {
       "version": "0.4.6",
-      "resolved": "http://registry.npm.taobao.org/uglifyjs-webpack-plugin/download/uglifyjs-webpack-plugin-0.4.6.tgz",
+      "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz",
       "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=",
       "dev": true,
       "requires": {
@@ -23180,7 +23180,7 @@
         },
         "enhanced-resolve": {
           "version": "3.4.1",
-          "resolved": "http://registry.npm.taobao.org/enhanced-resolve/download/enhanced-resolve-3.4.1.tgz",
+          "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz",
           "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=",
           "dev": true,
           "requires": {
@@ -23192,7 +23192,7 @@
         },
         "has-flag": {
           "version": "2.0.0",
-          "resolved": "http://registry.npm.taobao.org/has-flag/download/has-flag-2.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
           "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
           "dev": true
         },
@@ -23213,7 +23213,7 @@
         },
         "supports-color": {
           "version": "4.5.0",
-          "resolved": "http://registry.npm.taobao.org/supports-color/download/supports-color-4.5.0.tgz",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
           "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
           "dev": true,
           "requires": {
@@ -23588,7 +23588,7 @@
         },
         "is-fullwidth-code-point": {
           "version": "1.0.0",
-          "resolved": "http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
           "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
           "dev": true,
           "requires": {
@@ -23676,7 +23676,7 @@
         },
         "os-locale": {
           "version": "1.4.0",
-          "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+          "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
           "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
           "dev": true,
           "requires": {
@@ -23765,7 +23765,7 @@
         },
         "string-width": {
           "version": "1.0.2",
-          "resolved": "http://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
           "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
           "dev": true,
           "requires": {
@@ -23993,7 +23993,7 @@
     },
     "wrap-ansi": {
       "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
       "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
       "dev": true,
       "requires": {
@@ -24003,7 +24003,7 @@
       "dependencies": {
         "is-fullwidth-code-point": {
           "version": "1.0.0",
-          "resolved": "http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
           "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
           "dev": true,
           "requires": {
@@ -24012,7 +24012,7 @@
         },
         "string-width": {
           "version": "1.0.2",
-          "resolved": "http://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
           "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
           "dev": true,
           "requires": {
diff --git a/query-protocol b/query-protocol
index eb643d2..acffe13 160000
--- a/query-protocol
+++ b/query-protocol
@@ -1 +1 @@
-Subproject commit eb643d27813dc53adf550b31d35059a687ed337a
+Subproject commit acffe1341e6bcefcdd3e2a6ebfb0c00db6e9af35
diff --git a/src/components/Topology/AppTopology.js b/src/components/Topology/AppTopology.js
index 05fbb04..873fdd7 100644
--- a/src/components/Topology/AppTopology.js
+++ b/src/components/Topology/AppTopology.js
@@ -77,10 +77,14 @@ export default class AppTopology extends Base {
         onSelectedApplication();
       });
     }
+    
+  }
+
+  updateMetrics = (cy, data) => {
+    const { sla: { values: slaValues } } = data;
     const layer = cy.cyCanvas();
     const canvas = layer.getCanvas();
     
-
     cy.on('render cyCanvas.resize', (evt) => {
       const ctx = canvas.getContext('2d');
       layer.resetTransform(ctx);
@@ -89,10 +93,16 @@ export default class AppTopology extends Base {
       layer.setTransform(ctx);
 
       // Draw model elements
-      cy.nodes('node[type != "USER"]').forEach( (node) => {
+      cy.nodes('node[?isReal]').forEach( (node) => {
         const pos = node.position();
         layer.setTransform(ctx);
         const colors = ["#f5222d", "#1890ff"];
+        const nodeId = node.id();
+        const nodeSla = slaValues.find(_ => _.id === nodeId);
+        let sla = 100;
+        if (nodeSla) {
+          sla = nodeSla.value;
+        }
 
         const arc = d3.arc()
             .outerRadius(33)
@@ -104,7 +114,7 @@ export default class AppTopology extends Base {
 
         ctx.translate(pos.x, pos.y);
 
-        const arcs = pie([30, 70]);
+        const arcs = pie([100 - sla, sla]);
 
         arcs.forEach((d, i) => {
           ctx.beginPath();
diff --git a/src/components/Topology/Base.js b/src/components/Topology/Base.js
index 07143f1..82b50c7 100644
--- a/src/components/Topology/Base.js
+++ b/src/components/Topology/Base.js
@@ -41,8 +41,9 @@ export default class Base extends Component {
   }
 
   componentDidMount() {
-    const { elements, layout = config.layout } = this.props;
+    const { elements, layout = config.layout, onLoadMetircs, metrics } = this.props;
     this.layout = layout;
+    this.metrics = metrics;
     let nextElements = this.transform(elements);
     if (this.setUp) {
       nextElements = this.setUp(nextElements);
@@ -60,19 +61,51 @@ export default class Base extends Component {
     if (this.bindEvent) {
       this.bindEvent(this.cy);
     }
+    if (onLoadMetircs) {
+      onLoadMetircs(nextElements.nodes.filter(_ => _.data.id.indexOf('USER') < 0).map(_ => _.data.id));
+    }
   }
 
   componentWillReceiveProps(nextProps) {
+    this.updateTopology(nextProps);
+    this.updateMetric(nextProps);
+  }
+
+  shouldComponentUpdate() {
+    return false;
+  }
+
+  componentWillUnmount() {
+    this.cy.destroy();
+  }
+
+  getCy() {
+    return this.cy;
+  }
+
+  isSame = (nodes, nextNodes) => {
+    if (nodes.length !== nextNodes.length) {
+      return false;
+    }
+    const diff = nextNodes.diff(nodes);
+    return diff.left.length < 1 && diff.right.length < 1;
+  }
+
+  updateTopology(nextProps) {
     if (nextProps.elements === this.elements && nextProps.layout === this.layout) {
       return;
     }
-    const { elements, layout: nextLayout } = nextProps;
+    const { elements, layout: nextLayout, onLoadMetircs } = nextProps;
     const nodes = this.cy.nodes();
     let nextElements = this.transform(elements);
     if (this.setUp) {
       nextElements = this.setUp(nextElements);
     }
     this.cy.json({ elements: nextElements, style: this.getStyle() });
+    
+    if (this.bindEvent) {
+      this.bindEvent(this.cy);
+    }
     if (nextLayout === this.layout && this.isSame(nodes, this.cy.nodes())) {
       return;
     }
@@ -82,26 +115,19 @@ export default class Base extends Component {
       this.cy.minZoom(this.cy.zoom() - 0.3);
     });
     layout.run();
+    if (onLoadMetircs) {
+      onLoadMetircs(nextElements.nodes.filter(_ => _.data.id.indexOf('USER') < 0).map(_ => _.data.id));
+    }
   }
 
-  shouldComponentUpdate() {
-    return false;
-  }
-
-  componentWillUnmount() {
-    this.cy.destroy();
-  }
-
-  getCy() {
-    return this.cy;
-  }
-
-  isSame = (nodes, nextNodes) => {
-    if (nodes.length !== nextNodes.length) {
-      return false;
+  updateMetric(nextProps) {
+    if (nextProps.metrics === this.metrics) {
+      return;
+    }
+    this.metrics = nextProps.metrics;
+    if (this.updateMetrics) {
+      this.updateMetrics(this.cy, this.metrics);
     }
-    const diff = nextNodes.diff(nodes);
-    return diff.left.length < 1 && diff.right.length < 1;
   }
 
   transform(elements) {
diff --git a/src/models/topology.js b/src/models/topology.js
index 760f1fa..5f2d853 100644
--- a/src/models/topology.js
+++ b/src/models/topology.js
@@ -17,6 +17,21 @@
 
 
 import { base } from '../utils/models';
+import { exec } from '../services/graphql';
+
+const nodeMetricQuery = `
+  query NodeMetric($duration: Duration!, $ids: [ID!]!) {
+    sla: getValues(metric: {
+      name: "service_relation_server_call_sla"
+      ids: $ids
+    }, duration: $duration) {
+      values {
+        id
+        value
+      }
+    }
+  }
+`;
 
 export default base({
   namespace: 'topology',
@@ -25,6 +40,9 @@ export default base({
       nodes: [],
       calls: [],
     },
+    sla: {
+      values: [],
+    },
   },
   dataQuery: `
     query Topology($duration: Duration!) {
@@ -36,14 +54,27 @@ export default base({
           isReal
         }
         calls {
+          id
           source
           target
           callType
-          cpm
+          detectPoint
         }
       }
     }
   `,
+  effects: {
+    *fetchNodeMetrics({ payload }, { call, put }) {
+      const response = yield call(exec, { query: nodeMetricQuery, variables: payload.variables });
+      if (!response.data) {
+        return;
+      }
+      yield put({
+        type: 'saveData',
+        payload: response.data,
+      });
+    },
+  },
   reducers: {
     filterApplication(preState, { payload: { aa } }) {
       const { variables } = preState;
diff --git a/src/routes/Topology/Topology.js b/src/routes/Topology/Topology.js
index ff56a81..e811c11 100644
--- a/src/routes/Topology/Topology.js
+++ b/src/routes/Topology/Topology.js
@@ -74,27 +74,41 @@ export default class Topology extends PureComponent {
   };
 
   handleChange = (variables) => {
-    this.props.dispatch({
+    const { dispatch } = this.props;
+    dispatch({
       type: 'topology/fetchData',
       payload: { variables },
     });
   }
 
   handleLayoutChange = ({ target: { value } }) => {
-    this.props.dispatch({
+    const { dispatch } = this.props;
+    dispatch({
       type: 'topology/saveData',
       payload: { layout: value },
     });
   }
 
+  handleLoadMetrics = (nodeIds) => {
+    const { dispatch, globalVariables: { duration } } = this.props;
+    dispatch({
+      type: 'topology/fetchNodeMetrics',
+      payload: { variables: {
+        duration,
+        ids: nodeIds,
+      }},
+    });
+  }
+
   handleSelectedApplication = (appInfo) => {
+    const { dispatch } = this.props;
     if (appInfo) {
-      this.props.dispatch({
+      dispatch({
         type: 'topology/saveData',
         payload: { appInfo },
       });
     } else {
-      this.props.dispatch({
+      dispatch({
         type: 'topology/saveData',
         payload: { appInfo: null },
       });
@@ -102,7 +116,8 @@ export default class Topology extends PureComponent {
   }
 
   handleFilterApplication = (aa) => {
-    this.props.dispatch({
+    const { dispatch } = this.props;
+    dispatch({
       type: 'topology/filterApplication',
       payload: { aa },
     });
@@ -180,7 +195,9 @@ export default class Topology extends PureComponent {
                 <AppTopology
                   height={this.props.graphHeight}
                   elements={topologData}
+                  metrics={data}
                   onSelectedApplication={this.handleSelectedApplication}
+                  onLoadMetircs={this.handleLoadMetrics}
                   layout={layouts[layout]}
                 />
               ) : null}