mirror of
https://github.com/yldio/copilot.git
synced 2024-12-01 07:30:07 +02:00
parent
9f03c65f05
commit
263d1518af
@ -49,7 +49,6 @@
|
|||||||
"react/prefer-stateless-function": 2,
|
"react/prefer-stateless-function": 2,
|
||||||
"react/self-closing-comp": 2,
|
"react/self-closing-comp": 2,
|
||||||
"react/sort-comp": 2,
|
"react/sort-comp": 2,
|
||||||
"react/sort-prop-types": 2,
|
|
||||||
"react/style-prop-object": 2,
|
"react/style-prop-object": 2,
|
||||||
"react/jsx-boolean-value": [2, "never"],
|
"react/jsx-boolean-value": [2, "never"],
|
||||||
"react/jsx-closing-bracket-location": 2,
|
"react/jsx-closing-bracket-location": 2,
|
||||||
|
@ -31,7 +31,6 @@
|
|||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.isempty": "^4.4.0",
|
"lodash.isempty": "^4.4.0",
|
||||||
"lodash.template": "^4.4.0",
|
"lodash.template": "^4.4.0",
|
||||||
"lodash.uniq": "^4.5.0",
|
|
||||||
"moment": "^2.17.1",
|
"moment": "^2.17.1",
|
||||||
"param-case": "^2.1.0",
|
"param-case": "^2.1.0",
|
||||||
"querystring": "^0.2.0",
|
"querystring": "^0.2.0",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import MetricsOutlet from '@components/metrics-outlet';
|
import ItemMetricGroup from '@components/item-metric-group';
|
||||||
import PropTypes from '@root/prop-types';
|
import PropTypes from '@root/prop-types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -20,7 +20,9 @@ const InstanceItem = ({
|
|||||||
<ListItemMeta onClick={toggleCollapsed}>
|
<ListItemMeta onClick={toggleCollapsed}>
|
||||||
<ListItemTitle>{instance.name}</ListItemTitle>
|
<ListItemTitle>{instance.name}</ListItemTitle>
|
||||||
</ListItemMeta>
|
</ListItemMeta>
|
||||||
<MetricsOutlet datasets={instance.metrics} />
|
<ItemMetricGroup
|
||||||
|
datasets={instance.metrics}
|
||||||
|
/>
|
||||||
</ListItemView>
|
</ListItemView>
|
||||||
<ListItemOptions>
|
<ListItemOptions>
|
||||||
…
|
…
|
||||||
|
@ -5,14 +5,7 @@ import Column from '@ui/components/column';
|
|||||||
import { ListItemOutlet } from '@ui/components/list';
|
import { ListItemOutlet } from '@ui/components/list';
|
||||||
import PropTypes from '@root/prop-types';
|
import PropTypes from '@root/prop-types';
|
||||||
import Row from '@ui/components/row';
|
import Row from '@ui/components/row';
|
||||||
|
import MetricItem from './item';
|
||||||
import {
|
|
||||||
MetricGraph,
|
|
||||||
MiniMetricMeta,
|
|
||||||
MiniMetricTitle,
|
|
||||||
MiniMetricSubtitle,
|
|
||||||
MetricView
|
|
||||||
} from '@ui/components/metric';
|
|
||||||
|
|
||||||
const StyledOutlet = styled(ListItemOutlet)`
|
const StyledOutlet = styled(ListItemOutlet)`
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
@ -28,19 +21,15 @@ const StyledRow = styled(Row)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const MetricsOutlet = ({
|
const ItemMetricGroup = ({
|
||||||
datasets = [],
|
datasets = [],
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const _datasets = datasets.map((metric, i) => (
|
const _datasets = datasets.map((metric, i) => (
|
||||||
<Column key={i} xs={4}>
|
<Column key={i} xs={12/datasets.length}>
|
||||||
<MetricView mini borderless>
|
<MetricItem
|
||||||
<MiniMetricMeta>
|
{...metric}
|
||||||
<MiniMetricTitle>Memory: 54%</MiniMetricTitle>
|
/>
|
||||||
<MiniMetricSubtitle>(1280/3000 MB)</MiniMetricSubtitle>
|
|
||||||
</MiniMetricMeta>
|
|
||||||
<MetricGraph data={metric.data} />
|
|
||||||
</MetricView>
|
|
||||||
</Column>
|
</Column>
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -53,8 +42,8 @@ const MetricsOutlet = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
MetricsOutlet.propTypes = {
|
ItemMetricGroup.propTypes = {
|
||||||
datasets: React.PropTypes.arrayOf(PropTypes.dataset)
|
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 styled from 'styled-components';
|
||||||
import { Link } from '@ui/components/anchor';
|
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 { Checkbox, FormGroup } from '@ui/components/form';
|
||||||
import PropTypes from '@root/prop-types';
|
import PropTypes from '@root/prop-types';
|
||||||
import forceArray from 'force-array';
|
import forceArray from 'force-array';
|
||||||
@ -101,7 +101,9 @@ const ServiceItem = ({
|
|||||||
{isChild && subtitle}
|
{isChild && subtitle}
|
||||||
{description}
|
{description}
|
||||||
</ListItemMeta>
|
</ListItemMeta>
|
||||||
<MetricsOutlet datasets={service.metrics} />
|
<ItemMetricGroup
|
||||||
|
datasets={service.metrics}
|
||||||
|
/>
|
||||||
</ListItemView>
|
</ListItemView>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@ const Services = (props) => {
|
|||||||
push
|
push
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
|
||||||
// TODO: Move into "components" and fix absolute
|
// TODO: Move into "components" and fix absolute
|
||||||
// positioning on responsive screens
|
// positioning on responsive screens
|
||||||
const instances = (instances = 0) => {
|
const instances = (instances = 0) => {
|
||||||
@ -33,6 +32,7 @@ const Services = (props) => {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
if ( instances.length <= 0 || instances <= 0 ) return;
|
if ( instances.length <= 0 || instances <= 0 ) return;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledButton tertiary>
|
<StyledButton tertiary>
|
||||||
You have 5 instances
|
You have 5 instances
|
||||||
@ -40,13 +40,15 @@ const Services = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleValue = path === '/:org/projects/:projectId/services' ?
|
const toggleValue = path === '/:org/projects/:projectId/services'
|
||||||
'topology' : 'list';
|
? 'topology'
|
||||||
|
: 'list';
|
||||||
|
|
||||||
const onToggle = (value) => {
|
const onToggle = (value) => {
|
||||||
const path = `/${org.id}/projects/${project.id}/services${
|
const path = `/${org.id}/projects/${project.id}/services${
|
||||||
value === 'list' ? '/list' : ''
|
value === 'list' ? '/list' : ''
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
push(path);
|
push(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -56,7 +58,7 @@ const Services = (props) => {
|
|||||||
toggleValue={toggleValue}
|
toggleValue={toggleValue}
|
||||||
services={services}
|
services={services}
|
||||||
>
|
>
|
||||||
{ instances() }
|
{instances()}
|
||||||
{children}
|
{children}
|
||||||
</ServicesView>
|
</ServicesView>
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,7 @@ import ServiceItem from '@components/service/item';
|
|||||||
import UnmanagedInstances from '@components/services/unmanaged-instances';
|
import UnmanagedInstances from '@components/services/unmanaged-instances';
|
||||||
import { toggleTooltip } from '@state/actions';
|
import { toggleTooltip } from '@state/actions';
|
||||||
import ServicesTooltip from '@components/services/tooltip';
|
import ServicesTooltip from '@components/services/tooltip';
|
||||||
|
import { subscribeMetric } from '@state/thunks';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
orgByIdSelector,
|
orgByIdSelector,
|
||||||
@ -18,8 +19,17 @@ const StyledContainer = styled.div`
|
|||||||
position: relative;
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// TMP - single source of truth
|
||||||
|
const duration = '1 hour';
|
||||||
|
const interval = '2 minutes';
|
||||||
|
|
||||||
class Services extends React.Component {
|
class Services extends React.Component {
|
||||||
|
|
||||||
|
// we DON'T want to unsubscribe once we started going
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.subscribeMetric(interval);
|
||||||
|
}
|
||||||
|
|
||||||
ref(name) {
|
ref(name) {
|
||||||
this._refs = this._refs || {};
|
this._refs = this._refs || {};
|
||||||
|
|
||||||
@ -33,7 +43,7 @@ class Services extends React.Component {
|
|||||||
org = {},
|
org = {},
|
||||||
project = {},
|
project = {},
|
||||||
services = [],
|
services = [],
|
||||||
toggleTooltip = (() => {}),
|
toggleTooltip = () => ({}),
|
||||||
uiTooltip = {}
|
uiTooltip = {}
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@ -87,7 +97,8 @@ Services.propTypes = {
|
|||||||
project: PropTypes.project,
|
project: PropTypes.project,
|
||||||
services: React.PropTypes.arrayOf(PropTypes.service),
|
services: React.PropTypes.arrayOf(PropTypes.service),
|
||||||
toggleTooltip: React.PropTypes.func,
|
toggleTooltip: React.PropTypes.func,
|
||||||
uiTooltip: React.PropTypes.object
|
uiTooltip: React.PropTypes.object,
|
||||||
|
subscribeMetric: React.PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, {
|
const mapStateToProps = (state, {
|
||||||
@ -98,12 +109,16 @@ const mapStateToProps = (state, {
|
|||||||
}) => ({
|
}) => ({
|
||||||
org: orgByIdSelector(match.params.org)(state),
|
org: orgByIdSelector(match.params.org)(state),
|
||||||
project: projectByIdSelector(match.params.projectId)(state),
|
project: projectByIdSelector(match.params.projectId)(state),
|
||||||
services: servicesByProjectIdSelector(match.params.projectId)(state),
|
services: servicesByProjectIdSelector(match.params.projectId, {
|
||||||
|
duration,
|
||||||
|
interval
|
||||||
|
})(state),
|
||||||
uiTooltip: serviceUiTooltipSelector(state)
|
uiTooltip: serviceUiTooltipSelector(state)
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
toggleTooltip: (data) => dispatch(toggleTooltip(data))
|
toggleTooltip: (data) => dispatch(toggleTooltip(data)),
|
||||||
|
subscribeMetric: (payload) => dispatch(subscribeMetric(payload))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
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 App from '@containers/app';
|
||||||
import MockState from './mock-state.json';
|
import MockState from './mock-state.json';
|
||||||
|
import Datasets from './datasets.json';
|
||||||
import Store from '@state/store';
|
import Store from '@state/store';
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
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(
|
ReactDOM.render(
|
||||||
<Provider store={Store(MockState)}>
|
<Provider store={Store(MockState)}>
|
||||||
<IntlProvider>
|
<IntlProvider>
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
},
|
},
|
||||||
"metrics": {
|
"metrics": {
|
||||||
"ui": {
|
"ui": {
|
||||||
|
"pos": 0,
|
||||||
"durations": [
|
"durations": [
|
||||||
"360",
|
"360",
|
||||||
"720",
|
"720",
|
||||||
@ -163,317 +164,11 @@
|
|||||||
"datasets": [{
|
"datasets": [{
|
||||||
"type": "2aaa237d-42b3-442f-9094-a17aa470014b",
|
"type": "2aaa237d-42b3-442f-9094-a17aa470014b",
|
||||||
"uuid": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
"uuid": "3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||||
"data": [{
|
"data": []
|
||||||
"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
|
|
||||||
}, {
|
|
||||||
"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",
|
"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
|
...BaseObject
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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({
|
const Dataset = React.PropTypes.shape({
|
||||||
uuid: React.PropTypes.string,
|
uuid: React.PropTypes.string,
|
||||||
type: MetricType,
|
type: MetricType,
|
||||||
data: React.PropTypes.arrayOf(
|
data: React.PropTypes.arrayOf(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 Sections = React.PropTypes.arrayOf(
|
const Sections = React.PropTypes.arrayOf(
|
||||||
@ -74,5 +74,6 @@ export default {
|
|||||||
instance: Instance,
|
instance: Instance,
|
||||||
metric: Metric,
|
metric: Metric,
|
||||||
metricType: MetricType,
|
metricType: MetricType,
|
||||||
dataset: Dataset
|
dataset: Dataset,
|
||||||
|
data: Data
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import constantCase from 'constant-case';
|
import constantCase from 'constant-case';
|
||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
// import thunks from '@state/thunks';
|
|
||||||
|
|
||||||
const APP = constantCase(process.env['APP_NAME']);
|
const APP = constantCase(process.env['APP_NAME']);
|
||||||
|
|
||||||
@ -54,3 +53,5 @@ export const handleNewProject =
|
|||||||
createAction(`${APP}/CREATE_NEW_PROJECT`);
|
createAction(`${APP}/CREATE_NEW_PROJECT`);
|
||||||
export const toggleTooltip =
|
export const toggleTooltip =
|
||||||
createAction(`${APP}/TOGGLE_QUICK_ACTIONS_TOOLTIP`);
|
createAction(`${APP}/TOGGLE_QUICK_ACTIONS_TOOLTIP`);
|
||||||
|
export const refreshMetrics =
|
||||||
|
createAction(`${APP}/REFRESH_METRICS`);
|
||||||
|
@ -1,16 +1,24 @@
|
|||||||
import { handleActions } from 'redux-actions';
|
import { handleActions } from 'redux-actions';
|
||||||
import { metricDurationChange } from '@state/actions';
|
import { metricDurationChange, refreshMetrics } from '@state/actions';
|
||||||
|
|
||||||
export default handleActions({
|
export default handleActions({
|
||||||
[metricDurationChange.toString()]: (state, action) => {
|
[metricDurationChange.toString()]: (state, action) => ({
|
||||||
|
...state,
|
||||||
|
ui: {
|
||||||
|
...state.ui,
|
||||||
|
[action.payload.dataset]: {
|
||||||
|
...state.ui[action.payload.dataset],
|
||||||
|
duration: action.payload.duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[refreshMetrics.toString()]: (state) => {
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
...state,
|
...state,
|
||||||
ui: {
|
ui: {
|
||||||
...state.ui,
|
...state.ui,
|
||||||
[action.payload.dataset]: {
|
pos: state.ui.pos + 1
|
||||||
...state.ui[action.payload.dataset],
|
|
||||||
duration: action.payload.duration
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -66,14 +66,207 @@ const orgSections = (orgId) => createSelector(
|
|||||||
|
|
||||||
const isCollapsed = (collapsed, uuid) => collapsed.indexOf(uuid) >= 0;
|
const isCollapsed = (collapsed, uuid) => collapsed.indexOf(uuid) >= 0;
|
||||||
|
|
||||||
const datasets = (metricsData, serviceOrInstanceMetrics, metricsUI) =>
|
const metricByInterval = (data = [], {
|
||||||
serviceOrInstanceMetrics.map((soim) => ({
|
min = 0,
|
||||||
...find(metricsData.datasets, ['uuid', soim.dataset]),
|
max = 100
|
||||||
type: find(metricsData.types, ['uuid', soim.type]),
|
}, {
|
||||||
...metricsUI[soim.dataset]
|
duration = '1 hour',
|
||||||
}));
|
interval = '5 minutes'
|
||||||
|
}) => {
|
||||||
|
const getDurationArgs = (value) => {
|
||||||
|
const [durationNumber, durationType] = value.split(/\s/);
|
||||||
|
return [Number(durationNumber), durationType];
|
||||||
|
};
|
||||||
|
|
||||||
const servicesByProjectId = (projectId) => createSelector(
|
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, metricOptions) => createSelector(
|
||||||
[services, projectById(projectId), collapsedServices, metricsData, metricsUI],
|
[services, projectById(projectId), collapsedServices, metricsData, metricsUI],
|
||||||
(services, project, collapsed, metrics, metricsUI) =>
|
(services, project, collapsed, metrics, metricsUI) =>
|
||||||
services.filter((s) => s.project === project.uuid)
|
services.filter((s) => s.project === project.uuid)
|
||||||
@ -84,7 +277,7 @@ const servicesByProjectId = (projectId) => createSelector(
|
|||||||
.filter((s) => !s.parent)
|
.filter((s) => !s.parent)
|
||||||
.map((service) => ({
|
.map((service) => ({
|
||||||
...service,
|
...service,
|
||||||
metrics: datasets(metrics, service.metrics, metricsUI),
|
metrics: datasets(metrics, service.metrics, metricsUI, metricOptions),
|
||||||
collapsed: isCollapsed(collapsed, service.uuid),
|
collapsed: isCollapsed(collapsed, service.uuid),
|
||||||
services: service.services.map((service) => ({
|
services: service.services.map((service) => ({
|
||||||
...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 {
|
export {
|
||||||
account as accountSelector,
|
account as accountSelector,
|
||||||
accountUi as accountUISelector,
|
accountUi as accountUISelector,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
// import app from '@state/thunks/app';
|
export {
|
||||||
|
subscribe as subscribeMetric,
|
||||||
export {};
|
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 {
|
const {
|
||||||
metricByIntervalSelector
|
metricByIntervalSelector
|
||||||
} = require('@state/selectors');
|
} = require('@state/selectors');
|
||||||
|
const data = require('./metric-by-interval-selector.json');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const test = require('ava');
|
const test = require('ava');
|
||||||
|
|
||||||
const data = require('./metric-by-Interval-selector.json');
|
|
||||||
|
|
||||||
test('should ouput the right properties', (t) => {
|
test('should ouput the right properties', (t) => {
|
||||||
const stats = metricByIntervalSelector(data, {
|
const stats = metricByIntervalSelector(data, {
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
}, {
|
||||||
duration: '10 minutes',
|
duration: '10 minutes',
|
||||||
interval: '30 seconds'
|
interval: '30 seconds'
|
||||||
});
|
});
|
||||||
@ -33,6 +35,9 @@ test('should ouput the right properties', (t) => {
|
|||||||
|
|
||||||
test('should respect order of records', (t) => {
|
test('should respect order of records', (t) => {
|
||||||
const stats = metricByIntervalSelector(data, {
|
const stats = metricByIntervalSelector(data, {
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
}, {
|
||||||
duration: '10 minutes',
|
duration: '10 minutes',
|
||||||
interval: '30 seconds'
|
interval: '30 seconds'
|
||||||
});
|
});
|
||||||
@ -47,6 +52,9 @@ test('should respect order of records', (t) => {
|
|||||||
|
|
||||||
test('should respect the intervals', (t) => {
|
test('should respect the intervals', (t) => {
|
||||||
const stats = metricByIntervalSelector(data, {
|
const stats = metricByIntervalSelector(data, {
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
}, {
|
||||||
duration: '10 minutes',
|
duration: '10 minutes',
|
||||||
interval: '30 seconds'
|
interval: '30 seconds'
|
||||||
});
|
});
|
||||||
@ -71,6 +79,9 @@ test('should respect the intervals', (t) => {
|
|||||||
|
|
||||||
test('should respect the intervals', (t) => {
|
test('should respect the intervals', (t) => {
|
||||||
const stats = metricByIntervalSelector(data, {
|
const stats = metricByIntervalSelector(data, {
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
}, {
|
||||||
duration: '10 minutes',
|
duration: '10 minutes',
|
||||||
interval: '30 seconds'
|
interval: '30 seconds'
|
||||||
});
|
});
|
||||||
@ -95,6 +106,9 @@ test('should respect the intervals', (t) => {
|
|||||||
|
|
||||||
test('records should be within intervals', (t) => {
|
test('records should be within intervals', (t) => {
|
||||||
const stats = metricByIntervalSelector(data, {
|
const stats = metricByIntervalSelector(data, {
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
}, {
|
||||||
duration: '10 minutes',
|
duration: '10 minutes',
|
||||||
interval: '30 seconds'
|
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 halfData = data.slice(Math.floor(data.length / 2), data.length);
|
||||||
|
|
||||||
const stats1 = metricByIntervalSelector(data, {
|
const stats1 = metricByIntervalSelector(data, {
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
}, {
|
||||||
duration: '10 minutes',
|
duration: '10 minutes',
|
||||||
interval: '30 seconds'
|
interval: '30 seconds'
|
||||||
});
|
});
|
||||||
|
|
||||||
const stats2 = metricByIntervalSelector(halfData, {
|
const stats2 = metricByIntervalSelector(halfData, {
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
}, {
|
||||||
duration: '10 minutes',
|
duration: '10 minutes',
|
||||||
interval: '30 seconds'
|
interval: '30 seconds'
|
||||||
});
|
});
|
@ -4685,7 +4685,7 @@ lodash.templatesettings@^4.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash._reinterpolate "~3.0.0"
|
lodash._reinterpolate "~3.0.0"
|
||||||
|
|
||||||
lodash.uniq@^4.3.0, lodash.uniq@^4.5.0:
|
lodash.uniq@^4.3.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
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/prefer-stateless-function": 2,
|
||||||
"react/self-closing-comp": 2,
|
"react/self-closing-comp": 2,
|
||||||
"react/sort-comp": 2,
|
"react/sort-comp": 2,
|
||||||
"react/sort-prop-types": 2,
|
|
||||||
"react/style-prop-object": 2,
|
"react/style-prop-object": 2,
|
||||||
"react/jsx-boolean-value": [2, "never"],
|
"react/jsx-boolean-value": [2, "never"],
|
||||||
"react/jsx-closing-bracket-location": 2,
|
"react/jsx-closing-bracket-location": 2,
|
||||||
|
Loading…
Reference in New Issue
Block a user