Commit 08d8190c by Peter Holmberg Committed by GitHub

PanelInspector: Add Stats Tab (#22683)

* add tab

* add process measurement

* Fixed some design issues

* Align tabs margin

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
parent c7557429
...@@ -434,6 +434,10 @@ export interface DataQueryRequest<TQuery extends DataQuery = DataQuery> { ...@@ -434,6 +434,10 @@ export interface DataQueryRequest<TQuery extends DataQuery = DataQuery> {
endTime?: number; endTime?: number;
} }
export interface DataQueryTimings {
dataProcessingTime: number;
}
export interface QueryFix { export interface QueryFix {
type: string; type: string;
label: string; label: string;
......
import { ComponentClass, ComponentType } from 'react'; import { ComponentClass, ComponentType } from 'react';
import { DataQueryError, DataQueryRequest } from './datasource'; import { DataQueryError, DataQueryRequest, DataQueryTimings } from './datasource';
import { GrafanaPlugin, PluginMeta } from './plugin'; import { GrafanaPlugin, PluginMeta } from './plugin';
import { ScopedVars } from './ScopedVars'; import { ScopedVars } from './ScopedVars';
import { LoadingState } from './data'; import { LoadingState } from './data';
...@@ -19,6 +19,7 @@ export interface PanelData { ...@@ -19,6 +19,7 @@ export interface PanelData {
state: LoadingState; state: LoadingState;
series: DataFrame[]; series: DataFrame[];
request?: DataQueryRequest; request?: DataQueryRequest;
timings?: DataQueryTimings;
error?: DataQueryError; error?: DataQueryError;
// Contains the range from the request or a shifted time range if a request uses relative time // Contains the range from the request or a shifted time range if a request uses relative time
timeRange: TimeRange; timeRange: TimeRange;
......
...@@ -44,7 +44,7 @@ export const InspectHeader: FC<Props> = ({ ...@@ -44,7 +44,7 @@ export const InspectHeader: FC<Props> = ({
<h3>{panel.title}</h3> <h3>{panel.title}</h3>
<div>{formatStats(stats)}</div> <div>{formatStats(stats)}</div>
</div> </div>
<TabsBar> <TabsBar className={styles.tabsBar}>
{tabs.map((t, index) => { {tabs.map((t, index) => {
return ( return (
<Tab <Tab
...@@ -67,13 +67,15 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -67,13 +67,15 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
background-color: ${headerBackground}; background-color: ${headerBackground};
z-index: 1; z-index: 1;
flex-grow: 0; flex-grow: 0;
padding: ${theme.spacing.sm} ${theme.spacing.sm} 0 ${theme.spacing.lg};
`, `,
actions: css` actions: css`
display: flex; display: flex;
align-items: baseline; align-items: baseline;
justify-content: space-between; justify-content: space-between;
margin-bottom: ${theme.spacing.md}; margin: ${theme.spacing.md};
`,
tabsBar: css`
padding-left: ${theme.spacing.md};
`, `,
iconWrapper: css` iconWrapper: css`
cursor: pointer; cursor: pointer;
...@@ -88,6 +90,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -88,6 +90,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
`, `,
titleWrapper: css` titleWrapper: css`
margin-bottom: ${theme.spacing.lg}; margin-bottom: ${theme.spacing.lg};
padding: ${theme.spacing.sm} ${theme.spacing.sm} 0 ${theme.spacing.lg};
`, `,
}; };
}); });
......
...@@ -28,17 +28,18 @@ interface Props { ...@@ -28,17 +28,18 @@ interface Props {
export enum InspectTab { export enum InspectTab {
Data = 'data', Data = 'data',
Raw = 'raw', Request = 'request',
Issue = 'issue', Issue = 'issue',
Meta = 'meta', // When result metadata exists Meta = 'meta', // When result metadata exists
Error = 'error', Error = 'error',
Stats = 'stats',
} }
interface State { interface State {
// The last raw response // The last raw response
last: PanelData; last: PanelData;
// Data frem the last response // Data from the last response
data: DataFrame[]; data: DataFrame[];
// The selected data frame // The selected data frame
...@@ -50,7 +51,7 @@ interface State { ...@@ -50,7 +51,7 @@ interface State {
// If the datasource supports custom metadata // If the datasource supports custom metadata
metaDS?: DataSourceApi; metaDS?: DataSourceApi;
stats: { requestTime: number; queries: number; dataSources: number }; stats: { requestTime: number; queries: number; dataSources: number; processingTime: number };
drawerWidth: string; drawerWidth: string;
} }
...@@ -63,8 +64,8 @@ export class PanelInspector extends PureComponent<Props, State> { ...@@ -63,8 +64,8 @@ export class PanelInspector extends PureComponent<Props, State> {
data: [], data: [],
selected: 0, selected: 0,
tab: props.selectedTab || InspectTab.Data, tab: props.selectedTab || InspectTab.Data,
drawerWidth: '40%', drawerWidth: '50%',
stats: { requestTime: 0, queries: 0, dataSources: 0 }, stats: { requestTime: 0, queries: 0, dataSources: 0, processingTime: 0 },
}; };
} }
...@@ -90,6 +91,7 @@ export class PanelInspector extends PureComponent<Props, State> { ...@@ -90,6 +91,7 @@ export class PanelInspector extends PureComponent<Props, State> {
const targets = lastResult.request?.targets || []; const targets = lastResult.request?.targets || [];
const requestTime = lastResult.request?.endTime ? lastResult.request?.endTime - lastResult.request.startTime : -1; const requestTime = lastResult.request?.endTime ? lastResult.request?.endTime - lastResult.request.startTime : -1;
const dataSources = new Set(targets.map(t => t.datasource)).size; const dataSources = new Set(targets.map(t => t.datasource)).size;
const processingTime = lastResult.timings?.dataProcessingTime || -1;
// Find the first DataSource wanting to show custom metadata // Find the first DataSource wanting to show custom metadata
if (data && targets.length) { if (data && targets.length) {
...@@ -123,6 +125,7 @@ export class PanelInspector extends PureComponent<Props, State> { ...@@ -123,6 +125,7 @@ export class PanelInspector extends PureComponent<Props, State> {
requestTime, requestTime,
queries: targets.length, queries: targets.length,
dataSources, dataSources,
processingTime,
}, },
})); }));
} }
...@@ -163,11 +166,7 @@ export class PanelInspector extends PureComponent<Props, State> { ...@@ -163,11 +166,7 @@ export class PanelInspector extends PureComponent<Props, State> {
if (!metaDS || !metaDS.components?.MetadataInspector) { if (!metaDS || !metaDS.components?.MetadataInspector) {
return <div>No Metadata Inspector</div>; return <div>No Metadata Inspector</div>;
} }
return ( return <metaDS.components.MetadataInspector datasource={metaDS} data={data} />;
<CustomScrollbar>
<metaDS.components.MetadataInspector datasource={metaDS} data={data} />
</CustomScrollbar>
);
} }
renderDataTab() { renderDataTab() {
...@@ -232,32 +231,44 @@ export class PanelInspector extends PureComponent<Props, State> { ...@@ -232,32 +231,44 @@ export class PanelInspector extends PureComponent<Props, State> {
); );
} }
renderIssueTab() {
return <CustomScrollbar>TODO: show issue form</CustomScrollbar>;
}
renderErrorTab(error?: DataQueryError) { renderErrorTab(error?: DataQueryError) {
if (!error) { if (!error) {
return null; return null;
} }
if (error.data) { if (error.data) {
return ( return (
<CustomScrollbar> <>
<h3>{error.data.message}</h3> <h3>{error.data.message}</h3>
<pre> <JSONFormatter json={error} open={2} />
<code>{error.data.error}</code> </>
</pre>
</CustomScrollbar>
); );
} }
return <div>{error.message}</div>; return <div>{error.message}</div>;
} }
renderRawJsonTab(last: PanelData) { renderRequestTab() {
return <JSONFormatter json={this.state.last} open={3} />;
}
renderStatsTab() {
const { stats } = this.state;
return ( return (
<CustomScrollbar> <table className="filter-table width-30">
<JSONFormatter json={last} open={2} /> <tbody>
</CustomScrollbar> <tr>
<td>Query time</td>
<td>{`${stats.requestTime === -1 ? 'N/A' : stats.requestTime + 'ms'}`}</td>
</tr>
<tr>
<td>Data processing time</td>
<td>{`${
stats.processingTime === -1
? 'N/A'
: Math.round((stats.processingTime + Number.EPSILON) * 100) / 100 + 'ms'
}`}</td>
</tr>
</tbody>
</table>
); );
} }
...@@ -270,6 +281,9 @@ export class PanelInspector extends PureComponent<Props, State> { ...@@ -270,6 +281,9 @@ export class PanelInspector extends PureComponent<Props, State> {
tabs.push({ label: 'Data', value: InspectTab.Data }); tabs.push({ label: 'Data', value: InspectTab.Data });
} }
tabs.push({ label: 'Stats', value: InspectTab.Stats });
tabs.push({ label: 'Request', value: InspectTab.Request });
if (this.state.metaDS) { if (this.state.metaDS) {
tabs.push({ label: 'Meta Data', value: InspectTab.Meta }); tabs.push({ label: 'Meta Data', value: InspectTab.Meta });
} }
...@@ -278,8 +292,6 @@ export class PanelInspector extends PureComponent<Props, State> { ...@@ -278,8 +292,6 @@ export class PanelInspector extends PureComponent<Props, State> {
tabs.push({ label: 'Error', value: InspectTab.Error }); tabs.push({ label: 'Error', value: InspectTab.Error });
} }
tabs.push({ label: 'Raw JSON', value: InspectTab.Raw });
return ( return (
<InspectHeader <InspectHeader
tabs={tabs} tabs={tabs}
...@@ -302,25 +314,13 @@ export class PanelInspector extends PureComponent<Props, State> { ...@@ -302,25 +314,13 @@ export class PanelInspector extends PureComponent<Props, State> {
return ( return (
<Drawer title={this.drawerHeader} width={drawerWidth} onClose={this.onDismiss}> <Drawer title={this.drawerHeader} width={drawerWidth} onClose={this.onDismiss}>
<TabContent className={styles.tabContent}> <TabContent className={styles.tabContent}>
{tab === InspectTab.Data ? ( <CustomScrollbar autoHeightMin="100%">
this.renderDataTab() {tab === InspectTab.Data && this.renderDataTab()}
) : (
<AutoSizer>
{({ width, height }) => {
if (width === 0) {
return null;
}
return (
<div style={{ width, height }}>
{tab === InspectTab.Meta && this.renderMetadataInspector()} {tab === InspectTab.Meta && this.renderMetadataInspector()}
{tab === InspectTab.Issue && this.renderIssueTab()} {tab === InspectTab.Request && this.renderRequestTab()}
{tab === InspectTab.Raw && this.renderRawJsonTab(last)}
{tab === InspectTab.Error && this.renderErrorTab(error)} {tab === InspectTab.Error && this.renderErrorTab(error)}
</div> {tab === InspectTab.Stats && this.renderStatsTab()}
); </CustomScrollbar>
}}
</AutoSizer>
)}
</TabContent> </TabContent>
</Drawer> </Drawer>
); );
......
...@@ -216,8 +216,13 @@ export function preProcessPanelData(data: PanelData, lastResult: PanelData): Pan ...@@ -216,8 +216,13 @@ export function preProcessPanelData(data: PanelData, lastResult: PanelData): Pan
} }
// Make sure the data frames are properly formatted // Make sure the data frames are properly formatted
const STARTTIME = performance.now();
const processedDataFrames = getProcessedDataFrames(series);
const STOPTIME = performance.now();
return { return {
...data, ...data,
series: getProcessedDataFrames(series), series: processedDataFrames,
timings: { dataProcessingTime: STOPTIME - STARTTIME },
}; };
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment