stacking graphs

This commit is contained in:
Sérgio Ramos 2016-11-11 17:56:42 +00:00
parent 0a6a74765d
commit 875ca384a0
14 changed files with 266 additions and 207 deletions

View File

@ -19,7 +19,7 @@ module.exports = (server) => ({
when: new Date().getTime(), when: new Date().getTime(),
cpu: Math.random() * 100 cpu: Math.random() * 100
}); });
}, 400); }, 45);
cdm[id] = { cdm[id] = {
interval, interval,

View File

@ -2,11 +2,29 @@ const take = require('lodash.take');
const actions = { const actions = {
'UPDATE_STATS': (state, action) => { 'UPDATE_STATS': (state, action) => {
const data = [action.payload].concat(state[action.subscription] || []); const data = (state[action.subscription] || {
cpu: [],
mem: [],
disk: []
});
const newData = ['cpu', 'mem', 'disk'].reduce((sum, key) => {
const item = {
...action.payload.stats[key],
when: action.payload.when
};
const prepended = [item].concat(data[key]);
return {
...sum,
[key]: take(prepended, state.windowSize)
};
}, {});
return { return {
...state, ...state,
[action.subscription]: take(data, state.windowSize) [action.subscription]: newData
}; };
} }
}; };
@ -37,7 +55,7 @@ module.exports.subscribe = (id) => (dispatch, getState) => {
}); });
return dispatch({ return dispatch({
action: 'SUBSCRIBE', type: 'SUBSCRIBE',
payload: p payload: p
}); });
}; };
@ -58,7 +76,7 @@ module.exports.unsubscribe = (id) => (dispatch, getState) => {
}); });
return dispatch({ return dispatch({
action: 'UNSUBSCRIBE', type: 'UNSUBSCRIBE',
payload: p payload: p
}); });
}; };

View File

@ -1,144 +0,0 @@
const buildArray = require('build-array');
const Chart = require('chart.js');
const React = require('react');
// borderSkipped
// patch `.draw` to support `borderSkipped`:
// Chart.elements.Rectangle.prototype.draw = function() {
// var ctx = this._chart.ctx;
// var vm = this._view;
//
// var halfWidth = vm.width / 2,
// leftX = vm.x - halfWidth,
// rightX = vm.x + halfWidth,
// top = vm.base - (vm.base - vm.y),
// halfStroke = vm.borderWidth / 2;
//
// // Canvas doesn't allow us to stroke inside the width so we can
// // adjust the sizes to fit if we're setting a stroke on the line
// if (vm.borderWidth) {
// leftX += halfStroke;
// rightX -= halfStroke;
// top += halfStroke;
// }
//
// ctx.beginPath();
// ctx.fillStyle = vm.backgroundColor;
// ctx.strokeStyle = vm.borderColor;
// ctx.lineWidth = vm.borderWidth;
//
// var borderSkipped = !Array.isArray(vm.borderSkipped)
// ? [vm.borderSkipped]
// : vm.borderSkipped;
//
// // Corner points, from bottom-left to bottom-right clockwise
// // | 1 2 |
// // | 0 3 |
// var corners = [
// [leftX, vm.base, (borderSkipped.indexOf('bottom') >= 0), 'bottom'],
// [leftX, top, (borderSkipped.indexOf('left') >= 0), 'left'],
// [rightX, top, (borderSkipped.indexOf('top') >= 0), 'top'],
// [rightX, vm.base, (borderSkipped.indexOf('right') >= 0), 'right']
// ];
//
// function cornerAt(index) {
// return corners[index % 4];
// }
//
// // Draw rectangle from 'startCorner'
// var corner = cornerAt(0);
// ctx.moveTo(corner[0], corner[1]);
//
// for (var i = 1; i < 5; i++) {
// corner = cornerAt(i);
// ctx.lineTo(corner[0], corner[1]);
//
// if (!corner[2]) {
// ctx.stroke();
// }
// }
//
// console.log(corners);
//
// ctx.fill();
// };
module.exports = React.createClass({
ref: function(name) {
this._refs = this._refs || {};
return (el) => {
this._refs[name] = el;
};
},
fromData: function(data) {
return (data || []).map((d) => {
return d.cpu;
});
},
componentDidMount: function() {
const {
data = [],
bg,
border
} = this.props;
const bars = this.fromData(data);
this._chart = new Chart(this._refs.component, {
type: 'bar',
options: {
elements: {
rectangle: {
borderSkipped: ['bottom', 'left', 'right']
}
},
scales: {
xAxes: [{
display: false
}],
yAxes: [{
display: false
}]
},
legend: {
display: false
}
},
data: {
labels: buildArray(bars.length).map((v, i) => ''),
datasets: [{
borderWidth: 1,
borderColor: border,
backgroundColor: bg,
data: bars
}]
}
});
},
componentWillReceiveProps: function(nextProps) {
const {
data = [],
bg,
border
} = this.props;
const bars = this.fromData(data);
this._chart.data.labels = buildArray(bars.length).map((v, i) => '');
this._chart.data.datasets[0].backgroundColor = bg;
this._chart.data.datasets[0].borderColor = border;
this._chart.data.datasets[0].data = bars;
this._chart.update(0);
},
render: function() {
return (
<canvas
ref={this.ref('component')}
width='400'
height='400'
/>
);
}
});

View File

@ -0,0 +1,71 @@
const buildArray = require('build-array');
const Chart = require('chart.js');
const React = require('react');
module.exports = React.createClass({
ref: function(name) {
this._refs = this._refs || {};
return (el) => {
this._refs[name] = el;
};
},
componentDidMount: function() {
const {
datasets = [],
labels = 0,
stacked = false,
xAxe = false,
yAxe = false,
legend = false
} = this.props;
const _labels = !Array.isArray(labels)
? buildArray(labels).map((v, i) => '')
: labels;
this._chart = new Chart(this._refs.component, {
type: 'bar',
stacked: stacked,
responsive: true,
options: {
scales: {
xAxes: [{
display: xAxe,
stacked: stacked
}],
yAxes: [{
display: yAxe,
stacked: stacked
}]
},
legend: {
display: legend
}
},
data: {
labels:
datasets: datasets
}
});
},
componentWillReceiveProps: function(nextProps) {
const {
datasets = [],
labels = 0
} = this.props;
this._chart.data.datasets = datasets;
this._chart.data.labels = buildArray(labels).map((v, i) => '');
this._chart.update(0);
},
render: function() {
return (
<canvas
ref={this.ref('component')}
width='400'
height='400'
/>
);
}
});

View File

@ -0,0 +1,31 @@
const buildArray = require('build-array');
const Chart = require('./base');
const React = require('react');
const colors = {
user: 'rgb(255, 99, 132)',
sys: 'rgb(255, 159, 64)'
};
module.exports = ({
data = {},
windowSize
}) => {
const datasets = ['user', 'sys'].map((key) => {
return {
label: key,
backgroundColor: colors[key],
data: buildArray(windowSize).map((v, i) => ((data[i] || {})[key] || 0))
};
});
return (
<Chart
datasets={datasets}
stacked={true}
labels={datasets[0].data.length}
legend={true}
/>
);
};

View File

@ -0,0 +1,35 @@
const pretty = require('prettysize');
const buildArray = require('build-array');
const Chart = require('./base');
const React = require('react');
const colors = {
user: 'rgb(255, 99, 132)',
sys: 'rgb(255, 159, 64)'
};
module.exports = ({
data = [],
windowSize
}) => {
const datasets = [{
label: 'disk',
backgroundColor: 'rgb(255, 159, 64)',
data: buildArray(windowSize).map((v, i) => {
return data[i] ? (data[i].total - data[i].free) : 0;
})
}];
const labels = buildArray(windowSize).map((v, i) => {
return data[i] ? pretty(datasets[0].data[i]) : '';
});
return (
<Chart
datasets={datasets}
labels={labels}
legend={true}
/>
);
};

View File

@ -0,0 +1,8 @@
module.exports = {
CPU: require('./cpu'),
cpu: require('./cpu'),
Mem: require('./mem'),
mem: require('./mem'),
Disk: require('./disk'),
disk: require('./disk')
};

View File

@ -0,0 +1,28 @@
const buildArray = require('build-array');
const Chart = require('./base');
const React = require('react');
const colors = {
user: 'rgb(255, 99, 132)',
sys: 'rgb(255, 159, 64)'
};
module.exports = ({
data = [],
windowSize
}) => {
const datasets = [{
label: 'mem',
backgroundColor: 'rgb(255, 99, 132)',
data: buildArray(windowSize).map((v, i) => ((data[i] || {}).used || 0))
}];
return (
<Chart
datasets={datasets}
labels={datasets[0].data.length}
legend={true}
/>
);
};

View File

@ -13,8 +13,6 @@ client.connect((err) => {
if (err) { if (err) {
throw err; throw err;
} }
console.log('connected');
}); });
const store = Store({ const store = Store({

View File

@ -31,7 +31,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
} }
}; };
const Graph = connect( const Row = connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps, mapDispatchToProps,
)(React.createClass({ )(React.createClass({
@ -43,62 +43,47 @@ const Graph = connect(
}, },
render: function() { render: function() {
const { const {
data = [], data = {},
windowSize windowSize
} = this.props; } = this.props;
const _data = buildArray(windowSize).map((v, i) => { const charts = Object.keys(data).map((key, i, arr) => {
return data[i] ? data[i] : { if (!Chart[key]) {
cpu: 0, return null;
when: new Date().getTime() }
};
const chart = React.createElement(Chart[key], {
data: data[key],
windowSize
}); });
const median = data.reduce((sum, v) => (sum + v.cpu), 0) / data.length;
const bg = median > 50
? 'rgba(205, 54, 54, 0.3)'
: 'rgba(54, 74, 205, 0.3)';
const border = median > 50
? 'rgba(248, 51, 51, 0.5)'
: 'rgba(54, 73, 205, 0.5)';
return ( return (
<Chart <div key={key} className={`col-xs-${12/arr.length}`}>
data={_data} {chart}
bg={bg}
border={border}
median={median}
/>
);
}
}));
module.exports = ({
x,
y
}) => {
const m = buildArray(y).map((v, i) => {
const m = buildArray(x).map((v, y) => {
const id = `${i}${y}`;
return (
<div className={`col-xs-${12/x}`}>
<Graph key={id} id={id} />
</div> </div>
); );
}); });
return ( return (
<div className='row'> <div className='row'>
{m} {charts}
</div> </div>
); );
}
}));
module.exports = ({
rows
}) => {
const _rows = buildArray(rows).map((v, i) => {
return (
<Row id={i} key={i} />
);
}); });
return ( return (
<div> <div>
{m} {_rows}
</div> </div>
); );
}; };

View File

@ -17,7 +17,7 @@ module.exports = ({
return ( return (
<AppContainer> <AppContainer>
<Provider store={store}> <Provider store={store}>
<Matrix x={3} y={4} /> <Matrix rows={4} />
</Provider> </Provider>
</AppContainer> </AppContainer>
); );

View File

@ -4,6 +4,7 @@
"license": "private", "license": "private",
"main": "server/index.js", "main": "server/index.js",
"dependencies": { "dependencies": {
"async": "^2.1.2",
"autoprefixer": "^6.5.1", "autoprefixer": "^6.5.1",
"babel-eslint": "^7.0.0", "babel-eslint": "^7.0.0",
"babel-loader": "^6.2.5", "babel-loader": "^6.2.5",
@ -19,16 +20,21 @@
"chart.js": "^2.3.0", "chart.js": "^2.3.0",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"component-emitter": "^1.2.1", "component-emitter": "^1.2.1",
"cpu-percent": "^2.0.1",
"css-loader": "^0.25.0", "css-loader": "^0.25.0",
"d3": "^4.3.0", "d3": "^4.3.0",
"diskusage": "^0.1.5",
"hapi": "^15.2.0", "hapi": "^15.2.0",
"hapi-webpack-dev-plugin": "^1.1.4", "hapi-webpack-dev-plugin": "^1.1.4",
"inert": "^4.0.2", "inert": "^4.0.2",
"lodash.take": "^4.1.1", "lodash.take": "^4.1.1",
"metrics-os": "^1.0.1",
"nes": "^6.3.1", "nes": "^6.3.1",
"pidusage": "^1.1.0",
"postcss-loader": "^1.0.0", "postcss-loader": "^1.0.0",
"postcss-modules-values": "^1.2.2", "postcss-modules-values": "^1.2.2",
"postcss-nested": "^1.0.0", "postcss-nested": "^1.0.0",
"prettysize": "0.0.3",
"react": "^15.3.2", "react": "^15.3.2",
"react-dom": "^15.3.2", "react-dom": "^15.3.2",
"react-hot-loader": "^3.0.0-beta.6", "react-hot-loader": "^3.0.0-beta.6",

View File

@ -1,7 +1,39 @@
const Emitter = require('component-emitter'); const async = require('async');
const disk = require('diskusage');
const os = require('os');
const cdm = {}; const cdm = {};
const getCPU = (fn) => {
return fn(null, {
user: os.cpus().reduce((sum, cpu) => sum + cpu.times.user, 0),
sys: os.cpus().reduce((sum, cpu) => sum + cpu.times.sys, 0)
});
};
const getMem = (fn) => {
const free = os.freemem();
const total = os.totalmem();
const using = total - free;
const perc = (using / total) * 100;
return fn(null, {
used: perc
});
};
const getDisk = (fn) => {
disk.check('/', fn);
};
const getStats = (fn) => {
async.parallel({
cpu: getCPU,
mem: getMem,
disk: getDisk
}, fn);
};
module.exports = (server) => ({ module.exports = (server) => ({
on: (id) => { on: (id) => {
console.log('on', cdm[id]); console.log('on', cdm[id]);
@ -10,16 +42,17 @@ module.exports = (server) => ({
return; return;
} }
let messageId = 0; let messageId = 0;
const interval = setInterval(() => { const interval = setInterval(() => {
console.log(`publishing /stats/${id}`); console.log(`publishing /stats/${id}`);
getStats((err, stats) => {
server.publish(`/stats/${id}`, { server.publish(`/stats/${id}`, {
when: new Date().getTime(), when: new Date().getTime(),
cpu: Math.random() * 100 stats
}); });
}, 100); });
}, 1000);
cdm[id] = { cdm[id] = {
interval, interval,

View File

@ -3,16 +3,6 @@
<head> <head>
<title>React Boilerplate</title> <title>React Boilerplate</title>
<link rel="stylesheet" type="text/css" href="https://necolas.github.io/normalize.css/latest/normalize.css" /> <link rel="stylesheet" type="text/css" href="https://necolas.github.io/normalize.css/latest/normalize.css" />
<link rel="stylesheet" type="text/css" href="https://rawgit.com/epochjs/epoch/master/dist/css/epoch.css" />
<style>
.epoch.red canvas {
fill: rgba(205, 54, 54, 0.3);
}
.epoch.blue canvas {
fill: rgba(54, 74, 205, 0.3);
}
</style>
<style> <style>
.container-fluid, .container-fluid,
.container { .container {