Skip to content

Commit

Permalink
Merge pull request #1592 from HubSpot/resource-usage-ui
Browse files Browse the repository at this point in the history
Resource Usage UI
  • Loading branch information
ssalinas authored Jul 24, 2017
2 parents b88b221 + 8cd518a commit 8730005
Show file tree
Hide file tree
Showing 37 changed files with 598 additions and 229 deletions.
2 changes: 1 addition & 1 deletion SingularityUI/app/actions/api/requests.es6
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const FetchRequestsInState = buildApiAction(
(state, renderNotFoundIf404) => {
if (_.contains(['pending', 'cleanup'], state)) {
return {url: `/requests/queued/${state}`, renderNotFoundIf404};
} else if (_.contains(['all', 'noDeploy', 'activeDeploy'], state)) {
} else if (_.contains(['all', 'noDeploy', 'activeDeploy', 'overUtilizedCpu', 'underUtilizedCpu', 'underUtilizedMem'], state)) {
return {url: '/requests', renderNotFoundIf404};
}
return {url: `/requests/${state}`, renderNotFoundIf404};
Expand Down
7 changes: 7 additions & 0 deletions SingularityUI/app/actions/api/utilization.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { buildApiAction } from './base';

export const FetchUtilization = buildApiAction(
'FETCH_UTILIZATION',
{url: '/usage/cluster/utilization'}
);

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Tooltip from 'react-bootstrap/lib/Tooltip';
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';

const Breakdown = ({total, data}) => {

const sections = data.map((item, key) => {
return (
<Link key={key} to={item.link}>
Expand All @@ -12,7 +13,7 @@ const Breakdown = ({total, data}) => {
data-type="column"
data-state-attribute={item.attribute}
style={{height: `${item.percent}%`}}
className={`chart__data-point chart-fill-${item.type}`}
className={`chart__data-point bg-${item.type}`}
data-original-title={`${item.count} ${item.label}`}
/>
</OverlayTrigger>
Expand All @@ -33,10 +34,11 @@ const Breakdown = ({total, data}) => {
Breakdown.propTypes = {
total: React.PropTypes.number.isRequired,
data: React.PropTypes.arrayOf(React.PropTypes.shape({
attribute: React.PropTypes.string.isRequired,
count: React.PropTypes.number.isRequired,
type: React.PropTypes.string.isRequired,
label: React.PropTypes.string.isRequired,
link: React.PropTypes.string.isRequired,
link: React.PropTypes.string,
percent: React.PropTypes.number.isRequired
})).isRequired
};
Expand Down
4 changes: 3 additions & 1 deletion SingularityUI/app/components/common/CollapsableSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ export default class CollapsableSection extends React.Component {
static propTypes = {
defaultExpanded: PropTypes.bool,
title: PropTypes.string,
subtitle: PropTypes.string,
children: PropTypes.node,
id: PropTypes.string
}
};

constructor(props) {
super();
Expand All @@ -28,6 +29,7 @@ export default class CollapsableSection extends React.Component {
<div className="page-header">
<h2>
{this.props.title}
<small>{this.props.subtitle}</small>
<small>
<a data-action="expandToggle" onClick={() => this.toggle()}>{this.state.expanded ? 'Collapse' : 'View'}</a>
</small>
Expand Down
15 changes: 15 additions & 0 deletions SingularityUI/app/components/common/Loader.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, { PropTypes } from 'react';

const Loader = ({fixed}) => (
<div className={`page-loader ${fixed ? 'fixed' : ''}`} />
);

Loader.propTypes = {
fixed: PropTypes.bool
};

Loader.defaultProps = {
fixed: true
};

export default Loader;
2 changes: 1 addition & 1 deletion SingularityUI/app/components/common/Navigation.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const Navigation = (props) => {
<ul className="dropdown-menu">
<li><Link to="/racks">Racks</Link></li>
<li><Link to="/slaves">Slaves</Link></li>
<li><Link to="/slave-usage">Slave Usage</Link></li>
<li><Link to="/utilization">Utilization</Link></li>
<li><Link to="/webhooks">Webhooks</Link></li>
<li><Link to="/disasters">Disasters</Link></li>
<li role="separator" className="divider"></li>
Expand Down
10 changes: 5 additions & 5 deletions SingularityUI/app/components/common/statelessComponents.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import classNames from 'classnames';
export const DeployState = (props) => {
return (
<span className="deploy-state" data-state={props.state || 'PENDING'}>
{props.state}
{props.state}
</span>
);
};
Expand All @@ -21,10 +21,10 @@ export const InfoBox = (props) => {
}
return (
<li className="col-sm-6 col-md-3">
<div>
<h4>{props.name}<a className={classNames(props.copyableClassName, 'copy-btn')} data-clipboard-text={value}>Copy</a></h4>
<p>{value}</p>
</div>
<div>
<h4>{props.name}<a className={classNames(props.copyableClassName, 'copy-btn')} data-clipboard-text={value}>Copy</a></h4>
<p>{value}</p>
</div>
</li>
);
};
Expand Down
3 changes: 2 additions & 1 deletion SingularityUI/app/components/common/table/UITable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import classNames from 'classnames';

import BootstrapTable from 'react-bootstrap/lib/Table';
import { Pagination } from 'react-bootstrap';
import Loader from "../Loader";

class UITable extends Component {

Expand Down Expand Up @@ -425,7 +426,7 @@ class UITable extends Component {

render() {
if (this.props.showPageLoaderWhenFetching && this.props.isFetching) {
return <div className="page-loader fixed" />;
return <Loader />;
}
let maybeTable = (
<BootstrapTable responsive={true} striped={true} className={this.props.className}>
Expand Down
71 changes: 0 additions & 71 deletions SingularityUI/app/components/machines/SlaveAggregates.jsx

This file was deleted.

56 changes: 56 additions & 0 deletions SingularityUI/app/components/machines/usage/Aggregate.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { PropTypes } from 'react';
import classNames from 'classnames';
import CircularProgressbar from 'react-circular-progressbar';
import {Link} from 'react-router';

const Aggregate = ({width, vcenter, graph, className, value, label, link}) => {
const valueComponent = (
graph ?
<CircularProgressbar percentage={value} initialAnimation={true} textForPercentage={(pct) => `${pct}%`} /> :
<div className={classNames('value', {[className]: className})}>
{value}
</div>
);
const labelComponent = (
<div className={classNames('label', {[className]: className})}>
{label}
</div>
);

return (
<div className={classNames(
'aggregate',
`col-xs-${width}`,
{vcenter},
{graph}
)}>
{link ?
<Link to={link}>
{valueComponent}
{labelComponent}
</Link> :
<div>
{valueComponent}
{labelComponent}
</div>
}
</div>
);
};

Aggregate.propTypes = {
width: PropTypes.number.isRequired,
vcenter: PropTypes.bool,
graph: PropTypes.bool,
className: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
label: PropTypes.string.isRequired,
link: PropTypes.string
};

Aggregate.defaultProps = {
vcenter: false,
graph: false
};

export default Aggregate;
112 changes: 112 additions & 0 deletions SingularityUI/app/components/machines/usage/ClusterAggregates.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { PropTypes } from 'react';
import { Link } from 'react-router';
import Utils from '../../../utils';
import Breakdown from '../../common/Breakdown';
import { HUNDREDTHS_PLACE } from '../Constants';
import Loader from "../../common/Loader";
import LabeledColumn from "./LabeledColumn";
import Aggregate from './Aggregate';

const SlaveAggregates = ({utilization, totalRequests}) => {
return (
<div>
<h3>CPU</h3>
<div className="row">
<div className="col-md-2">
<h4>Requests</h4>
{utilization.numRequestsWithOverUtilizedCpu !== undefined ?
<Breakdown
total={totalRequests}
data={[
{
attribute: 'overCpu',
count: utilization.numRequestsWithOverUtilizedCpu,
type: 'danger',
label: 'Over-utilized',
link: '/requests/overUtilizedCpu/all/',
percent: (utilization.numRequestsWithOverUtilizedCpu / totalRequests) * 100
},
{
attribute: 'normal',
count: totalRequests - utilization.numRequestsWithUnderUtilizedCpu - utilization.numRequestsWithOverUtilizedCpu,
type: 'success',
label: 'Normal',
percent: ((totalRequests - utilization.numRequestsWithUnderUtilizedCpu - utilization.numRequestsWithOverUtilizedCpu) / totalRequests) * 100
},
{
attribute: 'underCpu',
count: utilization.numRequestsWithUnderUtilizedCpu,
type: 'warning',
label: 'Under-utilized',
link: '/requests/underUtilizedCpu/all/',
percent: (utilization.numRequestsWithUnderUtilizedCpu / totalRequests) * 100
}
]}
/> : <Loader fixed={false} />}
</div>

<LabeledColumn width={10}>
<div className="row">
<Aggregate width={3} value={Utils.roundTo(utilization.totalOverUtilizedCpu, HUNDREDTHS_PLACE)} label="Total Over-utilized CPUs" className="text-danger" />
<Aggregate width={3} value={Utils.roundTo(utilization.avgOverUtilizedCpu, HUNDREDTHS_PLACE)} label="Avg Over-utilized CPUs" className="text-danger" />
<Aggregate width={3} value={Utils.roundTo(utilization.minOverUtilizedCpu, HUNDREDTHS_PLACE)} label="Min Over-utilized CPUs" className="text-danger" />
<Aggregate width={3} value={Utils.roundTo(utilization.maxOverUtilizedCpu, HUNDREDTHS_PLACE)} label="Max Over-utilized CPUs" className="text-danger" link={utilization.maxOverUtilizedCpuRequestId && `/request/${utilization.maxOverUtilizedCpuRequestId}`} />
</div>
</LabeledColumn>

<LabeledColumn width={10}>
<div className="row">
<Aggregate width={3} value={Utils.roundTo(utilization.totalUnderUtilizedCpu, HUNDREDTHS_PLACE)} label="Total Under-utilized CPUs" className="text-warning" />
<Aggregate width={3} value={Utils.roundTo(utilization.avgUnderUtilizedCpu, HUNDREDTHS_PLACE)} label="Avg Under-utilized CPUs" className="text-warning" />
<Aggregate width={3} value={Utils.roundTo(utilization.minUnderUtilizedCpu, HUNDREDTHS_PLACE)} label="Min Under-utilized CPUs" className="text-warning" />
<Aggregate width={3} value={Utils.roundTo(utilization.maxUnderUtilizedCpu, HUNDREDTHS_PLACE)} label="Max Under-utilized CPUs" className="text-warning" link={utilization.maxUnderUtilizedCpuRequestId && `/request/${utilization.maxUnderUtilizedCpuRequestId}`} />
</div>
</LabeledColumn>
</div>

<h3>Memory</h3>
<div className="row">
<div className="col-md-2">
<h4>Requests</h4>
{utilization.numRequestsWithUnderUtilizedMemBytes !== undefined ?
<Breakdown
total={totalRequests}
data={[
{
attribute: 'normal',
count: totalRequests - utilization.numRequestsWithUnderUtilizedMemBytes,
type: 'success',
label: 'Normal',
percent: ((totalRequests - utilization.numRequestsWithUnderUtilizedMemBytes) / totalRequests) * 100
},
{
attribute: 'underMem',
count: utilization.numRequestsWithUnderUtilizedMemBytes,
type: 'warning',
label: 'Under-utilized',
link: '/requests/underUtilizedMem/all/',
percent: (utilization.numRequestsWithUnderUtilizedMemBytes / totalRequests) * 100
}
]}
/> : <Loader fixed={false} />}
</div>

<LabeledColumn width={10}>
<div className="row">
<Aggregate width={3} value={Utils.humanizeFileSize(utilization.totalUnderUtilizedMemBytes)} label="Total Under-utilized Memory" className="text-warning" />
<Aggregate width={3} value={Utils.humanizeFileSize(utilization.avgUnderUtilizedMemBytes)} label="Avg Under-utilized Memory" className="text-warning" />
<Aggregate width={3} value={Utils.humanizeFileSize(utilization.minUnderUtilizedMemBytes)} label="Min Under-utilized Memory" className="text-warning" />
<Aggregate width={3} value={Utils.humanizeFileSize(utilization.maxUnderUtilizedMemBytes)} label="Max Under-utilized Memory" className="text-warning" link={utilization.maxUnderUtilizedMemBytesRequestId && `/request/${utilization.maxUnderUtilizedMemBytesRequestId}`} />
</div>
</LabeledColumn>
</div>
</div>
);
};

SlaveAggregates.propTypes = {
utilization: PropTypes.object.isRequired,
totalRequests: PropTypes.number.isRequired
};

export default SlaveAggregates;
24 changes: 24 additions & 0 deletions SingularityUI/app/components/machines/usage/LabeledColumn.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { PropTypes } from 'react';

const LabeledSection = ({title, width, children, className}) => (
<div className={`col-xs-${width} ${className || ''}`}>
<h4>{title}</h4>
<div className="row">
<div className="col-xs-12">
{children}
</div>
</div>
</div>
);

LabeledSection.propTypes = {
title: PropTypes.string,
width: PropTypes.number.isRequired,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.node),
React.PropTypes.node
]).isRequired,
className: PropTypes.string
};

export default LabeledSection;
Loading

0 comments on commit 8730005

Please sign in to comment.