mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 10:03:33 -06:00
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>
This commit is contained in:
parent
c75574298c
commit
08d8190c02
@ -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;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
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 {
|
|||||||
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> = ({
|
|||||||
<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) => {
|
|||||||
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) => {
|
|||||||
`,
|
`,
|
||||||
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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
// 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> {
|
|||||||
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> {
|
|||||||
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> {
|
|||||||
requestTime,
|
requestTime,
|
||||||
queries: targets.length,
|
queries: targets.length,
|
||||||
dataSources,
|
dataSources,
|
||||||
|
processingTime,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -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> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
|||||||
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> {
|
|||||||
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> {
|
|||||||
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()}
|
||||||
) : (
|
{tab === InspectTab.Meta && this.renderMetadataInspector()}
|
||||||
<AutoSizer>
|
{tab === InspectTab.Request && this.renderRequestTab()}
|
||||||
{({ width, height }) => {
|
{tab === InspectTab.Error && this.renderErrorTab(error)}
|
||||||
if (width === 0) {
|
{tab === InspectTab.Stats && this.renderStatsTab()}
|
||||||
return null;
|
</CustomScrollbar>
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div style={{ width, height }}>
|
|
||||||
{tab === InspectTab.Meta && this.renderMetadataInspector()}
|
|
||||||
{tab === InspectTab.Issue && this.renderIssueTab()}
|
|
||||||
{tab === InspectTab.Raw && this.renderRawJsonTab(last)}
|
|
||||||
{tab === InspectTab.Error && this.renderErrorTab(error)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</AutoSizer>
|
|
||||||
)}
|
|
||||||
</TabContent>
|
</TabContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
|
@ -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 },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user