Skip to content

Commit

Permalink
Normalize x-axis across TopN subcharts (#81)
Browse files Browse the repository at this point in the history
* Normalize TopN charts

* Use correct stack accessor for bar chart

* Simplify sample ordering

* Rename variables for clarity

* Refactor sample normalization
  • Loading branch information
jbcrail authored and rockdaboot committed Jul 4, 2022
1 parent 2ed01f8 commit a45cd08
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 74 deletions.
37 changes: 1 addition & 36 deletions src/plugins/profiling/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,41 +23,6 @@ export function getRoutePaths() {
};
}

function toMilliseconds(seconds: string): number {
return parseInt(seconds, 10) * 1000;
}

export function getTopN(obj: any) {
const data = [];

if (obj.TopN!) {
for (const x in obj.TopN) {
if (obj.TopN.hasOwnProperty(x)) {
const values = obj.TopN[x];
for (let i = 0; i < values.length; i++) {
const v = values[i];
data.push({ x: toMilliseconds(x), y: v.Count, g: v.Value });
}
}
}
}

return data;
}

export function groupSamplesByCategory(samples: any) {
const series = new Map();
for (let i = 0; i < samples.length; i++) {
const v = samples[i];
if (!series.has(v.g)) {
series.set(v.g, []);
}
const value = series.get(v.g);
value.push([v.x, v.y]);
}
return series;
}

export function timeRangeFromRequest(request: any): [number, number] {
const timeFrom = parseInt(request.query.timeFrom!, 10);
const timeTo = parseInt(request.query.timeTo!, 10);
Expand All @@ -67,7 +32,7 @@ export function timeRangeFromRequest(request: any): [number, number] {
// Converts from a Map object to a Record object since Map objects are not
// serializable to JSON by default
export function fromMapToRecord<K extends string, V>(m: Map<K, V>): Record<string, V> {
let output: Record<string, V> = {};
const output: Record<string, V> = {};

for (const [key, value] of m) {
output[key] = value;
Expand Down
78 changes: 50 additions & 28 deletions src/plugins/profiling/common/topn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,71 @@
* Side Public License, v 1.
*/

import { orderBy } from 'lodash';

import {
AggregationsHistogramAggregate,
AggregationsHistogramBucket,
} from '@elastic/elasticsearch/lib/api/types';

import { StackFrameMetadata } from './profiling';

type TopNBucket = {
Value: string;
export interface TopNSample {
Timestamp: number;
Count: number;
};

type TopNBucketsByDate = {
TopN: Record<number, TopNBucket[]>;
};
Category: string;
}

type TopNContainers = TopNBucketsByDate;
type TopNDeployments = TopNBucketsByDate;
type TopNHosts = TopNBucketsByDate;
type TopNThreads = TopNBucketsByDate;
export interface TopNSamples {
TopN: TopNSample[];
}

type TopNTraces = TopNBucketsByDate & {
interface TopNTraces extends TopNSamples {
Metadata: Record<string, StackFrameMetadata[]>;
};

type TopN = TopNContainers | TopNDeployments | TopNHosts | TopNThreads | TopNTraces;
}

export function createTopNBucketsByDate(
histogram: AggregationsHistogramAggregate
): TopNBucketsByDate {
const topNBucketsByDate: Record<number, TopNBucket[]> = {};
export function createTopNSamples(histogram: AggregationsHistogramAggregate): TopNSample[] {
const bucketsByTimestamp = new Map();
const uniqueCategories = new Set<string>();

// Convert the histogram into nested maps and record the unique categories
const histogramBuckets = (histogram?.buckets as AggregationsHistogramBucket[]) ?? [];
for (let i = 0; i < histogramBuckets.length; i++) {
const key = histogramBuckets[i].key / 1000;
topNBucketsByDate[key] = [];
histogramBuckets[i].group_by.buckets.forEach((item: any) => {
topNBucketsByDate[key].push({
Value: item.key,
Count: item.count.value,
});
});
const frameCountsByCategory = new Map();
const items = histogramBuckets[i].group_by.buckets;
for (let j = 0; j < items.length; j++) {
uniqueCategories.add(items[j].key);
frameCountsByCategory.set(items[j].key, items[j].count.value);
}
bucketsByTimestamp.set(histogramBuckets[i].key, frameCountsByCategory);
}

return { TopN: topNBucketsByDate };
// Normalize samples so there are an equal number of data points per each timestamp
const samples: TopNSample[] = [];
for (const timestamp of bucketsByTimestamp.keys()) {
for (const category of uniqueCategories.values()) {
const frameCountsByCategory = bucketsByTimestamp.get(timestamp);
const sample: TopNSample = {
Timestamp: timestamp,
Count: frameCountsByCategory.get(category) ?? 0,
Category: category,
};
samples.push(sample);
}
}

return orderBy(samples, ['Timestamp', 'Count', 'Category'], ['asc', 'desc', 'asc']);
}

export function groupSamplesByCategory(samples: TopNSample[]) {
const series = new Map();
for (let i = 0; i < samples.length; i++) {
const v = samples[i];
if (!series.has(v.Category)) {
series.set(v.Category, []);
}
const value = series.get(v.Category);
value.push([v.Timestamp, v.Count]);
}
return series;
}
9 changes: 8 additions & 1 deletion src/plugins/profiling/public/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,14 @@ function App({ fetchTopN, fetchElasticFlamechart }: Props) {
fetchTopN={fetchTopN}
setTopN={setTopN}
/>
<StackedBarChart id="topn" name="topn" height={400} x="x" y="y" category="g" />
<StackedBarChart
id="topn"
name="topn"
height={400}
x="Timestamp"
y="Count"
category="Category"
/>
<ChartGrid maximum={10} />
</TopNContext.Provider>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export const StackedBarChart: React.FC<StackedBarChartProps> = ({
data={ctx.samples}
xAccessor={x}
yAccessors={[y]}
stackAccessors={[x]}
splitSeriesAccessors={[category]}
stackAccessors={[category]}
/>
<Axis id="bottom-axis" position="bottom" tickFormat={timeFormatter('YYYY-MM-DD HH:mm:ss')} />
<Axis id="left-axis" position="left" showGridLines tickFormat={(d) => Number(d).toFixed(0)} />
Expand Down
9 changes: 5 additions & 4 deletions src/plugins/profiling/public/components/stacktrace-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import React, { useEffect, useState } from 'react';

import { EuiButtonGroup } from '@elastic/eui';

import { getTopN, groupSamplesByCategory } from '../../common';
import { groupSamplesByCategory, TopNSample, TopNSamples } from '../../common/topn';

export const StackTraceNavigation = ({ index, projectID, n, timeRange, fetchTopN, setTopN }) => {
const topnButtonGroupPrefix = 'topnButtonGroup';
Expand Down Expand Up @@ -59,11 +59,12 @@ export const StackTraceNavigation = ({ index, projectID, n, timeRange, fetchTopN

console.log(new Date().toISOString(), 'started payload retrieval');
fetchTopN(topnValue[0].value, index, projectID, timeRange.unixStart, timeRange.unixEnd, n).then(
(response) => {
(response: TopNSamples) => {
console.log(new Date().toISOString(), 'finished payload retrieval');
const samples = getTopN(response);
const samples = response.TopN;
const series = groupSamplesByCategory(samples);
setTopN({ samples, series });
const samplesWithoutZero = samples.filter((sample: TopNSample) => sample.Count > 0);
setTopN({ samples: samplesWithoutZero, series });
console.log(new Date().toISOString(), 'updated local state');
}
);
Expand Down
8 changes: 4 additions & 4 deletions src/plugins/profiling/server/routes/topn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
import type { DataRequestHandlerContext } from '../../../data/server';
import { fromMapToRecord, getRoutePaths } from '../../common';
import { groupStackFrameMetadataByStackTrace, StackTraceID } from '../../common/profiling';
import { createTopNBucketsByDate } from '../../common/topn';
import { createTopNSamples } from '../../common/topn';
import { findDownsampledIndex } from './downsampling';
import { logExecutionLatency } from './logger';
import { autoHistogramSumCountOnGroupByField, newProjectTimeQuery } from './query';
Expand Down Expand Up @@ -69,11 +69,11 @@ export async function topNElasticSearchQuery(
);

const histogram = getAggs(resEvents)?.histogram as AggregationsHistogramAggregate;
const topN = createTopNBucketsByDate(histogram);
const topN = createTopNSamples(histogram);

if (searchField !== 'StackTraceID') {
return response.ok({
body: topN,
body: { TopN: topN },
});
}

Expand Down Expand Up @@ -113,7 +113,7 @@ export async function topNElasticSearchQuery(
);
return response.ok({
body: {
...topN,
TopN: topN,
Metadata: metadata,
},
});
Expand Down

0 comments on commit a45cd08

Please sign in to comment.