From 03f016aa8513bdd56ae6efaf9958fe6b6f314c1e Mon Sep 17 00:00:00 2001 From: Sam Spencer Date: Fri, 15 Sep 2023 14:08:42 -0700 Subject: [PATCH 1/3] Basic charting for metrics using plotly.js rather than Mud Blazor. This removes the last dependency we have on mud blazor, so we have good alternatives. --- .../Components/DimensionedCounterView.razor | 3 +- .../DimensionedCounterView.razor.cs | 45 +++++++++++++++++-- OTLPView/Extensions/Helpers.cs | 6 +++ OTLPView/Pages/_Host.cshtml | 1 + 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/OTLPView/Components/DimensionedCounterView.razor b/OTLPView/Components/DimensionedCounterView.razor index 9fc62f6..b174e79 100644 --- a/OTLPView/Components/DimensionedCounterView.razor +++ b/OTLPView/Components/DimensionedCounterView.razor @@ -25,7 +25,8 @@ - + @* *@ + @( _graph)
Recent Values:
diff --git a/OTLPView/Components/DimensionedCounterView.razor.cs b/OTLPView/Components/DimensionedCounterView.razor.cs index f4f7e6f..9709b3d 100644 --- a/OTLPView/Components/DimensionedCounterView.razor.cs +++ b/OTLPView/Components/DimensionedCounterView.razor.cs @@ -1,15 +1,26 @@ +using Microsoft.JSInterop; +using static System.Runtime.InteropServices.JavaScript.JSType; + namespace OTLPView.Components; public sealed partial class DimensionedCounterView { + static int lastId = 0; + // Define the size of the graph based on the number of points and the duration of each point private const int GRAPH_POINT_COUNT = 18; // 3 minutes private const int GRAPH_POINT_SIZE = 10; // 10s + [Inject] + private IJSRuntime JSRuntime { get; set; } private DimensionScope _dimension; private string[] chartLabels; private List chartValues; + private readonly int _instanceID = ++lastId; + + private double[] _chartLabels; + private double[] _chartValues; [Parameter, EditorRequired] public required DimensionScope Dimension @@ -26,6 +37,7 @@ public required DimensionScope Dimension Data = CalcChartValues(_dimension, GRAPH_POINT_COUNT, GRAPH_POINT_SIZE) } }; + _chartValues = CalcChartValues(_dimension, GRAPH_POINT_COUNT, GRAPH_POINT_SIZE); } } @@ -35,6 +47,7 @@ public required DimensionScope Dimension protected override void OnInitialized() { chartLabels = CalcLabels(GRAPH_POINT_COUNT, GRAPH_POINT_SIZE); + _chartLabels = _CalcLabels(GRAPH_POINT_COUNT, GRAPH_POINT_SIZE); } private string[] CalcLabels(int pointCount, int pointSize) @@ -48,6 +61,17 @@ private string[] CalcLabels(int pointCount, int pointSize) return labels; } + private double[] _CalcLabels(int pointCount, int pointSize) + { + var duration = pointSize * pointCount; + var labels = new double[pointCount]; + for (var i = 0; i < pointCount; i++) + { + labels[i] = (pointSize * (i + 1)) - duration; + } + return labels; + } + // Graph is not based on x,y coordinates, but rather a series of data points with a value // Each point in the graph is the max value of all the values in that point's time range private double[] CalcChartValues(DimensionScope dimension, int pointCount, int pointSize) @@ -57,11 +81,11 @@ private double[] CalcChartValues(DimensionScope dimension, int pointCount, int p var now = DateTime.UtcNow; foreach (var point in dimension.Values) { - var start = CalcOffset(now-point.Start, pointCount, pointSize); - var end = CalcOffset(now-point.End, pointCount, pointSize); + var start = CalcOffset(now - point.Start, pointCount, pointSize); + var end = CalcOffset(now - point.End, pointCount, pointSize); if (start is not null && end is not null) { - for (var i = start.GetValueOrDefault(0); i <= end.GetValueOrDefault(pointCount-1); i++) + for (var i = start.GetValueOrDefault(0); i <= end.GetValueOrDefault(pointCount - 1); i++) { values[i] = point switch { @@ -95,6 +119,21 @@ private double[] CalcFakeValues(int pointCount) return values; } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + var data = new[]{ new { + x= _chartLabels, + y= _chartValues, + type= "scatter" + } }; + await JSRuntime.InvokeVoidAsync("Plotly.newPlot", $"lineChart{_instanceID}", data); + } + RenderFragment _graph => (builder) => + { + builder.AddMarkupContent(0, $$""" +
+"""); + }; } diff --git a/OTLPView/Extensions/Helpers.cs b/OTLPView/Extensions/Helpers.cs index 6beadb1..bb0d8b6 100644 --- a/OTLPView/Extensions/Helpers.cs +++ b/OTLPView/Extensions/Helpers.cs @@ -109,6 +109,12 @@ public static DateTime UnixNanoSecondsToDateTime(ulong unixTimeNanoSeconds) return DateTimeOffset.FromUnixTimeMilliseconds(milliseconds).DateTime; } + public static string ToJSArray(this double[] values) => + $"[{string.Join(",", values)}]"; + + public static string ToJSArray(this string[] values) => + $"['{string.Join("','", values)}']"; + public static string ToHexString(this Google.Protobuf.ByteString bytes) { if (bytes is null or { Length: 0 }) diff --git a/OTLPView/Pages/_Host.cshtml b/OTLPView/Pages/_Host.cshtml index 8116a6e..64cbff6 100644 --- a/OTLPView/Pages/_Host.cshtml +++ b/OTLPView/Pages/_Host.cshtml @@ -36,5 +36,6 @@ + From 85465a79d660059ce478b8c43db2a01dd9168a19 Mon Sep 17 00:00:00 2001 From: Sam Spencer Date: Fri, 15 Sep 2023 16:33:54 -0700 Subject: [PATCH 2/3] Histogram graphs, improved graph settings --- .../Components/DimensionedCounterView.razor | 3 +- .../DimensionedCounterView.razor.cs | 53 +++++++++++-------- .../Components/DimensionedHistogramView.razor | 2 +- .../DimensionedHistogramView.razor.cs | 53 +++++++++++++++---- 4 files changed, 77 insertions(+), 34 deletions(-) diff --git a/OTLPView/Components/DimensionedCounterView.razor b/OTLPView/Components/DimensionedCounterView.razor index b174e79..d9dff74 100644 --- a/OTLPView/Components/DimensionedCounterView.razor +++ b/OTLPView/Components/DimensionedCounterView.razor @@ -25,8 +25,7 @@ - @* *@ - @( _graph) + @( _graph)
Recent Values:
diff --git a/OTLPView/Components/DimensionedCounterView.razor.cs b/OTLPView/Components/DimensionedCounterView.razor.cs index 9709b3d..3ec6e8a 100644 --- a/OTLPView/Components/DimensionedCounterView.razor.cs +++ b/OTLPView/Components/DimensionedCounterView.razor.cs @@ -15,8 +15,8 @@ public sealed partial class DimensionedCounterView private IJSRuntime JSRuntime { get; set; } private DimensionScope _dimension; - private string[] chartLabels; - private List chartValues; + //private string[] chartLabels; + //private List chartValues; private readonly int _instanceID = ++lastId; private double[] _chartLabels; @@ -29,14 +29,14 @@ public required DimensionScope Dimension set { _dimension = value; - chartValues = new List() - { - new ChartSeries() - { - Name = Counter?.CounterName ?? "unknown", - Data = CalcChartValues(_dimension, GRAPH_POINT_COUNT, GRAPH_POINT_SIZE) - } - }; + //chartValues = new List() + //{ + // new ChartSeries() + // { + // Name = Counter?.CounterName ?? "unknown", + // Data = CalcChartValues(_dimension, GRAPH_POINT_COUNT, GRAPH_POINT_SIZE) + // } + //}; _chartValues = CalcChartValues(_dimension, GRAPH_POINT_COUNT, GRAPH_POINT_SIZE); } } @@ -46,20 +46,20 @@ public required DimensionScope Dimension protected override void OnInitialized() { - chartLabels = CalcLabels(GRAPH_POINT_COUNT, GRAPH_POINT_SIZE); + //chartLabels = CalcLabels(GRAPH_POINT_COUNT, GRAPH_POINT_SIZE); _chartLabels = _CalcLabels(GRAPH_POINT_COUNT, GRAPH_POINT_SIZE); } - private string[] CalcLabels(int pointCount, int pointSize) - { - var duration = pointSize * pointCount; - var labels = new string[pointCount]; - for (var i = 0; i < pointCount; i++) - { - labels[i] = (i < pointCount - 1) ? $"{(pointSize * (i + 1)) - duration}s" : "Now"; - } - return labels; - } + //private string[] CalcLabels(int pointCount, int pointSize) + //{ + // var duration = pointSize * pointCount; + // var labels = new string[pointCount]; + // for (var i = 0; i < pointCount; i++) + // { + // labels[i] = (i < pointCount - 1) ? $"{(pointSize * (i + 1)) - duration}s" : "Now"; + // } + // return labels; + //} private double[] _CalcLabels(int pointCount, int pointSize) { @@ -127,7 +127,16 @@ protected override async Task OnAfterRenderAsync(bool firstRender) type= "scatter" } }; - await JSRuntime.InvokeVoidAsync("Plotly.newPlot", $"lineChart{_instanceID}", data); + var layout = new { + title = Counter?.CounterName ?? "unknown", + showlegend = false, + xaxis = new { + title = "Time (s)" + } + }; + var options = new { staticPlot = true }; + + await JSRuntime.InvokeVoidAsync("Plotly.newPlot", $"lineChart{_instanceID}", data, layout, options); } RenderFragment _graph => (builder) => diff --git a/OTLPView/Components/DimensionedHistogramView.razor b/OTLPView/Components/DimensionedHistogramView.razor index 4e440a2..f54c305 100644 --- a/OTLPView/Components/DimensionedHistogramView.razor +++ b/OTLPView/Components/DimensionedHistogramView.razor @@ -25,7 +25,7 @@ - + @(_graph) diff --git a/OTLPView/Components/DimensionedHistogramView.razor.cs b/OTLPView/Components/DimensionedHistogramView.razor.cs index 4b6fa90..4fc1bc8 100644 --- a/OTLPView/Components/DimensionedHistogramView.razor.cs +++ b/OTLPView/Components/DimensionedHistogramView.razor.cs @@ -1,3 +1,6 @@ +using Microsoft.JSInterop; +using static System.Runtime.InteropServices.JavaScript.JSType; + namespace OTLPView.Components; public sealed partial class DimensionedHistogramView @@ -6,10 +9,16 @@ public sealed partial class DimensionedHistogramView private const int GRAPH_POINT_COUNT = 18; // 3 minutes private const int GRAPH_POINT_SIZE = 10; // 10s + static int lastId = 0; + private readonly int _instanceID = ++lastId; + + [Inject] + private IJSRuntime JSRuntime { get; set; } private DimensionScope _dimension; private string[] _chartLabels; - private List _chartValues; + //private List _chartValues; + private double[] _chartValues; [Parameter, EditorRequired] public required DimensionScope Dimension @@ -19,14 +28,15 @@ public required DimensionScope Dimension { _dimension = value; _chartLabels = CalcLabels((Dimension.Values?.First() as HistogramValue).ExplicitBounds); - _chartValues = new List() - { - new ChartSeries() - { - Name = Counter?.CounterName ?? "unknown", - Data = (Dimension.Values.First() as HistogramValue).Values.Select(v => (double)v).ToArray() - } - }; + //_chartValues = new List() + //{ + // new ChartSeries() + // { + // Name = Counter?.CounterName ?? "unknown", + // Data = (Dimension.Values.First() as HistogramValue).Values.Select(v => (double)v).ToArray() + // } + //}; + _chartValues = (Dimension.Values.First() as HistogramValue).Values.Select(v => (double)v).ToArray(); } } @@ -95,5 +105,30 @@ private double[] CalcFakeValues(int pointCount) return values; } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + var data = new[]{ new { + x= _chartLabels, + y= _chartValues, + type= "bar" + } }; + var layout = new { + title = Counter?.CounterName ?? "unknown", + showlegend = false, + xaxis = new { + title = "Time (s)" + } + }; + var options = new { staticPlot = true }; + + await JSRuntime.InvokeVoidAsync("Plotly.newPlot", $"barChart{_instanceID}", data, layout, options); + } + + RenderFragment _graph => (builder) => + { + builder.AddMarkupContent(0, $$""" +
+"""); + }; } From 5daba3302fbbd1dbd93c4471868022b791488903 Mon Sep 17 00:00:00 2001 From: Sam Spencer Date: Fri, 15 Sep 2023 16:52:38 -0700 Subject: [PATCH 3/3] further cleanup --- .../Components/DimensionedCounterView.razor | 2 +- .../DimensionedCounterView.razor.cs | 33 ++----------------- .../Components/DimensionedHistogramView.razor | 2 +- .../DimensionedHistogramView.razor.cs | 31 +++++------------ 4 files changed, 14 insertions(+), 54 deletions(-) diff --git a/OTLPView/Components/DimensionedCounterView.razor b/OTLPView/Components/DimensionedCounterView.razor index d9dff74..5203778 100644 --- a/OTLPView/Components/DimensionedCounterView.razor +++ b/OTLPView/Components/DimensionedCounterView.razor @@ -25,7 +25,7 @@ - @( _graph) +
Recent Values:
diff --git a/OTLPView/Components/DimensionedCounterView.razor.cs b/OTLPView/Components/DimensionedCounterView.razor.cs index 3ec6e8a..8aa9706 100644 --- a/OTLPView/Components/DimensionedCounterView.razor.cs +++ b/OTLPView/Components/DimensionedCounterView.razor.cs @@ -15,8 +15,6 @@ public sealed partial class DimensionedCounterView private IJSRuntime JSRuntime { get; set; } private DimensionScope _dimension; - //private string[] chartLabels; - //private List chartValues; private readonly int _instanceID = ++lastId; private double[] _chartLabels; @@ -29,14 +27,6 @@ public required DimensionScope Dimension set { _dimension = value; - //chartValues = new List() - //{ - // new ChartSeries() - // { - // Name = Counter?.CounterName ?? "unknown", - // Data = CalcChartValues(_dimension, GRAPH_POINT_COUNT, GRAPH_POINT_SIZE) - // } - //}; _chartValues = CalcChartValues(_dimension, GRAPH_POINT_COUNT, GRAPH_POINT_SIZE); } } @@ -44,23 +34,13 @@ public required DimensionScope Dimension [Parameter, EditorRequired] public required Counter Counter { get; set; } + private string chartDivId => $"lineChart{_instanceID}"; + protected override void OnInitialized() { - //chartLabels = CalcLabels(GRAPH_POINT_COUNT, GRAPH_POINT_SIZE); _chartLabels = _CalcLabels(GRAPH_POINT_COUNT, GRAPH_POINT_SIZE); } - //private string[] CalcLabels(int pointCount, int pointSize) - //{ - // var duration = pointSize * pointCount; - // var labels = new string[pointCount]; - // for (var i = 0; i < pointCount; i++) - // { - // labels[i] = (i < pointCount - 1) ? $"{(pointSize * (i + 1)) - duration}s" : "Now"; - // } - // return labels; - //} - private double[] _CalcLabels(int pointCount, int pointSize) { var duration = pointSize * pointCount; @@ -136,13 +116,6 @@ protected override async Task OnAfterRenderAsync(bool firstRender) }; var options = new { staticPlot = true }; - await JSRuntime.InvokeVoidAsync("Plotly.newPlot", $"lineChart{_instanceID}", data, layout, options); + await JSRuntime.InvokeVoidAsync("Plotly.newPlot", chartDivId, data, layout, options); } - - RenderFragment _graph => (builder) => - { - builder.AddMarkupContent(0, $$""" -
-"""); - }; } diff --git a/OTLPView/Components/DimensionedHistogramView.razor b/OTLPView/Components/DimensionedHistogramView.razor index f54c305..d3f1f00 100644 --- a/OTLPView/Components/DimensionedHistogramView.razor +++ b/OTLPView/Components/DimensionedHistogramView.razor @@ -25,7 +25,7 @@ - @(_graph) +
diff --git a/OTLPView/Components/DimensionedHistogramView.razor.cs b/OTLPView/Components/DimensionedHistogramView.razor.cs index 4fc1bc8..53298e0 100644 --- a/OTLPView/Components/DimensionedHistogramView.razor.cs +++ b/OTLPView/Components/DimensionedHistogramView.razor.cs @@ -28,14 +28,6 @@ public required DimensionScope Dimension { _dimension = value; _chartLabels = CalcLabels((Dimension.Values?.First() as HistogramValue).ExplicitBounds); - //_chartValues = new List() - //{ - // new ChartSeries() - // { - // Name = Counter?.CounterName ?? "unknown", - // Data = (Dimension.Values.First() as HistogramValue).Values.Select(v => (double)v).ToArray() - // } - //}; _chartValues = (Dimension.Values.First() as HistogramValue).Values.Select(v => (double)v).ToArray(); } } @@ -43,16 +35,18 @@ public required DimensionScope Dimension [Parameter, EditorRequired] public required Counter Counter { get; set; } + private string chartDivId => $"barChart{_instanceID}"; + protected override void OnInitialized() { } private string[] CalcLabels(double[] bounds) { - var labels = new string[bounds.Length+1]; + var labels = new string[bounds.Length + 1]; for (var i = 0; i < bounds.Length; i++) { - labels[i] = $"{bounds[i]}{Counter.CounterUnit??"s"}"; + labels[i] = $"{bounds[i]}{Counter.CounterUnit ?? "s"}"; } labels[bounds.Length] = "Inf"; return labels; @@ -67,11 +61,11 @@ private double[] CalcChartValues(DimensionScope dimension, int pointCount, int p var now = DateTime.UtcNow; foreach (var point in dimension.Values) { - var start = CalcOffset(now-point.Start, pointCount, pointSize); - var end = CalcOffset(now-point.End, pointCount, pointSize); + var start = CalcOffset(now - point.Start, pointCount, pointSize); + var end = CalcOffset(now - point.End, pointCount, pointSize); if (start is not null && end is not null) { - for (var i = start.GetValueOrDefault(0); i <= end.GetValueOrDefault(pointCount-1); i++) + for (var i = start.GetValueOrDefault(0); i <= end.GetValueOrDefault(pointCount - 1); i++) { values[i] = point switch { @@ -117,18 +111,11 @@ protected override async Task OnAfterRenderAsync(bool firstRender) title = Counter?.CounterName ?? "unknown", showlegend = false, xaxis = new { - title = "Time (s)" + title = $"Time ({Counter?.CounterUnit})" } }; var options = new { staticPlot = true }; - await JSRuntime.InvokeVoidAsync("Plotly.newPlot", $"barChart{_instanceID}", data, layout, options); + await JSRuntime.InvokeVoidAsync("Plotly.newPlot", chartDivId, data, layout, options); } - - RenderFragment _graph => (builder) => - { - builder.AddMarkupContent(0, $$""" -
-"""); - }; }