parent
9f03c65f05
commit
263d1518af
@ -49,7 +49,6 @@
|
||||
"react/prefer-stateless-function": 2,
|
||||
"react/self-closing-comp": 2,
|
||||
"react/sort-comp": 2,
|
||||
"react/sort-prop-types": 2,
|
||||
"react/style-prop-object": 2,
|
||||
"react/jsx-boolean-value": [2, "never"],
|
||||
"react/jsx-closing-bracket-location": 2,
|
||||
|
@ -31,7 +31,6 @@
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.isempty": "^4.4.0",
|
||||
"lodash.template": "^4.4.0",
|
||||
"lodash.uniq": "^4.5.0",
|
||||
"moment": "^2.17.1",
|
||||
"param-case": "^2.1.0",
|
||||
"querystring": "^0.2.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import MetricsOutlet from '@components/metrics-outlet';
|
||||
import ItemMetricGroup from '@components/item-metric-group';
|
||||
import PropTypes from '@root/prop-types';
|
||||
|
||||
import {
|
||||
@ -20,7 +20,9 @@ const InstanceItem = ({
|
||||
<ListItemMeta onClick={toggleCollapsed}>
|
||||
<ListItemTitle>{instance.name}</ListItemTitle>
|
||||
</ListItemMeta>
|
||||
<MetricsOutlet datasets={instance.metrics} />
|
||||
<ItemMetricGroup
|
||||
datasets={instance.metrics}
|
||||
/>
|
||||
</ListItemView>
|
||||
<ListItemOptions>
|
||||
…
|
||||
|
@ -5,14 +5,7 @@ import Column from '@ui/components/column';
|
||||
import { ListItemOutlet } from '@ui/components/list';
|
||||
import PropTypes from '@root/prop-types';
|
||||
import Row from '@ui/components/row';
|
||||
|
||||
import {
|
||||
MetricGraph,
|
||||
MiniMetricMeta,
|
||||
MiniMetricTitle,
|
||||
MiniMetricSubtitle,
|
||||
MetricView
|
||||
} from '@ui/components/metric';
|
||||
import MetricItem from './item';
|
||||
|
||||
const StyledOutlet = styled(ListItemOutlet)`
|
||||
padding-left: 0;
|
||||
@ -28,19 +21,15 @@ const StyledRow = styled(Row)`
|
||||
}
|
||||
`;
|
||||
|
||||
const MetricsOutlet = ({
|
||||
const ItemMetricGroup = ({
|
||||
datasets = [],
|
||||
...props
|
||||
}) => {
|
||||
const _datasets = datasets.map((metric, i) => (
|
||||
<Column key={i} xs={4}>
|
||||
<MetricView mini borderless>
|
||||
<MiniMetricMeta>
|
||||
<MiniMetricTitle>Memory: 54%</MiniMetricTitle>
|
||||
<MiniMetricSubtitle>(1280/3000 MB)</MiniMetricSubtitle>
|
||||
</MiniMetricMeta>
|
||||
<MetricGraph data={metric.data} />
|
||||
</MetricView>
|
||||
<Column key={i} xs={12/datasets.length}>
|
||||
<MetricItem
|
||||
{...metric}
|
||||
/>
|
||||
</Column>
|
||||
));
|
||||
|
||||
@ -53,8 +42,8 @@ const MetricsOutlet = ({
|
||||
);
|
||||
};
|
||||
|
||||
MetricsOutlet.propTypes = {
|
||||
ItemMetricGroup.propTypes = {
|
||||
datasets: React.PropTypes.arrayOf(PropTypes.dataset)
|
||||
};
|
||||
|
||||
export default MetricsOutlet;
|
||||
export default ItemMetricGroup;
|
20
frontend/src/components/item-metric-group/item.js
Normal file
20
frontend/src/components/item-metric-group/item.js
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
import { MetricGraph, MetricView } from '@ui/components/metric';
|
||||
import PropTypes from '@root/prop-types';
|
||||
|
||||
const MetricItem = ({
|
||||
uuid,
|
||||
data
|
||||
}) => (
|
||||
<MetricView borderless mini>
|
||||
<MetricGraph data={data} />
|
||||
</MetricView>
|
||||
);
|
||||
|
||||
MetricItem.propTypes = {
|
||||
uuid: React.PropTypes.string,
|
||||
data: PropTypes.data
|
||||
};
|
||||
|
||||
export default MetricItem;
|
@ -1,6 +1,6 @@
|
||||
import styled from 'styled-components';
|
||||
import { Link } from '@ui/components/anchor';
|
||||
import MetricsOutlet from '@components/metrics-outlet';
|
||||
import ItemMetricGroup from '@components/item-metric-group';
|
||||
import { Checkbox, FormGroup } from '@ui/components/form';
|
||||
import PropTypes from '@root/prop-types';
|
||||
import forceArray from 'force-array';
|
||||
@ -101,7 +101,9 @@ const ServiceItem = ({
|
||||
{isChild && subtitle}
|
||||
{description}
|
||||
</ListItemMeta>
|
||||
<MetricsOutlet datasets={service.metrics} />
|
||||
<ItemMetricGroup
|
||||
datasets={service.metrics}
|
||||
/>
|
||||
</ListItemView>
|
||||
);
|
||||
|
||||
|
@ -21,7 +21,6 @@ const Services = (props) => {
|
||||
push
|
||||
} = props;
|
||||
|
||||
|
||||
// TODO: Move into "components" and fix absolute
|
||||
// positioning on responsive screens
|
||||
const instances = (instances = 0) => {
|
||||
@ -33,6 +32,7 @@ const Services = (props) => {
|
||||
`;
|
||||
|
||||
if ( instances.length <= 0 || instances <= 0 ) return;
|
||||
|
||||
return (
|
||||
<StyledButton tertiary>
|
||||
You have 5 instances
|
||||
@ -40,13 +40,15 @@ const Services = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const toggleValue = path === '/:org/projects/:projectId/services' ?
|
||||
'topology' : 'list';
|
||||
const toggleValue = path === '/:org/projects/:projectId/services'
|
||||
? 'topology'
|
||||
: 'list';
|
||||
|
||||
const onToggle = (value) => {
|
||||
const path = `/${org.id}/projects/${project.id}/services${
|
||||
value === 'list' ? '/list' : ''
|
||||
}`;
|
||||
|
||||
push(path);
|
||||
};
|
||||
|
||||
|
@ -6,6 +6,7 @@ import ServiceItem from '@components/service/item';
|
||||
import UnmanagedInstances from '@components/services/unmanaged-instances';
|
||||
import { toggleTooltip } from '@state/actions';
|
||||
import ServicesTooltip from '@components/services/tooltip';
|
||||
import { subscribeMetric } from '@state/thunks';
|
||||
|
||||
import {
|
||||
orgByIdSelector,
|
||||
@ -18,8 +19,17 @@ const StyledContainer = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
// TMP - single source of truth
|
||||
const duration = '1 hour';
|
||||
const interval = '2 minutes';
|
||||
|
||||
class Services extends React.Component {
|
||||
|
||||
// we DON'T want to unsubscribe once we started going
|
||||
componentWillMount() {
|
||||
this.props.subscribeMetric(interval);
|
||||
}
|
||||
|
||||
ref(name) {
|
||||
this._refs = this._refs || {};
|
||||
|
||||
@ -33,7 +43,7 @@ class Services extends React.Component {
|
||||
org = {},
|
||||
project = {},
|
||||
services = [],
|
||||
toggleTooltip = (() => {}),
|
||||
toggleTooltip = () => ({}),
|
||||
uiTooltip = {}
|
||||
} = this.props;
|
||||
|
||||
@ -87,7 +97,8 @@ Services.propTypes = {
|
||||
project: PropTypes.project,
|
||||
services: React.PropTypes.arrayOf(PropTypes.service),
|
||||
toggleTooltip: React.PropTypes.func,
|
||||
uiTooltip: React.PropTypes.object
|
||||
uiTooltip: React.PropTypes.object,
|
||||
subscribeMetric: React.PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, {
|
||||
@ -98,12 +109,16 @@ const mapStateToProps = (state, {
|
||||
}) => ({
|
||||
org: orgByIdSelector(match.params.org)(state),
|
||||
project: projectByIdSelector(match.params.projectId)(state),
|
||||
services: servicesByProjectIdSelector(match.params.projectId)(state),
|
||||
services: servicesByProjectIdSelector(match.params.projectId, {
|
||||
duration,
|
||||
interval
|
||||
})(state),
|
||||
uiTooltip: serviceUiTooltipSelector(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleTooltip: (data) => dispatch(toggleTooltip(data))
|
||||
toggleTooltip: (data) => dispatch(toggleTooltip(data)),
|
||||
subscribeMetric: (payload) => dispatch(subscribeMetric(payload))
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
1144
frontend/src/datasets.json
Normal file
1144
frontend/src/datasets.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@ import React from 'react';
|
||||
|
||||
import App from '@containers/app';
|
||||
import MockState from './mock-state.json';
|
||||
import Datasets from './datasets.json';
|
||||
import Store from '@state/store';
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
@ -15,6 +16,24 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
});
|
||||
}
|
||||
|
||||
// TMP - ensure datasets are at least 2 hrs long - START
|
||||
import getTwoHourDatasets from './utils/two-hour-metric-datasets';
|
||||
const twoHourLongDatasets = getTwoHourDatasets(Datasets);
|
||||
// TMP - ensure datasets are at least 2 hrs long - END
|
||||
|
||||
// TMP - plug fake metric data - START
|
||||
const datasets = MockState.metrics.data.datasets.map((dataset, index) => {
|
||||
const keyIndex = index%2 ? 0 : 1;
|
||||
const key = Object.keys(twoHourLongDatasets)[keyIndex];
|
||||
return {
|
||||
...dataset,
|
||||
data: twoHourLongDatasets[key]
|
||||
};
|
||||
});
|
||||
|
||||
MockState.metrics.data.datasets = datasets;
|
||||
// TMP - plug fake metric data - END
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={Store(MockState)}>
|
||||
<IntlProvider>
|
||||
|
@ -53,6 +53,7 @@
|
||||
},
|
||||
"metrics": {
|
||||
"ui": {
|
||||
"pos": 0,
|
||||
"durations": [
|
||||
"360",
|
||||
"720",
|
||||
@ -163,317 +164,11 @@
|
||||
"datasets": [{
|
||||
"type": "2aaa237d-42b3-442f-9094-a17aa470014b",
|
||||
"uuid": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"data": [{
|
||||
"firstQuartile": 15,
|
||||
"thirdQuartile": 15,
|
||||
"median": 15,
|
||||
"max": 15,
|
||||
"min": 15
|
||||
"data": []
|
||||
},{
|
||||
"firstQuartile": 26,
|
||||
"thirdQuartile": 26,
|
||||
"median": 26,
|
||||
"max": 26,
|
||||
"min": 26
|
||||
}, {
|
||||
"firstQuartile": 17,
|
||||
"thirdQuartile": 17,
|
||||
"median": 17,
|
||||
"max": 17,
|
||||
"min": 17
|
||||
}, {
|
||||
"firstQuartile": 15,
|
||||
"thirdQuartile": 25,
|
||||
"median": 19,
|
||||
"max": 19,
|
||||
"min": 20
|
||||
}, {
|
||||
"firstQuartile": 19,
|
||||
"thirdQuartile": 25,
|
||||
"median": 21,
|
||||
"max": 20,
|
||||
"min": 25
|
||||
}, {
|
||||
"firstQuartile": 24,
|
||||
"thirdQuartile": 30,
|
||||
"median": 25,
|
||||
"max": 26,
|
||||
"min": 27
|
||||
}, {
|
||||
"firstQuartile": 28,
|
||||
"thirdQuartile": 34,
|
||||
"median": 30,
|
||||
"max": 30,
|
||||
"min": 30
|
||||
}, {
|
||||
"firstQuartile": 30,
|
||||
"thirdQuartile": 45,
|
||||
"median": 35,
|
||||
"max": 40,
|
||||
"min": 40
|
||||
}, {
|
||||
"firstQuartile": 20,
|
||||
"thirdQuartile": 55,
|
||||
"median": 45,
|
||||
"max": 44,
|
||||
"min": 44
|
||||
}, {
|
||||
"firstQuartile": 55,
|
||||
"thirdQuartile": 55,
|
||||
"median": 55,
|
||||
"max": 55,
|
||||
"min": 55
|
||||
}, {
|
||||
"firstQuartile": 57,
|
||||
"thirdQuartile": 56,
|
||||
"median": 57,
|
||||
"max": 58,
|
||||
"min": 57
|
||||
}, {
|
||||
"firstQuartile": 57,
|
||||
"thirdQuartile": 56,
|
||||
"median": 56,
|
||||
"max": 56,
|
||||
"min": 56
|
||||
}, {
|
||||
"firstQuartile": 60,
|
||||
"thirdQuartile": 56,
|
||||
"median": 60,
|
||||
"max": 60,
|
||||
"min": 60
|
||||
}, {
|
||||
"firstQuartile": 57,
|
||||
"thirdQuartile": 57,
|
||||
"median": 57,
|
||||
"max": 57,
|
||||
"min": 57
|
||||
}, {
|
||||
"firstQuartile": 57,
|
||||
"thirdQuartile": 55,
|
||||
"median": 55,
|
||||
"max": 55,
|
||||
"min": 55
|
||||
}, {
|
||||
"firstQuartile": 20,
|
||||
"thirdQuartile": 45,
|
||||
"median": 45,
|
||||
"max": 45,
|
||||
"min": 45
|
||||
}, {
|
||||
"firstQuartile": 15,
|
||||
"thirdQuartile": 40,
|
||||
"median": 30,
|
||||
"max": 49,
|
||||
"min": 30
|
||||
}, {
|
||||
"firstQuartile": 15,
|
||||
"thirdQuartile": 15,
|
||||
"median": 15,
|
||||
"max": 15,
|
||||
"min": 15
|
||||
}, {
|
||||
"firstQuartile": 26,
|
||||
"thirdQuartile": 26,
|
||||
"median": 26,
|
||||
"max": 26,
|
||||
"min": 26
|
||||
}, {
|
||||
"firstQuartile": 17,
|
||||
"thirdQuartile": 17,
|
||||
"median": 17,
|
||||
"max": 17,
|
||||
"min": 17
|
||||
}, {
|
||||
"firstQuartile": 15,
|
||||
"thirdQuartile": 25,
|
||||
"median": 19,
|
||||
"max": 19,
|
||||
"min": 20
|
||||
}, {
|
||||
"firstQuartile": 19,
|
||||
"thirdQuartile": 25,
|
||||
"median": 21,
|
||||
"max": 20,
|
||||
"min": 25
|
||||
}, {
|
||||
"firstQuartile": 24,
|
||||
"thirdQuartile": 30,
|
||||
"median": 25,
|
||||
"max": 26,
|
||||
"min": 10
|
||||
}, {
|
||||
"firstQuartile": 28,
|
||||
"thirdQuartile": 34,
|
||||
"median": 30,
|
||||
"max": 30,
|
||||
"min": 30
|
||||
}, {
|
||||
"firstQuartile": 30,
|
||||
"thirdQuartile": 45,
|
||||
"median": 35,
|
||||
"max": 40,
|
||||
"min": 40
|
||||
}, {
|
||||
"firstQuartile": 20,
|
||||
"thirdQuartile": 55,
|
||||
"median": 45,
|
||||
"max": 44,
|
||||
"min": 44
|
||||
}, {
|
||||
"firstQuartile": 55,
|
||||
"thirdQuartile": 55,
|
||||
"median": 55,
|
||||
"max": 55,
|
||||
"min": 55
|
||||
}, {
|
||||
"firstQuartile": 57,
|
||||
"thirdQuartile": 56,
|
||||
"median": 57,
|
||||
"max": 58,
|
||||
"min": 57
|
||||
}, {
|
||||
"firstQuartile": 57,
|
||||
"thirdQuartile": 56,
|
||||
"median": 56,
|
||||
"max": 56,
|
||||
"min": 56
|
||||
}, {
|
||||
"firstQuartile": 60,
|
||||
"thirdQuartile": 56,
|
||||
"median": 60,
|
||||
"max": 60,
|
||||
"min": 60
|
||||
}, {
|
||||
"firstQuartile": 57,
|
||||
"thirdQuartile": 57,
|
||||
"median": 57,
|
||||
"max": 57,
|
||||
"min": 57
|
||||
}, {
|
||||
"firstQuartile": 57,
|
||||
"thirdQuartile": 55,
|
||||
"median": 55,
|
||||
"max": 55,
|
||||
"min": 55
|
||||
}, {
|
||||
"firstQuartile": 20,
|
||||
"thirdQuartile": 45,
|
||||
"median": 45,
|
||||
"max": 45,
|
||||
"min": 45
|
||||
}, {
|
||||
"firstQuartile": 15,
|
||||
"thirdQuartile": 40,
|
||||
"median": 30,
|
||||
"max": 49,
|
||||
"min": 30
|
||||
}, {
|
||||
"firstQuartile": 15,
|
||||
"thirdQuartile": 15,
|
||||
"median": 15,
|
||||
"max": 15,
|
||||
"min": 15
|
||||
}, {
|
||||
"firstQuartile": 26,
|
||||
"thirdQuartile": 26,
|
||||
"median": 26,
|
||||
"max": 26,
|
||||
"min": 26
|
||||
}, {
|
||||
"firstQuartile": 17,
|
||||
"thirdQuartile": 17,
|
||||
"median": 17,
|
||||
"max": 17,
|
||||
"min": 17
|
||||
}, {
|
||||
"firstQuartile": 15,
|
||||
"thirdQuartile": 25,
|
||||
"median": 19,
|
||||
"max": 19,
|
||||
"min": 20
|
||||
}, {
|
||||
"firstQuartile": 19,
|
||||
"thirdQuartile": 25,
|
||||
"median": 21,
|
||||
"max": 20,
|
||||
"min": 25
|
||||
}, {
|
||||
"firstQuartile": 24,
|
||||
"thirdQuartile": 30,
|
||||
"median": 25,
|
||||
"max": 26,
|
||||
"min": 27
|
||||
}, {
|
||||
"firstQuartile": 28,
|
||||
"thirdQuartile": 34,
|
||||
"median": 30,
|
||||
"max": 30,
|
||||
"min": 30
|
||||
}, {
|
||||
"firstQuartile": 30,
|
||||
"thirdQuartile": 45,
|
||||
"median": 35,
|
||||
"max": 40,
|
||||
"min": 40
|
||||
}, {
|
||||
"firstQuartile": 20,
|
||||
"thirdQuartile": 55,
|
||||
"median": 45,
|
||||
"max": 44,
|
||||
"min": 44
|
||||
}, {
|
||||
"firstQuartile": 55,
|
||||
"thirdQuartile": 55,
|
||||
"median": 55,
|
||||
"max": 55,
|
||||
"min": 55
|
||||
}, {
|
||||
"firstQuartile": 57,
|
||||
"thirdQuartile": 56,
|
||||
"median": 57,
|
||||
"max": 58,
|
||||
"min": 57
|
||||
}, {
|
||||
"firstQuartile": 57,
|
||||
"thirdQuartile": 56,
|
||||
"median": 56,
|
||||
"max": 56,
|
||||
"min": 56
|
||||
}, {
|
||||
"firstQuartile": 60,
|
||||
"thirdQuartile": 56,
|
||||
"median": 60,
|
||||
"max": 60,
|
||||
"min": 60
|
||||
}, {
|
||||
"firstQuartile": 57,
|
||||
"thirdQuartile": 57,
|
||||
"median": 57,
|
||||
"max": 57,
|
||||
"min": 57
|
||||
}, {
|
||||
"firstQuartile": 57,
|
||||
"thirdQuartile": 55,
|
||||
"median": 55,
|
||||
"max": 55,
|
||||
"min": 55
|
||||
}, {
|
||||
"firstQuartile": 20,
|
||||
"thirdQuartile": 45,
|
||||
"median": 45,
|
||||
"max": 45,
|
||||
"min": 45
|
||||
}, {
|
||||
"firstQuartile": 15,
|
||||
"thirdQuartile": 40,
|
||||
"median": 30,
|
||||
"max": 49,
|
||||
"min": 30
|
||||
}]
|
||||
},{
|
||||
"type": "dca08514-72e5-46ce-ad91-e68b3b0914d9",
|
||||
"type": "dca08514-72e5-46ce-ad92-e68b3b0914d4",
|
||||
"uuid": "4e6ee79a-7453-4fc6-b9da-7ae1e41138ed",
|
||||
"data": [{"firstQuartile":1.62,"thirdQuartile":1.62,"median":1.62,"max":1.62,"min":1.62},{"firstQuartile":1.67,"thirdQuartile":1.67,"median":1.67,"max":1.67,"min":1.67},{"firstQuartile":1.63,"thirdQuartile":1.63,"median":1.63,"max":1.63,"min":1.63},{"firstQuartile":1.62,"thirdQuartile":1.66,"median":1.64,"max":1.64,"min":1.64},{"firstQuartile":1.64,"thirdQuartile":1.66,"median":1.64,"max":1.64,"min":1.66},{"firstQuartile":1.66,"thirdQuartile":1.69,"median":1.66,"max":1.67,"min":1.67},{"firstQuartile":1.68,"thirdQuartile":1.70,"median":1.69,"max":1.69,"min":1.69},{"firstQuartile":1.69,"thirdQuartile":1.75,"median":1.71,"max":1.73,"min":1.73},{"firstQuartile":1.64,"thirdQuartile":1.80,"median":1.75,"max":1.75,"min":1.75},{"firstQuartile":1.80,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.81,"max":1.81,"min":1.81},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.82,"thirdQuartile":1.80,"median":1.82,"max":1.82,"min":1.82},{"firstQuartile":1.81,"thirdQuartile":1.81,"median":1.81,"max":1.81,"min":1.81},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.64,"thirdQuartile":1.75,"median":1.75,"max":1.75,"min":1.75},{"firstQuartile":1.62,"thirdQuartile":1.73,"median":1.69,"max":1.77,"min":1.69},{"firstQuartile":1.62,"thirdQuartile":1.62,"median":1.62,"max":1.62,"min":1.62},{"firstQuartile":1.67,"thirdQuartile":1.67,"median":1.67,"max":1.67,"min":1.67},{"firstQuartile":1.63,"thirdQuartile":1.63,"median":1.63,"max":1.63,"min":1.63},{"firstQuartile":1.62,"thirdQuartile":1.66,"median":1.64,"max":1.64,"min":1.64},{"firstQuartile":1.64,"thirdQuartile":1.66,"median":1.64,"max":1.64,"min":1.66},{"firstQuartile":1.66,"thirdQuartile":1.69,"median":1.66,"max":1.67,"min":1.59},{"firstQuartile":1.68,"thirdQuartile":1.70,"median":1.69,"max":1.69,"min":1.69},{"firstQuartile":1.69,"thirdQuartile":1.75,"median":1.71,"max":1.73,"min":1.73},{"firstQuartile":1.64,"thirdQuartile":1.80,"median":1.75,"max":1.75,"min":1.75},{"firstQuartile":1.80,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.81,"max":1.81,"min":1.81},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.82,"thirdQuartile":1.80,"median":1.82,"max":1.82,"min":1.82},{"firstQuartile":1.81,"thirdQuartile":1.81,"median":1.81,"max":1.81,"min":1.81},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.64,"thirdQuartile":1.75,"median":1.75,"max":1.75,"min":1.75},{"firstQuartile":1.62,"thirdQuartile":1.73,"median":1.69,"max":1.77,"min":1.69},{"firstQuartile":1.62,"thirdQuartile":1.62,"median":1.62,"max":1.62,"min":1.62},{"firstQuartile":1.67,"thirdQuartile":1.67,"median":1.67,"max":1.67,"min":1.67},{"firstQuartile":1.63,"thirdQuartile":1.63,"median":1.63,"max":1.63,"min":1.63},{"firstQuartile":1.62,"thirdQuartile":1.66,"median":1.64,"max":1.64,"min":1.64},{"firstQuartile":1.64,"thirdQuartile":1.66,"median":1.64,"max":1.64,"min":1.66},{"firstQuartile":1.66,"thirdQuartile":1.69,"median":1.66,"max":1.67,"min":1.67},{"firstQuartile":1.68,"thirdQuartile":1.70,"median":1.69,"max":1.69,"min":1.69},{"firstQuartile":1.69,"thirdQuartile":1.75,"median":1.71,"max":1.73,"min":1.73},{"firstQuartile":1.64,"thirdQuartile":1.80,"median":1.75,"max":1.75,"min":1.75},{"firstQuartile":1.80,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.81,"max":1.81,"min":1.81},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.82,"thirdQuartile":1.80,"median":1.82,"max":1.82,"min":1.82},{"firstQuartile":1.81,"thirdQuartile":1.81,"median":1.81,"max":1.81,"min":1.81},{"firstQuartile":1.81,"thirdQuartile":1.80,"median":1.80,"max":1.80,"min":1.80},{"firstQuartile":1.64,"thirdQuartile":1.75,"median":1.75,"max":1.75,"min":1.75},{"firstQuartile":1.62,"thirdQuartile":1.73,"median":1.69,"max":1.77,"min":1.69}]
|
||||
"data": []
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
@ -46,18 +46,18 @@ const MetricType = React.PropTypes.shape({
|
||||
...BaseObject
|
||||
});
|
||||
|
||||
const Dataset = React.PropTypes.shape({
|
||||
uuid: React.PropTypes.string,
|
||||
type: MetricType,
|
||||
data: React.PropTypes.arrayOf(
|
||||
React.PropTypes.shape({
|
||||
const Data = React.PropTypes.shape({
|
||||
firstQuartile: React.PropTypes.number,
|
||||
thirdQuartile: React.PropTypes.number,
|
||||
median: React.PropTypes.number,
|
||||
max: React.PropTypes.number,
|
||||
min: React.PropTypes.number
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
const Dataset = React.PropTypes.shape({
|
||||
uuid: React.PropTypes.string,
|
||||
type: MetricType,
|
||||
data: React.PropTypes.arrayOf(Data)
|
||||
});
|
||||
|
||||
const Sections = React.PropTypes.arrayOf(
|
||||
@ -74,5 +74,6 @@ export default {
|
||||
instance: Instance,
|
||||
metric: Metric,
|
||||
metricType: MetricType,
|
||||
dataset: Dataset
|
||||
dataset: Dataset,
|
||||
data: Data
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import constantCase from 'constant-case';
|
||||
import { createAction } from 'redux-actions';
|
||||
// import thunks from '@state/thunks';
|
||||
|
||||
const APP = constantCase(process.env['APP_NAME']);
|
||||
|
||||
@ -54,3 +53,5 @@ export const handleNewProject =
|
||||
createAction(`${APP}/CREATE_NEW_PROJECT`);
|
||||
export const toggleTooltip =
|
||||
createAction(`${APP}/TOGGLE_QUICK_ACTIONS_TOOLTIP`);
|
||||
export const refreshMetrics =
|
||||
createAction(`${APP}/REFRESH_METRICS`);
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { handleActions } from 'redux-actions';
|
||||
import { metricDurationChange } from '@state/actions';
|
||||
import { metricDurationChange, refreshMetrics } from '@state/actions';
|
||||
|
||||
export default handleActions({
|
||||
[metricDurationChange.toString()]: (state, action) => {
|
||||
return ({
|
||||
[metricDurationChange.toString()]: (state, action) => ({
|
||||
...state,
|
||||
ui: {
|
||||
...state.ui,
|
||||
@ -12,6 +11,15 @@ export default handleActions({
|
||||
duration: action.payload.duration
|
||||
}
|
||||
}
|
||||
}),
|
||||
[refreshMetrics.toString()]: (state) => {
|
||||
|
||||
return ({
|
||||
...state,
|
||||
ui: {
|
||||
...state.ui,
|
||||
pos: state.ui.pos + 1
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {});
|
||||
|
@ -66,14 +66,207 @@ const orgSections = (orgId) => createSelector(
|
||||
|
||||
const isCollapsed = (collapsed, uuid) => collapsed.indexOf(uuid) >= 0;
|
||||
|
||||
const datasets = (metricsData, serviceOrInstanceMetrics, metricsUI) =>
|
||||
serviceOrInstanceMetrics.map((soim) => ({
|
||||
...find(metricsData.datasets, ['uuid', soim.dataset]),
|
||||
const metricByInterval = (data = [], {
|
||||
min = 0,
|
||||
max = 100
|
||||
}, {
|
||||
duration = '1 hour',
|
||||
interval = '5 minutes'
|
||||
}) => {
|
||||
const getDurationArgs = (value) => {
|
||||
const [durationNumber, durationType] = value.split(/\s/);
|
||||
return [Number(durationNumber), durationType];
|
||||
};
|
||||
|
||||
const _duration = moment.duration(...getDurationArgs(duration));
|
||||
const _interval = moment.duration(...getDurationArgs(interval));
|
||||
|
||||
const roundUpDate = (date) => {
|
||||
const mod = date.valueOf() % _interval.valueOf();
|
||||
const diff = moment.duration(_interval.valueOf() - mod);
|
||||
return moment(date).add(diff);
|
||||
};
|
||||
|
||||
const roundDownDate = (date) => {
|
||||
const mod = date.valueOf() % _interval.valueOf();
|
||||
return moment(date).subtract(mod);
|
||||
};
|
||||
|
||||
const lastDate = roundUpDate(moment(data[data.length - 1][0], 'X'));
|
||||
const firstDate = moment(data[0][0], 'X');
|
||||
|
||||
const getStart = () => {
|
||||
const hardStart = moment(lastDate).subtract(_duration);
|
||||
|
||||
return hardStart.isBefore(firstDate)
|
||||
? roundDownDate(firstDate)
|
||||
: roundUpDate(hardStart);
|
||||
};
|
||||
|
||||
const _start = getStart();
|
||||
|
||||
const genSample = (start) => ({
|
||||
start: start,
|
||||
end: moment(start).add(_interval),
|
||||
values: []
|
||||
});
|
||||
|
||||
const genStats = (sample) => {
|
||||
const data = sample.values.map((r) => r.v);
|
||||
|
||||
return {
|
||||
start: sample.start.valueOf(),
|
||||
end: sample.end.valueOf(),
|
||||
firstQuartile: statistics.quantile(data, 0.25),
|
||||
median: statistics.median(data),
|
||||
thirdQuartile: statistics.quantile(data, 0.75),
|
||||
max: statistics.max(data),
|
||||
min: statistics.min(data),
|
||||
stddev: statistics.sampleStandardDeviation(data)
|
||||
};
|
||||
};
|
||||
|
||||
const intervals = data.reduce((samples, value, i) => {
|
||||
const sample = samples[samples.length - 1];
|
||||
const previousSample = samples[samples.length - 2];
|
||||
|
||||
const record = {
|
||||
v: Number(value[1]),
|
||||
t: moment(value[0], 'X')
|
||||
};
|
||||
|
||||
if (record.t.isBefore(_start)) {
|
||||
return samples;
|
||||
}
|
||||
|
||||
const split = () => {
|
||||
const stats = genStats(sample);
|
||||
|
||||
const nextSample = {
|
||||
...genSample(sample.end),
|
||||
values: [record]
|
||||
};
|
||||
|
||||
samples[samples.length - 1] = {
|
||||
...sample,
|
||||
stats
|
||||
};
|
||||
|
||||
return [
|
||||
...samples,
|
||||
nextSample
|
||||
];
|
||||
};
|
||||
|
||||
const append = (sample) => {
|
||||
samples[samples.length - 1] = {
|
||||
...sample,
|
||||
values: [...sample.values, record]
|
||||
};
|
||||
|
||||
return samples;
|
||||
};
|
||||
|
||||
const isWithin = (
|
||||
record.t.isSameOrAfter(sample.start) &&
|
||||
record.t.isSameOrBefore(sample.end)
|
||||
);
|
||||
|
||||
const isBefore = record.t.isBefore(sample.start);
|
||||
const isAfter = record.t.isAfter(sample.end);
|
||||
let newSamples = samples;
|
||||
|
||||
if (isWithin) {
|
||||
newSamples = append(sample);
|
||||
}
|
||||
|
||||
if (isBefore) {
|
||||
newSamples = append(previousSample);
|
||||
}
|
||||
|
||||
if (isAfter) {
|
||||
newSamples = split();
|
||||
}
|
||||
|
||||
if ((i + 1) >= data.length) {
|
||||
const thisSample = newSamples[newSamples.length - 1];
|
||||
const lastStats = genStats(thisSample);
|
||||
|
||||
newSamples[newSamples.length - 1] = {
|
||||
...thisSample,
|
||||
stats: lastStats
|
||||
};
|
||||
}
|
||||
|
||||
return newSamples;
|
||||
}, [
|
||||
genSample(_start)
|
||||
]);
|
||||
|
||||
return {
|
||||
start: _start.valueOf(),
|
||||
end: lastDate.valueOf(),
|
||||
duration: _duration.valueOf(),
|
||||
interval: _interval.valueOf(),
|
||||
min: min,
|
||||
max: max,
|
||||
values: intervals.map((sample) => sample.stats),
|
||||
__intervals: IS_TEST ? intervals : []
|
||||
};
|
||||
};
|
||||
|
||||
// TMP - get min and max for total data - START
|
||||
const getMinMax = (data) => {
|
||||
const values = data.map((d) => Number(d[1]));
|
||||
const min = statistics.min(values);
|
||||
const max = statistics.max(values);
|
||||
|
||||
return {
|
||||
min,
|
||||
max
|
||||
};
|
||||
};
|
||||
// TMP - get min and max for total dataset - END
|
||||
|
||||
// TMP - dataset playback - START
|
||||
import { getDurationMilliseconds } from '../utils/duration-interval';
|
||||
|
||||
const getDataSubset = (data, merticsUI, metricOptions) => {
|
||||
const duration = getDurationMilliseconds(metricOptions.duration)/1000;
|
||||
const interval = getDurationMilliseconds(metricOptions.interval)/1000;
|
||||
const start = data[0][0] + interval*merticsUI.pos;
|
||||
const end = start + duration;
|
||||
return data.reduce((acc, d) => {
|
||||
if(d[0] >= start && d[0] <= end) {
|
||||
acc.push(d);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
// TMP - dataset playback - END
|
||||
|
||||
const datasets = (
|
||||
metricsData,
|
||||
serviceOrInstanceMetrics,
|
||||
metricsUI,
|
||||
metricOptions = {
|
||||
duration: '1 hour',
|
||||
interval: '2 minutes'
|
||||
}
|
||||
) =>
|
||||
serviceOrInstanceMetrics.map((soim) => {
|
||||
const dataset = find(metricsData.datasets, ['uuid', soim.dataset]);
|
||||
const dataSubset = getDataSubset(dataset.data, metricsUI, metricOptions);
|
||||
const minMax = getMinMax(dataset.data);
|
||||
return ({
|
||||
...dataset,
|
||||
data: metricByInterval(dataSubset, minMax, metricOptions),
|
||||
type: find(metricsData.types, ['uuid', soim.type]),
|
||||
...metricsUI[soim.dataset]
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
const servicesByProjectId = (projectId) => createSelector(
|
||||
const servicesByProjectId = (projectId, metricOptions) => createSelector(
|
||||
[services, projectById(projectId), collapsedServices, metricsData, metricsUI],
|
||||
(services, project, collapsed, metrics, metricsUI) =>
|
||||
services.filter((s) => s.project === project.uuid)
|
||||
@ -84,7 +277,7 @@ const servicesByProjectId = (projectId) => createSelector(
|
||||
.filter((s) => !s.parent)
|
||||
.map((service) => ({
|
||||
...service,
|
||||
metrics: datasets(metrics, service.metrics, metricsUI),
|
||||
metrics: datasets(metrics, service.metrics, metricsUI, metricOptions),
|
||||
collapsed: isCollapsed(collapsed, service.uuid),
|
||||
services: service.services.map((service) => ({
|
||||
...service,
|
||||
@ -229,161 +422,6 @@ const peopleByProjectId = (projectId) => createSelector(
|
||||
}
|
||||
);
|
||||
|
||||
const metricByInterval = (data = [], {
|
||||
duration = '1 hour',
|
||||
interval = '5 minutes'
|
||||
}) => {
|
||||
const getDurationArgs = (value) => {
|
||||
const [durationNumber, durationType] = value.split(/\s/);
|
||||
return [Number(durationNumber), durationType];
|
||||
};
|
||||
|
||||
const _duration = moment.duration(...getDurationArgs(duration));
|
||||
const _interval = moment.duration(...getDurationArgs(interval));
|
||||
|
||||
const roundUpDate = (date) => {
|
||||
const mod = date.valueOf() % _interval.valueOf();
|
||||
const diff = moment.duration(_interval.valueOf() - mod);
|
||||
return moment(date).add(diff);
|
||||
};
|
||||
|
||||
const roundDownDate = (date) => {
|
||||
const mod = date.valueOf() % _interval.valueOf();
|
||||
return moment(date).subtract(mod);
|
||||
};
|
||||
|
||||
const lastDate = roundUpDate(moment(data[data.length - 1][0], 'X'));
|
||||
const firstDate = moment(data[0][0], 'X');
|
||||
|
||||
const getStart = () => {
|
||||
const hardStart = moment(lastDate).subtract(_duration);
|
||||
|
||||
return hardStart.isBefore(firstDate)
|
||||
? roundDownDate(firstDate)
|
||||
: roundUpDate(hardStart);
|
||||
};
|
||||
|
||||
const _start = getStart();
|
||||
|
||||
const genSample = (start) => ({
|
||||
start: start,
|
||||
end: moment(start).add(_interval),
|
||||
values: []
|
||||
});
|
||||
|
||||
const genStats = (sample) => {
|
||||
const data = sample.values.map((r) => r.v);
|
||||
|
||||
return {
|
||||
start: sample.start.valueOf(),
|
||||
end: sample.end.valueOf(),
|
||||
firstQuartile: statistics.quantile(data, 0.25),
|
||||
median: statistics.median(data),
|
||||
thirdQuartile: statistics.quantile(data, 0.75),
|
||||
max: statistics.max(data),
|
||||
min: statistics.min(data),
|
||||
stddev: statistics.sampleStandardDeviation(data)
|
||||
};
|
||||
};
|
||||
|
||||
const intervals = data.reduce((samples, value, i) => {
|
||||
const sample = samples[samples.length - 1];
|
||||
const previousSample = samples[samples.length - 2];
|
||||
|
||||
const record = {
|
||||
v: Number(value[1]),
|
||||
t: moment(value[0], 'X')
|
||||
};
|
||||
|
||||
if (record.t.isBefore(_start)) {
|
||||
return samples;
|
||||
}
|
||||
|
||||
const split = () => {
|
||||
const stats = genStats(sample);
|
||||
|
||||
const nextSample = {
|
||||
...genSample(sample.end),
|
||||
values: [record]
|
||||
};
|
||||
|
||||
samples[samples.length - 1] = {
|
||||
...sample,
|
||||
stats
|
||||
};
|
||||
|
||||
return [
|
||||
...samples,
|
||||
nextSample
|
||||
];
|
||||
};
|
||||
|
||||
const append = (sample) => {
|
||||
samples[samples.length - 1] = {
|
||||
...sample,
|
||||
values: [...sample.values, record]
|
||||
};
|
||||
|
||||
return samples;
|
||||
};
|
||||
|
||||
const isWithin = (
|
||||
record.t.isSameOrAfter(sample.start) &&
|
||||
record.t.isSameOrBefore(sample.end)
|
||||
);
|
||||
|
||||
const isBefore = record.t.isBefore(sample.start);
|
||||
const isAfter = record.t.isAfter(sample.end);
|
||||
let newSamples = samples;
|
||||
|
||||
if (isWithin) {
|
||||
newSamples = append(sample);
|
||||
}
|
||||
|
||||
if (isBefore) {
|
||||
newSamples = append(previousSample);
|
||||
}
|
||||
|
||||
if (isAfter) {
|
||||
newSamples = split();
|
||||
}
|
||||
|
||||
if ((i + 1) >= data.length) {
|
||||
const thisSample = newSamples[newSamples.length - 1];
|
||||
const lastStats = genStats(thisSample);
|
||||
|
||||
newSamples[newSamples.length - 1] = {
|
||||
...thisSample,
|
||||
stats: lastStats
|
||||
};
|
||||
}
|
||||
|
||||
return newSamples;
|
||||
}, [
|
||||
genSample(_start)
|
||||
]);
|
||||
|
||||
// TMP for min / max
|
||||
const allValues = intervals.reduce((stats, sample) => {
|
||||
const sampleValues = sample.values.map((value) => value.v);
|
||||
return stats.concat(sampleValues);
|
||||
},[]);
|
||||
|
||||
const min = statistics.min(allValues);
|
||||
const max = statistics.max(allValues);
|
||||
|
||||
return {
|
||||
start: _start.valueOf(),
|
||||
end: lastDate.valueOf(),
|
||||
duration: _duration.valueOf(),
|
||||
interval: _interval.valueOf(),
|
||||
min: min,
|
||||
max: max,
|
||||
values: intervals.map((sample) => sample.stats),
|
||||
__intervals: IS_TEST ? intervals : []
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
account as accountSelector,
|
||||
accountUi as accountUISelector,
|
||||
|
@ -1,3 +1,4 @@
|
||||
// import app from '@state/thunks/app';
|
||||
|
||||
export {};
|
||||
export {
|
||||
subscribe as subscribeMetric,
|
||||
unsubscribe as unsubscribeMetric
|
||||
} from './metrics';
|
||||
|
24
frontend/src/state/thunks/metrics.js
Normal file
24
frontend/src/state/thunks/metrics.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { getDurationMilliseconds } from '../../utils/duration-interval';
|
||||
import { refreshMetrics } from '../actions';
|
||||
|
||||
let timeoutId = null;
|
||||
|
||||
const tick = (dispatch) => {
|
||||
dispatch(refreshMetrics());
|
||||
};
|
||||
|
||||
export const subscribe = (interval) => (dispatch) => {
|
||||
if(timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
const timeout = interval ?
|
||||
getDurationMilliseconds(interval) :
|
||||
120 * 1000;
|
||||
timeoutId = setTimeout(tick, timeout, dispatch);
|
||||
};
|
||||
|
||||
export const unsubscribe = () => () => {
|
||||
if(timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
};
|
15
frontend/src/utils/duration-interval.js
Normal file
15
frontend/src/utils/duration-interval.js
Normal file
@ -0,0 +1,15 @@
|
||||
import moment from 'moment';
|
||||
|
||||
const getDurationArgs = (value) => {
|
||||
const [durationNumber, durationType] = value.split(/\s/);
|
||||
return [Number(durationNumber), durationType];
|
||||
};
|
||||
|
||||
const getDurationMilliseconds = (value) => {
|
||||
return moment.duration(...getDurationArgs(value)).valueOf();
|
||||
};
|
||||
|
||||
export {
|
||||
getDurationArgs,
|
||||
getDurationMilliseconds
|
||||
};
|
49
frontend/src/utils/two-hour-metric-datasets.js
Normal file
49
frontend/src/utils/two-hour-metric-datasets.js
Normal file
@ -0,0 +1,49 @@
|
||||
/* eslint-disable */
|
||||
// This needs to be at least two hours long
|
||||
// We need to establish the start time and calculate what the end time is - two hours later
|
||||
// Then, add the dataset to the array, and calculate its duration
|
||||
// Then keep adding the dataset to the array, updating each values timstamp with the duration diff * adding,
|
||||
// until we have data that's at least two hours long
|
||||
/* eslint-enable */
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
const getTwoHourDatasets = (Datasets) => {
|
||||
|
||||
return Object.keys(Datasets).reduce((datasets, key) => {
|
||||
const dataset = Datasets[key];
|
||||
|
||||
const datasetStart = moment(dataset[0][0], 'X');
|
||||
const datasetEnd = moment(dataset[dataset.length - 1][0], 'X');
|
||||
const datasetDuration = moment(datasetEnd.valueOf())
|
||||
.subtract(datasetStart.valueOf()).valueOf();
|
||||
|
||||
// number of times we need to add the dataset
|
||||
// so that it's at least 2 hrs long
|
||||
const count = Math.ceil(moment.duration(2, 'hours')
|
||||
.valueOf()/datasetDuration);
|
||||
|
||||
// update each data's timestamp depending on round of being added
|
||||
const getDataset = (dataset, duration, iterationIndex) => {
|
||||
return dataset.map((dataset) => {
|
||||
const timestamp = dataset[0] + duration*iterationIndex;
|
||||
return [
|
||||
timestamp,
|
||||
dataset[1]
|
||||
];
|
||||
});
|
||||
};
|
||||
|
||||
const datasetDurationSec = datasetDuration/1000;
|
||||
let twoHourDataset = [];
|
||||
let i = 0;
|
||||
while(i++ < count) {
|
||||
const ds = getDataset(Datasets[key], datasetDurationSec, i);
|
||||
twoHourDataset = twoHourDataset.concat(ds);
|
||||
}
|
||||
datasets[key] = twoHourDataset;
|
||||
return datasets;
|
||||
}, {});
|
||||
};
|
||||
|
||||
export default getTwoHourDatasets;
|
@ -2,13 +2,15 @@ const flatten = require('lodash.flatten');
|
||||
const {
|
||||
metricByIntervalSelector
|
||||
} = require('@state/selectors');
|
||||
const data = require('./metric-by-interval-selector.json');
|
||||
const moment = require('moment');
|
||||
const test = require('ava');
|
||||
|
||||
const data = require('./metric-by-Interval-selector.json');
|
||||
|
||||
test('should ouput the right properties', (t) => {
|
||||
const stats = metricByIntervalSelector(data, {
|
||||
min: 0,
|
||||
max: 100
|
||||
}, {
|
||||
duration: '10 minutes',
|
||||
interval: '30 seconds'
|
||||
});
|
||||
@ -33,6 +35,9 @@ test('should ouput the right properties', (t) => {
|
||||
|
||||
test('should respect order of records', (t) => {
|
||||
const stats = metricByIntervalSelector(data, {
|
||||
min: 0,
|
||||
max: 100
|
||||
}, {
|
||||
duration: '10 minutes',
|
||||
interval: '30 seconds'
|
||||
});
|
||||
@ -47,6 +52,9 @@ test('should respect order of records', (t) => {
|
||||
|
||||
test('should respect the intervals', (t) => {
|
||||
const stats = metricByIntervalSelector(data, {
|
||||
min: 0,
|
||||
max: 100
|
||||
}, {
|
||||
duration: '10 minutes',
|
||||
interval: '30 seconds'
|
||||
});
|
||||
@ -71,6 +79,9 @@ test('should respect the intervals', (t) => {
|
||||
|
||||
test('should respect the intervals', (t) => {
|
||||
const stats = metricByIntervalSelector(data, {
|
||||
min: 0,
|
||||
max: 100
|
||||
}, {
|
||||
duration: '10 minutes',
|
||||
interval: '30 seconds'
|
||||
});
|
||||
@ -95,6 +106,9 @@ test('should respect the intervals', (t) => {
|
||||
|
||||
test('records should be within intervals', (t) => {
|
||||
const stats = metricByIntervalSelector(data, {
|
||||
min: 0,
|
||||
max: 100
|
||||
}, {
|
||||
duration: '10 minutes',
|
||||
interval: '30 seconds'
|
||||
});
|
||||
@ -111,11 +125,17 @@ test('different data chunks should produce almost the same stats', (t) => {
|
||||
const halfData = data.slice(Math.floor(data.length / 2), data.length);
|
||||
|
||||
const stats1 = metricByIntervalSelector(data, {
|
||||
min: 0,
|
||||
max: 100
|
||||
}, {
|
||||
duration: '10 minutes',
|
||||
interval: '30 seconds'
|
||||
});
|
||||
|
||||
const stats2 = metricByIntervalSelector(halfData, {
|
||||
min: 0,
|
||||
max: 100
|
||||
}, {
|
||||
duration: '10 minutes',
|
||||
interval: '30 seconds'
|
||||
});
|
@ -4685,7 +4685,7 @@ lodash.templatesettings@^4.0.0:
|
||||
dependencies:
|
||||
lodash._reinterpolate "~3.0.0"
|
||||
|
||||
lodash.uniq@^4.3.0, lodash.uniq@^4.5.0:
|
||||
lodash.uniq@^4.3.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
|
||||
|
17
spikes/leak/prometheus.yml
Normal file
17
spikes/leak/prometheus.yml
Normal file
@ -0,0 +1,17 @@
|
||||
scrape_configs:
|
||||
- job_name: 'leak-fast'
|
||||
scrape_interval: 15s
|
||||
static_configs:
|
||||
- targets: ['fast-node:8000', 'another-fast-node:8000']
|
||||
- job_name: 'leak-slow'
|
||||
scrape_interval: 15s
|
||||
static_configs:
|
||||
- targets: ['slow-node:8000']
|
||||
- job_name: 'no-leak'
|
||||
scrape_interval: 15s
|
||||
static_configs:
|
||||
- targets: ['plain-node:8000']
|
||||
# - job_name: 'leak'
|
||||
# scrape_interval: 1s
|
||||
# static_configs:
|
||||
# - targets: ['fast-node:8000', 'another-fast-node:8000', 'slow-node:8000', 'plain-node:8000']
|
103
spikes/leak/src/server/metric.js
Normal file
103
spikes/leak/src/server/metric.js
Normal file
@ -0,0 +1,103 @@
|
||||
const relativeDate = require('relative-date');
|
||||
const statistics = require('simple-statistics');
|
||||
const prometheus = require('../../scripts/prometheus');
|
||||
const async = require('async');
|
||||
|
||||
const cdm = {};
|
||||
|
||||
const calc = (sample) => {
|
||||
return {
|
||||
firstQuartile: statistics.quantile(sample, 0.25),
|
||||
median: statistics.median(sample),
|
||||
thirdQuartile: statistics.quantile(sample, 0.75),
|
||||
max: statistics.max(sample),
|
||||
min: statistics.min(sample),
|
||||
stddev: statistics.sampleStandardDeviation(sample)
|
||||
};
|
||||
};
|
||||
|
||||
const getMem = ({
|
||||
job
|
||||
}, fn) => {
|
||||
prometheus.query({
|
||||
query: [`node_memory_heap_used_bytes{job="${job}"}`]
|
||||
}).then((res) => {
|
||||
if (!res || !res[job]) {
|
||||
return null
|
||||
}
|
||||
|
||||
const aggregate = calc(Object.keys(res[job]).map((inst) => {
|
||||
return Number(res[job][inst].node_memory_heap_used_bytes[1]);
|
||||
}));
|
||||
|
||||
const instances = Object.keys(res[job]).reduce((sum, inst) => {
|
||||
return Object.assign(sum, {
|
||||
[inst]: calc([Number(res[job][inst].node_memory_heap_used_bytes[1])])
|
||||
})
|
||||
}, {});
|
||||
|
||||
return {
|
||||
raw: res[job],
|
||||
aggregate,
|
||||
instances
|
||||
};
|
||||
}).then((res) => {
|
||||
return fn(null, res);
|
||||
}).catch((err) => {
|
||||
return fn(err);
|
||||
});
|
||||
};
|
||||
|
||||
const getStats = (ctx, fn) => {
|
||||
async.parallel({
|
||||
mem: async.apply(getMem, ctx)
|
||||
}, fn);
|
||||
};
|
||||
|
||||
module.exports = (server) => ({
|
||||
on: (job) => {
|
||||
console.log('on', job);
|
||||
|
||||
if (cdm[job] && (cdm[job].sockets > 0)) {
|
||||
cdm[job].sockets += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
let messageId = 0;
|
||||
|
||||
const update = () => {
|
||||
console.log(`publishing /stats/${job}/${messageId += 1}`);
|
||||
|
||||
getStats({
|
||||
job: job
|
||||
}, (err, stats) => {
|
||||
if (err) {
|
||||
return console.error(err);
|
||||
}
|
||||
|
||||
server.publish(`/stats/${job}`, {
|
||||
when: new Date().getTime(),
|
||||
stats
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
cdm[job] = {
|
||||
interval: setInterval(update, 1000),
|
||||
sockets: 1
|
||||
};
|
||||
},
|
||||
off: (job) => {
|
||||
console.log('off', job);
|
||||
|
||||
if (!(cdm[job].sockets -= 1)) {
|
||||
clearInterval(cdm[job].interval);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.tree = (ctx) => {
|
||||
return prometheus.tree({
|
||||
query: ['node_memory_heap_used_bytes']
|
||||
});
|
||||
};
|
@ -77,7 +77,6 @@
|
||||
"react/prefer-stateless-function": 2,
|
||||
"react/self-closing-comp": 2,
|
||||
"react/sort-comp": 2,
|
||||
"react/sort-prop-types": 2,
|
||||
"react/style-prop-object": 2,
|
||||
"react/jsx-boolean-value": [2, "never"],
|
||||
"react/jsx-closing-bracket-location": 2,
|
||||
|
Loading…
Reference in New Issue
Block a user