Skip to content

Commit

Permalink
feat: runtime metrics UI (#747)
Browse files Browse the repository at this point in the history
* feat: runtime metrics

* feat: add mock data

* fix: chart distance spacing breaking

* chore: use traversal logic to get operation id from nodes

* chore: minor fixes

Signed-off-by: Soham <[email protected]>

* fix: phase color

* chore: threshold on milliseconds

* chore: add borders

* feat: runtime metrics

* chore: minor fixes

* chore: remove tooltip datasets, sanitize tooltip design

* Added parsing function for span data

Signed-off-by: Jason Porter <[email protected]>

* chore: minor fixes

* chore: minor fixes

* chore: minor fixes

* chore: minor fixes

* fix: width

* chore: remove highlight, fix fonts

* fixed parsing algo to include node operations

Signed-off-by: Jason Porter <[email protected]>

* chore: support ms

* fix: formatting

* chore: cleanup

* Minor fixes

Signed-off-by: Jason Porter <[email protected]>

* Fixed formatting issue with displayed duration

Signed-off-by: Jason Porter <[email protected]>

* chore: fix yarn.lock

Signed-off-by: Carina Ursu <[email protected]>

* chore: up package version

Signed-off-by: Carina Ursu <[email protected]>

* fix: add space

Signed-off-by: Soham <[email protected]>

---------

Signed-off-by: Soham <[email protected]>
Signed-off-by: Jason Porter <[email protected]>
Signed-off-by: Carina Ursu <[email protected]>
Co-authored-by: Jason Porter <[email protected]>
Co-authored-by: Carina Ursu <[email protected]>
  • Loading branch information
3 people authored May 10, 2023
1 parent 5caa0ab commit 29babaa
Show file tree
Hide file tree
Showing 13 changed files with 357 additions and 19 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"react-dom": "^16.13.1",
"serve-static": "^1.12.3",
"source-map-loader": "^4.0.1",
"traverse": "^0.6.7",
"ts-jest": "^26.3.0",
"ts-loader": "^9.2.6",
"ts-node": "^8.0.2",
Expand All @@ -126,7 +127,8 @@
"@storybook/builder-webpack5": "^6.4.19",
"@storybook/manager-webpack5": "^6.4.19",
"@storybook/react": "^6.4.19",
"@storybook/testing-library": "^0.0.9"
"@storybook/testing-library": "^0.0.9",
"@types/traverse": "^0.6.32"
},
"resolutions": {
"@babel/cli": "~7.16.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/console/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@flyteorg/console",
"version": "0.0.28",
"version": "0.0.29",
"description": "Flyteconsole main app module",
"main": "./dist/index.js",
"module": "./lib/index.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import React, { createRef, useEffect, useRef, useState } from 'react';
import React, {
createRef,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import { makeStyles, Typography } from '@material-ui/core';
import { tableHeaderColor } from 'components/Theme/constants';
import { timestampToDate } from 'common/utils';
Expand All @@ -10,13 +16,19 @@ import {
import { useQueryClient } from 'react-query';
import { eq, merge } from 'lodash';
import { useNodeExecutionsById } from 'components/Executions/contextProvider/NodeExecutionDetails';
import { ExecutionContext } from 'components/Executions/contexts';
import { useExecutionMetrics } from 'components/Executions/useExecutionMetrics';
import { convertToPlainNodes } from './helpers';
import { ChartHeader } from './ChartHeader';
import { useScaleContext } from './scaleContext';
import { TaskNames } from './TaskNames';
import { getChartDurationData } from './TimelineChart/chartData';
import { TimelineChart } from './TimelineChart';
import t from '../strings';
import {
getExecutionMetricsOperationIds,
parseSpanData,
} from './TimelineChart/utils';

interface StyleProps {
chartWidth: number;
Expand Down Expand Up @@ -91,6 +103,8 @@ export const ExecutionTimeline: React.FC<ExProps> = ({
const { nodeExecutionsById, setCurrentNodeExecutionsById } =
useNodeExecutionsById();
const { chartInterval: chartTimeInterval } = useScaleContext();
const { execution } = useContext(ExecutionContext);
const executionMetricsData = useExecutionMetrics(execution.id, 10);

useEffect(() => {
setOriginalNodes(ogn => {
Expand Down Expand Up @@ -178,6 +192,12 @@ export const ExecutionTimeline: React.FC<ExProps> = ({
setOriginalNodes([...originalNodes]);
};

const operationIds = getExecutionMetricsOperationIds(
executionMetricsData.value,
);

const parsedExecutionMetricsData = parseSpanData(executionMetricsData.value);

return (
<>
<div className={styles.taskNames}>
Expand Down Expand Up @@ -213,6 +233,9 @@ export const ExecutionTimeline: React.FC<ExProps> = ({
<TimelineChart
items={barItemsData}
chartTimeIntervalSec={chartTimeInterval}
operationIds={operationIds}
parsedExecutionMetricsData={parsedExecutionMetricsData}
nodes={showNodes}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Chart as ChartJS, registerables, Tooltip } from 'chart.js';
import ChartDataLabels from 'chartjs-plugin-datalabels';
import { isEqual, isNil } from 'lodash';

ChartJS.register(...registerables, ChartDataLabels);

Expand All @@ -11,6 +12,9 @@ Tooltip.positioners.cursor = function (_chartElements, coordinates) {
export const getBarOptions = (
chartTimeIntervalSec: number,
tooltipLabels: string[][],
chartRef: React.MutableRefObject<any>,
tooltip: any,
setTooltip: any,
) => {
return {
animation: false as const,
Expand All @@ -31,17 +35,56 @@ export const getBarOptions = (
},
tooltip: {
// Setting up tooltip: https://www.chartjs.org/docs/latest/configuration/tooltip.html
enabled: false,
position: 'cursor',
filter: function (tooltipItem) {
// no tooltip for offsets
return tooltipItem.datasetIndex === 1;
return tooltipItem.datasetIndex !== 0;
},
callbacks: {
label: function (context) {
const index = context.dataIndex;
return tooltipLabels[index] ?? '';

return tooltipLabels ? [`${tooltipLabels[index]}`] : '';
},
labelColor: function () {
return {
fontColor: 'white',
};
},
},
external: context => {
const tooltipModel = context.tooltip;

if (!chartRef || !chartRef.current) {
return;
}

if (tooltipModel.opacity === 0) {
if (tooltip.opacity !== 0)
setTooltip(prev => ({ ...prev, opacity: 0 }));
return;
}

const position = context.chart.canvas.getBoundingClientRect();

const dataIndex = tooltipModel.dataPoints[0]?.dataIndex;

if (isNil(dataIndex)) {
return;
}

const newTooltipData = {
opacity: 1,
left: position.left + tooltipModel.caretX,
top: position.top + tooltipModel.caretY,
dataIndex: dataIndex,
};

if (!isEqual(tooltip, newTooltipData)) {
setTooltip(newTooltipData);
}
},
},
},
scales: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,117 @@
import * as React from 'react';
import { Bar } from 'react-chartjs-2';
import { dNode } from 'models/Graph/types';
import { Box, Theme, makeStyles } from '@material-ui/core';

import { NodeExecutionPhase } from 'models';
import { getNodeExecutionPhaseConstants } from 'components/Executions/utils';
import {
BarItemData,
formatSecondsToHmsFormat,
generateChartData,
getChartData,
getDuration,
parseSpanData,
} from './utils';
import { getBarOptions } from './barOptions';
import { BarItemData, generateChartData, getChartData } from './utils';

interface TimelineChartProps {
items: BarItemData[];
nodes: dNode[];
chartTimeIntervalSec: number;
operationIds: string[];
parsedExecutionMetricsData: ReturnType<typeof parseSpanData>;
}

interface StyleProps {
opacity: number;
top: number;
left: number;
phaseColor: string;
}

const useStyles = makeStyles<Theme, StyleProps>(theme => ({
tooltipContainer: {
position: 'absolute',
background: theme.palette.grey[100],
color: theme.palette.common.black,
padding: theme.spacing(2),
borderRadius: 8,
width: 'fit-content',
maxContent: 'fit-content',
top: ({ top }) => top + 10,
left: ({ left }) => left + 10,
display: ({ opacity }) => (opacity ? 'block' : 'none'),
},
phaseText: {
width: 'fit-content',
marginBlockEnd: theme.spacing(1),
},
tooltipText: {
minWidth: '50px',
},
tooltipTextContainer: {
display: 'flex',
gap: 1,
color: theme.palette.grey[700],
},
operationIdContainer: {
textAlign: 'left',
flex: 1,
},
}));

export const TimelineChart = (props: TimelineChartProps) => {
const [tooltip, setTooltip] = React.useState({
opacity: 0,
top: 0,
left: 0,
dataIndex: -1,
});
const chartRef = React.useRef<any>(null);
const phaseData = generateChartData(props.items);

const options = getBarOptions(
props.chartTimeIntervalSec,
phaseData.tooltipLabel,
chartRef,
tooltip,
setTooltip,
) as any;

const data = getChartData(phaseData);
const node = props.nodes[tooltip.dataIndex];
const phase = node?.execution?.closure.phase ?? NodeExecutionPhase.UNDEFINED;
const phaseConstant = getNodeExecutionPhaseConstants(phase);
const spans = (node && props.parsedExecutionMetricsData[node.scopedId]) || [];

const styles = useStyles({
opacity: tooltip.opacity,
top: tooltip.top,
left: tooltip.left,
phaseColor: phaseConstant.badgeColor,
});

return (
<Bar
options={
getBarOptions(props.chartTimeIntervalSec, phaseData.tooltipLabel) as any
}
data={getChartData(phaseData)}
/>
<>
<Bar options={options} data={data} ref={chartRef} />
<Box className={styles.tooltipContainer}>
{phase && <Box className={styles.phaseText}>{phaseConstant.text}</Box>}
{spans?.map(span => (
<Box className={styles.tooltipTextContainer}>
<Box className={styles.tooltipText}>
{formatSecondsToHmsFormat(
Math.round(
(getDuration(span.startTime, span.endTime) / 1000) * 100,
) / 100,
)}
</Box>
<Box className={styles.operationIdContainer}>
{span.operationId}
</Box>
</Box>
))}
</Box>
</>
);
};
Loading

0 comments on commit 29babaa

Please sign in to comment.