Add broker statistics
This commit is contained in:
6
app/package-lock.json
generated
6
app/package-lock.json
generated
@@ -4934,6 +4934,12 @@
|
|||||||
"throttleit": "0.0.2"
|
"throttleit": "0.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"number-abbreviate": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/number-abbreviate/-/number-abbreviate-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-6cstGNuADhU88HKClVPEr+DfeJg=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"css-loader": "^2.1.0",
|
"css-loader": "^2.1.0",
|
||||||
"electron-nucleus": "^1.11.0",
|
"electron-nucleus": "^1.11.0",
|
||||||
"electron-telemetry": "git+https://github.com/thomasnordquist/electron-telemetry.git",
|
"electron-telemetry": "git+https://github.com/thomasnordquist/electron-telemetry.git",
|
||||||
|
"number-abbreviate": "^2.0.0",
|
||||||
"react": "^16.8.0-alpha.1",
|
"react": "^16.8.0-alpha.1",
|
||||||
"react-dom": "^16.7.0",
|
"react-dom": "^16.7.0",
|
||||||
"react-redux": "^6.0.0",
|
"react-redux": "^6.0.0",
|
||||||
|
|||||||
131
app/src/components/BrokerStatistics.tsx
Normal file
131
app/src/components/BrokerStatistics.tsx
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import * as q from '../../../backend/src/Model'
|
||||||
|
|
||||||
|
import { AppState } from '../reducers'
|
||||||
|
import { Typography } from '@material-ui/core'
|
||||||
|
import { StyleRulesCallback, withStyles } from '@material-ui/core/styles'
|
||||||
|
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
const abbreviate = require('number-abbreviate')
|
||||||
|
|
||||||
|
interface Stats {
|
||||||
|
topic: string
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles: StyleRulesCallback = theme => ({
|
||||||
|
flex: {
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
width: '100%',
|
||||||
|
height: '224px',
|
||||||
|
backgroundColor: 'rebeccapurple',
|
||||||
|
marginBottom: 0,
|
||||||
|
marginTop: 'auto',
|
||||||
|
padding: '8px',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
classes: any
|
||||||
|
tree?: q.Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
class BrokerStatistics extends React.Component<Props, {}> {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props)
|
||||||
|
this.state = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { tree, classes } = this.props
|
||||||
|
if (!tree) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats: any = {
|
||||||
|
broker: {
|
||||||
|
topic: '$SYS/broker/version',
|
||||||
|
title: 'Broker',
|
||||||
|
},
|
||||||
|
clients: {
|
||||||
|
topic: '$SYS/broker/clients/total',
|
||||||
|
title: 'Clients',
|
||||||
|
},
|
||||||
|
subscriptions: {
|
||||||
|
topic: '$SYS/broker/subscriptions/count',
|
||||||
|
title: 'Subscriptions',
|
||||||
|
},
|
||||||
|
received: {
|
||||||
|
topic: '$SYS/broker/messages/received',
|
||||||
|
title: 'Received',
|
||||||
|
},
|
||||||
|
sent: {
|
||||||
|
topic: '$SYS/broker/messages/sent',
|
||||||
|
title: 'Sent',
|
||||||
|
},
|
||||||
|
received5m: {
|
||||||
|
topic: '$SYS/broker/load/messages/received/5min',
|
||||||
|
title: 'Received last 5min',
|
||||||
|
},
|
||||||
|
sent5m: {
|
||||||
|
topic: '$SYS/broker/load/messages/sent/5min',
|
||||||
|
title: 'Sent 5m',
|
||||||
|
},
|
||||||
|
heap: {
|
||||||
|
topic: '$SYS/broker/heap/current',
|
||||||
|
title: 'Memory',
|
||||||
|
},
|
||||||
|
heapMax: {
|
||||||
|
topic: '$SYS/broker/heap/maximum',
|
||||||
|
title: 'Memory (max)',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
{this.renderStat(tree, stats.broker)}
|
||||||
|
{this.renderPair(tree, stats.sent, stats.received)}
|
||||||
|
{this.renderPair(tree, stats.clients, stats.subscriptions)}
|
||||||
|
{this.renderPair(tree, stats.sent5m, stats.received5m)}
|
||||||
|
{this.renderPair(tree, stats.heap, stats.heapMax)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderPair(tree: q.Tree, a: Stats, b: Stats) {
|
||||||
|
return (
|
||||||
|
<div className={this.props.classes.flex}>
|
||||||
|
<div style={{ flex: 1 }}>{this.renderStat(tree, a)}</div>
|
||||||
|
<div style={{ flex: 1 }}>{this.renderStat(tree, b)}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public renderStat(tree: q.Tree, stat: Stats) {
|
||||||
|
const node = tree.findNode(stat.topic)
|
||||||
|
if (!node) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = node.message && node.message.value
|
||||||
|
value = !isNaN(value) ? abbreviate(value) : value
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={stat.title}>
|
||||||
|
<Typography><b>{stat.title}</b></Typography>
|
||||||
|
<Typography style={{ paddingLeft: '8px' }}><i>{value}</i></Typography>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state: AppState) => {
|
||||||
|
return {
|
||||||
|
tree: state.connection.tree,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(connect(mapStateToProps)(BrokerStatistics))
|
||||||
@@ -16,8 +16,9 @@ import { StyleRulesCallback, withStyles } from '@material-ui/core/styles'
|
|||||||
import ChevronRight from '@material-ui/icons/ChevronRight'
|
import ChevronRight from '@material-ui/icons/ChevronRight'
|
||||||
import { bindActionCreators } from 'redux'
|
import { bindActionCreators } from 'redux'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { settingsActions } from '../actions'
|
import { settingsActions, treeActions } from '../actions'
|
||||||
import { TopicOrder } from '../reducers/Settings'
|
import { TopicOrder } from '../reducers/Settings'
|
||||||
|
import BrokerStatistics from './BrokerStatistics'
|
||||||
|
|
||||||
const styles: StyleRulesCallback = theme => ({
|
const styles: StyleRulesCallback = theme => ({
|
||||||
drawer: {
|
drawer: {
|
||||||
@@ -39,12 +40,12 @@ const styles: StyleRulesCallback = theme => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
actions: typeof settingsActions
|
|
||||||
autoExpandLimit: number
|
autoExpandLimit: number
|
||||||
visible: boolean
|
visible: boolean
|
||||||
store?: any
|
store?: any
|
||||||
classes: any
|
|
||||||
topicOrder: TopicOrder
|
topicOrder: TopicOrder
|
||||||
|
classes: any
|
||||||
|
actions: typeof settingsActions
|
||||||
}
|
}
|
||||||
|
|
||||||
class Settings extends React.Component<Props, {}> {
|
class Settings extends React.Component<Props, {}> {
|
||||||
@@ -79,6 +80,7 @@ class Settings extends React.Component<Props, {}> {
|
|||||||
{this.renderAutoExpand()}
|
{this.renderAutoExpand()}
|
||||||
{this.renderNodeOrder()}
|
{this.renderNodeOrder()}
|
||||||
</div>
|
</div>
|
||||||
|
<BrokerStatistics />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import * as q from '../../../backend/src/Model'
|
|
||||||
|
|
||||||
import { AppBar, Button, IconButton, InputBase, Toolbar, Typography } from '@material-ui/core'
|
import { AppBar, Button, IconButton, InputBase, Toolbar, Typography } from '@material-ui/core'
|
||||||
import { StyleRulesCallback, withStyles } from '@material-ui/core/styles'
|
import { StyleRulesCallback, withStyles } from '@material-ui/core/styles'
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
|
|||||||
|
|
||||||
private renderValue() {
|
private renderValue() {
|
||||||
const style: React.CSSProperties = {
|
const style: React.CSSProperties = {
|
||||||
maxWidth: '15em',
|
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
|
|||||||
@@ -148,6 +148,25 @@ export class TreeNode {
|
|||||||
return this.cachedChildTopics
|
return this.cachedChildTopics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public findNode (path: String): TreeNode | undefined {
|
||||||
|
const topics = path.split('/')
|
||||||
|
|
||||||
|
return this.findChild(topics)
|
||||||
|
}
|
||||||
|
|
||||||
|
private findChild(edges: string[]): TreeNode | undefined {
|
||||||
|
if (edges.length === 0) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextEdge = this.edges[edges[0]]
|
||||||
|
if (!nextEdge) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextEdge.target.findChild(edges.slice(1))
|
||||||
|
}
|
||||||
|
|
||||||
private mergeEdges(node: TreeNode) {
|
private mergeEdges(node: TreeNode) {
|
||||||
const edgeKeys = Object.keys(node.edges)
|
const edgeKeys = Object.keys(node.edges)
|
||||||
let edgesDidUpdate = false
|
let edgesDidUpdate = false
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'mocha'
|
|||||||
import { EventDispatcher } from '../../../../events'
|
import { EventDispatcher } from '../../../../events'
|
||||||
|
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { doesNotReject } from 'assert'
|
|
||||||
|
|
||||||
describe('EventDispatcher', async () => {
|
describe('EventDispatcher', async () => {
|
||||||
it('should dispatch', async function () {
|
it('should dispatch', async function () {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'mocha'
|
import 'mocha'
|
||||||
import './TreeNode.findNode'
|
|
||||||
|
|
||||||
import { Tree, TreeNodeFactory } from '../'
|
import { Tree, TreeNodeFactory } from '../'
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'mocha'
|
import 'mocha'
|
||||||
import './TreeNode.findNode'
|
|
||||||
|
|
||||||
import { TreeNodeFactory } from '../'
|
import { TreeNodeFactory } from '../'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { TreeNode } from '../'
|
|
||||||
declare module '../' {
|
|
||||||
interface TreeNode {
|
|
||||||
findNode(path: String): TreeNode | undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TreeNode.prototype.findNode = function (path: String): TreeNode | undefined {
|
|
||||||
const topics = path.split('/')
|
|
||||||
const edge = this.edges[topics[0]]
|
|
||||||
const remainingTopics = topics.slice(1, topics.length)
|
|
||||||
if (edge && remainingTopics.length === 0) {
|
|
||||||
return edge.target
|
|
||||||
} else if (edge) {
|
|
||||||
return edge.target.findNode(remainingTopics.join('/'))
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import './TreeNode.findNode'
|
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
|
|
||||||
import { TreeNodeFactory } from '../'
|
import { TreeNodeFactory } from '../'
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'mocha'
|
import 'mocha'
|
||||||
import './TreeNode.findNode'
|
|
||||||
|
|
||||||
import { TreeNodeFactory } from '../'
|
import { TreeNodeFactory } from '../'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
|
|||||||
Reference in New Issue
Block a user