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

qiuxiafan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-rocketbot-ui.git


The following commit(s) were added to refs/heads/master by this push:
     new 70f45d0  Feat: Add a function which show the statistics infomation 
during the trace query  (#488)
70f45d0 is described below

commit 70f45d09f8e32a6dbce237f63558be389c172312
Author: Xialijun <constan...@163.com>
AuthorDate: Wed May 26 10:34:57 2021 +0800

    Feat: Add a function which show the statistics infomation during the trace 
query  (#488)
    
    * add new tag to render statistics data info
    
    * import statistice-bulleted.svg into index.ts to show svg on tag's button
    
    * 1.add the table header into statistics render;2.add locale word in i18n 
file;3.init statistics function
    
    * make statistics render work success
    
    * modify statistice compute function to work correctly
    
    * show the target of modify
    
    * remove folder of new_issue which boken coding guidelines
    
    * statistics render needn't to handleSelectSpan
    
    * statistics-bulleted.svg add apache2.0-license
    
    * 1.en.ts add statistics locale_en word;2.common/index.ts need line breaks 
to fix check;3.accept suggest to place statistics button on most the right side
    
    * Eliminate useless functions
    
    * Remove more useless functions
    
    * Add the SumTime column to the statistics table
    
    * The statistical table modifies the column name. Modify column sort
    
    * Extract public file. Modify statistics table location
    
    * Restores a function that was accidentally deleted
    
    * Add sorting function to statistics table
    
    * 1.merge statistics compoents into trace-item.vue,trac-container.vue and 
reuse by vue's parametererr
    
    * Merge duplicate components.
    
    * after merge trace-header' vue component,think different component has 
itself triggers,so function of mounted need tthe judge of parametertype
    
    * Fixed data statistics error.
    
    * use 'const' instead of 'let',Optimize the code for use 'a || b' not if 
else, anddelete no use code
    
    * 1.change *.vue file, replace <script type=js> with  <script 
type=ts>;2.Integrate code and extract common methods with function changetree 
is add to trace-util.ts and it provide common funtion to trace-*.vue
    
    * 1.fix tslint check bug,2 Accept Fine0830' suggestion use return in 
trace-container.vue
    
    * replace svg with rk-icon
    
    * change function public to private
    
    * fix the bug which infinite recursion of oneself
    
    * 1.change script's code of rk-page.vue with ts, 2.fix bug which cause 
vue's warnning: Avoid mutating a prop directly since the value will be 
overwritten whenever the parent component re-renders. Instead, use a data or 
computed property based on the prop’s value. Prop being mutated: “'currentPage'
    
    * fix statistics calculation bug,group by endpointname + type, and data 
sources consider the whole tree structures not just level 1 children
    
    Co-authored-by: lys <yansha...@163.com>
    Co-authored-by: 吴晟 Wu Sheng <wu.sh...@foxmail.com>
    Co-authored-by: Qiuxia Fan <fine0...@outlook.com>
---
 src/assets/index.ts                                |   2 +
 src/assets/lang/en.ts                              |   1 +
 src/assets/lang/zh.ts                              |   1 +
 src/assets/svg/sort.svg                            |  15 ++
 src/assets/svg/statistics-bulleted.svg             |  15 ++
 src/components/rk-page.vue                         | 108 +++-----
 src/types/trace.d.ts                               |  55 +++-
 src/views/components/common/index.ts               |   3 +-
 .../common/trace-chart-table/trace-constant.ts     |  38 +++
 .../common/trace-chart-table/trace-container.vue   | 123 +++++++--
 .../common/trace-chart-table/trace-item.vue        | 157 ++++++-----
 .../components/common/trace-chart-table/trace.scss |  21 ++
 .../components/common/trace-detail-chart-table.vue | 155 +----------
 .../common/trace-detail-statistics-table.vue       | 130 +++++++++
 src/views/components/trace/trace-detail.vue        |  16 +-
 src/views/components/trace/trace-table.vue         |   5 +-
 src/views/components/trace/trace-util.ts           | 292 +++++++++++++++++++++
 17 files changed, 819 insertions(+), 318 deletions(-)

diff --git a/src/assets/index.ts b/src/assets/index.ts
index 4b548c4..7b0549a 100644
--- a/src/assets/index.ts
+++ b/src/assets/index.ts
@@ -55,9 +55,11 @@ import './svg/soft-unwrap.svg';
 import './svg/sort-lowest.svg';
 import './svg/spam.svg';
 import './svg/spinner.svg';
+import './svg/statistics-bulleted.svg';
 import './svg/table.svg';
 import './svg/todo-add.svg';
 import './svg/token.svg';
 import './svg/unlink.svg';
 import './svg/user.svg';
 import './svg/warning.svg';
+import './svg/sort.svg';
diff --git a/src/assets/lang/en.ts b/src/assets/lang/en.ts
index 442ff73..33a931a 100644
--- a/src/assets/lang/en.ts
+++ b/src/assets/lang/en.ts
@@ -221,6 +221,7 @@ const m = {
   tableValues: 'Table Values',
   show: 'Show',
   hide: 'Hide',
+  statistics: 'Statistics',
   message: 'Message',
   tooltipsContent: 'Tooltip Content',
   alarmDetail: 'Alarm Detail',
diff --git a/src/assets/lang/zh.ts b/src/assets/lang/zh.ts
index d206aa0..972d197 100644
--- a/src/assets/lang/zh.ts
+++ b/src/assets/lang/zh.ts
@@ -219,6 +219,7 @@ const m = {
   tableValues: '表值',
   show: '展示',
   hide: '隐藏',
+  statistics: '统计',
   message: '信息',
   tooltipsContent: '提示内容',
   alarmDetail: '警告详情',
diff --git a/src/assets/svg/sort.svg b/src/assets/svg/sort.svg
new file mode 100644
index 0000000..8770c01
--- /dev/null
+++ b/src/assets/svg/sort.svg
@@ -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="1620798822338" class="icon" viewBox="0 0 1024 1024" version="1.1" 
xmlns="http://www.w3.org/2000/svg"; p-id="1847" width="200" height="200"><path 
d="M679.765333 597.333333a21.333333 21.333333 0 0 1 16.426667 
34.986667l-167.808 201.386667a21.333333 21.333333 0 0 1-32.768 
0l-167.808-201.386667a21.333333 21.333333 0 0 1 16.426667-34.986667zM525.653333 
187.605333a21.333333 21.333333 0 0 1 2.730667 2.730667l167.808 
201.344a21.333333 21.333333 0 0 1-16.426667 34.986667H344.234667a21.3333 [...]
\ No newline at end of file
diff --git a/src/assets/svg/statistics-bulleted.svg 
b/src/assets/svg/statistics-bulleted.svg
new file mode 100644
index 0000000..236a28c
--- /dev/null
+++ b/src/assets/svg/statistics-bulleted.svg
@@ -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="1619507658599" class="icon" viewBox="0 0 1024 1024" version="1.1" 
xmlns="http://www.w3.org/2000/svg"; p-id="2073" 
xmlns:xlink="http://www.w3.org/1999/xlink"; width="200" 
height="200"><defs><style type="text/css"></style></defs><path d="M331.840623 
793.484755 331.840623 
387.450978c0-16.191531-12.457456-28.645338-27.399836-28.645338L222.235197 
358.80564c-14.94238 0-27.399836 13.698094-27.399836 28.645338L194.835361 
793.484755 331.840623 793.484755 331.840623 793.484755zM506.210956 79 [...]
\ No newline at end of file
diff --git a/src/components/rk-page.vue b/src/components/rk-page.vue
index ee2c9b5..f6b2ffa 100644
--- a/src/components/rk-page.vue
+++ b/src/components/rk-page.vue
@@ -4,9 +4,7 @@ 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.
@@ -17,7 +15,7 @@ limitations under the License. -->
     <svg class="icon cp mr-5" @click="pre">
       <use xlink:href="#chevron-left"></use>
     </svg>
-    <input class="rk-page-input tc mr-5" type="text" v-model="currentPage" 
@keyup.enter="goToCertainPage" />
+    <input class="rk-page-input tc mr-5" type="text" v-model="currentShowPage" 
@keyup.enter="goToCertainPage" />
     <span class="mr-5">/</span>
     <span class="mr-5">{{ Math.ceil(this.total / this.currentSize) }}</span>
     <svg class="icon cp" @click="next">
@@ -25,71 +23,47 @@ limitations under the License. -->
     </svg>
   </span>
 </template>
-<script lang="js">
-        // tslint:disable
-  export default {
-    name: 'RkPage',
-    props: {
-      name: {
-        type: String,
-        default: '',
-      },
-      currentPage: {
-        type: Number,
-        default: 1,
-      },
-      currentSize: {
-        type: Number,
-        default: 10,
-      },
-      total: {
-        type: Number,
-        default: 10,
-      },
-    },
-    computed: {
-      last() {
-        if (this.currentPage * this.currentSize > this.total) {
-          return this.total;
-        }
-        return this.currentPage * this.currentSize;
-      },
-      totalPages() {
-        return Math.ceil(this.total / this.currentSize)
+<script lang="ts">
+  import { Component, Vue, Watch, Prop } from 'vue-property-decorator';
+
+  @Component
+  export default class RkPage extends Vue {
+    @Prop() public currentPage!: number ;
+    @Prop() public currentSize!: number ;
+    @Prop() public total!: number ;
+
+    public currentShowPage: string = this.currentPage.toString();
+    get last(): number {
+      if (this.currentPage * this.currentSize > this.total) {
+        return this.total;
       }
-    },
-    data() {
-      return {
-        current: '',
-      };
-    },
-    watch: {
-      currentPage() { this.current = parseInt(this.currentPage); },
-    },
-    beforeMount() {
-      this.current = this.currentPage;
-    },
-    methods: {
-      next() {
-        if (this.current !== this.totalPages) {
-          this.current = this.current + 1;
-          this.$emit('changePage', this.current);
-        }
-      },
-      pre() {
-        if (this.current !== 1) {
-          this.current = this.current - 1;
-          this.$emit('changePage', this.current);
-        }
-      },
-      goToCertainPage() {
-        var regInt = /^0*[1-9]\d*$/
-        if (regInt.test(this.current) && this.current <= this.totalPages) {
-          this.$emit('changePage', this.current);
-        }
+      return this.currentPage * this.currentSize;
+    }
+    get totalPages(): number {
+      return Math.ceil(this.total / this.currentSize);
+    }
+
+    private next(): void {
+      if ( Number(this.currentShowPage) < this.totalPages) {
+        this.currentShowPage = String(Number(this.currentShowPage) + 1);
+        this.$emit('changePage', this.currentShowPage);
       }
-    },
-  };
+    }
+
+    private  pre(): void {
+      if ( Number(this.currentShowPage) > 1 ) {
+        this.currentShowPage = String(Number(this.currentShowPage) - 1);
+        this.$emit('changePage', this.currentShowPage);
+      }
+    }
+
+    private  goToCertainPage() {
+      const regInt: RegExp = /^0*[1-9]\d*$/;
+      if (regInt.test( this.currentShowPage.toString() ) && 
Number(this.currentShowPage) <= this.totalPages) {
+        this.$emit('changePage', this.currentShowPage);
+      }
+    }
+  }
 </script>
 <style lang="scss">
   .rk-page {
@@ -110,4 +84,4 @@ limitations under the License. -->
     border-radius: 4px;
     border: 1px solid #c1c5ca55;
   }
-</style>
+</style>
\ No newline at end of file
diff --git a/src/types/trace.d.ts b/src/types/trace.d.ts
index 25a7353..a3163c0 100644
--- a/src/types/trace.d.ts
+++ b/src/types/trace.d.ts
@@ -25,10 +25,55 @@ export interface Trace {
 }
 
 export interface Span {
-  duration: number;
+  endpointName: string;
+  serviceCode: string;
+  parentSpanId: number;
+  segmentId: string;
+  label?: string;
+  layer: string;
+  spanId: number;
+  traceId: string;
+  type: string;
+  peer: string;
+  component: string;
   isError: boolean;
-  key: string;
-  operationNames: string[];
-  start: string;
-  traceIds: string[];
+  isBroken?: boolean;
+  refs: Array<Ref>;
+  startTime: number;
+  endTime: number;
+  dur?: number;
+  children?: Span[];
+  tags?: Array<Map<string,string>>;
+  logs?: log[];
+}
+
+export interface log{
+  time :number;
+  data :Map<string,string>;
 }
+
+export interface Ref{
+  traceId: string;
+  parentSegmentId: string;
+  parentSpanId: number;
+  type: string;
+}
+
+export interface StatisticsSpan{
+  groupRef: StatisticsGroupRef;
+  maxTime: number;
+  minTime: number;
+  sumTime: number;
+  avgTime: number;
+  count: number;
+}
+
+export interface StatisticsGroupRef{
+  endpointName: string;
+  type: string;
+}
+
+export class TraceTreeRef {
+  segmentMap: Map<string, Span>;
+  segmentIdGroup: string[];
+}
\ No newline at end of file
diff --git a/src/views/components/common/index.ts 
b/src/views/components/common/index.ts
index ae93b4f..b6eaf3a 100644
--- a/src/views/components/common/index.ts
+++ b/src/views/components/common/index.ts
@@ -16,6 +16,7 @@
  */
 
 import TraceDetailChartTable from './trace-detail-chart-table.vue';
+import TraceDetailStatisticsTable from './trace-detail-statistics-table.vue';
 import ConditionTags from './condition-tags.vue';
 
-export { TraceDetailChartTable, ConditionTags };
+export { TraceDetailChartTable, ConditionTags, TraceDetailStatisticsTable };
diff --git a/src/views/components/common/trace-chart-table/trace-constant.ts 
b/src/views/components/common/trace-chart-table/trace-constant.ts
index d982063..1430b99 100644
--- a/src/views/components/common/trace-chart-table/trace-constant.ts
+++ b/src/views/components/common/trace-chart-table/trace-constant.ts
@@ -80,3 +80,41 @@ export const TraceConstant = [
     value: 'Service',
   },
 ];
+
+export const StatisticsConstant = [
+  {
+    label: 'method',
+    value: 'Endpoint Name',
+    key: 'endpointName',
+  },
+  {
+    label: 'type',
+    value: 'Type',
+    key: 'type',
+  },
+  {
+    label: 'max-time',
+    value: 'Max Time(ms)',
+    key: 'maxTime',
+  },
+  {
+    label: 'min-time',
+    value: 'Min Time(ms)',
+    key: 'minTime',
+  },
+  {
+    label: 'sum-time',
+    value: 'Sum Time(ms)',
+    key: 'sumTime',
+  },
+  {
+    label: 'avg-time',
+    value: 'Avg Time(ms)',
+    key: 'avgTime',
+  },
+  {
+    label: 'count',
+    value: 'Hits',
+    key: 'count',
+  },
+];
diff --git a/src/views/components/common/trace-chart-table/trace-container.vue 
b/src/views/components/common/trace-chart-table/trace-container.vue
index 047eee4..9d75b43 100644
--- a/src/views/components/common/trace-chart-table/trace-container.vue
+++ b/src/views/components/common/trace-chart-table/trace-container.vue
@@ -15,18 +15,26 @@ limitations under the License. -->
 
 <template>
   <div class="trace">
-    <div class="trace-header">
+    <div class="trace-header" v-if="type === 'statistics'">
+      <div :class="item.label" v-for="(item, index) in headerData" 
:key="index">
+        {{ item.value }}
+        <span class="r cp" @click="sortStatistics(item.key)" 
:key="componentKey" v-if="item.key != 'endpointName'">
+          <svg class="icon">
+            <use xlink:href="#sort"></use>
+          </svg>
+        </span>
+      </div>
+    </div>
+    <div class="trace-header" v-else>
       <div class="method" :style="`width: ${method}px`">
         <span class="r cp" ref="dragger">
-          <svg
-            class="icon"
-          >
+          <svg class="icon">
             <use xlink:href="#settings_ethernet"></use>
           </svg>
         </span>
-        {{ data[0].value }}
+        {{ headerData[0].value }}
       </div>
-      <div :class="item.label" v-for="(item, index) in data.slice(1)" 
:key="index">
+      <div :class="item.label" v-for="(item, index) in headerData.slice(1)" 
:key="index">
         {{ item.value }}
       </div>
     </div>
@@ -34,25 +42,44 @@ limitations under the License. -->
     <slot></slot>
   </div>
 </template>
-<script lang="js">
-  import { ProfileConstant, TraceConstant } from './trace-constant';
-  import Item from './trace-item';
+<script lang="ts">
+  import { Vue, Component, Prop } from 'vue-property-decorator';
+  import { ProfileConstant, TraceConstant, StatisticsConstant } from 
'./trace-constant';
+  import Item from './trace-item.vue';
 
-  export default {
-    components: {Item},
-    name: 'TraceContainer',
-    props: ['type', 'tableData'],
-    data() {
-      return {
-        method: 300,
-      };
-    },
-    created() {
-      this.data = this.type === 'profile' ? ProfileConstant : TraceConstant;
+  @Component({
+    components: {
+      Item,
     },
-    mounted() {
-      const drag = this.$refs.dragger;
-      drag.onmousedown = (event) => {
+  })
+
+  export default class TraceContainer extends Vue {
+    @Prop()
+    public tableData: any;
+    @Prop()
+    public type!: string;
+
+    private headerData: Array<{ label: string; value: string; }>|undefined;
+    private method: number = 300;
+    private componentKey: number = 0;
+    private flag: boolean =  true;
+
+    public created(): void {
+      if ( this.type === 'profile' ) {
+          this.headerData = ProfileConstant;
+      } else if ( this.type === 'statistics' ) {
+          this.headerData = StatisticsConstant;
+      } else {
+          this.headerData = TraceConstant;
+      }
+    }
+
+    public mounted(): void {
+      if (this.type === 'statistics') {
+        return;
+      }
+      const drag: any = this.$refs.dragger;
+      drag.onmousedown = (event: any) => {
         const diffX = event.clientX;
         const copy = this.method;
         document.onmousemove = (documentEvent) => {
@@ -64,8 +91,54 @@ limitations under the License. -->
           document.onmouseup = null;
         };
       };
-    },
-  };
+    }
+
+    private sortStatistics(key: string): void {
+      const element = this.tableData;
+      for (let i = 0; i < element.length; i++) {
+        for (let j = 0; j < element.length - i - 1; j++) {
+          let val1;
+          let val2;
+          if (key === 'maxTime') {
+            val1 = element[j].maxTime;
+            val2 = element[j + 1].maxTime;
+          }
+          if (key === 'minTime') {
+            val1 = element[j].minTime;
+            val2 = element[j + 1].minTime;
+          }
+          if (key === 'avgTime') {
+            val1 = element[j].avgTime;
+            val2 = element[j + 1].avgTime;
+          }
+          if (key === 'sumTime') {
+            val1 = element[j].sumTime;
+            val2 = element[j + 1].sumTime;
+          }
+          if (key === 'count') {
+            val1 = element[j].count;
+            val2 = element[j + 1].count;
+          }
+          if (this.flag) {
+            if (val1 < val2) {
+              const tmp = element[j];
+              element[j] = element[j + 1];
+              element[j + 1] = tmp;
+            }
+          } else {
+            if (val1 > val2) {
+              const tmp = element[j];
+              element[j] = element[j + 1];
+              element[j + 1] = tmp;
+            }
+          }
+        }
+      }
+      this.tableData = element;
+      this.componentKey += 1;
+      this.flag = !this.flag;
+    }
+  }
 </script>
 <style lang="scss" scoped>
   @import './trace.scss';
diff --git a/src/views/components/common/trace-chart-table/trace-item.vue 
b/src/views/components/common/trace-chart-table/trace-item.vue
index d7d3237..c3ab5d2 100644
--- a/src/views/components/common/trace-chart-table/trace-item.vue
+++ b/src/views/components/common/trace-chart-table/trace-item.vue
@@ -14,7 +14,36 @@ See the License for the specific language governing 
permissions and
 limitations under the License. -->
 
 <template>
-  <div>
+  <div v-if="type === 'statistics'">
+    <div :class="['trace-item']" ref="traceItem">
+      <div :class="['method']">
+        <span v-tooltip:bottom="{ content: data.groupRef.endpointName, 
popperCls: ['trace-table-tooltip'] }">
+          {{ data.groupRef.endpointName }}
+        </span>
+      </div>
+      <div :class="['type']">
+        <span v-tooltip:bottom="{ content: data.groupRef.type, popperCls: 
['trace-table-tooltip'] }">
+          {{ data.groupRef.type }}
+        </span>
+      </div>
+      <div class="max-time">
+        {{ data.maxTime }}
+      </div>
+      <div class="min-time">
+        {{ data.minTime }}
+      </div>
+      <div class="sum-time">
+        {{ data.sumTime }}
+      </div>
+      <div class="avg-time">
+        {{ parseInt(data.avgTime) }}
+      </div>
+      <div class="count">
+        {{ data.count }}
+      </div>
+    </div>
+  </div>
+  <div v-else>
     <div
       @click="showSelectSpan"
       :class="['trace-item', 'level' + (data.level - 1), ...{ 
'trace-item-error': data.isError }]"
@@ -65,68 +94,72 @@ limitations under the License. -->
     </div>
   </div>
 </template>
-<script lang="js">
-  export default {
-    name: 'item',
-    props: ['data', 'type', 'method'],
-    watch: {
-      data() {
-        const items = document.querySelectorAll('.trace-item');
-        for (const item of items) {
-          item.style.background = '#fff';
-        }
-      },
-    },
-    data() {
-      return {
-        displayChildren: true,
-        selectedSpan: 0,
-      };
-    },
-    computed: {
-      selfTime() {
-        const {data} = this;
-        return  data.dur ? data.dur : 0;
-      },
-      execTime() {
-        const {data} = this;
-        return  (data.endTime - data.startTime) ? (data.endTime - 
data.startTime) : 0;
-      },
-      outterPercent() {
-        if (this.data.level === 1) {
-          return '100%';
-        } else {
-          const data = this.data;
-          const exec = (data.endTime - data.startTime) ? (data.endTime - 
data.startTime) : 0;
-          let result = (exec / data.totalExec * 100);
-          result = result > 100 ? 100 : result;
-          result = result.toFixed(4) + '%';
-          return result === '0.0000%' ? '0.9%' : result;
-        }
-      },
-      innerPercent() {
-        const result = (this.selfTime / this.execTime) * 100 .toFixed(4) + '%';
-        return result === '0.0000%' ? '0.9%' : result;
-      },
-    },
-    methods: {
-      toggle() {
-        this.displayChildren = !this.displayChildren;
-      },
-      showSelectSpan() {
-        const items = document.querySelectorAll('.trace-item');
-        for (const item of items) {
-          item.style.background = '#fff';
-        }
-        this.$refs.traceItem.style.background = 'rgba(0, 0, 0, 0.1)';
-        this.$eventBus.$emit('HANDLE-SELECT-SPAN', this.data);
-      },
-      viewSpanDetail() {
-        this.showSelectSpan();
-        this.$eventBus.$emit('HANDLE-VIEW-SPAN', this.data);
-      },
+<script lang="ts">
+  import { Vue, Prop, Watch, Component } from 'vue-property-decorator';
+
+  @Component({
+    components: {
     },
-  };
+  })
+
+  export default class Item extends Vue {
+    @Prop()
+    public data: any ;
+    @Prop()
+    public type!: string;
+    @Prop()
+    public method!: string;
+
+    public displayChildren: boolean = true;
+
+    get selfTime() {
+      return  this.data.dur ? this.data.dur : 0;
+    }
+    get execTime() {
+      return  (this.data.endTime - this.data.startTime) ? (this.data.endTime - 
this.data.startTime) : 0;
+    }
+    get outterPercent() {
+      if (this.data.level === 1) {
+        return '100%';
+      } else {
+        const data = this.data;
+        const exec = (data.endTime - data.startTime) ? (data.endTime - 
data.startTime) : 0;
+        let result: number = (exec / data.totalExec * 100);
+        result = result > 100 ? 100 : result;
+        const resultStr: string = result.toFixed(4) + '%';
+        return resultStr === '0.0000%' ? '0.9%' : resultStr;
+      }
+    }
+    get innerPercent() {
+      const result: number = ((this.selfTime / this.execTime) * 100);
+      const resultStr: string =  result.toFixed(4) + '%';
+      return resultStr === '0.0000%' ? '0.9%' : resultStr;
+    }
+
+    @Watch('data')
+    public onDataChanged(): void {
+      const items = document.querySelectorAll('.trace-item') as 
NodeListOf<HTMLElement>;
+      for (const item of items) {
+        item.style.background = '#fff';
+      }
+    }
+
+    private toggle() {
+      this.displayChildren = !this.displayChildren;
+    }
+    private showSelectSpan() {
+      const items = document.querySelectorAll('.trace-item') as 
NodeListOf<HTMLElement>;
+      for (const item of items) {
+        item.style.background = '#fff';
+      }
+      (this.$refs.traceItem as HTMLElement).style.background = 'rgba(0, 0, 0, 
0.1)';
+      this.$eventBus.$emit('HANDLE-SELECT-SPAN', this.data);
+    }
+    private viewSpanDetail() {
+      this.showSelectSpan();
+      this.$eventBus.$emit('HANDLE-VIEW-SPAN', this.data);
+    }
+  }
 </script>
 <style lang="scss" scoped>
   @import './trace.scss';
diff --git a/src/views/components/common/trace-chart-table/trace.scss 
b/src/views/components/common/trace-chart-table/trace.scss
index 2c3848e..b40692d 100644
--- a/src/views/components/common/trace-chart-table/trace.scss
+++ b/src/views/components/common/trace-chart-table/trace.scss
@@ -40,3 +40,24 @@
   width: 150px;
   text-align: center;
 }
+.max-time {
+  width: 150px;
+}
+.method {
+  width: 300px;
+}
+.avg-time {
+  width: 150px;
+}
+.min-time {
+  width: 150px;
+}
+.count {
+  width: 120px;
+}
+.sum-time {
+  width: 150px;
+}
+.type {
+  width: 60px;
+}
diff --git a/src/views/components/common/trace-detail-chart-table.vue 
b/src/views/components/common/trace-detail-chart-table.vue
index 3776c49..b7b1ae1 100644
--- a/src/views/components/common/trace-detail-chart-table.vue
+++ b/src/views/components/common/trace-detail-chart-table.vue
@@ -41,7 +41,7 @@ limitations under the License. -->
 <script lang="js">
   import copy from '@/utils/copy';
   import TraceContainer from './trace-chart-table/trace-container';
-  import _ from 'lodash';
+  import TraceUtil from '../trace/trace-util';
   import TraceSpanLogs from '../trace/trace-span-logs.vue';
   /* eslint-disable */
   /* tslint:disable */
@@ -57,7 +57,7 @@ limitations under the License. -->
           this.tableData = [];
           return;
         }
-        this.tableData = this.formatData(this.changeTree());
+        this.tableData = 
this.formatData(TraceUtil.changeTree(this.data,this.traceId));
         this.loading = false;
       },
     },
@@ -86,155 +86,6 @@ limitations under the License. -->
         }
         return arr;
       },
-      traverseTree(node, spanId, segmentId, data) {
-        if (!node || node.isBroken) {
-          return;
-        }
-        if (node.spanId === spanId && node.segmentId === segmentId) {
-          node.children.push(data);
-          return;
-        }
-        if (node.children && node.children.length > 0) {
-          for (const item of node.children) {
-            this.traverseTree(item, spanId, segmentId, data);
-          }
-        }
-      },
-      changeTree() {
-        if (this.data.length === 0) {
-          return [];
-        }
-        this.list = Array.from(new Set(this.data.map((i) => i.serviceCode)));
-        this.segmentId = [];
-        const segmentGroup = {};
-        const segmentIdGroup = [];
-        const fixSpans = [];
-        const segmentHeaders = [];
-        this.data.forEach((span) => {
-          if (span.parentSpanId === -1) {
-            segmentHeaders.push(span);
-          } else {
-            const index = this.data.findIndex(i => (i.segmentId === 
span.segmentId && i.spanId === (span.spanId - 1)));
-            const fixSpanKeyContent = {
-              traceId: span.traceId,
-              segmentId: span.segmentId,
-              spanId: span.spanId - 1,
-              parentSpanId: span.spanId - 2,
-            };
-            if (index === -1 && !_.find(fixSpans, fixSpanKeyContent)) {
-              fixSpans.push(
-                {
-                  ...fixSpanKeyContent, refs: [], endpointName: `VNode: 
${span.segmentId}`, serviceCode: 'VirtualNode', type: `[Broken] ${span.type}`, 
peer: '', component: `VirtualNode: #${span.spanId - 1}`, isError: true, 
isBroken: true, layer: 'Broken', tags: [], logs: [],
-                },
-              );
-            }
-          }
-        });
-          segmentHeaders.forEach((span) => {
-            if (span.refs && span.refs.length) {
-              span.refs.forEach((ref) => {
-                const index = this.data.findIndex(i => (ref.parentSegmentId 
=== i.segmentId && ref.parentSpanId === i.spanId));
-                if (index === -1) {
-                  // create a known broken node.
-                  const i = ref.parentSpanId;
-                  const fixSpanKeyContent = {
-                    traceId: ref.traceId,
-                    segmentId: ref.parentSegmentId,
-                    spanId: i,
-                    parentSpanId: i > -1 ? 0 : -1,
-                  };
-                  !_.find(fixSpans, fixSpanKeyContent) && fixSpans.push(
-                    {
-                      ...fixSpanKeyContent, refs: [], endpointName: `VNode: 
${ref.parentSegmentId}`, serviceCode: 'VirtualNode', type: `[Broken] 
${ref.type}`, peer: '', component: `VirtualNode: #${i}`, isError: true, 
isBroken: true, layer: 'Broken', tags: [], logs: [],
-                    },
-                  );
-                  // if root broken node is not exist, create a root broken 
node.
-                  if (fixSpanKeyContent.parentSpanId > -1) {
-                    const fixRootSpanKeyContent = {
-                      traceId: ref.traceId,
-                      segmentId: ref.parentSegmentId,
-                      spanId: 0,
-                      parentSpanId: -1,
-                    };
-                    !_.find(fixSpans, fixRootSpanKeyContent) && fixSpans.push(
-                      {
-                        ...fixRootSpanKeyContent,
-                        refs: [],
-                        endpointName: `VNode: ${ref.parentSegmentId}`,
-                        serviceCode: 'VirtualNode',
-                        type: `[Broken] ${ref.type}`,
-                        peer: '',
-                        component: `VirtualNode: #0`,
-                        isError: true,
-                        isBroken: true,
-                        layer: 'Broken',
-                        tags: [],
-                        logs: [],
-                      },
-                    );
-                  }
-                }
-              });
-            }
-          });
-          [...fixSpans, ...this.data].forEach(i => {
-            i.label=i.endpointName || 'no operation name';
-            i.children = [];
-            if(segmentGroup[i.segmentId] === undefined){
-              segmentIdGroup.push(i.segmentId);
-              segmentGroup[i.segmentId] = [];
-              segmentGroup[i.segmentId].push(i);
-            }else{
-              segmentGroup[i.segmentId].push(i);
-            }
-          });
-          segmentIdGroup.forEach(id => {
-            let currentSegment = segmentGroup[id].sort((a,b) => 
b.parentSpanId-a.parentSpanId);
-            currentSegment.forEach(s =>{
-              let index = currentSegment.findIndex(i => i.spanId === 
s.parentSpanId);
-              if (index !== -1) {
-                if ((currentSegment[index].isBroken && 
currentSegment[index].parentSpanId === -1) || !currentSegment[index].isBroken) {
-                  currentSegment[index].children.push(s);
-                  currentSegment[index].children.sort((a, b) => a.spanId - 
b.spanId);
-                }
-              }
-              if (s.isBroken) {
-                const children = _.filter(this.data, (span) => {
-                  return _.find(span.refs, {traceId: s.traceId, 
parentSegmentId: s.segmentId, parentSpanId: s.spanId});
-                });
-                children.length > 0 && s.children.push(...children);
-              }
-            })
-            segmentGroup[id] = currentSegment[currentSegment.length-1]
-          })
-          segmentIdGroup.forEach(id => {
-            segmentGroup[id].refs && segmentGroup[id].refs.forEach(ref => {
-              if(ref.traceId === this.traceId) {
-                
this.traverseTree(segmentGroup[ref.parentSegmentId],ref.parentSpanId,ref.parentSegmentId,segmentGroup[id])
-              };
-            })
-            // if(segmentGroup[id].refs.length !==0 ) delete segmentGroup[id];
-          })
-        for (const i in segmentGroup) {
-          if (segmentGroup[i].refs && segmentGroup[i].refs.length === 0 || 
!segmentGroup[i].refs) {
-            this.segmentId.push(segmentGroup[i]);
-          }
-        }
-        this.segmentId.forEach((_, i) => {
-          this.collapse(this.segmentId[i]);
-        });
-        return this.segmentId;
-      },
-      collapse(d) {
-        if (d.children) {
-          let dur = d.endTime - d.startTime;
-          d.children.forEach((i) => {
-            dur -= (i.endTime - i.startTime);
-          });
-          d.dur = dur < 0 ? 0 : dur;
-          d.children.forEach((i) => this.collapse(i));
-        }
-      },
       handleSelectSpan(data) {
         this.currentSpan = data;
         if (!this.showBtnDetail) {
@@ -271,7 +122,7 @@ limitations under the License. -->
       this.loading = true;
     },
     mounted() {
-      this.tableData = this.formatData(this.changeTree());
+      this.tableData = 
this.formatData(TraceUtil.changeTree(this.data,this.traceId));
       this.loading = false;
       this.$eventBus.$on('HANDLE-SELECT-SPAN', this, this.handleSelectSpan);
       this.$eventBus.$on('HANDLE-VIEW-SPAN', this, this.handleViewSpan);
diff --git a/src/views/components/common/trace-detail-statistics-table.vue 
b/src/views/components/common/trace-detail-statistics-table.vue
new file mode 100644
index 0000000..797d681
--- /dev/null
+++ b/src/views/components/common/trace-detail-statistics-table.vue
@@ -0,0 +1,130 @@
+<!-- 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="trace-detail-chart-table">
+    <div class="rk-trace-t-loading" v-show="loading">
+      <svg class="icon loading">
+        <use xlink:href="#spinner"></use>
+      </svg>
+    </div>
+    <TraceContainer :tableData="tableData" :type="HeaderType">
+      <div class="trace-tips" v-if="!tableData.length">{{ $t('noData') }}</div>
+    </TraceContainer>
+  </div>
+</template>
+<style lang="scss">
+  .rk-tooltip-popper.trace-table-tooltip .rk-tooltip-inner {
+    max-width: 600px;
+  }
+  .trace-detail-chart-table {
+    position: relative;
+    min-height: 300px;
+    border-bottom: 1px solid #ccc;
+  }
+</style>
+
+<script lang="ts">
+  import { Vue, Component, Prop, Watch } from 'vue-property-decorator';
+  import { Span, StatisticsSpan, StatisticsGroupRef } from '@/types/trace';
+  import TraceContainer from './trace-chart-table/trace-container.vue';
+  import TraceUtil from '../trace/trace-util';
+
+  @Component({
+    components: {
+      TraceContainer,
+    },
+  })
+
+  export default class TraceDetailStatisticsTable extends Vue {
+
+    @Prop() public data!: Span[];
+    @Prop() public traceId!: string;
+    @Prop() public showBtnDetail!: boolean;
+    @Prop() public HeaderType!: string;
+
+    public tableData: StatisticsSpan[] = [];
+    public diaplay: boolean = true;
+    public list: any[] = [];
+    public loading: boolean = true;
+
+    @Watch('data')
+    public onDataChanged(val: any, oldVal: any) {
+      if ( !this.data.length ) {
+        this.tableData = [];
+        return;
+      }
+      this.tableData = this.calculationDataforStatistics(this.data);
+      this.loading = false;
+    }
+
+    private calculationDataforStatistics(data: Span[]): StatisticsSpan[] {
+      this.list = TraceUtil.buildTraceDataList(data);
+      const result: StatisticsSpan[] = [];
+      const map = TraceUtil.changeStatisticsTree(data, this.traceId);
+      map.forEach( (nodes, nodeKey) => {
+        const nodeKeyData = nodeKey.split(':');
+        result.push( this.getSpanGroupData( nodes, { endpointName: 
nodeKeyData[0], type: nodeKeyData[1] }));
+      });
+      return result;
+    }
+
+    private getSpanGroupData(groupspans: Span[], groupRef: 
StatisticsGroupRef): StatisticsSpan {
+      let maxTime = 0;
+      let minTime = 0;
+      let sumTime = 0;
+      const count = groupspans.length;
+      groupspans.forEach( (groupspan: Span) => {
+        const duration = groupspan.dur || 0;
+        if ( duration > maxTime) {
+          maxTime = duration;
+        }
+        if (duration < minTime) {
+          minTime = duration;
+        }
+        sumTime = sumTime + duration;
+      });
+      const avgTime = count === 0 ? 0 : (sumTime / count);
+      return {
+        groupRef,
+        maxTime,
+        minTime,
+        sumTime,
+        avgTime,
+        count,
+      };
+    }
+
+
+    private created(): void {
+      this.loading = true;
+    }
+
+    private mounted(): void {
+      this.tableData = this.calculationDataforStatistics(this.data);
+      this.loading = false;
+      this.$eventBus.$on('TRACE-TABLE-LOADING', this, () => { this.loading = 
true; });
+    }
+  }
+</script>
+<style>
+  .dialog-c-text {
+    white-space: pre;
+    overflow: auto;
+    font-family: monospace;
+  }
+  .trace-tips {
+    width: 100%;
+    text-align: center;
+    margin-top: 10px;
+  }
+</style>
\ No newline at end of file
diff --git a/src/views/components/trace/trace-detail.vue 
b/src/views/components/trace/trace-detail.vue
index 3318123..0f8015b 100644
--- a/src/views/components/trace/trace-detail.vue
+++ b/src/views/components/trace/trace-detail.vue
@@ -39,7 +39,10 @@ limitations under the License. -->
           <use xlink:href="#review-list"></use>
         </svg>
       </div>
-
+      <a class="rk-btn mr-5 sm r" :class="{ ghost: displayMode !== 
'statistics' }" @click="displayMode = 'statistics'">
+        <rk-icon icon="statistics-bulleted" />
+        {{ $t('statistics') }}</a
+      >
       <a class="rk-btn mr-5 sm r" :class="{ ghost: displayMode !== 'table' }" 
@click="displayMode = 'table'">
         <svg class="icon vm sm rk-trace-table_svg-icon">
           <use xlink:href="#table"></use>
@@ -82,6 +85,13 @@ limitations under the License. -->
       :traceId="current.traceIds[0]"
       :showBtnDetail="false"
     />
+    <TraceDetailStatisticsTable
+      v-if="displayMode == 'statistics' && current.endpointNames"
+      :data="spans"
+      :traceId="current.traceIds[0]"
+      :showBtnDetail="false"
+      HeaderType="statistics"
+    />
 
     <div v-if="!current.endpointNames" class="flex-h container">
       <svg class="icon rk-icon-trace">
@@ -95,7 +105,7 @@ limitations under the License. -->
   import { Vue, Component, Prop } from 'vue-property-decorator';
   import TraceDetailChartList from './trace-detail-chart-list.vue';
   import TraceDetailChartTree from './trace-detail-chart-tree.vue';
-  import { TraceDetailChartTable } from '../common';
+  import { TraceDetailChartTable, TraceDetailStatisticsTable } from 
'../common';
   import { Trace, Span } from '@/types/trace';
   import { Action, State } from 'vuex-class';
   import copy from '@/utils/copy';
@@ -107,9 +117,11 @@ limitations under the License. -->
       TraceDetailChartList,
       TraceDetailChartTree,
       TraceDetailChartTable,
+      TraceDetailStatisticsTable,
       LogTable,
     },
   })
+
   export default class TraceDetail extends Vue {
     @State('rocketTrace') private rocketTrace!: traceState;
     @Action('rocketTrace/GET_TRACE_SPANS') private GET_TRACE_SPANS: any;
diff --git a/src/views/components/trace/trace-table.vue 
b/src/views/components/trace/trace-table.vue
index 58be73b..ca4627b 100644
--- a/src/views/components/trace/trace-table.vue
+++ b/src/views/components/trace/trace-table.vue
@@ -4,9 +4,7 @@ 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.
@@ -65,7 +63,6 @@ limitations under the License. -->
   import { Component, Vue, Watch } from 'vue-property-decorator';
   import { Action, Mutation, State } from 'vuex-class';
   import { State as traceState } from '@/store/modules/trace/index';
-
   @Component
   export default class TraceTable extends Vue {
     @State('rocketTrace') private rocketTrace!: traceState;
@@ -200,4 +197,4 @@ limitations under the License. -->
     background-color: #40454e;
     color: #eee;
   }
-</style>
+</style>
\ No newline at end of file
diff --git a/src/views/components/trace/trace-util.ts 
b/src/views/components/trace/trace-util.ts
new file mode 100644
index 0000000..2f22cb5
--- /dev/null
+++ b/src/views/components/trace/trace-util.ts
@@ -0,0 +1,292 @@
+import { CalculationType } from './../dashboard/charts/constant';
+/**
+ * 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 { Ref, Span, StatisticsSpan, StatisticsGroupRef, TraceTreeRef } from 
'@/types/trace';
+import lodash from 'lodash';
+
+export default class TraceUtil {
+  public static buildTraceDataList(data: Span[]): string[] {
+    return Array.from(new Set(data.map((span: Span) => span.serviceCode)));
+  }
+
+  public static changeTree(data: Span[], cureentTraceId: string) {
+    const segmentIdList: Span[] = [];
+    const traceTreeRef = this.changeTreeCore(data, cureentTraceId);
+    traceTreeRef.segmentIdGroup.forEach( (segmentId: string) => {
+      if (traceTreeRef.segmentMap.get(segmentId)!.refs) {
+        traceTreeRef.segmentMap.get(segmentId)!.refs.forEach( (ref: Ref) => {
+          if (ref.traceId === cureentTraceId) {
+            this.traverseTree(traceTreeRef.segmentMap.get(ref.parentSegmentId) 
as Span,
+            ref.parentSpanId, ref.parentSegmentId, 
traceTreeRef.segmentMap.get(segmentId) as Span);
+          }
+        });
+      }
+    });
+    // set a breakpoint at this line
+    traceTreeRef.segmentMap.forEach((value , key) => {
+      if ( value!.refs.length === 0 ) {
+        segmentIdList.push(value as Span);
+      }
+    });
+    segmentIdList.forEach( (segmentId: Span) => {
+      this.collapse(segmentId);
+    });
+    return segmentIdList;
+  }
+
+  public static changeStatisticsTree(data: Span[], cureentTraceId: string): 
Map<string, Span[]> {
+    const result = new Map<string, Span[]>();
+    const traceTreeRef = this.changeTreeCore(data, cureentTraceId);
+    traceTreeRef.segmentMap.forEach((span, segmentId) => {
+      const groupRef = span.endpointName + ':' + span.type;
+      if ( span.children && span.children.length > 0 ) {
+        this.calculationChildren(span.children, result);
+        this.collapse(span);
+      }
+      if (result.get(groupRef) === undefined ) {
+        result.set(groupRef, []);
+        result.get(groupRef)!.push(span);
+      } else {
+        result.get(groupRef)!.push(span);
+      }
+    });
+    return result;
+  }
+
+  private static changeTreeCore(data: Span[], cureentTraceId: string): 
TraceTreeRef {
+    // set a breakpoint at this line
+    if (data.length === 0) {
+      return {
+        segmentMap: new Map(),
+        segmentIdGroup: [],
+      };
+    }
+    const tempMap: Map<string, Span[]> = new Map();
+    const segmentMap: Map<string, Span> = new Map();
+    const segmentIdGroup: string[] = [];
+    const fixSpans: Span[] = [];
+    const segmentHeaders: Span[] = [];
+    data.forEach((span) => {
+      if (span.parentSpanId === -1) {
+          segmentHeaders.push(span);
+      } else {
+        const index = data.findIndex( (patchSpan: Span) => {
+          return (patchSpan.segmentId === span.segmentId && patchSpan.spanId 
=== (span.spanId - 1));
+        });
+        const fixSpanKeyContent = {
+          traceId: span.traceId,
+          segmentId: span.segmentId,
+          spanId: span.spanId - 1,
+          parentSpanId: span.spanId - 2,
+        };
+        if (index === -1 &&  !lodash.find(fixSpans, fixSpanKeyContent) ) {
+          fixSpans.push(
+            {
+              ...fixSpanKeyContent,
+              refs: [],
+              endpointName: `VNode: ${span.segmentId}`,
+              serviceCode: 'VirtualNode',
+              type: `[Broken] ${span.type}`,
+              peer: '',
+              component: `VirtualNode: #${span.spanId - 1}`,
+              isError: true,
+              isBroken: true,
+              layer: 'Broken',
+              tags: [],
+              logs: [],
+              startTime: 0,
+              endTime: 0,
+            },
+          );
+        }
+      }
+    });
+    segmentHeaders.forEach((span) => {
+      if (span.refs && span.refs.length) {
+        span.refs.forEach((ref) => {
+          const index = data.findIndex(
+            (patchSpan: Span) => {
+              return (ref.parentSegmentId === patchSpan.segmentId && 
ref.parentSpanId === patchSpan.spanId);
+          });
+          if (index === -1) {
+            // create a known broken node.
+            const parentSpanId: number = ref.parentSpanId;
+            const fixSpanKeyContent = {
+              traceId: ref.traceId,
+              segmentId: ref.parentSegmentId,
+              spanId: parentSpanId,
+              parentSpanId: parentSpanId > -1 ? 0 : -1,
+            };
+            if (lodash.find(fixSpans, fixSpanKeyContent)) {
+              fixSpans.push({
+                ...fixSpanKeyContent,
+                refs: [],
+                endpointName: `VNode: ${ref.parentSegmentId}`,
+                serviceCode: 'VirtualNode',
+                type: `[Broken] ${ref.type}`,
+                peer: '',
+                component: `VirtualNode: #${parentSpanId}`,
+                isError: true,
+                isBroken: true,
+                layer: 'Broken',
+                tags: [],
+                logs: [],
+                startTime: 0,
+                endTime: 0,
+              });
+            }
+            // if root broken node is not exist, create a root broken node.
+            if (fixSpanKeyContent.parentSpanId > -1) {
+              const fixRootSpanKeyContent = {
+                traceId: ref.traceId,
+                segmentId: ref.parentSegmentId,
+                spanId: 0,
+                parentSpanId: -1,
+              };
+              if (lodash.find(fixSpans, fixRootSpanKeyContent)) {
+                fixSpans.push(
+                  {
+                    ...fixRootSpanKeyContent,
+                    refs: [],
+                    endpointName: `VNode: ${ref.parentSegmentId}`,
+                    serviceCode: 'VirtualNode',
+                    type: `[Broken] ${ref.type}`,
+                    peer: '',
+                    component: `VirtualNode: #0`,
+                    isError: true,
+                    isBroken: true,
+                    layer: 'Broken',
+                    tags: [],
+                    logs: [],
+                    startTime: 0,
+                    endTime: 0,
+                  });
+              }
+            }
+          }
+        });
+      }
+    });
+    [...fixSpans, ...data].forEach( (fixSpan: Span) => {
+      fixSpan.label = fixSpan.endpointName || 'no operation name';
+      fixSpan.children = [];
+      if (tempMap.get(fixSpan.segmentId) === undefined ) {
+        segmentIdGroup.push(fixSpan.segmentId);
+        tempMap.set(fixSpan.segmentId, []);
+        tempMap.get(fixSpan.segmentId)!.push(fixSpan);
+      } else {
+        tempMap.get(fixSpan.segmentId)!.push(fixSpan);
+      }
+    });
+    segmentIdGroup.forEach( (segmentId: string) => {
+      const currentSegmentSet = tempMap.get(segmentId)!.sort((a, b) => 
b.parentSpanId - a.parentSpanId);
+      currentSegmentSet.forEach( ( curSegment: Span ) => {
+        const index = currentSegmentSet.findIndex(
+          (curSegment2: Span) => curSegment2.spanId === 
curSegment.parentSpanId );
+        if (index !== -1) {
+          if ((currentSegmentSet[index].isBroken && 
currentSegmentSet[index].parentSpanId === -1)
+            || !currentSegmentSet[index].isBroken) {
+              currentSegmentSet[index].children!.push(curSegment);
+              currentSegmentSet[index].children!.sort((a, b) => a.spanId - 
b.spanId);
+          }
+        }
+        if (curSegment.isBroken) {
+          const children = lodash.filter(data, (span: Span) => {
+            return lodash.find(span.refs,
+              {traceId: curSegment.traceId, parentSegmentId: 
curSegment.segmentId, parentSpanId: curSegment.spanId});
+          }) as Span[];
+          curSegment.children!.concat(children);
+        }
+      });
+      segmentMap.set(segmentId, currentSegmentSet[currentSegmentSet.length - 
1]);
+    });
+    return {
+      segmentMap,
+      segmentIdGroup,
+    };
+  }
+
+  private static collapse(span: Span) {
+    if (span.children) {
+      let dur = span.endTime - span.startTime;
+      span.children.forEach((chlid: Span) => {
+        dur -= (chlid.endTime - chlid.startTime);
+      });
+      span.dur = dur < 0 ? 0 : dur;
+      span.children.forEach((chlid) => this.collapse(chlid));
+    }
+  }
+
+  private static traverseTree(node: Span, spanId: number, segmentId: string, 
childNode: Span) {
+    if (!node || node.isBroken) {
+      return;
+    }
+    if (node.spanId === spanId && node.segmentId === segmentId) {
+      node.children!.push(childNode);
+      return;
+    }
+    if (node.children && node.children.length > 0) {
+      for (const grandchild of node.children) {
+        this.traverseTree(grandchild, spanId, segmentId, childNode);
+      }
+    }
+  }
+
+  private static getSpanGroupData(groupspans: Span[], groupRef: 
StatisticsGroupRef): StatisticsSpan {
+    let maxTime = 0;
+    let minTime = 0;
+    let sumTime = 0;
+    const count = groupspans.length;
+    groupspans.forEach( (groupspan: Span) => {
+      const duration = groupspan.dur || 0;
+      if ( duration > maxTime) {
+        maxTime = duration;
+      }
+      if (duration < minTime) {
+        minTime = duration;
+      }
+      sumTime = sumTime + duration;
+    });
+    const avgTime = count === 0 ? 0 : (sumTime / count);
+    return {
+      groupRef,
+      maxTime,
+      minTime,
+      sumTime,
+      avgTime,
+      count,
+    };
+  }
+
+  private static calculationChildren(nodes: Span[], result: Map<string, 
Span[]>): void  {
+    nodes.forEach( (node: Span) => {
+      const groupRef = node.endpointName + ':' + node.type;
+      if ( node.children && node.children.length > 0 ) {
+        this.calculationChildren(node.children, result);
+      }
+      if (result.get(groupRef) === undefined ) {
+        result.set(groupRef, []);
+        result.get(groupRef)!.push(node);
+      } else {
+        result.get(groupRef)!.push(node);
+      }
+    });
+  }
+}
+
+

Reply via email to