Adding TSDB Stats Page in React UI (#6281)
Signed-off-by: Sharad Gaur <sgaur@splunk.com>pull/6308/head
parent
fc309a35bb
commit
a85e7aac0e
@ -0,0 +1,125 @@ |
||||
import * as React from 'react'; |
||||
import { mount, shallow, ReactWrapper } from 'enzyme'; |
||||
import { act } from 'react-dom/test-utils'; |
||||
import { Alert, Table, Badge } from 'reactstrap'; |
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; |
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons'; |
||||
import { TSDBStatus } from '.'; |
||||
import { TSDBMap, Stats } from './TSDBStatus'; |
||||
|
||||
const fakeTSDBStatusResponse: { |
||||
status: string; |
||||
data: TSDBMap; |
||||
} = { |
||||
status: 'success', |
||||
data: { |
||||
labelValueCountByLabelName: [ |
||||
{ |
||||
name: '__name__', |
||||
value: 5, |
||||
}, |
||||
], |
||||
seriesCountByMetricName: [ |
||||
{ |
||||
name: 'scrape_duration_seconds', |
||||
value: 1, |
||||
}, |
||||
{ |
||||
name: 'scrape_samples_scraped', |
||||
value: 1, |
||||
}, |
||||
], |
||||
memoryInBytesByLabelName: [ |
||||
{ |
||||
name: '__name__', |
||||
value: 103, |
||||
}, |
||||
], |
||||
seriesCountByLabelValuePair: [ |
||||
{ |
||||
name: 'instance=localhost:9100', |
||||
value: 5, |
||||
}, |
||||
], |
||||
}, |
||||
}; |
||||
|
||||
describe('TSDB Stats', () => { |
||||
beforeEach(() => { |
||||
fetch.resetMocks(); |
||||
}); |
||||
|
||||
it('before data is returned', () => { |
||||
const tsdbStatus = shallow(<TSDBStatus />); |
||||
const icon = tsdbStatus.find(FontAwesomeIcon); |
||||
expect(icon.prop('icon')).toEqual(faSpinner); |
||||
expect(icon.prop('spin')).toBeTruthy(); |
||||
}); |
||||
|
||||
describe('when an error is returned', () => { |
||||
it('displays an alert', async () => { |
||||
const mock = fetch.mockReject(new Error('error loading tsdb status')); |
||||
|
||||
let page: ReactWrapper; |
||||
await act(async () => { |
||||
page = mount(<TSDBStatus pathPrefix="/path/prefix" />); |
||||
}); |
||||
page.update(); |
||||
|
||||
expect(mock).toHaveBeenCalledWith('/path/prefix/api/v1/status/tsdb', undefined); |
||||
const alert = page.find(Alert); |
||||
expect(alert.prop('color')).toBe('danger'); |
||||
expect(alert.text()).toContain('error loading tsdb status'); |
||||
}); |
||||
}); |
||||
|
||||
describe('Table Data Validation', () => { |
||||
it('Table Test', async () => { |
||||
const tables = [ |
||||
{ |
||||
data: fakeTSDBStatusResponse.data.labelValueCountByLabelName, |
||||
table_index: 0, |
||||
}, |
||||
{ |
||||
data: fakeTSDBStatusResponse.data.seriesCountByMetricName, |
||||
table_index: 1, |
||||
}, |
||||
{ |
||||
data: fakeTSDBStatusResponse.data.memoryInBytesByLabelName, |
||||
table_index: 2, |
||||
}, |
||||
{ |
||||
data: fakeTSDBStatusResponse.data.seriesCountByLabelValuePair, |
||||
table_index: 3, |
||||
}, |
||||
]; |
||||
|
||||
const mock = fetch.mockResponse(JSON.stringify(fakeTSDBStatusResponse)); |
||||
let page: ReactWrapper; |
||||
await act(async () => { |
||||
page = mount(<TSDBStatus pathPrefix="/path/prefix" />); |
||||
}); |
||||
page.update(); |
||||
|
||||
expect(mock).toHaveBeenCalledWith('/path/prefix/api/v1/status/tsdb', undefined); |
||||
|
||||
for (let i = 0; i < tables.length; i++) { |
||||
let data = tables[i].data; |
||||
let table = page |
||||
.find(Table) |
||||
.at(tables[i].table_index) |
||||
.find('tbody'); |
||||
let rows = table.find('tr'); |
||||
for (let i = 0; i < data.length; i++) { |
||||
const firstRowColumns = rows |
||||
.at(i) |
||||
.find('td') |
||||
.map(column => column.text()); |
||||
expect(rows.length).toBe(data.length); |
||||
expect(firstRowColumns[0]).toBe(data[i].name); |
||||
expect(firstRowColumns[1]).toBe(data[i].value.toString()); |
||||
} |
||||
} |
||||
}); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,87 @@ |
||||
import React, { FC, Fragment } from 'react'; |
||||
import { RouteComponentProps } from '@reach/router'; |
||||
import { Alert, Table } from 'reactstrap'; |
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; |
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons'; |
||||
import { useFetch } from '../utils/useFetch'; |
||||
import PathPrefixProps from '../PathPrefixProps'; |
||||
|
||||
export interface Stats { |
||||
name: string; |
||||
value: number; |
||||
} |
||||
|
||||
export interface TSDBMap { |
||||
seriesCountByMetricName: Array<Stats>; |
||||
labelValueCountByLabelName: Array<Stats>; |
||||
memoryInBytesByLabelName: Array<Stats>; |
||||
seriesCountByLabelValuePair: Array<Stats>; |
||||
} |
||||
|
||||
const paddingStyle = { |
||||
padding: '10px', |
||||
}; |
||||
|
||||
function createTable(title: string, unit: string, stats: Array<Stats>) { |
||||
return ( |
||||
<div style={paddingStyle}> |
||||
<h3>{title}</h3> |
||||
<Table bordered={true} size="sm" striped={true}> |
||||
<thead> |
||||
<tr> |
||||
<th>Name</th> |
||||
<th>{unit}</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
{stats.map((element: Stats, i: number) => { |
||||
return ( |
||||
<Fragment key={i}> |
||||
<tr> |
||||
<td>{element.name}</td> |
||||
<td>{element.value}</td> |
||||
</tr> |
||||
</Fragment> |
||||
); |
||||
})} |
||||
</tbody> |
||||
</Table> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
const TSDBStatus: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => { |
||||
const { response, error } = useFetch(`${pathPrefix}/api/v1/status/tsdb`); |
||||
const headStats = () => { |
||||
const stats: TSDBMap = response && response.data; |
||||
if (error) { |
||||
return ( |
||||
<Alert color="danger"> |
||||
<strong>Error:</strong> Error fetching TSDB Status: {error.message} |
||||
</Alert> |
||||
); |
||||
} else if (stats) { |
||||
return ( |
||||
<div> |
||||
<div style={paddingStyle}> |
||||
<h3>Head Cardinality Stats</h3> |
||||
</div> |
||||
{createTable('Top 10 label names with value count', 'Count', stats.labelValueCountByLabelName)} |
||||
{createTable('Top 10 series count by metric names', 'Count', stats.seriesCountByMetricName)} |
||||
{createTable('Top 10 label names with high memory usage', 'Bytes', stats.memoryInBytesByLabelName)} |
||||
{createTable('Top 10 series count by label value pairs', 'Count', stats.seriesCountByLabelValuePair)} |
||||
</div> |
||||
); |
||||
} |
||||
return <FontAwesomeIcon icon={faSpinner} spin />; |
||||
}; |
||||
|
||||
return ( |
||||
<div> |
||||
<h2>TSDB Status</h2> |
||||
{headStats()} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default TSDBStatus; |
||||
Loading…
Reference in new issue