From ac367f554a3e6f8bfa13438184ffb55d4ffe2f92 Mon Sep 17 00:00:00 2001 From: Joseph Crail Date: Thu, 30 Jun 2022 10:42:41 -0700 Subject: [PATCH] Display and sort by percentages in TopN subcharts (#82) * Switch to EuiSplitPanel for subcharts * Add percentages to subcharts * Simplify subchart ordering * Simplify calculating percentages per category --- src/plugins/profiling/common/topn.ts | 43 +++++++--- src/plugins/profiling/public/app.tsx | 2 +- .../profiling/public/components/bar-chart.tsx | 2 +- .../public/components/chart-grid.tsx | 79 ++++++++++--------- .../public/components/stacktrace-nav.tsx | 4 +- 5 files changed, 81 insertions(+), 49 deletions(-) diff --git a/src/plugins/profiling/common/topn.ts b/src/plugins/profiling/common/topn.ts index 3abc827bd4fa6..12c4454c6101f 100644 --- a/src/plugins/profiling/common/topn.ts +++ b/src/plugins/profiling/common/topn.ts @@ -15,9 +15,12 @@ import { import { StackFrameMetadata } from './profiling'; -export interface TopNSample { +export interface CountPerTime { Timestamp: number; Count: number; +} + +export interface TopNSample extends CountPerTime { Category: string; } @@ -62,15 +65,37 @@ export function createTopNSamples(histogram: AggregationsHistogramAggregate): To return orderBy(samples, ['Timestamp', 'Count', 'Category'], ['asc', 'desc', 'asc']); } -export function groupSamplesByCategory(samples: TopNSample[]) { - const series = new Map(); +export interface TopNSubchart { + Category: string; + Percentage: number; + Series: CountPerTime[]; +} + +export function groupSamplesByCategory(samples: TopNSample[]): TopNSubchart[] { + const seriesByCategory = new Map(); + let total = 0; + for (let i = 0; i < samples.length; i++) { - const v = samples[i]; - if (!series.has(v.Category)) { - series.set(v.Category, []); + const sample = samples[i]; + + if (!seriesByCategory.has(sample.Category)) { + seriesByCategory.set(sample.Category, []); } - const value = series.get(v.Category); - value.push([v.Timestamp, v.Count]); + const series = seriesByCategory.get(sample.Category)!; + series.push({ Timestamp: sample.Timestamp, Count: sample.Count }); + + total += sample.Count; } - return series; + + const subcharts: TopNSubchart[] = []; + for (const [category, series] of seriesByCategory) { + const totalPerCategory = series.reduce((sum, { Count }) => sum + Count, 0); + subcharts.push({ + Category: category, + Percentage: (totalPerCategory / total) * 100, + Series: series, + }); + } + + return orderBy(subcharts, ['Percentage', 'Category'], ['desc', 'asc']); } diff --git a/src/plugins/profiling/public/app.tsx b/src/plugins/profiling/public/app.tsx index 7111d07f84930..05e7e13d73464 100644 --- a/src/plugins/profiling/public/app.tsx +++ b/src/plugins/profiling/public/app.tsx @@ -103,7 +103,7 @@ function App({ fetchTopN, fetchElasticFlamechart }: Props) { const [topn, setTopN] = useState({ samples: [], - series: new Map(), + subcharts: [], }); const [elasticFlamegraph, setElasticFlamegraph] = useState({}); diff --git a/src/plugins/profiling/public/components/bar-chart.tsx b/src/plugins/profiling/public/components/bar-chart.tsx index ab70cff8e70c6..3dc7d0a9b01b7 100644 --- a/src/plugins/profiling/public/components/bar-chart.tsx +++ b/src/plugins/profiling/public/components/bar-chart.tsx @@ -29,7 +29,7 @@ export const BarChart: React.FC = ({ id, name, height, data, x, y return ( - + Number(d).toFixed(0)} /> diff --git a/src/plugins/profiling/public/components/chart-grid.tsx b/src/plugins/profiling/public/components/chart-grid.tsx index d2fb0064ff100..7f94191fcc1ba 100644 --- a/src/plugins/profiling/public/components/chart-grid.tsx +++ b/src/plugins/profiling/public/components/chart-grid.tsx @@ -9,59 +9,66 @@ import React, { useContext, useEffect } from 'react'; import { - EuiCard, EuiFlexGrid, + EuiFlexGroup, EuiFlexItem, EuiNotificationBadge, EuiSpacer, + EuiSplitPanel, EuiTitle, } from '@elastic/eui'; -import { TopNContext } from './contexts/topn'; import { BarChart } from './bar-chart'; +import { TopNContext } from './contexts/topn'; +import { TopNSubchart } from '../../common/topn'; export interface ChartGridProps { maximum: number; } -export const ChartGrid: React.FC = ({ maximum }) => { - const ctx = useContext(TopNContext); - const printSubCharts = (series: any) => { - let keys: string[] = Array.from(series.keys()); - const ncharts = Math.min(maximum, series.size); - keys = keys.slice(0, ncharts); +function printSubCharts(subcharts: TopNSubchart[], maximum: number) { + const ncharts = Math.min(maximum, subcharts.length); - const charts = []; - for (let i = 0; i < ncharts; i++) { - const subdata = ctx.series.get(keys[i]); - const uniqueID = `bar-chart-${i}`; + const charts = []; + for (let i = 0; i < ncharts; i++) { + const subchart = subcharts[i]; + const uniqueID = `bar-chart-${i}`; - const barchart = ( - - ); + const barchart = ( + + ); - const title = ( -
+ const title = ( + + {i + 1} -   - {keys[i]} -
- ); + + {subchart.Category} + {subchart.Percentage.toFixed(2)}% + + ); - const card = ( - {}} - title={title} - display="plain" - description={barchart} - /> - ); + const card = ( + + {title} + {barchart} + + ); - charts.push({card}); - } - return charts; - }; + charts.push({card}); + } + return charts; +} + +export const ChartGrid: React.FC = ({ maximum }) => { + const ctx = useContext(TopNContext); useEffect(() => { console.log(new Date().toISOString(), 'updated chart-grid'); @@ -71,11 +78,11 @@ export const ChartGrid: React.FC = ({ maximum }) => { <> -

Top {ctx.series.size}

+

Top {ctx.subcharts.length}

- {printSubCharts(ctx.series)} + {printSubCharts(ctx.subcharts, maximum)} ); diff --git a/src/plugins/profiling/public/components/stacktrace-nav.tsx b/src/plugins/profiling/public/components/stacktrace-nav.tsx index f62f8ca4c90c2..26c28a6fe7711 100644 --- a/src/plugins/profiling/public/components/stacktrace-nav.tsx +++ b/src/plugins/profiling/public/components/stacktrace-nav.tsx @@ -62,9 +62,9 @@ export const StackTraceNavigation = ({ index, projectID, n, timeRange, fetchTopN (response: TopNSamples) => { console.log(new Date().toISOString(), 'finished payload retrieval'); const samples = response.TopN; - const series = groupSamplesByCategory(samples); + const subcharts = groupSamplesByCategory(samples); const samplesWithoutZero = samples.filter((sample: TopNSample) => sample.Count > 0); - setTopN({ samples: samplesWithoutZero, series }); + setTopN({ samples: samplesWithoutZero, subcharts }); console.log(new Date().toISOString(), 'updated local state'); } );