From da7686803f2605d4e8b152dfa56c066770c43238 Mon Sep 17 00:00:00 2001 From: Terry Lucas Date: Tue, 8 Dec 2020 08:18:26 -0800 Subject: [PATCH 1/4] Updated X-labels to be more informative and added collected Raster metrics to chart. --- .../devtools_app/lib/src/charts/chart.dart | 112 ++------ .../lib/src/charts/chart_controller.dart | 262 ++++++++++-------- .../lib/src/memory/memory_android_chart.dart | 3 +- .../lib/src/memory/memory_controller.dart | 2 +- .../lib/src/memory/memory_screen.dart | 6 +- .../lib/src/memory/memory_vm_chart.dart | 55 +++- packages/devtools_app/pubspec.lock | 2 +- packages/devtools_app/test/chart_test.dart | 150 ++++------ .../test/goldens/memory_chart_fixed_all.png | Bin 18111 -> 18119 bytes .../memory_chart_fixed_five_minutes.png | Bin 16320 -> 16335 bytes .../test/goldens/memory_chart_fixed_live.png | Bin 13077 -> 13079 bytes .../test/goldens/memory_chart_scaled_all.png | Bin 36779 -> 36784 bytes .../memory_chart_scaled_five_minute.png | Bin 26483 -> 26498 bytes .../test/goldens/memory_chart_scaled_live.png | Bin 22110 -> 22115 bytes 14 files changed, 287 insertions(+), 305 deletions(-) diff --git a/packages/devtools_app/lib/src/charts/chart.dart b/packages/devtools_app/lib/src/charts/chart.dart index 2a48c302ae9..4d3c32129b0 100644 --- a/packages/devtools_app/lib/src/charts/chart.dart +++ b/packages/devtools_app/lib/src/charts/chart.dart @@ -164,11 +164,6 @@ class ChartPainter extends CustomPainter { }, ); - /// Number of minor ticks between major ticks. - const tickMarkInterval = 4; - - int tickIndex = 0; - /// Key is trace index and value is x,y point. final previousTracesData = {}; @@ -188,29 +183,9 @@ class ChartPainter extends CustomPainter { int xTickIndex = chartController.timestampsLength; while (--xTickIndex >= 0) { - // Hide left-side label when its ticks scrolls out. - final leftTimestamp = chartController.leftLabelTimestamp; - if (endVisibleIndex >= 0 && - leftTimestamp != null && - leftTimestamp < chartController.timestamps[endVisibleIndex]) { - chartController.leftLabelTimestamp = null; - } - - // Make sure duration label has been computed. - chartController.computeDurationLabel(); - final currentTimestamp = chartController.timestamps[xTickIndex]; - final xLabelIndex = - chartController.getLabeledIndexByTimestamp(currentTimestamp); if (xTickIndex < endVisibleIndex) { - // Has left label has slid out of visible range? - if (xLabelIndex == leftLabelIndex) { - // Yes, slide other labels left. - chartController.slideLabelsLeft(); - chartController.rightLabelTimestamp = currentTimestamp; - } - // Once outside of visible range of data skip the rest of the collected data. break; } @@ -315,79 +290,44 @@ class ChartPainter extends CustomPainter { // Undo the clipRect at beginning of for loop. canvas.restore(); - - ///////////////////////////////////////////////////////////////////////// - // Compute the ticks to label. - ///////////////////////////////////////////////////////////////////////// - - final minorTick = tickIndex++ < tickMarkInterval; - - // Compute major tick labels - if (!minorTick) { - chartController.recomputeLabels( - timestamp: chartController.timestamps.last, - ); - } - - if (chartController.displayXAxis || chartController.displayXLabels) - // Y translation is below X-axis line. - drawTranslate( - canvas, - xTranslation, - chartController.zeroYPosition + 1, - (canvas) { - // Draw right-most tick label (first major tick). - drawXTick( - canvas, - currentTimestamp, - chartController.timestampXCanvasCoord(currentTimestamp), - axis, - shortTick: minorTick, - ); - }, - ); - - if (!minorTick) tickIndex = 0; } - if (chartController.displayXAxis || chartController.displayXLabels) + chartController.computeChartArea(); + chartController.buildLabelTimestamps(); + + if (chartController.displayXAxis || chartController.displayXLabels) { // Y translation is below X-axis line. drawTranslate( canvas, xTranslation, chartController.zeroYPosition + 1, (canvas) { - // Draw the major labels. - for (var labelIndex = 0; - labelIndex < chartController.getLabelsCount(); - labelIndex++) { - final timestamp = - chartController.getLabelTimestampByIndex(labelIndex); - if (timestamp != null) { - final xCoord = chartController.timestampXCanvasCoord(timestamp); - drawXTick(canvas, timestamp, xCoord, axis, displayTime: true); - } + // Draw the X-axis labels. + for (var timestamp in chartController.labelTimestamps) { + final xCoord = chartController.timestampXCanvasCoord(timestamp); + drawXTick(canvas, timestamp, xCoord, axis, displayTime: true); } }, ); - // X translation is left-most edge of chart widget. - drawTranslate( - canvas, - chartController.xCanvasChart, - yTranslation, - (canvas) { - // Rescale Y-axis to max visible Y range. - chartController.resetYMaxValue(visibleYMax); - - // Draw Y-axis ticks and labels. - // TODO(terry): Optimization add a listener for Y-axis range changing - // only need to redraw Y-axis if the range changed. - if (chartController.displayYLabels) { - drawYTicks(canvas, chartController, axis); - } - }, - ); + // X translation is left-most edge of chart widget. + drawTranslate( + canvas, + chartController.xCanvasChart, + yTranslation, + (canvas) { + // Rescale Y-axis to max visible Y range. + chartController.resetYMaxValue(visibleYMax); + + // Draw Y-axis ticks and labels. + // TODO(terry): Optimization add a listener for Y-axis range changing + // only need to redraw Y-axis if the range changed. + if (chartController.displayYLabels) { + drawYTicks(canvas, chartController, axis); + } + }, + ); + } drawTitle(canvas, size, chartController.title); diff --git a/packages/devtools_app/lib/src/charts/chart_controller.dart b/packages/devtools_app/lib/src/charts/chart_controller.dart index 40da17c113b..613dc228d33 100644 --- a/packages/devtools_app/lib/src/charts/chart_controller.dart +++ b/packages/devtools_app/lib/src/charts/chart_controller.dart @@ -25,11 +25,6 @@ String prettyTimestamp( return intl.DateFormat.Hms().format(timestampDT); // HH:mm:ss } -/// Indexes into xAxisLabeledTimestamps. -const leftLabelIndex = 0; -const centerLabelIndex = 1; -const rightLabelIndex = 2; - ///_____________________________________________________________________ /// /// xPaddingRight @@ -61,9 +56,15 @@ class ChartController extends DisposableController this.displayXLabels = true, this.displayYLabels = true, this.name, + List sharedLabelsTimestamps, }) { // TODO(terry): Compute dynamically based on X-axis lables text height. bottomPadding = !displayXLabels ? 0.0 : 40.0; + + if (sharedLabelsTimestamps != null) { + labelTimestamps = sharedLabelsTimestamps; + _labelsShared = true; + } } /// Used for debugging to determine which chart. @@ -105,24 +106,6 @@ class ChartController extends DisposableController int getLabelTimestampByIndex(int index) => _xAxisLabeledTimestamps[index]; - // TODO(terry): See TODO on _xAxisLabeledTimestamps. - int get leftLabelTimestamp => _xAxisLabeledTimestamps[leftLabelIndex]; - set leftLabelTimestamp(int timestamp) { - _xAxisLabeledTimestamps[leftLabelIndex] = timestamp; - } - - // TODO(terry): See TODO on _xAxisLabeledTimestamps. - int get centerLabelTimestamp => _xAxisLabeledTimestamps[centerLabelIndex]; - set centerLabelTimestamp(int timestamp) { - _xAxisLabeledTimestamps[centerLabelIndex] = timestamp; - } - - // TODO(terry): See TODO on _xAxisLabeledTimestamps. - int get rightLabelTimestamp => _xAxisLabeledTimestamps[rightLabelIndex]; - set rightLabelTimestamp(int timestamp) { - _xAxisLabeledTimestamps[rightLabelIndex] = timestamp; - } - /// If false displays top horizontal line of chart. final bool displayTopLine; @@ -136,98 +119,6 @@ class ChartController extends DisposableController final bool displayYLabels; - Duration durationLabel; - - // TODO(terry): Duration based on x-axis zoom factor (live, 5 min, 15 min, etc). - void computeDurationLabel() { - if (durationLabel == null && rightLabelTimestamp != null) { - final midTick = (visibleTicks / 2).truncate(); - if (timestampsLength > visibleTicks) { - // Lots of collected data > visible ticks so compute the visible mid tick. - final midTimestamp = timestamps[timestampsLength - midTick]; - final lastTimestamp = rightLabelTimestamp; - final midDT = DateTime.fromMillisecondsSinceEpoch(midTimestamp); - final lastDT = DateTime.fromMillisecondsSinceEpoch(lastTimestamp); - durationLabel = lastDT.difference(midDT); - } else if (timestampsLength > midTick) { - // Still collecting data when mid tick is collected compute the duration - // of the mid tick. - final midTimestamp = timestamps[midTick]; - final lastTimestamp = rightLabelTimestamp; - final midDT = DateTime.fromMillisecondsSinceEpoch(midTimestamp); - final lastDT = DateTime.fromMillisecondsSinceEpoch(lastTimestamp); - durationLabel = midDT.difference(lastDT); - } - } - } - - /// Used to compute incoming ticks. - /// @param refresh true rebuild all labels (probably zoom changed). - /// @param timestamp current timestamp received implies building all - /// parts of the x-axis labels. - void recomputeLabels({int timestamp, bool refresh = false}) { - // Chart not realized yet. - if (size == null) return; - - if (refresh && timestamps.isNotEmpty) { - // Need the correct tickWidth based on current zoom. - computeChartArea(); - - // All labels need to be recomputed. - durationLabel = null; - _xAxisLabeledTimestamps.replaceRange( - 0, - _xAxisLabeledTimestamps.length, - [null, null, null], - ); - - final midPt = (visibleTicks / 2).truncate(); - if (timestampsLength > visibleTicks) { - leftLabelTimestamp = timestamps[timestampsLength - visibleTicks + 10]; - centerLabelTimestamp = timestamps[timestampsLength - midPt]; - rightLabelTimestamp = timestamps.last; - } else if (timestampsLength > midPt) { - centerLabelTimestamp = timestamps[timestampsLength - midPt]; - rightLabelTimestamp = timestamps.last; - } else if (timestamps.isNotEmpty) { - rightLabelTimestamp = timestamps.first; - } - return; - } - - if (durationLabel == null && rightLabelTimestamp == null) { - // No center label so start first label (right-side). - rightLabelTimestamp = timestamp; - } else if (durationLabel != null && centerLabelTimestamp == null) { - // Need a center label are we at the duration we want for the - // next tick label? - final rightDT = DateTime.fromMillisecondsSinceEpoch(rightLabelTimestamp); - final currentDT = DateTime.fromMillisecondsSinceEpoch(timestamp); - if (currentDT.difference(rightDT).inSeconds >= durationLabel.inSeconds) { - slideLabelsLeft(); - rightLabelTimestamp = timestamp; - } - } else if (durationLabel != null && leftLabelTimestamp == null) { - // Need a left label are we at the duration we want for the - // next tick label? - final rightDT = DateTime.fromMillisecondsSinceEpoch(rightLabelTimestamp); - final currentDT = DateTime.fromMillisecondsSinceEpoch(timestamp); - if (currentDT.difference(rightDT).inSeconds >= durationLabel.inSeconds) { - slideLabelsLeft(); - rightLabelTimestamp = timestamp; - } - } - } - - void slideLabelsLeft() { - // Left-side out of visible range. Slide center and right labels to - // left-most and center. Need to eventually recompute right-most when - // duration gap is met. - leftLabelTimestamp = centerLabelTimestamp; - centerLabelTimestamp = rightLabelTimestamp; - rightLabelTimestamp = null; - } - /// xCanvas coord for plotting data. double xCanvasChart; @@ -329,6 +220,19 @@ class ChartController extends DisposableController bool get isZoomAll => _zoomDuration == null; + /// Label is displayed every N seconds, default is 20 seconds + /// for live view. See computeLabelInterval method. + int labelInterval = 20; + + /// List of timestamps where a label is displayed. First in the left-most + /// label (which will eventually scroll out of view and be replaced). + var labelTimestamps = []; + + bool get isLabelsShared => _labelsShared; + + /// If true signals that labels are compute from another controller. + var _labelsShared = false; + void computeZoomRatio() { // Check if ready to start computations? if (size == null) return; @@ -387,17 +291,127 @@ class ChartController extends DisposableController computeZoomRatio(); // All tick labels need to be recompted. - recomputeLabels(refresh: true); + computeChartArea(); + computeLabelInterval(); dirty = true; } + void computeLabelInterval() { + if (zoomDuration == null && timestamps.isNotEmpty) { + final firstDT = DateTime.fromMillisecondsSinceEpoch(timestamps.first); + final lastDT = DateTime.fromMillisecondsSinceEpoch(timestamps.last); + final totalDuration = lastDT.difference(firstDT); + final totalHours = totalDuration.inHours; + if (totalHours == 0) { + final totalMinutes = totalDuration.inMinutes; + if (totalMinutes == 0) { + labelInterval = 30; // Every 30 seconds + } else if (totalMinutes < 10) { + labelInterval = 60; // Every minute + } else { + labelInterval = 120; // Every 2 minutes + } + } else if (totalHours > 0 && totalHours < 8) { + labelInterval = 60 * 60; // Every hour. + } else if (totalHours < 24) { + labelInterval = 60 * 60 * 4; // Every 4 hour. + } else { + labelInterval = 60 * 60 * 12; // Every 12 hours + } + } else { + final rangeInMinutes = zoomDuration?.inMinutes; + if (rangeInMinutes == null) return; + switch (rangeInMinutes) { + case 0: // Live + labelInterval = 20; + break; + case 1: + labelInterval = 15; + break; + case 5: + labelInterval = 60; + break; + case 10: + labelInterval = 120; + break; + default: + assert(false, 'Unexpected Duration $rangeInMinutes'); + } + } + + buildLabelTimestamps(refresh: true); + } + + void buildLabelTimestamps({refresh = false}) { + if (isLabelsShared || timestamps.isEmpty) return; + + if (refresh) { + labelTimestamps.clear(); + final leftMostTimestamp = leftMostVisibleTimestampIndex; + final lastTimestamp = timestamps[leftMostTimestamp]; + labelTimestamps.add(lastTimestamp); + var lastLabelDT = DateTime.fromMillisecondsSinceEpoch(lastTimestamp); + for (var index = leftMostTimestamp; index < timestampsLength; index++) { + final currentTimestamp = timestamps[index]; + final currentDT = DateTime.fromMillisecondsSinceEpoch(currentTimestamp); + if (currentDT.difference(lastLabelDT).inSeconds >= labelInterval) { + labelTimestamps.add(currentTimestamp); + lastLabelDT = currentDT; + } + } + + return; + } + + if (labelTimestamps.isEmpty) { + labelTimestamps.add(timestamps.last); + } else { + // Check left label is it out of range? + final leftEdge = leftMostVisibleTimestampIndex; + if (labelTimestamps.first < timestamps[leftEdge]) { + // Label is outside of visible range, remove the left label. + labelTimestamps.removeAt(0); + } + } + + if (labelTimestamps.isEmpty) return; + + final rightLabelTimestamp = labelTimestamps.last; + final rightMostLableDT = + DateTime.fromMillisecondsSinceEpoch(rightLabelTimestamp); + final rightMostTimestampDT = + DateTime.fromMillisecondsSinceEpoch(timestamps.last); + + final nSeconds = + rightMostTimestampDT.difference(rightMostLableDT).inSeconds; + + if (nSeconds >= labelInterval) { + int foundTimestamp; + if (nSeconds == labelInterval) { + foundTimestamp = timestamps.last; + } else { + // Find the interval that's closest to the next interval. + final startIndex = timestamps.indexOf(rightLabelTimestamp); + for (var index = startIndex; index < timestampsLength; index++) { + foundTimestamp = timestamps[index]; + final nextDT = DateTime.fromMillisecondsSinceEpoch(foundTimestamp); + final secsDiff = nextDT.difference(rightMostTimestampDT).inSeconds; + if (secsDiff >= labelInterval) break; + } + } + assert(foundTimestamp != null); + labelTimestamps.add(foundTimestamp); + } + } + /// Clear all data in the chart. void reset() { for (var trace in traces) { trace.clearData(); } timestampsClear(); + labelTimestamps.clear(); } /// Override to load data from another source e.g., live, offline, etc. @@ -493,15 +507,23 @@ class ChartController extends DisposableController return -1; } + /// Return timestamps index of the left most visible datum. + int get leftMostVisibleTimestampIndex { + var index = 0; + if (timestampsLength > numberOfVisibleXAxisTicks) { + index = timestampsLength - numberOfVisibleXAxisTicks; + } + + return index; + } + /// X coordinate of left most visible datum. The timestampXCanvasCoord /// returns a zero based X-coord this X-coord must be translated by the /// value of this getter. double get xCoordLeftMostVisibleTimestamp { double indexOffset = xCanvasChart; - final totalTimestamps = timestampsLength; - final visibleCount = numberOfVisibleXAxisTicks; - if (totalTimestamps < visibleCount) { - final startIndex = visibleCount - totalTimestamps; + if (timestampsLength < numberOfVisibleXAxisTicks) { + final startIndex = numberOfVisibleXAxisTicks - timestampsLength; indexOffset += startIndex * tickWidth; } diff --git a/packages/devtools_app/lib/src/memory/memory_android_chart.dart b/packages/devtools_app/lib/src/memory/memory_android_chart.dart index 78683b9c7fd..bb915cfcf61 100644 --- a/packages/devtools_app/lib/src/memory/memory_android_chart.dart +++ b/packages/devtools_app/lib/src/memory/memory_android_chart.dart @@ -16,10 +16,11 @@ import 'memory_controller.dart'; import 'memory_timeline.dart'; class AndroidChartController extends ChartController { - AndroidChartController(this._memoryController) + AndroidChartController(this._memoryController, {List sharedLabels}) : super( displayTopLine: false, name: 'Android', + sharedLabelsTimestamps: sharedLabels, ); final MemoryController _memoryController; diff --git a/packages/devtools_app/lib/src/memory/memory_controller.dart b/packages/devtools_app/lib/src/memory/memory_controller.dart index 82b9d2658a2..f0b0bc1a815 100644 --- a/packages/devtools_app/lib/src/memory/memory_controller.dart +++ b/packages/devtools_app/lib/src/memory/memory_controller.dart @@ -52,7 +52,7 @@ enum ChartInterval { /// Duration for each ChartInterval. const displayDurations = [ - Duration(), // ChartInterval.Default + Duration.zero, // ChartInterval.Default Duration(minutes: 1), // ChartInterval.OneMinute Duration(minutes: 5), // ChartInterval.FiveMinutes Duration(minutes: 10), // ChartInterval.TenMinutes diff --git a/packages/devtools_app/lib/src/memory/memory_screen.dart b/packages/devtools_app/lib/src/memory/memory_screen.dart index 4e7c5cf21c9..64b1e76c851 100644 --- a/packages/devtools_app/lib/src/memory/memory_screen.dart +++ b/packages/devtools_app/lib/src/memory/memory_screen.dart @@ -127,7 +127,11 @@ class MemoryBodyState extends State eventChartController = EventChartController(controller); vmChartController = VMChartController(controller); - androidChartController = AndroidChartController(controller); + // Android Chart uses the VM Chart's computed labels. + androidChartController = AndroidChartController( + controller, + sharedLabels: vmChartController.labelTimestamps, + ); // Update the chart when the memorySource changes. addAutoDisposeListener(controller.selectedSnapshotNotifier, () { diff --git a/packages/devtools_app/lib/src/memory/memory_vm_chart.dart b/packages/devtools_app/lib/src/memory/memory_vm_chart.dart index 4fdcb4f5a72..1f15f46bccb 100644 --- a/packages/devtools_app/lib/src/memory/memory_vm_chart.dart +++ b/packages/devtools_app/lib/src/memory/memory_vm_chart.dart @@ -67,6 +67,18 @@ class VMChartController extends ChartController { final rssValue = sample.rss.toDouble(); addDataToTrace(TraceName.rSS.index, trace.Data(timestamp, rssValue)); + + final rasterLayerValue = sample.rasterCache.layerBytes.toDouble(); + addDataToTrace( + TraceName.rasterLayer.index, + trace.Data(timestamp, rasterLayerValue), + ); + + final rasterPictureValue = sample.rasterCache.pictureBytes.toDouble(); + addDataToTrace( + TraceName.rasterPicture.index, + trace.Data(timestamp, rasterPictureValue), + ); } void addDataToTrace(int traceIndex, trace.Data data) { @@ -90,6 +102,8 @@ enum TraceName { used, capacity, rSS, + rasterLayer, + rasterPicture, } class MemoryVMChartState extends State with AutoDisposeMixin { @@ -150,12 +164,11 @@ class MemoryVMChartState extends State with AutoDisposeMixin { // TODO(terry): Move colors to theme? final capacityColor = Colors.grey[400]; - //static const usedColor = Color.fromARGB(255, 51, 181, 229); - static const usedColor = Color(0xff33b5e5); // HoloBlue + static const externalColor = Color(0xff42a5f5); // Color.blue[400] static const rasterLayerColor = Color(0xff99cc00); // HoloGreenLight - static const rssColor = Color(0xffffbb33); // HoloOrangeLight static const rasterPictureColor = Color(0xffff4444); // HoloRedLight - static const externalColor = Color(0xff42a5f5); // Color.blue[400] + static const rssColor = Color(0xffffbb33); // HoloOrangeLight + static const usedColor = Color(0xff33b5e5); // HoloBlue void setupTraces() { if (_chartController.traces.isNotEmpty) { @@ -177,6 +190,14 @@ class MemoryVMChartState extends State with AutoDisposeMixin { assert(_chartController.trace(rSSIndex).name == TraceName.values[rSSIndex].toString()); + final rasterLayerIndex = TraceName.rasterLayer.index; + assert(_chartController.trace(rasterLayerIndex).name == + TraceName.values[rasterLayerIndex].toString()); + + final rasterPictureIndex = TraceName.rasterPicture.index; + assert(_chartController.trace(rasterPictureIndex).name == + TraceName.values[rasterPictureIndex].toString()); + return; } @@ -235,7 +256,31 @@ class MemoryVMChartState extends State with AutoDisposeMixin { assert(_chartController.trace(rSSIndex).name == TraceName.values[rSSIndex].toString()); - // TODO(terry): Add Raster Cache Picture and Layers traces. + final rasterLayerIndex = _chartController.createTrace( + trace.ChartType.line, + trace.PaintCharacteristics( + color: rasterLayerColor, + symbol: trace.ChartSymbol.disc, + strokeWidth: 2, + ), + name: TraceName.rasterLayer.toString(), + ); + assert(rasterLayerIndex == TraceName.rasterLayer.index); + assert(_chartController.trace(rasterLayerIndex).name == + TraceName.values[rasterLayerIndex].toString()); + + final rasterPictureIndex = _chartController.createTrace( + trace.ChartType.line, + trace.PaintCharacteristics( + color: rasterPictureColor, + symbol: trace.ChartSymbol.disc, + strokeWidth: 2, + ), + name: TraceName.rasterPicture.toString(), + ); + assert(rasterPictureIndex == TraceName.rasterPicture.index); + assert(_chartController.trace(rasterPictureIndex).name == + TraceName.values[rasterPictureIndex].toString()); assert(_chartController.traces.length == TraceName.values.length); } diff --git a/packages/devtools_app/pubspec.lock b/packages/devtools_app/pubspec.lock index 7c605e1211b..508636a0a4c 100644 --- a/packages/devtools_app/pubspec.lock +++ b/packages/devtools_app/pubspec.lock @@ -56,7 +56,7 @@ packages: name: browser_launcher url: "https://pub.dartlang.org" source: hosted - version: "0.1.7" + version: "0.1.8" build: dependency: transitive description: diff --git a/packages/devtools_app/test/chart_test.dart b/packages/devtools_app/test/chart_test.dart index b2d0cc64b3e..630b214ab7b 100644 --- a/packages/devtools_app/test/chart_test.dart +++ b/packages/devtools_app/test/chart_test.dart @@ -281,29 +281,44 @@ void main() { validateScaledYLabels(controller); // Validate the x-axis labels. - expect(controller.leftLabelTimestamp, isNotNull); - expect(controller.centerLabelTimestamp, isNotNull); - expect(controller.rightLabelTimestamp, isNotNull); - expect(controller.leftLabelTimestamp, equals(1595682514316)); - expect(controller.centerLabelTimestamp, equals(1595682534381)); - expect(controller.rightLabelTimestamp, equals(1595682556248)); + expect(controller.labelTimestamps.length, equals(3)); + expect(controller.labelTimestamps[0], equals(1595682513450)); + expect(controller.labelTimestamps[1], equals(1595682533744)); + expect(controller.labelTimestamps[2], equals(1595682553811)); // Validate using UTC timezone. expect( - prettyTimestamp(controller.leftLabelTimestamp, isUtc: true), - equals('13:08:34'), + prettyTimestamp(controller.labelTimestamps[0], isUtc: true), + equals('13:08:33'), ); expect( - prettyTimestamp(controller.centerLabelTimestamp, isUtc: true), - equals('13:08:54'), + prettyTimestamp(controller.labelTimestamps[1], isUtc: true), + equals('13:08:53'), ); expect( - prettyTimestamp(controller.rightLabelTimestamp, isUtc: true), - equals('13:09:16'), + prettyTimestamp(controller.labelTimestamps[2], isUtc: true), + equals('13:09:13'), ); }, ); + void checkScaledXAxis2Labels(ChartController controller) { + // Validate the x-axis labels. + expect(controller.labelTimestamps.length, equals(2)); + expect(controller.labelTimestamps[0], equals(1595682492441)); + expect(controller.labelTimestamps[1], equals(1595682552535)); + + // Validate using UTC timezone. + expect( + prettyTimestamp(controller.labelTimestamps[0], isUtc: true), + equals('13:08:12'), + ); + expect( + prettyTimestamp(controller.labelTimestamps[1], isUtc: true), + equals('13:09:12'), + ); + } + testWidgetsWithWindowSize('Scaled Y-axis all', windowSize, (WidgetTester tester) async { const chartKey = Key('Chart'); @@ -340,27 +355,7 @@ void main() { validateScaledYLabels(controller); - // Validate the x-axis labels. - expect(controller.leftLabelTimestamp, isNotNull); - expect(controller.centerLabelTimestamp, isNotNull); - expect(controller.rightLabelTimestamp, isNotNull); - expect(controller.leftLabelTimestamp, equals(1595682525980)); - expect(controller.centerLabelTimestamp, equals(1595682556248)); - expect(controller.rightLabelTimestamp, equals(1595682556248)); - - // Validate to UTC timezone. - expect( - prettyTimestamp(controller.leftLabelTimestamp, isUtc: true), - equals('13:08:45'), - ); - expect( - prettyTimestamp(controller.centerLabelTimestamp, isUtc: true), - equals('13:09:16'), - ); - expect( - prettyTimestamp(controller.rightLabelTimestamp, isUtc: true), - equals('13:09:16'), - ); + checkScaledXAxis2Labels(controller); }); testWidgetsWithWindowSize('Scaled Y-axis Five Minutes', windowSize, @@ -399,17 +394,7 @@ void main() { validateScaledYLabels(controller); - // Validate the x-axis labels. - expect(controller.leftLabelTimestamp, isNull); - expect(controller.centerLabelTimestamp, isNull); - expect(controller.rightLabelTimestamp, isNotNull); - expect(controller.rightLabelTimestamp, equals(1595682492441)); - - // Validate to UTC timezone. - expect( - prettyTimestamp(controller.rightLabelTimestamp, isUtc: true), - equals('13:08:12'), - ); + checkScaledXAxis2Labels(controller); }); /////////////////////////////////////////////////////////////////////////// @@ -682,29 +667,44 @@ void main() { } // Validate the x-axis labels. - expect(controller.leftLabelTimestamp, isNotNull); - expect(controller.centerLabelTimestamp, isNotNull); - expect(controller.rightLabelTimestamp, isNotNull); - expect(controller.leftLabelTimestamp, equals(1595682514316)); - expect(controller.centerLabelTimestamp, equals(1595682534381)); - expect(controller.rightLabelTimestamp, equals(1595682556248)); - - // Validate to UTC timezone. + expect(controller.labelTimestamps.length, equals(3)); + expect(controller.labelTimestamps[0], equals(1595682513450)); + expect(controller.labelTimestamps[1], equals(1595682533744)); + expect(controller.labelTimestamps[2], equals(1595682553811)); + + // Validate using UTC timezone. expect( - prettyTimestamp(controller.leftLabelTimestamp, isUtc: true), - equals('13:08:34'), + prettyTimestamp(controller.labelTimestamps[0], isUtc: true), + equals('13:08:33'), ); expect( - prettyTimestamp(controller.centerLabelTimestamp, isUtc: true), - equals('13:08:54'), + prettyTimestamp(controller.labelTimestamps[1], isUtc: true), + equals('13:08:53'), ); expect( - prettyTimestamp(controller.rightLabelTimestamp, isUtc: true), - equals('13:09:16'), + prettyTimestamp(controller.labelTimestamps[2], isUtc: true), + equals('13:09:13'), ); }, ); + void checkFixedXAxis2Labels(ChartController controller) { + // Validate the x-axis labels. + expect(controller.labelTimestamps.length, equals(2)); + expect(controller.labelTimestamps[0], equals(1595682492441)); + expect(controller.labelTimestamps[1], equals(1595682552535)); + + // Validate using UTC timezone. + expect( + prettyTimestamp(controller.labelTimestamps[0], isUtc: true), + equals('13:08:12'), + ); + expect( + prettyTimestamp(controller.labelTimestamps[1], isUtc: true), + equals('13:09:12'), + ); + } + testWidgetsWithWindowSize('Fixed Y-axis all', windowSize, (WidgetTester tester) async { const chartKey = Key('Chart'); @@ -754,27 +754,7 @@ void main() { expect(labelName, expectedLabels[labelIndex.toInt()]); } - // Validate the x-axis labels. - expect(controller.leftLabelTimestamp, isNotNull); - expect(controller.centerLabelTimestamp, isNotNull); - expect(controller.rightLabelTimestamp, isNotNull); - expect(controller.leftLabelTimestamp, equals(1595682525980)); - expect(controller.centerLabelTimestamp, equals(1595682556248)); - expect(controller.rightLabelTimestamp, equals(1595682556248)); - - // Validate to UTC timezone. - expect( - prettyTimestamp(controller.leftLabelTimestamp, isUtc: true), - equals('13:08:45'), - ); - expect( - prettyTimestamp(controller.centerLabelTimestamp, isUtc: true), - equals('13:09:16'), - ); - expect( - prettyTimestamp(controller.rightLabelTimestamp, isUtc: true), - equals('13:09:16'), - ); + checkFixedXAxis2Labels(controller); }); testWidgetsWithWindowSize('Fixed Y-axis 5 Minutes', windowSize, @@ -826,17 +806,7 @@ void main() { expect(labelName, expectedLabels[labelIndex.toInt()]); } - // Validate the x-axis labels. - expect(controller.leftLabelTimestamp, isNull); - expect(controller.centerLabelTimestamp, isNull); - expect(controller.rightLabelTimestamp, isNotNull); - expect(controller.rightLabelTimestamp, equals(1595682492441)); - - // Validate to UTC timezone. - expect( - prettyTimestamp(controller.rightLabelTimestamp, isUtc: true), - equals('13:08:12'), - ); + checkFixedXAxis2Labels(controller); }); }, ); diff --git a/packages/devtools_app/test/goldens/memory_chart_fixed_all.png b/packages/devtools_app/test/goldens/memory_chart_fixed_all.png index 64973351047cc35e587b3093cf442b3684d7d4d8..7f16726c7f735f0f8b76a6e8c771fb430fdc2af1 100644 GIT binary patch literal 18119 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 diff --git a/packages/devtools_app/test/goldens/memory_chart_fixed_live.png b/packages/devtools_app/test/goldens/memory_chart_fixed_live.png index f5069cde8bc0d29bfa35c77c6554fe1c698833df..9be0aaae59732575038d5cb1f568790c99bc3a77 100644 GIT binary patch delta 2380 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 H%=!NSo}{Xe delta 2412 zcma)7X;f2L622_TAPBgDh!}CBJ&nd5gordm!4{FN5d$blqG^dnAOb>!n7pVQ2L}cF zL`5+`(>Brsgk~~;5VFvU85b6ZC9;H72!@b=Aw;r}d0^AO=FFM<=iT$weYdK<`s!A0 z?7i4KN1=n6ObJyuZSZvc$F<3h8_YmblU+MKqv*+Tr~RYHWdV16Pdo8Awi%j^NuS0| z<4#x4pmGb&)VFPtWgWFH=Dq(aclqCcFg9=U`p^;J8#G+sb#5Sya)&dUPHsA)te`1) z3Q6+6W!|pJw_4uyF?*_t*G#OIt^fc$_(d!hubWxha(gssu1#H?kE(4>T@D*sY8lIo zofv|Zl&|wTiJisHMliUr1pnNnp$Xu>Zok}>`-WGz;R_7t$*)H<7)8cwPl&Xm8OLVT z>h`cdOkjO+ety0yl$~r>m5EsbUrPGEb5Se5Vj-3g4OzbBKEr1nfw1Ik-$3j~^CdH# zad4&W+AJR8VqNsC{>~D7i|NX4v`I6965gpz5v2LN$J=h3XO!4fymXP6*u zCvodW2tVkFO; z(qS@Q7O69r0lJcqD#_1-W=HU7l2t;dZZI6946e4rR;Zd3%VW(T!S~Ivq^>LTJynIM zX02NxAB!PjdgYzzdgP$HUz7geSxTJU&xPspJ zsFhDSBS`8Hx)mBuG)qLAUNOOgO%wJ2thZZb0R$P%@`f-r2Q9v$6F+j}C|m4X{z6yc z+$ro>hz(t#Pos$L6s0A>RIv6$D_pG4OwJqgNrj@M1b{s&g0%0d3{}r~7mK@cPI1WG z&ttDBB?CMqgJ_k2$bLAG#;I>QE4pj;0A_=wI48}_6c`az8?Y7aor(pQBfDJTpo1my zKo?I>Pa$ng<^q6q#d(rr9hWBSDaH3@Aw|Q3W+nZ0^pEHAAazb(Ol4)^{+f6-vFdY6 zgq%YT4>Q5}loJ5JF_|})0o>1dvyZpda+*fNgf!Lh-XJZ4@KMtbaB>7 zi?8R6J$HS$2DIZB>$MHeZP4iwEW?Yt=d>ntrz!O+04(ppiopGxttY*>-X2m)l|dd5 zZIlNBh12Y^2I%#_7%e?XAMgGMrf^@}+fvbkNp&)JsKP z3QgiHlv@b)rieR)>v3M1y}rw=5#8UF}io;LNWJwtbrwP=qd6_87pmgYE~w1 z&V%a7imX97gH;cy`Sx}O<@K#ts|e)_uGV;u|AZ<4qv{x%?3Nvdwd8FbP@D zsazzwz@<$az2rwus8p`ZRUqt#MUJ;F=;zsjYVKKYT+Jj#pw|pm5ylKXC#hq zQu{mFB&bWi=nC1uq$QQF@{%i{M1G=K$-I1TcG&xZrCj1ziq;FhSZwxaa-1|JFrE`Q zsW$=#pu_f6x=*5s=V-$ORB_w$bo9WD`38J<%cBh%-Hn8C;XOSkzerKb6Y5h_q`zrC z7E_oKyjqEwaSZMREaF9891gzogEim z9kBf6YeE`5c+2P4hg!CNH^b_@^cmyk;m|36YrRC=S)I;EQLfs$u1(#QeZw7$$raA{D{0~t$NH1z zhwR2x+sxT*HtS4R(+zl0_OgXAmd{05<3kcn*+turEwdCwxB3IHn(cq+;Bj{2=B-wY xHitNy1Cu>f3ugtJp^4M7o!RVzPFAM29Ppwx=l;un-y;h)|L=kiRruhp{Rb%hw&nl; diff --git a/packages/devtools_app/test/goldens/memory_chart_scaled_all.png b/packages/devtools_app/test/goldens/memory_chart_scaled_all.png index 4a5b9a141219492ed333a78c92779e2d64ec3ee4..e1c8223dd85c69fb11f832d110880d146589027c 100644 GIT binary patch delta 12874 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 delta 12803 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 Xt*|`T-2?Of%7F4lpQ!uhyKDagFses} delta 4111 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 delta 2166 zcmaF7hVkAS#tnQX@u~W=Y;(>po;IyMjDdmSg0;OrGmx&%-M?2p@ZEPNAR7!8T*)!( zp1bG4?fm_BgY&Yovefgrp;8AJ(}ZAbhT83qCr3af7!o%7Og3aRtp8VE_U;awgG5); zf(b8fZJb@kq+7tEeZ+@V|L?U^P{k8&zh<;CKDI)==GL6e^MHa52W@^F z&Ycf8^+YF$OiPFrg!ScSuU#z12zAhb{XaV=%bRLVwln1(#iL;`ng&Kh8sOw<5{#O_ zz##g;)5S4lvZ1NOXvB|*h#xKFNvr5M1RvyF?_&kkmTW(x`IG$%SvCiH{pH?#z{`zM ZRPV_7O~2Qc-6#Rcd%F6$taD0e0sy=WO9TJ_ From 151ae4d5a2b915f8be63c23b585fa7d552cc0c9c Mon Sep 17 00:00:00 2001 From: Terry Lucas Date: Tue, 8 Dec 2020 08:51:19 -0800 Subject: [PATCH 2/4] Used constant names instead of #s. --- .../lib/src/charts/chart_controller.dart | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/devtools_app/lib/src/charts/chart_controller.dart b/packages/devtools_app/lib/src/charts/chart_controller.dart index 613dc228d33..633b35d6e79 100644 --- a/packages/devtools_app/lib/src/charts/chart_controller.dart +++ b/packages/devtools_app/lib/src/charts/chart_controller.dart @@ -222,7 +222,7 @@ class ChartController extends DisposableController /// Label is displayed every N seconds, default is 20 seconds /// for live view. See computeLabelInterval method. - int labelInterval = 20; + int labelInterval = labelsLiveSeconds; /// List of timestamps where a label is displayed. First in the left-most /// label (which will eventually scroll out of view and be replaced). @@ -297,6 +297,16 @@ class ChartController extends DisposableController dirty = true; } + /// Label rate unit in seconds. Default label every 20 seconds for live view. + static const labelsLiveSeconds = 20; + static const labelsFifteenSeconds = 15; + static const labelsThirtySeconds = 30; + static const labelsOneMinute = 60; + static const labelsTwoMinutes = 120; + static const labelsOneHour = 60 * 60; + static const labelsFourHours = 60 * 60 * 4; + static const labelsTwelveHours = 60 * 60 * 12; + void computeLabelInterval() { if (zoomDuration == null && timestamps.isNotEmpty) { final firstDT = DateTime.fromMillisecondsSinceEpoch(timestamps.first); @@ -306,34 +316,34 @@ class ChartController extends DisposableController if (totalHours == 0) { final totalMinutes = totalDuration.inMinutes; if (totalMinutes == 0) { - labelInterval = 30; // Every 30 seconds + labelInterval = labelsThirtySeconds; } else if (totalMinutes < 10) { - labelInterval = 60; // Every minute + labelInterval = labelsOneMinute; } else { - labelInterval = 120; // Every 2 minutes + labelInterval = labelsTwoMinutes; } } else if (totalHours > 0 && totalHours < 8) { - labelInterval = 60 * 60; // Every hour. + labelInterval = labelsOneHour; } else if (totalHours < 24) { - labelInterval = 60 * 60 * 4; // Every 4 hour. + labelInterval = labelsFourHours; } else { - labelInterval = 60 * 60 * 12; // Every 12 hours + labelInterval = labelsTwelveHours; } } else { final rangeInMinutes = zoomDuration?.inMinutes; if (rangeInMinutes == null) return; switch (rangeInMinutes) { case 0: // Live - labelInterval = 20; + labelInterval = labelsLiveSeconds; break; - case 1: - labelInterval = 15; + case 1: // 1 minute + labelInterval = labelsFifteenSeconds; break; - case 5: - labelInterval = 60; + case 5: // 5 minute + labelInterval = labelsOneMinute; break; - case 10: - labelInterval = 120; + case 10: // 10 minute + labelInterval = labelsTwoMinutes; break; default: assert(false, 'Unexpected Duration $rangeInMinutes'); From 48844f2f88fc3420c548ff8fcfb80921c83748a5 Mon Sep 17 00:00:00 2001 From: Terry Lucas Date: Tue, 8 Dec 2020 09:12:06 -0800 Subject: [PATCH 3/4] Incorporated PR suggestions. --- packages/devtools_app/lib/src/charts/chart_controller.dart | 2 ++ packages/devtools_app/lib/src/memory/memory_vm_chart.dart | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/devtools_app/lib/src/charts/chart_controller.dart b/packages/devtools_app/lib/src/charts/chart_controller.dart index 633b35d6e79..ab8d48ba5b0 100644 --- a/packages/devtools_app/lib/src/charts/chart_controller.dart +++ b/packages/devtools_app/lib/src/charts/chart_controller.dart @@ -379,6 +379,8 @@ class ChartController extends DisposableController } else { // Check left label is it out of range? final leftEdge = leftMostVisibleTimestampIndex; + // TODO(terry): Need to insure that more than one label may not be + // visible e.g., when panning the chart. if (labelTimestamps.first < timestamps[leftEdge]) { // Label is outside of visible range, remove the left label. labelTimestamps.removeAt(0); diff --git a/packages/devtools_app/lib/src/memory/memory_vm_chart.dart b/packages/devtools_app/lib/src/memory/memory_vm_chart.dart index 1f15f46bccb..afbca38f2ce 100644 --- a/packages/devtools_app/lib/src/memory/memory_vm_chart.dart +++ b/packages/devtools_app/lib/src/memory/memory_vm_chart.dart @@ -163,8 +163,8 @@ class MemoryVMChartState extends State with AutoDisposeMixin { } // TODO(terry): Move colors to theme? - final capacityColor = Colors.grey[400]; - static const externalColor = Color(0xff42a5f5); // Color.blue[400] + static final capacityColor = Colors.grey[400]; + static final externalColor = Colors.blue[400]; static const rasterLayerColor = Color(0xff99cc00); // HoloGreenLight static const rasterPictureColor = Color(0xffff4444); // HoloRedLight static const rssColor = Color(0xffffbb33); // HoloOrangeLight From 3ffbfd245ac52ce7b159ee8e9a76ad5179262f8f Mon Sep 17 00:00:00 2001 From: Terry Lucas Date: Tue, 8 Dec 2020 09:18:01 -0800 Subject: [PATCH 4/4] PR Suggestion. --- packages/devtools_app/lib/src/charts/chart_controller.dart | 6 +++--- .../devtools_app/lib/src/memory/memory_android_chart.dart | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/devtools_app/lib/src/charts/chart_controller.dart b/packages/devtools_app/lib/src/charts/chart_controller.dart index ab8d48ba5b0..6ad8f7c6748 100644 --- a/packages/devtools_app/lib/src/charts/chart_controller.dart +++ b/packages/devtools_app/lib/src/charts/chart_controller.dart @@ -56,13 +56,13 @@ class ChartController extends DisposableController this.displayXLabels = true, this.displayYLabels = true, this.name, - List sharedLabelsTimestamps, + List sharedLabelimestamps, }) { // TODO(terry): Compute dynamically based on X-axis lables text height. bottomPadding = !displayXLabels ? 0.0 : 40.0; - if (sharedLabelsTimestamps != null) { - labelTimestamps = sharedLabelsTimestamps; + if (sharedLabelimestamps != null) { + labelTimestamps = sharedLabelimestamps; _labelsShared = true; } } diff --git a/packages/devtools_app/lib/src/memory/memory_android_chart.dart b/packages/devtools_app/lib/src/memory/memory_android_chart.dart index bb915cfcf61..9c79b7bf702 100644 --- a/packages/devtools_app/lib/src/memory/memory_android_chart.dart +++ b/packages/devtools_app/lib/src/memory/memory_android_chart.dart @@ -20,7 +20,7 @@ class AndroidChartController extends ChartController { : super( displayTopLine: false, name: 'Android', - sharedLabelsTimestamps: sharedLabels, + sharedLabelimestamps: sharedLabels, ); final MemoryController _memoryController;