Loading js-apps/meep-frontend/package-lock.json +5 −0 Original line number Diff line number Diff line Loading @@ -14606,6 +14606,11 @@ } } }, "react-d3-axis": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/react-d3-axis/-/react-d3-axis-0.1.2.tgz", "integrity": "sha512-Id2C208SGcLcIfgrdl9ffgZKXtp3wELV7dC3HJVjim+J0IQiAaZo9APSnlvJE2kB90C7wJKlXdFI1bYPg7jytA==" }, "react-d3-graph": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/react-d3-graph/-/react-d3-graph-2.1.0.tgz", js-apps/meep-frontend/package.json +1 −0 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ "material-design-icons": "3.0.1", "prop-types": "15.6.2", "react": "^16.8.6", "react-d3-axis": "^0.1.2", "react-d3-graph": "^2.0.2", "react-dom": "^16.8.6", "react-iframe": "^1.5.0", Loading js-apps/meep-frontend/src/js/containers/dashboard-container.js +128 −76 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ import ReactDOM from 'react-dom'; import { Button } from '@rmwc/button'; import { Checkbox } from '@rmwc/checkbox'; import { TextField, TextFieldHelperText } from '@rmwc/textfield'; import moment from 'moment'; import * as d3 from 'd3'; import axios from 'axios'; Loading @@ -24,9 +25,8 @@ import { } from '../util/scenario-utils'; import { dataAccessorForType, dataSetterForType, isDataPointOfType isDataPointOfType, valueOfPoint } from '../util/metrics'; import { Loading Loading @@ -68,41 +68,50 @@ function colorArray(dataLength) { const metricsBasePath = 'http://10.3.16.73:30008/v1'; const dataPointFromEpochDataPoints = destinations => sourceNodeId => dataAccessor => epochDataPoints => { if (!epochDataPoints.length) { return null; } let dp = { date: epochDataPoints[0].timestamp }; // const dataPointFromEpochDataPoints = destinations => sourceNodeId => dataAccessor => epochDataPoints => { // if (!epochDataPoints.length) { // return null; // } // let dp = { // date: epochDataPoints[0].timestamp // }; const avgForDest = dataPoints => acc => dest => { const hasSource = src => p => p.src === src; const hasDestination = dest => p => p.dest === dest; // const avgForDest = dataPoints => acc => dest => { // const hasSource = src => p => p.src === src; // const hasDestination = dest => p => p.dest === dest; const dataPointsForDestSource = dataPoints .filter(hasSource(sourceNodeId)) .filter(hasDestination(dest)); const avg = d3.mean(dataPointsForDestSource, acc); return avg; }; // const dataPointsForDestSource = dataPoints // .filter(hasSource(sourceNodeId)) // .filter(hasDestination(dest)); // const avg = d3.mean(dataPointsForDestSource, acc); // return avg; // }; destinations.forEach(dest => { dp[dest] = avgForDest(epochDataPoints)(dataAccessor)(dest) || 0; }); // destinations.forEach(dest => { // dp[dest] = avgForDest(epochDataPoints)(dataAccessor)(dest) || 0; // }); return dp; }; // return dp; // }; const notNull = x => x; const epochsToDataPoints = epochs => nb => destinations => dataAccessor => sourceNodeId => { const selectedEpochs = epochs.length ? epochs.slice(-nb) : []; if (selectedEpochs.length === 0) { console.log('epoch length is 0'); const buildSeriesFromEpoch = (series, epoch) => { epoch.data.forEach(p => { if (! series[p.dest]) { series[p.dest] = []; } const dataPoints = selectedEpochs.map(dataPointFromEpochDataPoints(destinations)(sourceNodeId)(dataAccessor)).filter(notNull); return dataPoints; series[p.dest].push(p); }); return series; }; const epochsToSeries = (epochs) => { let series = epochs.reduce((s, current) => { return buildSeriesFromEpoch(s, current); }, {}); return series; }; const ConfigurationView = (props) => { Loading Loading @@ -181,6 +190,8 @@ const ViewForName = ( min, max, data, series, startTime, mobilityEvents, dataPoints, dataAccessor, Loading Loading @@ -210,6 +221,8 @@ const ViewForName = ( width={width} height={600} data={data} series={series} startTime={startTime} dataAccessor={dataAccessor} dataType={dataType} selectedSource={selectedSource} Loading @@ -225,6 +238,8 @@ const ViewForName = ( return ( <IDCLineChart data={dataPoints} series={series} startTime={startTime} mobilityEvents={mobilityEvents} width={width} height={600} destinations={appIds} Loading @@ -242,6 +257,8 @@ const ViewForName = ( return ( <IDCLineChart data={dataPoints} series={series} startTime={startTime} mobilityEvents={mobilityEvents} width={width} height={600} destinations={appIds} Loading Loading @@ -314,6 +331,30 @@ const DashboardConfiguration = (props) => { ); }; const filterSeries = keys => filter => series => { let newSeries = {}; keys.forEach(key => { if (series[key]) { newSeries[key] = removeDuplicatePoints(series[key].filter(filter)); } }); return newSeries; }; const removeDuplicatePoints = sequence => { let timestampsMap = {}; let newSequence = []; sequence.forEach(p => { if (!timestampsMap[p.timestamp]) { timestampsMap[p.timestamp] = true; newSequence.push(p); } }); return newSequence; }; class DashboardContainer extends Component { constructor(props) { super(props); Loading Loading @@ -342,9 +383,18 @@ class DashboardContainer extends Component { } fetchMetrics() { return axios.get(`${metricsBasePath}/metrics?startTime=now-6s&stopTime=now`) const startTime = moment().add(-7, 'seconds').format(moment.HTML5_FMT.DATETIME_LOCAL_MS); const stopTime = moment().add(-6, 'seconds').format(moment.HTML5_FMT.DATETIME_LOCAL_MS); // const now = moment().format(moment.HTML5_FMT.DATETIME_LOCAL_MS); return axios.get(`${metricsBasePath}/metrics?startTime=${startTime}&stopTime=${stopTime}`) .then(res => { this.props.addMetricsEpoch(res.data.dataResponse || []); let epoch = { data: res.data.logResponse || [], //.sort((a, b) => new Date(a).getTime() - new Date(b).getTime() || []), startTime: startTime }; this.props.addMetricsEpoch(epoch); }).catch((e) => { console.log('Error while fetching metrics', e); }); Loading Loading @@ -389,16 +439,6 @@ class DashboardContainer extends Component { return {...res, [val.data.id]: colorRange[i]}; }, {}); let lastEpoch = this.props.epochs.length ? this.props.epochs.slice(-1)[0] : []; const hasValue = p => { const accessor = dataAccessorForType(p.dataType); if (! accessor(p)) { console.log(`No value for src ${p.src} and dest ${p.dest}`); } return accessor(p); }; lastEpoch = lastEpoch.filter(hasValue); const isDataOfType = type => dataPoint => dataPoint.dataType === type; const dataTypeForView = view => { Loading @@ -412,40 +452,48 @@ class DashboardContainer extends Component { } }; // Determine last 25 epochs // Determine first and last epochs const firstEpoch = this.props.epochs.length ? this.props.epochs[0] : { data: [], startTime: null }; let lastEpoch = this.props.epochs.length ? this.props.epochs.slice(-1)[0] : { data: [], startTime: null }; // Determine startTime of first epoch and endTime of last epoch // Create map of arrays of points, one array per source, indexed by source id // Pass that map to the views // Have each view consume that map const startTime = firstEpoch.data.length ? firstEpoch.startTime : null; const endTime = lastEpoch.data.length ? new Date(new Date(lastEpoch.startTime).getTime() + 1000).toString() : null; const series = epochsToSeries(this.props.epochs, selectedSource); const withTypeAndSource = type => source => point => { return point.dataType === type && point.src === source; }; // For view 1 const view1DataType = dataTypeForView(this.state.view1Name); const view1Accessor = dataAccessorForType(view1DataType); const view1DataPoints = epochsToDataPoints(this.props.epochs)(nbEpochs)(appIds)(view1Accessor)(selectedSource); const data1 = lastEpoch.filter(isDataOfType(view1DataType)); const max1 = d3.max(data1, view1Accessor); const min1 = d3.min(data1, view1Accessor); const series1 = filterSeries(appIds)(withTypeAndSource(view1DataType)(selectedSource))(series); const lastEpochData1 = lastEpoch.data.filter(isDataOfType(view1DataType)); // const max1 = d3.max(data1, p => p.value); // const min1 = d3.min(data1, p => p.value); // For view2 const view2DataType = dataTypeForView(this.state.view2Name); const view2Accessor = dataAccessorForType(view2DataType); const view2DataPoints = epochsToDataPoints(this.props.epochs)(nbEpochs)(appIds)(view2Accessor)(selectedSource); const data2 = lastEpoch.filter(isDataOfType(view2DataType)); const series2 = filterSeries(appIds)(withTypeAndSource(view2DataType)(selectedSource))(series); const lastEpochData2 = lastEpoch.data.filter(isDataOfType(view2DataType)); const extractPointsOfType = type => epoch => epoch.filter(isDataPointOfType(type)); // Mobility events const extractPointsOfType = type => epoch => epoch.data.filter(isDataPointOfType(type)); const extractMobilityEvents = extractPointsOfType(MOBILITY_EVENT); const mobilityEvents = this.props.epochs.flatMap(extractMobilityEvents); if (mobilityEvents.length) { console.log('Some mobility events ...'); } data2.forEach((d) => { const dd = view1Accessor(d); if (!dd) { console.log(`Null data: ${dd}. `); } }); const max2 = d3.max(data2, view2Accessor); const min2 = d3.min(data2, view2Accessor); // const max2 = d3.max(data2, view2Accessor); // const min2 = d3.min(data2, view2Accessor); const width = 700; const height = 600; Loading Loading @@ -475,12 +523,14 @@ class DashboardContainer extends Component { colorRange={colorRange} width={width1} height={height} data={data1} data={lastEpochData1} series={series1} startTime={startTime} endTime={endTime} mobilityEvents={mobilityEvents} min={min1} max={max1} dataPoints={view1DataPoints} dataAccessor={view1Accessor} // min={min1} // max={max1} // dataAccessor={view1Accessor} dataType={view1DataType} selectedSource={selectedSource} colorForApp={colorForApp} Loading @@ -496,12 +546,14 @@ class DashboardContainer extends Component { colorRange={colorRange} width={width2} height={height} data={data2} data={lastEpochData2} series={series2} startTime={startTime} endTime={endTime} mobilityEvents={mobilityEvents} min={min2} max={max2} dataPoints={view2DataPoints} dataAccessor={view2Accessor} // min={min2} // max={max2} // dataAccessor={view2Accessor} dataType={view2DataType} selectedSource={selectedSource} colorForApp={colorForApp} Loading js-apps/meep-frontend/src/js/containers/idc-apps-view.js +72 −9 Original line number Diff line number Diff line Loading @@ -18,7 +18,7 @@ import { lineGeneratorNodes } from './graph-utils'; const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => { const edgesFromData = (data, colorForApp, selectedSource) => { const pings = data; let m = {}; _.each(pings, p => { Loading @@ -38,7 +38,7 @@ const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => { const apps = Object.keys(m); const edgesFromSource = dataAccessor => src => { const edgesFromSource = src => { const rowObject = m[src]; if (!rowObject) { return []; Loading @@ -48,13 +48,13 @@ const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => { const edgesFromDestinations = (dest) => { // To debug const dataFromPing = p => { if (dataAccessor(p)) { if (p.value) { console.log('Bad value!'); } return dataAccessor(p); return p.value; }; if (!d3.mean(rowObject[dest].pings, dataAccessor)) { if (!d3.mean(rowObject[dest].pings, p => p.value)) { console.log('Bad value!'); } return { Loading @@ -62,7 +62,7 @@ const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => { dest: dest, count: rowObject[dest].pings.length, color: colorForApp[dest], avgData: d3.mean(rowObject[dest].pings, dataAccessor) avgData: d3.mean(rowObject[dest].pings, p => p.value) }; }; return _.map(destinations, edgesFromDestinations); Loading @@ -75,11 +75,74 @@ const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => { return true; } }; const edges = _.flatMap(apps.map(edgesFromSource(dataAccessor))).filter(outwardEdgesIfSourceSelected); const edges = _.flatMap(apps.map(edgesFromSource)).filter(outwardEdgesIfSourceSelected); return edges; }; const edgesFromSeries = (series, colorForApp, selectedSource) => { const pings = data; let m = {}; _.each(pings, p => { if (!m[p.src]) { m[p.src] = {}; } if (!m[p.src][p.dest]) { m[p.src][p.dest] = { pings: [] }; } const o = m[p.src][p.dest]; o.pings.push(p); }); const apps = Object.keys(m); const edgesFromSource = src => { const rowObject = m[src]; if (!rowObject) { return []; } const destinations = Object.keys(m[src]); const edgesFromDestinations = (dest) => { // To debug const dataFromPing = p => { if (p.value) { console.log('Bad value!'); } return p.value; }; if (!d3.mean(rowObject[dest].pings, p => p.value)) { console.log('Bad value!'); } return { src: src, dest: dest, count: rowObject[dest].pings.length, color: colorForApp[dest], avgData: d3.mean(rowObject[dest].pings, p => p.value) }; }; return _.map(destinations, edgesFromDestinations); }; const outwardEdgesIfSourceSelected = e => { if (selectedSource) { return e.src === selectedSource; } else { return true; } }; const edges = _.flatMap(apps.map(edgesFromSource)).filter(outwardEdgesIfSourceSelected); return edges; }; const positionAppsCircle = ({apps, width, height}) => { const cx = width/2.0; const cy = height/2.0; Loading Loading @@ -121,7 +184,7 @@ const IDCAppsView = ( colorRange, selectedSource, data, dataAccessor, series, dataType, width, height, Loading @@ -142,7 +205,7 @@ const IDCAppsView = ( const appsMap = {}; _.each(apps, a => appsMap[a.data.id] = a); const edges = edgesFromData(data.filter(dataAccessor), dataAccessor, colorForApp, selectedSource); const edges = edgesFromData(data.filter(p => p.value), colorForApp, selectedSource); const edgeLabel = edgeLabelForDataType(dataType); const edgeUnits = unitsForDataType(dataType); Loading js-apps/meep-frontend/src/js/containers/idc-line-chart-back.js 0 → 100644 +275 −0 Original line number Diff line number Diff line /* * Copyright (c) 2019 * InterDigital Communications, Inc. * All rights reserved. * * The information provided herein is the proprietary and confidential * information of InterDigital Communications, Inc. */ import _ from 'lodash'; import { connect } from 'react-redux'; import React, { useRef, useEffect } from 'react'; import ReactDOM from 'react-dom'; import moment from 'moment'; import * as d3 from 'd3'; import uuid from 'uuid'; import { uiChangeCurrentDialog } from '../state/ui'; import { execFakeChangeSelectedDestination } from '../state/exec'; import { LATENCY_METRICS, THROUGHPUT_METRICS } from '../meep-constants'; const notNull = x => x; const IDCLineChartBack = props => { const d3Container = useRef(null); /* The useEffect Hook is for running side effects outside of React, for instance inserting elements into the DOM using D3 */ useEffect( () => { const margin = {top: 20, right: 40, bottom: 30, left: 60}; const width = props.width - margin.left - margin.right; const height = props.height - margin.top - margin.bottom; const min = props.min; const max = props.max; const maxOfYScale = Math.ceil(max/100.0) * 100.0; let mainGroup = d3.select(d3Container.current); if (mainGroup.select('g').size() === 0) { mainGroup = mainGroup.append('g') .attr('width', props.width + margin.left + margin.right) .attr('height', props.height + margin.top + margin.bottom) .attr('transform', `translate(${margin.left}, ${margin.top})`); } const flattenSeries = series => { return _.flatMap(Object.values(series)); }; const chart = (series) => { const destinations = props.selectedSource ? props.destinations.slice(-props.destinations.length) : []; const colorRange = destinations.map(s => props.colorForApp[s]); const yRange = [0, 200]; const timeRange = d3.extent(flattenSeries(series), d => new Date(d.timestamp)); const x = d3.scaleTime().domain(timeRange).range([0, width]); const y = d3.scaleLinear().domain(yRange).range([height - 50, 0]); const z = d3.scaleOrdinal().range(colorRange); // Axes const xAxis = d3.axisBottom(x); //.ticks(d3.timeSeconds); const yAxis = d3.axisLeft(y).scale(y) .tickSize(0.01); // const yAxisr = d3.axisLeft(y); // const dataLinePointFromDataPoint = key => point => { // if (point[key] === undefined) { // console.log('point[key] is undefined, for key ' + key + ' and point ', point); // return null; // } // return { // date: point.date, // value: point[key] // }; // }; const dataLineFromSeries = series => key => { if (!series[key].filter) { console.log('Check'); } const line = series[key].filter(notNull).filter(p => p.value); // .sort((a, b) => { // return x(new Date(a.timestamp)) - x(new Date(b.timestamp)); // }); //TODO: add point at props.startTime and props.endTime line.key = key; return line; }; let dataLines = destinations.map(dataLineFromSeries(props.series)); // TODO: remove dataLines = dataLines.length ? [dataLines[0]] : []; if (dataLines.length < 5) { console.log('Too few dataLines: ', dataLines.length); } const valueLine = d3.line() .x(function(d) { return margin.left + x(new Date(d.timestamp)); }) .y(function(d) { return y(d.value) + margin.top; }) .curve(d3.curveMonotoneX); mainGroup.selectAll('.line') .data(dataLines) .join('path').attr('class', 'line') .attr('d', valueLine) .style('stroke', (d, i) => z(i)) .style('fill', 'none') .style('stroke-width', 3); // if (mainGroup.selectAll('.line').size() > 0) { // const linesMarginLeft = mainGroup.selectAll('.line').attr('margin-left'); // console.log('linesMarginLeft: ', linesMarginLeft); // } // Mobility events // const mobilityEventLine = d => `M${x(new Date(d.timestamp)) + margin.left},${y(yRange[0]) + margin.top} L${x(new Date(d.timestamp)) + margin.left},${y(yRange[1]) + margin.top}`; const mobilityEventLine = d => `M${x(new Date(d.timestamp)) + margin.left},${y(yRange[1]) + margin.top} L${x(new Date(d.timestamp)) + margin.left},${y(yRange[0]) + margin.top}`; mainGroup.selectAll('.mobilityEventLine') .data(props.mobilityEvents) .join('path') .attr('class', 'mobilityEventLine') .attr('d', mobilityEventLine) .attr('id', d => d.timestamp) .style('stroke', 'gray') .style('stroke-width', 1) .style('fill', 'none'); mainGroup.selectAll('.mobilityEventLineText') .data(props.mobilityEvents) .join('text') .attr('class', 'mobilityEventLineText') .style('stroke','gray') .style('stroke-width', 1) .style('fill','gray'); // .attr('x', d => x(new Date(d.timestamp)) + margin.left) // .attr('dy',50 + margin.top) mainGroup.selectAll('.mobilityEventLineTextPath').remove(); mainGroup.selectAll('.mobilityEventLineText') .data(props.mobilityEvents) .append('textPath') .attr('class', 'mobilityEventLineTextPath') .attr('xlink:href', d => `#${d.timestamp}`) .attr('stroke','gray') .attr('fill','gray') .text(d => `Mobility Event: ${d.src} to ${d.dest}`) .attr('transform', 'rotate(-180)'); const xAxisGroup = mainGroup.selectAll('.xaxis'); if (xAxisGroup.size() === 0) { mainGroup.append('g') .attr('class', 'xaxis') .attr('transform', 'translate(0,' + height + ')').call(xAxis); } else { xAxisGroup.attr('transform', 'translate(0,' + height + ')').call(xAxis); } mainGroup.selectAll('.xaxis').call(xAxis); const yAxisGroup = mainGroup.selectAll('.yaxis'); if (yAxisGroup.size() === 0) { mainGroup.append('g') .attr('class', 'yaxis') .attr('transform', 'translate(' + width + ', 0)') .style('z-index', '18') .call(yAxis); } else { yAxisGroup.attr('transform', 'translate(' + width + ', 0)'); } // text label for the y axis const labelForType = type => { switch (type) { case LATENCY_METRICS: return 'Latency (ms)'; case THROUGHPUT_METRICS: return 'Throughput (kbs)'; default: return ''; } }; const yAxisLabel = labelForType(props.dataType); if (!mainGroup.selectAll('.yLabel').size()) { mainGroup.append('text') .attr('class', 'yLabel') .attr('transform', 'rotate(-90)') .attr('y', 0 - margin.left + 10) .attr('x', 0 - (height / 2)) .attr('dy', '1em') .style('text-anchor', 'middle') .text(yAxisLabel); } else { mainGroup.selectAll('.yLabel') .text(yAxisLabel); } // Chart title const chartTitleForType = type => { switch (type) { case LATENCY_METRICS: return 'Latency Chart'; case THROUGHPUT_METRICS: return 'Throughput Chart'; default: return ''; } }; const chartTitle = chartTitleForType(props.dataType); if (!mainGroup.selectAll('.chartTitle').size()) { mainGroup.append('text') .attr('class', 'chartTitle') .attr('y', 0 + margin.top + 10) .attr('x', width / 2) // .attr('dy', '1em') .style('text-anchor', 'middle') .text(chartTitle); } else { mainGroup.selectAll('.chartTitle') .text(chartTitle); } const yAxisGroup0 = mainGroup.selectAll('.yaxis0'); if (yAxisGroup0.size() === 0) { mainGroup.append('g') .attr('class', 'yaxis0') .attr('transform', 'translate(0, 0)') .style('z-index', '18') .call(yAxis); } else { yAxisGroup0.attr('transform', 'translate(0, 0)').style('z-index', '18') } }; chart(props.series); }, /* useEffect has a dependency array (below). It's a list of dependency variables for this useEffect block. The block will run after mount and whenever any of these variables change. We still have to check if the variables are valid, but we do not have to compare old props to next props to decide whether to rerender. */ [props.data, d3Container.current]); return ( <div className='chart'> <svg //viewBox='0 -20 200 33' ref={d3Container} className='d3-component' height={props.height} width={props.width} > </svg> </div> ); }; export default IDCLineChartBack; No newline at end of file Loading
js-apps/meep-frontend/package-lock.json +5 −0 Original line number Diff line number Diff line Loading @@ -14606,6 +14606,11 @@ } } }, "react-d3-axis": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/react-d3-axis/-/react-d3-axis-0.1.2.tgz", "integrity": "sha512-Id2C208SGcLcIfgrdl9ffgZKXtp3wELV7dC3HJVjim+J0IQiAaZo9APSnlvJE2kB90C7wJKlXdFI1bYPg7jytA==" }, "react-d3-graph": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/react-d3-graph/-/react-d3-graph-2.1.0.tgz",
js-apps/meep-frontend/package.json +1 −0 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ "material-design-icons": "3.0.1", "prop-types": "15.6.2", "react": "^16.8.6", "react-d3-axis": "^0.1.2", "react-d3-graph": "^2.0.2", "react-dom": "^16.8.6", "react-iframe": "^1.5.0", Loading
js-apps/meep-frontend/src/js/containers/dashboard-container.js +128 −76 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ import ReactDOM from 'react-dom'; import { Button } from '@rmwc/button'; import { Checkbox } from '@rmwc/checkbox'; import { TextField, TextFieldHelperText } from '@rmwc/textfield'; import moment from 'moment'; import * as d3 from 'd3'; import axios from 'axios'; Loading @@ -24,9 +25,8 @@ import { } from '../util/scenario-utils'; import { dataAccessorForType, dataSetterForType, isDataPointOfType isDataPointOfType, valueOfPoint } from '../util/metrics'; import { Loading Loading @@ -68,41 +68,50 @@ function colorArray(dataLength) { const metricsBasePath = 'http://10.3.16.73:30008/v1'; const dataPointFromEpochDataPoints = destinations => sourceNodeId => dataAccessor => epochDataPoints => { if (!epochDataPoints.length) { return null; } let dp = { date: epochDataPoints[0].timestamp }; // const dataPointFromEpochDataPoints = destinations => sourceNodeId => dataAccessor => epochDataPoints => { // if (!epochDataPoints.length) { // return null; // } // let dp = { // date: epochDataPoints[0].timestamp // }; const avgForDest = dataPoints => acc => dest => { const hasSource = src => p => p.src === src; const hasDestination = dest => p => p.dest === dest; // const avgForDest = dataPoints => acc => dest => { // const hasSource = src => p => p.src === src; // const hasDestination = dest => p => p.dest === dest; const dataPointsForDestSource = dataPoints .filter(hasSource(sourceNodeId)) .filter(hasDestination(dest)); const avg = d3.mean(dataPointsForDestSource, acc); return avg; }; // const dataPointsForDestSource = dataPoints // .filter(hasSource(sourceNodeId)) // .filter(hasDestination(dest)); // const avg = d3.mean(dataPointsForDestSource, acc); // return avg; // }; destinations.forEach(dest => { dp[dest] = avgForDest(epochDataPoints)(dataAccessor)(dest) || 0; }); // destinations.forEach(dest => { // dp[dest] = avgForDest(epochDataPoints)(dataAccessor)(dest) || 0; // }); return dp; }; // return dp; // }; const notNull = x => x; const epochsToDataPoints = epochs => nb => destinations => dataAccessor => sourceNodeId => { const selectedEpochs = epochs.length ? epochs.slice(-nb) : []; if (selectedEpochs.length === 0) { console.log('epoch length is 0'); const buildSeriesFromEpoch = (series, epoch) => { epoch.data.forEach(p => { if (! series[p.dest]) { series[p.dest] = []; } const dataPoints = selectedEpochs.map(dataPointFromEpochDataPoints(destinations)(sourceNodeId)(dataAccessor)).filter(notNull); return dataPoints; series[p.dest].push(p); }); return series; }; const epochsToSeries = (epochs) => { let series = epochs.reduce((s, current) => { return buildSeriesFromEpoch(s, current); }, {}); return series; }; const ConfigurationView = (props) => { Loading Loading @@ -181,6 +190,8 @@ const ViewForName = ( min, max, data, series, startTime, mobilityEvents, dataPoints, dataAccessor, Loading Loading @@ -210,6 +221,8 @@ const ViewForName = ( width={width} height={600} data={data} series={series} startTime={startTime} dataAccessor={dataAccessor} dataType={dataType} selectedSource={selectedSource} Loading @@ -225,6 +238,8 @@ const ViewForName = ( return ( <IDCLineChart data={dataPoints} series={series} startTime={startTime} mobilityEvents={mobilityEvents} width={width} height={600} destinations={appIds} Loading @@ -242,6 +257,8 @@ const ViewForName = ( return ( <IDCLineChart data={dataPoints} series={series} startTime={startTime} mobilityEvents={mobilityEvents} width={width} height={600} destinations={appIds} Loading Loading @@ -314,6 +331,30 @@ const DashboardConfiguration = (props) => { ); }; const filterSeries = keys => filter => series => { let newSeries = {}; keys.forEach(key => { if (series[key]) { newSeries[key] = removeDuplicatePoints(series[key].filter(filter)); } }); return newSeries; }; const removeDuplicatePoints = sequence => { let timestampsMap = {}; let newSequence = []; sequence.forEach(p => { if (!timestampsMap[p.timestamp]) { timestampsMap[p.timestamp] = true; newSequence.push(p); } }); return newSequence; }; class DashboardContainer extends Component { constructor(props) { super(props); Loading Loading @@ -342,9 +383,18 @@ class DashboardContainer extends Component { } fetchMetrics() { return axios.get(`${metricsBasePath}/metrics?startTime=now-6s&stopTime=now`) const startTime = moment().add(-7, 'seconds').format(moment.HTML5_FMT.DATETIME_LOCAL_MS); const stopTime = moment().add(-6, 'seconds').format(moment.HTML5_FMT.DATETIME_LOCAL_MS); // const now = moment().format(moment.HTML5_FMT.DATETIME_LOCAL_MS); return axios.get(`${metricsBasePath}/metrics?startTime=${startTime}&stopTime=${stopTime}`) .then(res => { this.props.addMetricsEpoch(res.data.dataResponse || []); let epoch = { data: res.data.logResponse || [], //.sort((a, b) => new Date(a).getTime() - new Date(b).getTime() || []), startTime: startTime }; this.props.addMetricsEpoch(epoch); }).catch((e) => { console.log('Error while fetching metrics', e); }); Loading Loading @@ -389,16 +439,6 @@ class DashboardContainer extends Component { return {...res, [val.data.id]: colorRange[i]}; }, {}); let lastEpoch = this.props.epochs.length ? this.props.epochs.slice(-1)[0] : []; const hasValue = p => { const accessor = dataAccessorForType(p.dataType); if (! accessor(p)) { console.log(`No value for src ${p.src} and dest ${p.dest}`); } return accessor(p); }; lastEpoch = lastEpoch.filter(hasValue); const isDataOfType = type => dataPoint => dataPoint.dataType === type; const dataTypeForView = view => { Loading @@ -412,40 +452,48 @@ class DashboardContainer extends Component { } }; // Determine last 25 epochs // Determine first and last epochs const firstEpoch = this.props.epochs.length ? this.props.epochs[0] : { data: [], startTime: null }; let lastEpoch = this.props.epochs.length ? this.props.epochs.slice(-1)[0] : { data: [], startTime: null }; // Determine startTime of first epoch and endTime of last epoch // Create map of arrays of points, one array per source, indexed by source id // Pass that map to the views // Have each view consume that map const startTime = firstEpoch.data.length ? firstEpoch.startTime : null; const endTime = lastEpoch.data.length ? new Date(new Date(lastEpoch.startTime).getTime() + 1000).toString() : null; const series = epochsToSeries(this.props.epochs, selectedSource); const withTypeAndSource = type => source => point => { return point.dataType === type && point.src === source; }; // For view 1 const view1DataType = dataTypeForView(this.state.view1Name); const view1Accessor = dataAccessorForType(view1DataType); const view1DataPoints = epochsToDataPoints(this.props.epochs)(nbEpochs)(appIds)(view1Accessor)(selectedSource); const data1 = lastEpoch.filter(isDataOfType(view1DataType)); const max1 = d3.max(data1, view1Accessor); const min1 = d3.min(data1, view1Accessor); const series1 = filterSeries(appIds)(withTypeAndSource(view1DataType)(selectedSource))(series); const lastEpochData1 = lastEpoch.data.filter(isDataOfType(view1DataType)); // const max1 = d3.max(data1, p => p.value); // const min1 = d3.min(data1, p => p.value); // For view2 const view2DataType = dataTypeForView(this.state.view2Name); const view2Accessor = dataAccessorForType(view2DataType); const view2DataPoints = epochsToDataPoints(this.props.epochs)(nbEpochs)(appIds)(view2Accessor)(selectedSource); const data2 = lastEpoch.filter(isDataOfType(view2DataType)); const series2 = filterSeries(appIds)(withTypeAndSource(view2DataType)(selectedSource))(series); const lastEpochData2 = lastEpoch.data.filter(isDataOfType(view2DataType)); const extractPointsOfType = type => epoch => epoch.filter(isDataPointOfType(type)); // Mobility events const extractPointsOfType = type => epoch => epoch.data.filter(isDataPointOfType(type)); const extractMobilityEvents = extractPointsOfType(MOBILITY_EVENT); const mobilityEvents = this.props.epochs.flatMap(extractMobilityEvents); if (mobilityEvents.length) { console.log('Some mobility events ...'); } data2.forEach((d) => { const dd = view1Accessor(d); if (!dd) { console.log(`Null data: ${dd}. `); } }); const max2 = d3.max(data2, view2Accessor); const min2 = d3.min(data2, view2Accessor); // const max2 = d3.max(data2, view2Accessor); // const min2 = d3.min(data2, view2Accessor); const width = 700; const height = 600; Loading Loading @@ -475,12 +523,14 @@ class DashboardContainer extends Component { colorRange={colorRange} width={width1} height={height} data={data1} data={lastEpochData1} series={series1} startTime={startTime} endTime={endTime} mobilityEvents={mobilityEvents} min={min1} max={max1} dataPoints={view1DataPoints} dataAccessor={view1Accessor} // min={min1} // max={max1} // dataAccessor={view1Accessor} dataType={view1DataType} selectedSource={selectedSource} colorForApp={colorForApp} Loading @@ -496,12 +546,14 @@ class DashboardContainer extends Component { colorRange={colorRange} width={width2} height={height} data={data2} data={lastEpochData2} series={series2} startTime={startTime} endTime={endTime} mobilityEvents={mobilityEvents} min={min2} max={max2} dataPoints={view2DataPoints} dataAccessor={view2Accessor} // min={min2} // max={max2} // dataAccessor={view2Accessor} dataType={view2DataType} selectedSource={selectedSource} colorForApp={colorForApp} Loading
js-apps/meep-frontend/src/js/containers/idc-apps-view.js +72 −9 Original line number Diff line number Diff line Loading @@ -18,7 +18,7 @@ import { lineGeneratorNodes } from './graph-utils'; const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => { const edgesFromData = (data, colorForApp, selectedSource) => { const pings = data; let m = {}; _.each(pings, p => { Loading @@ -38,7 +38,7 @@ const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => { const apps = Object.keys(m); const edgesFromSource = dataAccessor => src => { const edgesFromSource = src => { const rowObject = m[src]; if (!rowObject) { return []; Loading @@ -48,13 +48,13 @@ const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => { const edgesFromDestinations = (dest) => { // To debug const dataFromPing = p => { if (dataAccessor(p)) { if (p.value) { console.log('Bad value!'); } return dataAccessor(p); return p.value; }; if (!d3.mean(rowObject[dest].pings, dataAccessor)) { if (!d3.mean(rowObject[dest].pings, p => p.value)) { console.log('Bad value!'); } return { Loading @@ -62,7 +62,7 @@ const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => { dest: dest, count: rowObject[dest].pings.length, color: colorForApp[dest], avgData: d3.mean(rowObject[dest].pings, dataAccessor) avgData: d3.mean(rowObject[dest].pings, p => p.value) }; }; return _.map(destinations, edgesFromDestinations); Loading @@ -75,11 +75,74 @@ const edgesFromData = (data, dataAccessor, colorForApp, selectedSource) => { return true; } }; const edges = _.flatMap(apps.map(edgesFromSource(dataAccessor))).filter(outwardEdgesIfSourceSelected); const edges = _.flatMap(apps.map(edgesFromSource)).filter(outwardEdgesIfSourceSelected); return edges; }; const edgesFromSeries = (series, colorForApp, selectedSource) => { const pings = data; let m = {}; _.each(pings, p => { if (!m[p.src]) { m[p.src] = {}; } if (!m[p.src][p.dest]) { m[p.src][p.dest] = { pings: [] }; } const o = m[p.src][p.dest]; o.pings.push(p); }); const apps = Object.keys(m); const edgesFromSource = src => { const rowObject = m[src]; if (!rowObject) { return []; } const destinations = Object.keys(m[src]); const edgesFromDestinations = (dest) => { // To debug const dataFromPing = p => { if (p.value) { console.log('Bad value!'); } return p.value; }; if (!d3.mean(rowObject[dest].pings, p => p.value)) { console.log('Bad value!'); } return { src: src, dest: dest, count: rowObject[dest].pings.length, color: colorForApp[dest], avgData: d3.mean(rowObject[dest].pings, p => p.value) }; }; return _.map(destinations, edgesFromDestinations); }; const outwardEdgesIfSourceSelected = e => { if (selectedSource) { return e.src === selectedSource; } else { return true; } }; const edges = _.flatMap(apps.map(edgesFromSource)).filter(outwardEdgesIfSourceSelected); return edges; }; const positionAppsCircle = ({apps, width, height}) => { const cx = width/2.0; const cy = height/2.0; Loading Loading @@ -121,7 +184,7 @@ const IDCAppsView = ( colorRange, selectedSource, data, dataAccessor, series, dataType, width, height, Loading @@ -142,7 +205,7 @@ const IDCAppsView = ( const appsMap = {}; _.each(apps, a => appsMap[a.data.id] = a); const edges = edgesFromData(data.filter(dataAccessor), dataAccessor, colorForApp, selectedSource); const edges = edgesFromData(data.filter(p => p.value), colorForApp, selectedSource); const edgeLabel = edgeLabelForDataType(dataType); const edgeUnits = unitsForDataType(dataType); Loading
js-apps/meep-frontend/src/js/containers/idc-line-chart-back.js 0 → 100644 +275 −0 Original line number Diff line number Diff line /* * Copyright (c) 2019 * InterDigital Communications, Inc. * All rights reserved. * * The information provided herein is the proprietary and confidential * information of InterDigital Communications, Inc. */ import _ from 'lodash'; import { connect } from 'react-redux'; import React, { useRef, useEffect } from 'react'; import ReactDOM from 'react-dom'; import moment from 'moment'; import * as d3 from 'd3'; import uuid from 'uuid'; import { uiChangeCurrentDialog } from '../state/ui'; import { execFakeChangeSelectedDestination } from '../state/exec'; import { LATENCY_METRICS, THROUGHPUT_METRICS } from '../meep-constants'; const notNull = x => x; const IDCLineChartBack = props => { const d3Container = useRef(null); /* The useEffect Hook is for running side effects outside of React, for instance inserting elements into the DOM using D3 */ useEffect( () => { const margin = {top: 20, right: 40, bottom: 30, left: 60}; const width = props.width - margin.left - margin.right; const height = props.height - margin.top - margin.bottom; const min = props.min; const max = props.max; const maxOfYScale = Math.ceil(max/100.0) * 100.0; let mainGroup = d3.select(d3Container.current); if (mainGroup.select('g').size() === 0) { mainGroup = mainGroup.append('g') .attr('width', props.width + margin.left + margin.right) .attr('height', props.height + margin.top + margin.bottom) .attr('transform', `translate(${margin.left}, ${margin.top})`); } const flattenSeries = series => { return _.flatMap(Object.values(series)); }; const chart = (series) => { const destinations = props.selectedSource ? props.destinations.slice(-props.destinations.length) : []; const colorRange = destinations.map(s => props.colorForApp[s]); const yRange = [0, 200]; const timeRange = d3.extent(flattenSeries(series), d => new Date(d.timestamp)); const x = d3.scaleTime().domain(timeRange).range([0, width]); const y = d3.scaleLinear().domain(yRange).range([height - 50, 0]); const z = d3.scaleOrdinal().range(colorRange); // Axes const xAxis = d3.axisBottom(x); //.ticks(d3.timeSeconds); const yAxis = d3.axisLeft(y).scale(y) .tickSize(0.01); // const yAxisr = d3.axisLeft(y); // const dataLinePointFromDataPoint = key => point => { // if (point[key] === undefined) { // console.log('point[key] is undefined, for key ' + key + ' and point ', point); // return null; // } // return { // date: point.date, // value: point[key] // }; // }; const dataLineFromSeries = series => key => { if (!series[key].filter) { console.log('Check'); } const line = series[key].filter(notNull).filter(p => p.value); // .sort((a, b) => { // return x(new Date(a.timestamp)) - x(new Date(b.timestamp)); // }); //TODO: add point at props.startTime and props.endTime line.key = key; return line; }; let dataLines = destinations.map(dataLineFromSeries(props.series)); // TODO: remove dataLines = dataLines.length ? [dataLines[0]] : []; if (dataLines.length < 5) { console.log('Too few dataLines: ', dataLines.length); } const valueLine = d3.line() .x(function(d) { return margin.left + x(new Date(d.timestamp)); }) .y(function(d) { return y(d.value) + margin.top; }) .curve(d3.curveMonotoneX); mainGroup.selectAll('.line') .data(dataLines) .join('path').attr('class', 'line') .attr('d', valueLine) .style('stroke', (d, i) => z(i)) .style('fill', 'none') .style('stroke-width', 3); // if (mainGroup.selectAll('.line').size() > 0) { // const linesMarginLeft = mainGroup.selectAll('.line').attr('margin-left'); // console.log('linesMarginLeft: ', linesMarginLeft); // } // Mobility events // const mobilityEventLine = d => `M${x(new Date(d.timestamp)) + margin.left},${y(yRange[0]) + margin.top} L${x(new Date(d.timestamp)) + margin.left},${y(yRange[1]) + margin.top}`; const mobilityEventLine = d => `M${x(new Date(d.timestamp)) + margin.left},${y(yRange[1]) + margin.top} L${x(new Date(d.timestamp)) + margin.left},${y(yRange[0]) + margin.top}`; mainGroup.selectAll('.mobilityEventLine') .data(props.mobilityEvents) .join('path') .attr('class', 'mobilityEventLine') .attr('d', mobilityEventLine) .attr('id', d => d.timestamp) .style('stroke', 'gray') .style('stroke-width', 1) .style('fill', 'none'); mainGroup.selectAll('.mobilityEventLineText') .data(props.mobilityEvents) .join('text') .attr('class', 'mobilityEventLineText') .style('stroke','gray') .style('stroke-width', 1) .style('fill','gray'); // .attr('x', d => x(new Date(d.timestamp)) + margin.left) // .attr('dy',50 + margin.top) mainGroup.selectAll('.mobilityEventLineTextPath').remove(); mainGroup.selectAll('.mobilityEventLineText') .data(props.mobilityEvents) .append('textPath') .attr('class', 'mobilityEventLineTextPath') .attr('xlink:href', d => `#${d.timestamp}`) .attr('stroke','gray') .attr('fill','gray') .text(d => `Mobility Event: ${d.src} to ${d.dest}`) .attr('transform', 'rotate(-180)'); const xAxisGroup = mainGroup.selectAll('.xaxis'); if (xAxisGroup.size() === 0) { mainGroup.append('g') .attr('class', 'xaxis') .attr('transform', 'translate(0,' + height + ')').call(xAxis); } else { xAxisGroup.attr('transform', 'translate(0,' + height + ')').call(xAxis); } mainGroup.selectAll('.xaxis').call(xAxis); const yAxisGroup = mainGroup.selectAll('.yaxis'); if (yAxisGroup.size() === 0) { mainGroup.append('g') .attr('class', 'yaxis') .attr('transform', 'translate(' + width + ', 0)') .style('z-index', '18') .call(yAxis); } else { yAxisGroup.attr('transform', 'translate(' + width + ', 0)'); } // text label for the y axis const labelForType = type => { switch (type) { case LATENCY_METRICS: return 'Latency (ms)'; case THROUGHPUT_METRICS: return 'Throughput (kbs)'; default: return ''; } }; const yAxisLabel = labelForType(props.dataType); if (!mainGroup.selectAll('.yLabel').size()) { mainGroup.append('text') .attr('class', 'yLabel') .attr('transform', 'rotate(-90)') .attr('y', 0 - margin.left + 10) .attr('x', 0 - (height / 2)) .attr('dy', '1em') .style('text-anchor', 'middle') .text(yAxisLabel); } else { mainGroup.selectAll('.yLabel') .text(yAxisLabel); } // Chart title const chartTitleForType = type => { switch (type) { case LATENCY_METRICS: return 'Latency Chart'; case THROUGHPUT_METRICS: return 'Throughput Chart'; default: return ''; } }; const chartTitle = chartTitleForType(props.dataType); if (!mainGroup.selectAll('.chartTitle').size()) { mainGroup.append('text') .attr('class', 'chartTitle') .attr('y', 0 + margin.top + 10) .attr('x', width / 2) // .attr('dy', '1em') .style('text-anchor', 'middle') .text(chartTitle); } else { mainGroup.selectAll('.chartTitle') .text(chartTitle); } const yAxisGroup0 = mainGroup.selectAll('.yaxis0'); if (yAxisGroup0.size() === 0) { mainGroup.append('g') .attr('class', 'yaxis0') .attr('transform', 'translate(0, 0)') .style('z-index', '18') .call(yAxis); } else { yAxisGroup0.attr('transform', 'translate(0, 0)').style('z-index', '18') } }; chart(props.series); }, /* useEffect has a dependency array (below). It's a list of dependency variables for this useEffect block. The block will run after mount and whenever any of these variables change. We still have to check if the variables are valid, but we do not have to compare old props to next props to decide whether to rerender. */ [props.data, d3Container.current]); return ( <div className='chart'> <svg //viewBox='0 -20 200 33' ref={d3Container} className='d3-component' height={props.height} width={props.width} > </svg> </div> ); }; export default IDCLineChartBack; No newline at end of file