mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 18:20:27 +00:00
Tidying up MoC
- no rand ids, org code - Fixes tests, no .includes in jest for now - Small comment on moc stuff - Patch up differences after MoC rebase
This commit is contained in:
@@ -5,7 +5,6 @@ import ActionTypes from '../constants/action-types';
|
||||
import { saveGraph } from '../utils/file-utils';
|
||||
import { modulo } from '../utils/math-utils';
|
||||
import { updateRoute } from '../utils/router-utils';
|
||||
import { addMetrics } from '../utils/data-utils';
|
||||
import { bufferDeltaUpdate, resumeUpdate,
|
||||
resetUpdateBuffer } from '../utils/update-buffer-utils';
|
||||
import { doControlRequest, getNodesDelta, getNodeDetails,
|
||||
@@ -271,13 +270,12 @@ export function receiveNodeDetails(details) {
|
||||
}
|
||||
|
||||
export function receiveNodesDelta(delta) {
|
||||
const deltaWithMetrics = addMetrics(delta, AppStore.getNodes());
|
||||
if (AppStore.isUpdatePaused()) {
|
||||
bufferDeltaUpdate(deltaWithMetrics);
|
||||
bufferDeltaUpdate(delta);
|
||||
} else {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.RECEIVE_NODES_DELTA,
|
||||
delta: deltaWithMetrics
|
||||
delta
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {getMetricValue, getMetricColor} from '../utils/data-utils.js';
|
||||
import {getMetricValue, getMetricColor} from '../utils/metric-utils.js';
|
||||
import {CANVAS_METRIC_FONT_SIZE} from '../constants/styles.js';
|
||||
|
||||
export default function NodeShapeCircle({highlighted, size, color, metric}) {
|
||||
export default function NodeShapeCircle({id, highlighted, size, color, metric}) {
|
||||
const hightlightNode = <circle r={size * 0.7} className="highlighted" />;
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const clipId = `mask-${id}`;
|
||||
const {height, value, formattedValue} = getMetricValue(metric, size);
|
||||
|
||||
const className = classNames('shape', {
|
||||
metrics: value !== null
|
||||
});
|
||||
const metricStyle = {
|
||||
fillOpacity: 0.5,
|
||||
fill: getMetricColor()
|
||||
fill: getMetricColor(metric)
|
||||
};
|
||||
const fontSize = size * 0.19;
|
||||
const fontSize = size * CANVAS_METRIC_FONT_SIZE;
|
||||
|
||||
return (
|
||||
<g className={className}>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import d3 from 'd3';
|
||||
import classNames from 'classnames';
|
||||
import {getMetricValue, getMetricColor} from '../utils/data-utils.js';
|
||||
import {getMetricValue, getMetricColor} from '../utils/metric-utils.js';
|
||||
import {CANVAS_METRIC_FONT_SIZE} from '../constants/styles.js';
|
||||
|
||||
const line = d3.svg.line()
|
||||
.interpolate('cardinal-closed')
|
||||
@@ -16,24 +17,23 @@ function polygon(r, sides) {
|
||||
return points;
|
||||
}
|
||||
|
||||
export default function NodeShapeHeptagon({highlighted, size, color, metric}) {
|
||||
export default function NodeShapeHeptagon({id, highlighted, size, color, metric}) {
|
||||
const scaledSize = size * 1.0;
|
||||
const pathProps = v => ({
|
||||
d: line(polygon(scaledSize * v, 7)),
|
||||
transform: 'rotate(90)'
|
||||
});
|
||||
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const clipId = `mask-${id}`;
|
||||
const {height, value, formattedValue} = getMetricValue(metric, size);
|
||||
|
||||
const className = classNames('shape', {
|
||||
metrics: value !== null
|
||||
});
|
||||
const fontSize = size * CANVAS_METRIC_FONT_SIZE;
|
||||
const metricStyle = {
|
||||
fillOpacity: 0.5,
|
||||
fill: getMetricColor()
|
||||
fill: getMetricColor(metric)
|
||||
};
|
||||
const fontSize = size * 0.19;
|
||||
|
||||
return (
|
||||
<g className={className}>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import d3 from 'd3';
|
||||
import classNames from 'classnames';
|
||||
import {getMetricValue, getMetricColor} from '../utils/data-utils.js';
|
||||
import {getMetricValue, getMetricColor} from '../utils/metric-utils.js';
|
||||
import {CANVAS_METRIC_FONT_SIZE} from '../constants/styles.js';
|
||||
|
||||
const line = d3.svg.line()
|
||||
.interpolate('cardinal-closed')
|
||||
@@ -26,7 +27,7 @@ function getPoints(h) {
|
||||
}
|
||||
|
||||
|
||||
export default function NodeShapeHex({highlighted, size, color, metric}) {
|
||||
export default function NodeShapeHex({id, highlighted, size, color, metric}) {
|
||||
const pathProps = v => ({
|
||||
d: getPoints(size * v * 2),
|
||||
transform: `rotate(90) translate(-${size * getWidth(v)}, -${size * v})`
|
||||
@@ -34,16 +35,15 @@ export default function NodeShapeHex({highlighted, size, color, metric}) {
|
||||
|
||||
const shadowSize = 0.45;
|
||||
const upperHexBitHeight = -0.25 * size * shadowSize;
|
||||
const fontSize = size * 0.19;
|
||||
const fontSize = size * CANVAS_METRIC_FONT_SIZE;
|
||||
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const clipId = `mask-${id}`;
|
||||
const {height, value, formattedValue} = getMetricValue(metric, size);
|
||||
const className = classNames('shape', {
|
||||
metrics: value !== null
|
||||
});
|
||||
const metricStyle = {
|
||||
fillOpacity: 0.5,
|
||||
fill: getMetricColor()
|
||||
fill: getMetricColor(metric)
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {getMetricValue, getMetricColor} from '../utils/data-utils.js';
|
||||
import {getMetricValue, getMetricColor} from '../utils/metric-utils.js';
|
||||
import {CANVAS_METRIC_FONT_SIZE} from '../constants/styles.js';
|
||||
|
||||
export default function NodeShapeSquare({
|
||||
highlighted, size, color, rx = 0, ry = 0, metric
|
||||
id, highlighted, size, color, rx = 0, ry = 0, metric
|
||||
}) {
|
||||
const rectProps = (v, vr) => ({
|
||||
width: v * size * 2,
|
||||
height: v * size * 2,
|
||||
rx: (vr || v) * size * rx,
|
||||
ry: (vr || v) * size * ry,
|
||||
x: -size * v,
|
||||
y: -size * v
|
||||
const rectProps = (scale, radiusScale) => ({
|
||||
width: scale * size * 2,
|
||||
height: scale * size * 2,
|
||||
rx: (radiusScale || scale) * size * rx,
|
||||
ry: (radiusScale || scale) * size * ry,
|
||||
x: -size * scale,
|
||||
y: -size * scale
|
||||
});
|
||||
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const clipId = `mask-${id}`;
|
||||
const {height, value, formattedValue} = getMetricValue(metric, size);
|
||||
const className = classNames('shape', {
|
||||
metrics: value !== null
|
||||
});
|
||||
const fontSize = size * 0.19;
|
||||
|
||||
const fontSize = size * CANVAS_METRIC_FONT_SIZE;
|
||||
const metricStyle = {
|
||||
fillOpacity: 0.5,
|
||||
fill: getMetricColor()
|
||||
fill: getMetricColor(metric)
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -131,6 +131,14 @@ export default class NodesChart extends React.Component {
|
||||
return 1;
|
||||
};
|
||||
|
||||
const metric = node => {
|
||||
const met = node.get('metrics') && node.get('metrics')
|
||||
.filter(m => m.get('id') === this.props.selectedMetric)
|
||||
.first();
|
||||
console.log(met);
|
||||
return met;
|
||||
};
|
||||
|
||||
return nodes
|
||||
.toIndexedSeq()
|
||||
.map(setHighlighted)
|
||||
@@ -151,7 +159,7 @@ export default class NodesChart extends React.Component {
|
||||
pseudo={node.get('pseudo')}
|
||||
nodeCount={node.get('nodeCount')}
|
||||
subLabel={node.get('subLabel')}
|
||||
metric={node.getIn(['metrics', this.props.selectedMetric])}
|
||||
metric={metric(node)}
|
||||
rank={node.get('rank')}
|
||||
selectedNodeScale={selectedNodeScale}
|
||||
nodeScale={nodeScale}
|
||||
|
||||
@@ -139,14 +139,14 @@ export default class App extends React.Component {
|
||||
topologyId={this.state.currentTopologyId} />
|
||||
|
||||
<Sidebar>
|
||||
<Status errorUrl={this.state.errorUrl} topology={this.state.currentTopology}
|
||||
topologiesLoaded={this.state.topologiesLoaded}
|
||||
websocketClosed={this.state.websocketClosed} />
|
||||
{this.state.availableCanvasMetrics.length > 0 && <MetricSelector
|
||||
availableCanvasMetrics={this.state.availableCanvasMetrics}
|
||||
lockedMetric={this.state.lockedMetric}
|
||||
selectedMetric={this.state.selectedMetric}
|
||||
/>}
|
||||
<Status errorUrl={this.state.errorUrl} topology={this.state.currentTopology}
|
||||
topologiesLoaded={this.state.topologiesLoaded}
|
||||
websocketClosed={this.state.websocketClosed} />
|
||||
<TopologyOptions options={this.state.currentTopologyOptions}
|
||||
topologyId={this.state.currentTopologyId}
|
||||
activeOptions={this.state.activeTopologyOptions} />
|
||||
|
||||
@@ -33,9 +33,9 @@ export class MetricSelectorItem extends React.Component {
|
||||
const id = metric.id;
|
||||
const isLocked = (id === lockedMetric);
|
||||
const isSelected = (id === selectedMetric);
|
||||
const className = classNames('sidebar-item', {
|
||||
locked: isLocked,
|
||||
selected: isSelected
|
||||
const className = classNames('metric-selector-action', {
|
||||
'metric-selector-action-locked': isLocked,
|
||||
'metric-selector-action-selected': isSelected
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -45,9 +45,7 @@ export class MetricSelectorItem extends React.Component {
|
||||
onMouseOver={this.onMouseOver}
|
||||
onClick={this.onMouseClick}>
|
||||
{metric.label}
|
||||
{isLocked && <span className="sidebar-item-actions">
|
||||
<span className="sidebar-item-action fa fa-thumb-tack"></span>
|
||||
</span>}
|
||||
{isLocked && <span className="fa fa-thumb-tack"></span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,12 +28,10 @@ export default class MetricSelector extends React.Component {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="available-metrics"
|
||||
onMouseLeave={this.onMouseOut}>
|
||||
<div className="sidebar-item">
|
||||
METRICS
|
||||
className="metric-selector">
|
||||
<div className="metric-selector-wrapper" onMouseLeave={this.onMouseOut}>
|
||||
{items}
|
||||
</div>
|
||||
{items}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,3 +8,5 @@ export const DETAILS_PANEL_MARGINS = {
|
||||
};
|
||||
|
||||
export const DETAILS_PANEL_OFFSET = 8;
|
||||
|
||||
export const CANVAS_METRIC_FONT_SIZE = 0.19;
|
||||
|
||||
@@ -8,7 +8,6 @@ import ActionTypes from '../constants/action-types';
|
||||
import { EDGE_ID_SEPARATOR } from '../constants/naming';
|
||||
import { findTopologyById, setTopologyUrlsById, updateTopologyIds,
|
||||
filterHiddenTopologies } from '../utils/topology-utils';
|
||||
import { METRIC_LABELS } from '../utils/data-utils';
|
||||
|
||||
const makeList = List;
|
||||
const makeMap = Map;
|
||||
@@ -611,15 +610,17 @@ export class AppStore extends Store {
|
||||
|
||||
availableCanvasMetrics = nodes
|
||||
.valueSeq()
|
||||
.flatMap(n => (n.get('metrics') || makeMap()).keys())
|
||||
.flatMap(n => (n.get('metrics') || makeList()).map(m => (
|
||||
makeMap({id: m.get('id'), label: m.get('label')})
|
||||
)))
|
||||
.toSet()
|
||||
.sortBy(n => METRIC_LABELS[n])
|
||||
.toJS()
|
||||
.map(v => ({id: v, label: METRIC_LABELS[v]}));
|
||||
.sortBy(m => m.get('label'))
|
||||
.toJS();
|
||||
|
||||
const similarTypeMetric = availableCanvasMetrics.find(m => m.label === lockedMetricType);
|
||||
lockedMetric = similarTypeMetric && similarTypeMetric.id;
|
||||
if (!availableCanvasMetrics.map(m => m.id).includes(selectedMetric)) {
|
||||
// if something in the current topo is not already selected, select it.
|
||||
if (availableCanvasMetrics.map(m => m.id).indexOf(selectedMetric) === -1) {
|
||||
selectedMetric = lockedMetric;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import d3 from 'd3';
|
||||
import { formatMetric } from './string-utils';
|
||||
import { colors } from './color-utils';
|
||||
import AppStore from '../stores/app-store';
|
||||
|
||||
|
||||
// Inspired by Lee Byron's test data generator.
|
||||
@@ -61,20 +58,6 @@ export function label(m) {
|
||||
}
|
||||
|
||||
|
||||
const METRIC_FORMATS = {
|
||||
docker_cpu_total_usage: 'percent',
|
||||
docker_memory_usage: 'filesize',
|
||||
host_cpu_usage_percent: 'percent',
|
||||
host_mem_usage_bytes: 'filesize',
|
||||
load1: 'number',
|
||||
load15: 'number',
|
||||
load5: 'number',
|
||||
open_files_count: 'integer',
|
||||
process_cpu_usage_percent: 'percent',
|
||||
process_memory_usage_bytes: 'filesize'
|
||||
};
|
||||
|
||||
|
||||
const memoryMetric = (node, name, max = 1024 * 1024 * 1024) => ({
|
||||
samples: [{value: getNextValue([node.id, name], max)}],
|
||||
max
|
||||
@@ -151,57 +134,3 @@ export function addMetrics(delta, prevNodes) {
|
||||
update: handleUpdated(delta.update, prevNodes)
|
||||
});
|
||||
}
|
||||
|
||||
const openFilesScale = d3.scale.log().domain([1, 100000]).range([0, 1]);
|
||||
//
|
||||
// loadScale(1) == 0.5; E.g. a nicely balanced system :).
|
||||
const loadScale = d3.scale.log().domain([0.01, 100]).range([0, 1]);
|
||||
|
||||
export function getMetricValue(metric, size) {
|
||||
if (!metric) {
|
||||
return {height: 0, value: null, formattedValue: 'n/a'};
|
||||
}
|
||||
|
||||
const max = metric.getIn(['max']);
|
||||
const value = metric.getIn(['samples', 0, 'value']);
|
||||
const selectedMetric = AppStore.getSelectedMetric();
|
||||
|
||||
let valuePercentage = value === 0 ? 0 : value / max;
|
||||
if (selectedMetric === 'open_files_count') {
|
||||
valuePercentage = openFilesScale(value);
|
||||
} else if (_.includes(['load1', 'load5', 'load15'], selectedMetric)) {
|
||||
valuePercentage = loadScale(value);
|
||||
}
|
||||
|
||||
let displayedValue = Number(value).toFixed(1);
|
||||
if (displayedValue > 0) {
|
||||
const baseline = 0.1;
|
||||
displayedValue = valuePercentage * (1 - baseline) + baseline;
|
||||
}
|
||||
const height = size * displayedValue;
|
||||
const metricWithFormat = Object.assign(
|
||||
{}, {format: METRIC_FORMATS[selectedMetric]}, metric.toJS());
|
||||
|
||||
return {
|
||||
height,
|
||||
value,
|
||||
formattedValue: formatMetric(value, metricWithFormat, true)
|
||||
};
|
||||
}
|
||||
|
||||
export function getMetricColor() {
|
||||
const selectedMetric = AppStore.getSelectedMetric();
|
||||
// bluey
|
||||
if (/memory/.test(selectedMetric)) {
|
||||
return '#1f77b4';
|
||||
} else if (/cpu/.test(selectedMetric)) {
|
||||
return colors('cpu');
|
||||
} else if (/files/.test(selectedMetric)) {
|
||||
// return colors('files');
|
||||
// purple
|
||||
return '#9467bd';
|
||||
} else if (/load/.test(selectedMetric)) {
|
||||
return colors('load');
|
||||
}
|
||||
return 'steelBlue';
|
||||
}
|
||||
@@ -1,5 +1,21 @@
|
||||
|
||||
// http://stackoverflow.com/questions/4467539/javascript-modulo-not-behaving
|
||||
//
|
||||
// A modulo that "behaves" w/ negatives.
|
||||
//
|
||||
// modulo(5, 5) => 0
|
||||
// modulo(4, 5) => 4
|
||||
// modulo(3, 5) => 3
|
||||
// modulo(2, 5) => 2
|
||||
// modulo(1, 5) => 1
|
||||
// modulo(0, 5) => 0
|
||||
// modulo(-1, 5) => 4
|
||||
// modulo(-2, 5) => 3
|
||||
// modulo(-2, 5) => 3
|
||||
// modulo(-3, 5) => 2
|
||||
// modulo(-4, 5) => 1
|
||||
// modulo(-5, 5) => 0
|
||||
//
|
||||
export function modulo(i, n) {
|
||||
return ((i % n) + n) % n;
|
||||
}
|
||||
|
||||
56
client/app/scripts/utils/metric-utils.js
Normal file
56
client/app/scripts/utils/metric-utils.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import _ from 'lodash';
|
||||
import d3 from 'd3';
|
||||
import { formatMetric } from './string-utils';
|
||||
import { colors } from './color-utils';
|
||||
|
||||
|
||||
const openFilesScale = d3.scale.log().domain([1, 100000]).range([0, 1]);
|
||||
//
|
||||
// loadScale(1) == 0.5; E.g. a nicely balanced system :).
|
||||
const loadScale = d3.scale.log().domain([0.01, 100]).range([0, 1]);
|
||||
|
||||
export function getMetricValue(metric, size) {
|
||||
if (!metric) {
|
||||
return {height: 0, value: null, formattedValue: 'n/a'};
|
||||
}
|
||||
const m = metric.toJS();
|
||||
const value = m.value;
|
||||
|
||||
let valuePercentage = value === 0 ? 0 : value / m.max;
|
||||
if (m.id === 'open_files_count') {
|
||||
valuePercentage = openFilesScale(value);
|
||||
} else if (_.includes(['load1', 'load5', 'load15'], m.id)) {
|
||||
valuePercentage = loadScale(value);
|
||||
}
|
||||
|
||||
let displayedValue = Number(value).toFixed(1);
|
||||
if (displayedValue > 0) {
|
||||
const baseline = 0.1;
|
||||
displayedValue = valuePercentage * (1 - baseline) + baseline;
|
||||
}
|
||||
const height = size * displayedValue;
|
||||
|
||||
return {
|
||||
height,
|
||||
value,
|
||||
formattedValue: formatMetric(value, m, true)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function getMetricColor(metric) {
|
||||
const selectedMetric = metric && metric.get('id');
|
||||
// bluey
|
||||
if (/memory/.test(selectedMetric)) {
|
||||
return '#1f77b4';
|
||||
} else if (/cpu/.test(selectedMetric)) {
|
||||
return colors('cpu');
|
||||
} else if (/files/.test(selectedMetric)) {
|
||||
// return colors('files');
|
||||
// purple
|
||||
return '#9467bd';
|
||||
} else if (/load/.test(selectedMetric)) {
|
||||
return colors('load');
|
||||
}
|
||||
return 'steelBlue';
|
||||
}
|
||||
@@ -401,6 +401,7 @@ h2 {
|
||||
.stack .shape .metric-fill {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.stack .onlyMetrics .shape .metric-fill {
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -422,6 +423,7 @@ h2 {
|
||||
.metric-fill {
|
||||
stroke: none;
|
||||
fill: #A0BE7E;
|
||||
fill-opacity: 0.7;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
@@ -1000,50 +1002,55 @@ h2 {
|
||||
}
|
||||
}
|
||||
|
||||
.topology-options {
|
||||
.topology-option, .metric-selector {
|
||||
color: @text-secondary-color;
|
||||
margin: 6px 0;
|
||||
|
||||
.topology-option {
|
||||
color: @text-secondary-color;
|
||||
margin: 6px 0;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
border-radius: @border-radius;
|
||||
border: 1px solid @background-darker-color;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&-action {
|
||||
.btn-opacity;
|
||||
padding: 3px 12px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
|
||||
&-selected, &:hover {
|
||||
color: @text-darker-color;
|
||||
background-color: @background-darker-color;
|
||||
}
|
||||
|
||||
&-selected {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
border-top-left-radius: @border-radius;
|
||||
border-bottom-left-radius: @border-radius;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: @border-radius;
|
||||
border-bottom-right-radius: @border-radius;
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.fa {
|
||||
margin-left: 4px;
|
||||
color: darkred;
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
border-radius: @border-radius;
|
||||
border: 1px solid @background-darker-color;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&-action {
|
||||
.btn-opacity;
|
||||
padding: 3px 12px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
|
||||
&-selected, &:hover {
|
||||
color: @text-darker-color;
|
||||
background-color: @background-darker-color;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
border-top-left-radius: @border-radius;
|
||||
border-bottom-left-radius: @border-radius;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: @border-radius;
|
||||
border-bottom-right-radius: @border-radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.topology-option {
|
||||
&-action {
|
||||
&-selected {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
|
||||
Reference in New Issue
Block a user