feat(xo-web): stats for SRs (#2847)
This commit is contained in:
parent
0c027247ec
commit
a8ba4a1a8e
@ -838,3 +838,23 @@ getUnhealthyVdiChainsLength.params = {
|
|||||||
getUnhealthyVdiChainsLength.resolve = {
|
getUnhealthyVdiChainsLength.resolve = {
|
||||||
sr: ['id', 'SR', 'operate'],
|
sr: ['id', 'SR', 'operate'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
export function stats ({ sr, granularity }) {
|
||||||
|
return this.getXapiSrStats(sr._xapiId, granularity)
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.description = 'returns statistic of the sr'
|
||||||
|
|
||||||
|
stats.params = {
|
||||||
|
id: { type: 'string' },
|
||||||
|
granularity: {
|
||||||
|
type: 'string',
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.resolve = {
|
||||||
|
sr: ['id', 'SR', 'view'],
|
||||||
|
}
|
||||||
|
@ -1,7 +1,19 @@
|
|||||||
import JSON5 from 'json5'
|
import JSON5 from 'json5'
|
||||||
import limitConcurrency from 'limit-concurrency-decorator'
|
import limitConcurrency from 'limit-concurrency-decorator'
|
||||||
import { BaseError } from 'make-error'
|
import { BaseError } from 'make-error'
|
||||||
import { endsWith, findKey, forEach, get, identity, map } from 'lodash'
|
import {
|
||||||
|
endsWith,
|
||||||
|
findKey,
|
||||||
|
forEach,
|
||||||
|
get,
|
||||||
|
identity,
|
||||||
|
map,
|
||||||
|
mapValues,
|
||||||
|
mean,
|
||||||
|
sum,
|
||||||
|
uniq,
|
||||||
|
zipWith,
|
||||||
|
} from 'lodash'
|
||||||
|
|
||||||
import { parseDateTime } from './xapi'
|
import { parseDateTime } from './xapi'
|
||||||
|
|
||||||
@ -62,6 +74,9 @@ const computeValues = (dataRow, legendIndex, transformValue = identity) =>
|
|||||||
transformValue(convertNanToNull(values[legendIndex]))
|
transformValue(convertNanToNull(values[legendIndex]))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const combineStats = (stats, path, combineValues) =>
|
||||||
|
zipWith(...map(stats, path), (...values) => combineValues(values))
|
||||||
|
|
||||||
// It browse the object in depth and initialise it's properties
|
// It browse the object in depth and initialise it's properties
|
||||||
// The targerPath can be a string or an array containing the depth
|
// The targerPath can be a string or an array containing the depth
|
||||||
// targetPath: [a, b, c] => a.b.c
|
// targetPath: [a, b, c] => a.b.c
|
||||||
@ -141,6 +156,45 @@ const STATS = {
|
|||||||
getPath: matches => ['pifs', 'tx', matches[1]],
|
getPath: matches => ['pifs', 'tx', matches[1]],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
iops: {
|
||||||
|
r: {
|
||||||
|
test: /^iops_read_(\w+)$/,
|
||||||
|
getPath: matches => ['iops', 'r', matches[1]],
|
||||||
|
},
|
||||||
|
w: {
|
||||||
|
test: /^iops_write_(\w+)$/,
|
||||||
|
getPath: matches => ['iops', 'w', matches[1]],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ioThroughput: {
|
||||||
|
r: {
|
||||||
|
test: /^io_throughput_read_(\w+)$/,
|
||||||
|
getPath: matches => ['ioThroughput', 'r', matches[1]],
|
||||||
|
transformValue: value => value * 2 ** 20,
|
||||||
|
},
|
||||||
|
w: {
|
||||||
|
test: /^io_throughput_write_(\w+)$/,
|
||||||
|
getPath: matches => ['ioThroughput', 'w', matches[1]],
|
||||||
|
transformValue: value => value * 2 ** 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
latency: {
|
||||||
|
r: {
|
||||||
|
test: /^read_latency_(\w+)$/,
|
||||||
|
getPath: matches => ['latency', 'r', matches[1]],
|
||||||
|
transformValue: value => value / 1e3,
|
||||||
|
},
|
||||||
|
w: {
|
||||||
|
test: /^write_latency_(\w+)$/,
|
||||||
|
getPath: matches => ['latency', 'w', matches[1]],
|
||||||
|
transformValue: value => value / 1e3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
iowait: {
|
||||||
|
test: /^iowait_(\w+)$/,
|
||||||
|
getPath: matches => ['iowait', matches[1]],
|
||||||
|
transformValue: value => value * 1e2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
vm: {
|
vm: {
|
||||||
memoryFree: {
|
memoryFree: {
|
||||||
@ -361,4 +415,47 @@ export default class XapiStats {
|
|||||||
granularity,
|
granularity,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSrStats (xapi, srId, granularity) {
|
||||||
|
const sr = xapi.getObject(srId)
|
||||||
|
|
||||||
|
const hostsStats = {}
|
||||||
|
await Promise.all(
|
||||||
|
map(uniq(map(sr.$PBDs, 'host')), hostId =>
|
||||||
|
this.getHostStats(xapi, hostId, granularity).then(stats => {
|
||||||
|
hostsStats[xapi.getObject(hostId).name_label] = stats
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const srShortUUID = sr.uuid.slice(0, 8)
|
||||||
|
return {
|
||||||
|
interval: hostsStats[Object.keys(hostsStats)[0]].interval,
|
||||||
|
endTimestamp: Math.max(...map(hostsStats, 'endTimestamp')),
|
||||||
|
localTimestamp: Math.min(...map(hostsStats, 'localTimestamp')),
|
||||||
|
stats: {
|
||||||
|
iops: {
|
||||||
|
r: combineStats(hostsStats, `stats.iops.r[${srShortUUID}]`, sum),
|
||||||
|
w: combineStats(hostsStats, `stats.iops.w[${srShortUUID}]`, sum),
|
||||||
|
},
|
||||||
|
ioThroughput: {
|
||||||
|
r: combineStats(
|
||||||
|
hostsStats,
|
||||||
|
`stats.ioThroughput.r[${srShortUUID}]`,
|
||||||
|
sum
|
||||||
|
),
|
||||||
|
w: combineStats(
|
||||||
|
hostsStats,
|
||||||
|
`stats.ioThroughput.w[${srShortUUID}]`,
|
||||||
|
sum
|
||||||
|
),
|
||||||
|
},
|
||||||
|
latency: {
|
||||||
|
r: combineStats(hostsStats, `stats.latency.r[${srShortUUID}]`, mean),
|
||||||
|
w: combineStats(hostsStats, `stats.latency.w[${srShortUUID}]`, mean),
|
||||||
|
},
|
||||||
|
iowait: mapValues(hostsStats, `stats.iowait[${srShortUUID}]`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -400,6 +400,10 @@ export default class {
|
|||||||
return this._stats.getHostStats(this.getXapi(hostId), hostId, granularity)
|
return this._stats.getHostStats(this.getXapi(hostId), hostId, granularity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getXapiSrStats (srId, granularity) {
|
||||||
|
return this._stats.getSrStats(this.getXapi(srId), srId, granularity)
|
||||||
|
}
|
||||||
|
|
||||||
async mergeXenPools (sourceId, targetId, force = false) {
|
async mergeXenPools (sourceId, targetId, force = false) {
|
||||||
const sourceXapi = this.getXapi(sourceId)
|
const sourceXapi = this.getXapi(sourceId)
|
||||||
const { _auth: { user, password }, _url: { hostname } } = this.getXapi(
|
const { _auth: { user, password }, _url: { hostname } } = this.getXapi(
|
||||||
|
@ -543,6 +543,14 @@ const messages = {
|
|||||||
srUnhealthyVdiDepth: 'Depth',
|
srUnhealthyVdiDepth: 'Depth',
|
||||||
srUnhealthyVdiTitle: 'VDI to coalesce ({total, number})',
|
srUnhealthyVdiTitle: 'VDI to coalesce ({total, number})',
|
||||||
|
|
||||||
|
// ----- SR stats tab -----
|
||||||
|
|
||||||
|
srNoStats: 'No stats',
|
||||||
|
statsIops: 'IOPS',
|
||||||
|
statsIoThroughput: 'IO throughput',
|
||||||
|
statsLatency: 'Latency',
|
||||||
|
statsIowait: 'IOwait',
|
||||||
|
|
||||||
// ----- SR actions -----
|
// ----- SR actions -----
|
||||||
srRescan: 'Rescan all disks',
|
srRescan: 'Rescan all disks',
|
||||||
srReconnectAll: 'Connect to all hosts',
|
srReconnectAll: 'Connect to all hosts',
|
||||||
|
@ -205,6 +205,19 @@ export const formatSizeRaw = bytes =>
|
|||||||
export const formatSpeed = (bytes, milliseconds) =>
|
export const formatSpeed = (bytes, milliseconds) =>
|
||||||
humanFormat(bytes * 1e3 / milliseconds, { scale: 'binary', unit: 'B/s' })
|
humanFormat(bytes * 1e3 / milliseconds, { scale: 'binary', unit: 'B/s' })
|
||||||
|
|
||||||
|
const timeScale = new humanFormat.Scale({
|
||||||
|
ns: 1e-6,
|
||||||
|
µs: 1e-3,
|
||||||
|
ms: 1,
|
||||||
|
s: 1e3,
|
||||||
|
min: 60 * 1e3,
|
||||||
|
h: 3600 * 1e3,
|
||||||
|
d: 86400 * 1e3,
|
||||||
|
y: 2592000 * 1e3,
|
||||||
|
})
|
||||||
|
export const formatTime = milliseconds =>
|
||||||
|
humanFormat(milliseconds, { scale: timeScale, decimals: 0 })
|
||||||
|
|
||||||
export const parseSize = size => {
|
export const parseSize = size => {
|
||||||
let bytes = humanFormat.parse.raw(size, { scale: 'binary' })
|
let bytes = humanFormat.parse.raw(size, { scale: 'binary' })
|
||||||
if (bytes.unit && bytes.unit !== 'B') {
|
if (bytes.unit && bytes.unit !== 'B') {
|
||||||
|
@ -4,11 +4,16 @@ import ChartistTooltip from 'chartist-plugin-tooltip'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { injectIntl } from 'react-intl'
|
import { injectIntl } from 'react-intl'
|
||||||
import { messages } from 'intl'
|
import { messages } from 'intl'
|
||||||
import { find, flatten, floor, map, max, size, sum, values } from 'lodash'
|
import { find, flatten, floor, get, map, max, size, sum, values } from 'lodash'
|
||||||
|
|
||||||
import propTypes from '../prop-types-decorator'
|
import propTypes from '../prop-types-decorator'
|
||||||
import { computeArraysSum } from '../xo-stats'
|
import { computeArraysSum } from '../xo-stats'
|
||||||
import { formatSize, getMemoryUsedMetric } from '../utils'
|
import {
|
||||||
|
formatSize,
|
||||||
|
formatSpeed,
|
||||||
|
formatTime,
|
||||||
|
getMemoryUsedMetric,
|
||||||
|
} from '../utils'
|
||||||
|
|
||||||
import styles from './index.css'
|
import styles from './index.css'
|
||||||
|
|
||||||
@ -555,3 +560,172 @@ export const PoolLoadLineChart = injectIntl(
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const buildSrSeries = ({ stats, label, addSumSeries }) => {
|
||||||
|
const series = map(stats, (data, key) => ({
|
||||||
|
name: `${label} (${key})`,
|
||||||
|
data,
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (addSumSeries) {
|
||||||
|
series.push({
|
||||||
|
name: `All ${label}`,
|
||||||
|
data: computeArraysSum(values(stats)),
|
||||||
|
className: styles.dashedLine,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return series
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IopsLineChart = injectIntl(
|
||||||
|
propTypes({
|
||||||
|
addSumSeries: propTypes.bool,
|
||||||
|
data: propTypes.array.isRequired,
|
||||||
|
options: propTypes.object,
|
||||||
|
})(({ addSumSeries, data, options = {}, intl }) => {
|
||||||
|
const { endTimestamp, interval, stats: { iops } } = data
|
||||||
|
|
||||||
|
const { length } = get(iops, 'r')
|
||||||
|
|
||||||
|
if (length === 0) {
|
||||||
|
return templateError
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChartistGraph
|
||||||
|
type='Line'
|
||||||
|
data={{
|
||||||
|
series: buildSrSeries({ stats: iops, label: 'Iops', addSumSeries }),
|
||||||
|
}}
|
||||||
|
options={{
|
||||||
|
...makeOptions({
|
||||||
|
intl,
|
||||||
|
nValues: length,
|
||||||
|
endTimestamp,
|
||||||
|
interval,
|
||||||
|
valueTransform: value => `${value.toPrecision(3)} /s`,
|
||||||
|
}),
|
||||||
|
...options,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const IoThroughputChart = injectIntl(
|
||||||
|
propTypes({
|
||||||
|
addSumSeries: propTypes.bool,
|
||||||
|
data: propTypes.array.isRequired,
|
||||||
|
options: propTypes.object,
|
||||||
|
})(({ addSumSeries, data, options = {}, intl }) => {
|
||||||
|
const { endTimestamp, interval, stats: { ioThroughput } } = data
|
||||||
|
|
||||||
|
const { length } = get(ioThroughput, 'r') || []
|
||||||
|
|
||||||
|
if (length === 0) {
|
||||||
|
return templateError
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChartistGraph
|
||||||
|
type='Line'
|
||||||
|
data={{
|
||||||
|
series: buildSrSeries({
|
||||||
|
stats: ioThroughput,
|
||||||
|
label: 'IO throughput',
|
||||||
|
addSumSeries,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
options={{
|
||||||
|
...makeOptions({
|
||||||
|
intl,
|
||||||
|
nValues: length,
|
||||||
|
endTimestamp,
|
||||||
|
interval,
|
||||||
|
valueTransform: value => formatSpeed(value, 1e3),
|
||||||
|
}),
|
||||||
|
...options,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const LatencyChart = injectIntl(
|
||||||
|
propTypes({
|
||||||
|
addSumSeries: propTypes.bool,
|
||||||
|
data: propTypes.array.isRequired,
|
||||||
|
options: propTypes.object,
|
||||||
|
})(({ addSumSeries, data, options = {}, intl }) => {
|
||||||
|
const { endTimestamp, interval, stats: { latency } } = data
|
||||||
|
|
||||||
|
const { length } = get(latency, 'r') || []
|
||||||
|
|
||||||
|
if (length === 0) {
|
||||||
|
return templateError
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChartistGraph
|
||||||
|
type='Line'
|
||||||
|
data={{
|
||||||
|
series: buildSrSeries({
|
||||||
|
stats: latency,
|
||||||
|
label: 'Latency',
|
||||||
|
addSumSeries,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
options={{
|
||||||
|
...makeOptions({
|
||||||
|
intl,
|
||||||
|
nValues: length,
|
||||||
|
endTimestamp,
|
||||||
|
interval,
|
||||||
|
valueTransform: value => formatTime(value),
|
||||||
|
}),
|
||||||
|
...options,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const IowaitChart = injectIntl(
|
||||||
|
propTypes({
|
||||||
|
addSumSeries: propTypes.bool,
|
||||||
|
data: propTypes.array.isRequired,
|
||||||
|
options: propTypes.object,
|
||||||
|
})(({ addSumSeries, data, options = {}, intl }) => {
|
||||||
|
const { endTimestamp, interval, stats: { iowait } } = data
|
||||||
|
|
||||||
|
const { length } = iowait[Object.keys(iowait)[0]] || []
|
||||||
|
|
||||||
|
if (length === 0) {
|
||||||
|
return templateError
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChartistGraph
|
||||||
|
type='Line'
|
||||||
|
data={{
|
||||||
|
series: buildSrSeries({
|
||||||
|
stats: iowait,
|
||||||
|
label: 'IOwait',
|
||||||
|
addSumSeries,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
options={{
|
||||||
|
...makeOptions({
|
||||||
|
intl,
|
||||||
|
nValues: length,
|
||||||
|
endTimestamp,
|
||||||
|
interval,
|
||||||
|
valueTransform: value => `${value.toPrecision(2)}%`,
|
||||||
|
}),
|
||||||
|
...options,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
@ -1495,6 +1495,9 @@ export const deleteSr = sr =>
|
|||||||
),
|
),
|
||||||
}).then(() => _call('sr.destroy', { id: resolveId(sr) }), noop)
|
}).then(() => _call('sr.destroy', { id: resolveId(sr) }), noop)
|
||||||
|
|
||||||
|
export const fetchSrStats = (sr, granularity) =>
|
||||||
|
_call('sr.stats', { id: resolveId(sr), granularity })
|
||||||
|
|
||||||
export const forgetSr = sr =>
|
export const forgetSr = sr =>
|
||||||
confirm({
|
confirm({
|
||||||
title: _('srForgetModalTitle'),
|
title: _('srForgetModalTitle'),
|
||||||
|
@ -237,6 +237,18 @@
|
|||||||
@extend .fa;
|
@extend .fa;
|
||||||
@extend .fa-database;
|
@extend .fa-database;
|
||||||
}
|
}
|
||||||
|
&-iops {
|
||||||
|
@extend .fa;
|
||||||
|
@extend .fa-cogs;
|
||||||
|
}
|
||||||
|
&-latency {
|
||||||
|
@extend .fa;
|
||||||
|
@extend .fa-clock-o;
|
||||||
|
}
|
||||||
|
&-iowait {
|
||||||
|
@extend .fa;
|
||||||
|
@extend .fa-pause;
|
||||||
|
}
|
||||||
&-delete {
|
&-delete {
|
||||||
@extend .fa;
|
@extend .fa;
|
||||||
@extend .fa-trash;
|
@extend .fa-trash;
|
||||||
|
@ -20,20 +20,22 @@ import {
|
|||||||
} from 'selectors'
|
} from 'selectors'
|
||||||
|
|
||||||
import TabAdvanced from './tab-advanced'
|
import TabAdvanced from './tab-advanced'
|
||||||
import TabGeneral from './tab-general'
|
|
||||||
import TabLogs from './tab-logs'
|
|
||||||
import TabHosts from './tab-host'
|
|
||||||
import TabDisks from './tab-disks'
|
import TabDisks from './tab-disks'
|
||||||
|
import TabGeneral from './tab-general'
|
||||||
|
import TabHosts from './tab-host'
|
||||||
|
import TabLogs from './tab-logs'
|
||||||
|
import TabStats from './tab-stats'
|
||||||
import TabXosan from './tab-xosan'
|
import TabXosan from './tab-xosan'
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@routes('general', {
|
@routes('general', {
|
||||||
advanced: TabAdvanced,
|
advanced: TabAdvanced,
|
||||||
general: TabGeneral,
|
|
||||||
logs: TabLogs,
|
|
||||||
hosts: TabHosts,
|
|
||||||
disks: TabDisks,
|
disks: TabDisks,
|
||||||
|
general: TabGeneral,
|
||||||
|
hosts: TabHosts,
|
||||||
|
logs: TabLogs,
|
||||||
|
stats: TabStats,
|
||||||
xosan: TabXosan,
|
xosan: TabXosan,
|
||||||
})
|
})
|
||||||
@connectStore(() => {
|
@connectStore(() => {
|
||||||
@ -143,6 +145,7 @@ export default class Sr extends Component {
|
|||||||
<NavLink to={`/srs/${sr.id}/general`}>
|
<NavLink to={`/srs/${sr.id}/general`}>
|
||||||
{_('generalTabName')}
|
{_('generalTabName')}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
<NavLink to={`/srs/${sr.id}/stats`}>{_('statsTabName')}</NavLink>
|
||||||
<NavLink to={`/srs/${sr.id}/disks`}>
|
<NavLink to={`/srs/${sr.id}/disks`}>
|
||||||
{_('disksTabName', { disks: sr.VDIs.length })}
|
{_('disksTabName', { disks: sr.VDIs.length })}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
165
packages/xo-web/src/xo-app/sr/tab-stats.js
Normal file
165
packages/xo-web/src/xo-app/sr/tab-stats.js
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import _ from 'intl'
|
||||||
|
import Component from 'base-component'
|
||||||
|
import Icon from 'icon'
|
||||||
|
import React from 'react'
|
||||||
|
import Tooltip from 'tooltip'
|
||||||
|
import Upgrade from 'xoa-upgrade'
|
||||||
|
import { Container, Row, Col } from 'grid'
|
||||||
|
import { fetchSrStats } from 'xo'
|
||||||
|
import { get } from 'lodash'
|
||||||
|
import { Toggle } from 'form'
|
||||||
|
import {
|
||||||
|
IopsLineChart,
|
||||||
|
IoThroughputChart,
|
||||||
|
IowaitChart,
|
||||||
|
LatencyChart,
|
||||||
|
} from 'xo-line-chart'
|
||||||
|
|
||||||
|
export default class SrStats extends Component {
|
||||||
|
state = {
|
||||||
|
granularity: 'seconds',
|
||||||
|
}
|
||||||
|
|
||||||
|
_loop (sr = get(this.props, 'sr')) {
|
||||||
|
if (sr === undefined) {
|
||||||
|
this._loop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cancel !== undefined) {
|
||||||
|
this.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancelled = false
|
||||||
|
this.cancel = () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchSrStats(sr, this.state.granularity).then(data => {
|
||||||
|
if (cancelled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.cancel = undefined
|
||||||
|
|
||||||
|
clearTimeout(this.timeout)
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
data,
|
||||||
|
selectStatsLoading: false,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.timeout = setTimeout(this._loop, data.interval * 1e3)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_loop = ::this._loop
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
this._loop()
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
clearTimeout(this.timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
_onGranularityChange = ({ target: { value: granularity } }) => {
|
||||||
|
clearTimeout(this.timeout)
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
granularity,
|
||||||
|
selectStatsLoading: true,
|
||||||
|
},
|
||||||
|
this._loop
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
granularity,
|
||||||
|
selectStatsLoading,
|
||||||
|
useCombinedValues,
|
||||||
|
} = this.state
|
||||||
|
|
||||||
|
return data === undefined ? (
|
||||||
|
<span>{_('srNoStats')}</span>
|
||||||
|
) : (
|
||||||
|
<Upgrade place='srStats' available={3}>
|
||||||
|
<Container>
|
||||||
|
<Row>
|
||||||
|
<Col mediumSize={5}>
|
||||||
|
<div className='form-group'>
|
||||||
|
<Tooltip content={_('useStackedValuesOnStats')}>
|
||||||
|
<Toggle
|
||||||
|
value={useCombinedValues}
|
||||||
|
onChange={this.linkState('useCombinedValues')}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col mediumSize={1}>
|
||||||
|
{selectStatsLoading && (
|
||||||
|
<div className='text-xs-right'>
|
||||||
|
<Icon icon='loading' size={2} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
<Col mediumSize={6}>
|
||||||
|
<div className='btn-tab'>
|
||||||
|
<select
|
||||||
|
className='form-control'
|
||||||
|
onChange={this._onGranularityChange}
|
||||||
|
defaultValue={granularity}
|
||||||
|
>
|
||||||
|
{_('statLastTenMinutes', message => (
|
||||||
|
<option value='seconds'>{message}</option>
|
||||||
|
))}
|
||||||
|
{_('statLastTwoHours', message => (
|
||||||
|
<option value='minutes'>{message}</option>
|
||||||
|
))}
|
||||||
|
{_('statLastWeek', message => (
|
||||||
|
<option value='hours'>{message}</option>
|
||||||
|
))}
|
||||||
|
{_('statLastYear', message => (
|
||||||
|
<option value='days'>{message}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col mediumSize={6}>
|
||||||
|
<h5 className='text-xs-center'>
|
||||||
|
<Icon icon='iops' size={1} /> {_('statsIops')}
|
||||||
|
</h5>
|
||||||
|
<IopsLineChart addSumSeries={useCombinedValues} data={data} />
|
||||||
|
</Col>
|
||||||
|
<Col mediumSize={6}>
|
||||||
|
<h5 className='text-xs-center'>
|
||||||
|
<Icon icon='disk' size={1} /> {_('statsIoThroughput')}
|
||||||
|
</h5>
|
||||||
|
<IoThroughputChart addSumSeries={useCombinedValues} data={data} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<br />
|
||||||
|
<hr />
|
||||||
|
<Row>
|
||||||
|
<Col mediumSize={6}>
|
||||||
|
<h5 className='text-xs-center'>
|
||||||
|
<Icon icon='latency' size={1} /> {_('statsLatency')}
|
||||||
|
</h5>
|
||||||
|
<LatencyChart addSumSeries={useCombinedValues} data={data} />
|
||||||
|
</Col>
|
||||||
|
<Col mediumSize={6}>
|
||||||
|
<h5 className='text-xs-center'>
|
||||||
|
<Icon icon='iowait' size={1} /> {_('statsIowait')}
|
||||||
|
</h5>
|
||||||
|
<IowaitChart addSumSeries={useCombinedValues} data={data} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
</Upgrade>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user