DEEP-9. Datasources management UI implemented.

This commit is contained in:
Andrey Shabarshov 2023-06-23 16:31:01 +01:00
parent 55044deaab
commit 6d7f17a34d
7 changed files with 338 additions and 66 deletions

2
.gitignore vendored
View File

@ -362,5 +362,5 @@ MigrationBackup/
# Fody - auto-generated XML schema # Fody - auto-generated XML schema
FodyWeavers.xsd FodyWeavers.xsd
Data TrainData
prometheus-2.45.0-rc.0.windows-amd64 prometheus-2.45.0-rc.0.windows-amd64

View File

@ -1,33 +1,42 @@
@using PrometheusAPI; @using PrometheusAPI;
<div> <ApexChart @ref="_chart"
<p hidden="@IsChartShown"><em>Loading...</em></p> TItem="TimeSeries"
<div hidden="@IsChartHidden"> Title="Data view"
<ApexChart @ref="_chart" Options="@_options"
TItem="TimeSeries" OnZoomed="OnZoomed"
Title="Data view" >
Options="@_options" @foreach (var ts in _currentData.Series)
OnZoomed="OnZoomed" {
> <ApexPointSeries TItem="TimeSeries"
Name="@ts.Name"
<ApexPointSeries TItem="TimeSeries" Items="@ts.Data"
Name="Value" SeriesType="SeriesType.Line"
Items="@_currentData" XValue="@(e => e.TimeStamp)"
SeriesType="SeriesType.Line" YAggregate="@(e => (decimal)e.Sum(e => e.Value))"
XValue="@(e => e.TimeStamp)" ShowDataLabels="false"
YAggregate="@(e => (decimal)e.Sum(e => e.Value))" />
ShowDataLabels="false" }
/> </ApexChart>
</ApexChart>
</div>
</div>
@code { @code {
public class TimeSeriesDataSet
{
public string Name { get; init; } = "Value";
public string Color { get; init; } = "";
public IReadOnlyCollection<TimeSeries> Data { get; init; } = Array.Empty<TimeSeries>();
}
public class TimeSeriesData
{
public List<TimeSeriesDataSet> Series { get; init; } = new List<TimeSeriesDataSet>();
}
[CascadingParameter] [CascadingParameter]
protected bool IsDarkMode { get; set; } protected bool IsDarkMode { get; set; }
[Parameter] public IList<TimeSeries>? Data { get; set; } [Parameter] public TimeSeriesData? Data { get; set; }
[Parameter] public DateTime? MinDate { get; set; } [Parameter] public DateTime? MinDate { get; set; }
[Parameter] public DateTime? MaxDate { get; set; } [Parameter] public DateTime? MaxDate { get; set; }
@ -36,10 +45,7 @@
private ApexChart<TimeSeries>? _chart; private ApexChart<TimeSeries>? _chart;
private ApexChartOptions<TimeSeries>? _options; private ApexChartOptions<TimeSeries>? _options;
private IList<TimeSeries>? _currentData; private TimeSeriesData _currentData = new() { Series = { new () } };
private bool IsChartHidden => _currentData == null;
private bool IsChartShown => !IsChartHidden;
protected override void OnInitialized() protected override void OnInitialized()
{ {
@ -60,9 +66,10 @@
if (Data == _currentData) if (Data == _currentData)
return; return;
_currentData = Data; _currentData = Data ?? new() { Series = { new() } }; ;
_options = CreateOptions();
if (_currentData == null) if (_chart == null)
return; return;
//await InvokeAsync(StateHasChanged); //await InvokeAsync(StateHasChanged);
@ -77,10 +84,7 @@
var backgroundColor = IsDarkMode ? "var(--mud-palette-surface)" : "#f3f3f3"; var backgroundColor = IsDarkMode ? "var(--mud-palette-surface)" : "#f3f3f3";
var gridColor = IsDarkMode ? "var(--mud-palette-drawer-background)" : "#f3f3f3"; var gridColor = IsDarkMode ? "var(--mud-palette-drawer-background)" : "#f3f3f3";
var borderColor = IsDarkMode ? "var(--mud-palette-text-primary)" : "#e7e7e7"; var borderColor = IsDarkMode ? "var(--mud-palette-text-primary)" : "#e7e7e7";
var lineColors = IsDarkMode var lineColors = _currentData.Series.Select( x => x.Color).ToList();
? new List<string> { "#F57F17", "#00838F" }
: new List<string> { "#77B6EA", "#545454" }
;
var mode = IsDarkMode var mode = IsDarkMode
? Mode.Dark ? Mode.Dark
: Mode.Light : Mode.Light
@ -139,7 +143,7 @@
} }
}, },
Colors = lineColors, Colors = lineColors,
Markers = new() { Shape = ShapeEnum.Circle, Size = 2, FillOpacity = new Opacity(0.8d) }, //Markers = new() { Shape = ShapeEnum.Circle, Size = 2, FillOpacity = new Opacity(0.8d) },
Stroke = new() { Curve = Curve.Straight, Width = 2 }, Stroke = new() { Curve = Curve.Straight, Width = 2 },
Legend = new() Legend = new()
{ {
@ -168,12 +172,12 @@
DateTimeOffset xMax; DateTimeOffset xMax;
xMin = zoomedData.XAxis?.Min == null xMin = zoomedData.XAxis?.Min == null
? Data!.Min(e => e.TimeStamp.Date) ? _currentData!.Series.First().Data.Min(e => e.TimeStamp.Date)
: DateTimeOffset.FromUnixTimeMilliseconds((long)zoomedData.XAxis.Min) : DateTimeOffset.FromUnixTimeMilliseconds((long)zoomedData.XAxis.Min)
; ;
xMax = zoomedData.XAxis?.Max == null xMax = zoomedData.XAxis?.Max == null
? Data!.Max(e => e.TimeStamp.Date) ? _currentData!.Series.First().Data.Max(e => e.TimeStamp.Date)
: DateTimeOffset.FromUnixTimeMilliseconds((long)zoomedData.XAxis.Max) : DateTimeOffset.FromUnixTimeMilliseconds((long)zoomedData.XAxis.Max)
; ;

View File

@ -0,0 +1,29 @@
namespace DeepTrace.Data;
public class DataSourceQuery
{
public DataSourceQuery(string query, string color)
{
Query = query;
Color = color;
}
public string Query { get; set; }
public MudBlazor.Utilities.MudColor Color { get; set; }
}
public class DataSourceDefinition
{
private static int _instanceId;
public DataSourceDefinition()
{
var id = Interlocked.Increment(ref _instanceId);
Name = $"Dataset #{id}";
}
public string Name { get; set; }
public List<DataSourceQuery> Queries { get; set; } = new();
public string Description { get; set; } = string.Empty;
}

View File

@ -0,0 +1,52 @@
using Microsoft.ML;
using PrometheusAPI;
using System.Data;
namespace DeepTrace.Data;
public static class DataViewHelper
{
public static DataTable? ToDataTable(this IDataView dataView)
{
DataTable? dt = null;
if (dataView != null)
{
dt = new DataTable();
var preview = dataView.Preview();
dt.Columns.AddRange(preview.Schema.Select(x => new DataColumn(x.Name)).ToArray());
foreach (var row in preview.RowView)
{
var r = dt.NewRow();
foreach (var col in row.Values)
{
r[col.Key] = col.Value;
}
dt.Rows.Add(r);
}
}
return dt;
}
public static List<TimeSeries> ToTimeSeries(this IDataView dataView)
{
var dt = new List<TimeSeries>();
if (dataView == null)
{
return dt;
}
var preview = dataView.Preview();
var tsCol = preview.Schema["TimeStamp"];
var valCol = preview.Schema["Value"];
foreach (var row in preview.RowView)
{
var r = new TimeSeries(
Convert.ToDateTime(row.Values[tsCol.Index].Value),
(float)Convert.ToDouble(row.Values[valCol.Index].Value)
);
dt.Add(r);
}
return dt;
}
}

View File

@ -0,0 +1,6 @@
{
"TrainingTime": 0,
"Scenario": "Default",
"Type": "TrainingConfig",
"Version": 2
}

View File

@ -0,0 +1,15 @@
using Microsoft.ML.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DeepTrace.Data;
public class MyPrediction
{
//vector to hold alert,score,p-value values
[VectorType(3)]
public double[]? Prediction { get; set; }
}

View File

@ -25,12 +25,53 @@
<MudGrid Justify="Justify.FlexStart"> <MudGrid Justify="Justify.FlexStart">
<MudItem xs="12" sm="6" md="6" lg="3"> <MudItem xs="12" sm="6" md="6" lg="3">
<MudCard Class="mb-3">
<MudCardActions>
<MudSelect T="DataSourceDefinition" Label="Query name" AnchorOrigin="Origin.BottomCenter" @bind-Value="_queryForm.Source">
@foreach (var source in _dataSources)
{
<MudSelectItem Value="@source">@source.Name</MudSelectItem>
}
</MudSelect>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="MudBlazor.Color.Primary" Class="ml-3" OnClick="@HandleAddSource">Add</MudButton>
@if (_dataSources.Count > 1)
{
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="MudBlazor.Color.Primary" Class="ml-3" OnClick="@HandleDeleteSource">Delete</MudButton>
}
</MudCardActions>
</MudCard>
<MudCard> <MudCard>
<EditForm Model="@_queryForm" OnSubmit="@HandleSubmit" class="form-group" Context="editContext"> <EditForm Model="@_queryForm" OnSubmit="@HandleSubmit" class="form-group" Context="editContext">
<DataAnnotationsValidator /> <DataAnnotationsValidator />
<ValidationSummary /> <ValidationSummary />
<MudCardContent> <MudCardContent>
<MudTextField Label="Query" @bind-Value="_queryForm.Query" Variant="Variant.Text" InputType="InputType.Search" Lines="5" />
<MudTextField Label="Name" @bind-Value="_queryForm.Source.Name" Variant="Variant.Text" InputType="InputType.Search" />
<MudGrid>
@for ( var i = 0; i < _queryForm.Source.Queries.Count; i++ )
{
int pos = i;
var def = _queryForm.Source.Queries[pos];
<MudItem xs="10">
<MudTextField Label="Query" @bind-Value="def.Query" Variant="Variant.Text" InputType="InputType.Search" Lines="2" />
</MudItem>
<MudItem xs="1">
<MudIconButton Icon="@Icons.Material.Outlined.Add" Variant="Variant.Outlined" aria-label="add" OnClick="@(() => AddQuery(pos))"/>
@if (_queryForm.Source.Queries.Count > 1)
{
<MudIconButton Icon="@Icons.Material.Outlined.Delete" Variant="Variant.Outlined" aria-label="delete" OnClick="@(() => DeleteQuery(pos))"/>
}
</MudItem>
<MudItem xs="12">
<MudColorPicker DisableToolbar="false" Label="Color" @bind-Value="def.Color" PickerVariant="PickerVariant.Inline" DisableAlpha=true />
</MudItem>
}
</MudGrid>
<MudDateRangePicker @ref="_picker" Label="Date range" @bind-DateRange="_queryForm.Dates" AutoClose="true"> <MudDateRangePicker @ref="_picker" Label="Date range" @bind-DateRange="_queryForm.Dates" AutoClose="true">
<PickerActions> <PickerActions>
<MudButton Class="mr-auto align-self-start" OnClick="@(() => _picker!.Clear())">Clear</MudButton> <MudButton Class="mr-auto align-self-start" OnClick="@(() => _picker!.Clear())">Clear</MudButton>
@ -38,9 +79,11 @@
<MudButton Color="MudBlazor.Color.Primary" OnClick="@(() => _picker!.Close())">Ok</MudButton> <MudButton Color="MudBlazor.Color.Primary" OnClick="@(() => _picker!.Close())">Ok</MudButton>
</PickerActions> </PickerActions>
</MudDateRangePicker> </MudDateRangePicker>
<MudTimePicker Label="Start time" @bind-Time="_queryForm.TimeStart" /> <MudTimePicker Label="Start time" @bind-Time="_queryForm.TimeStart" />
<MudTimePicker Label="End time" @bind-Time="_queryForm.TimeEnd" /> <MudTimePicker Label="End time" @bind-Time="_queryForm.TimeEnd" />
@*<MudNumericField @bind-Value="_queryForm.StepSec" Label="Step (sec)" Variant="Variant.Text" Min="0" />*@ @*<MudNumericField @bind-Value="_queryForm.StepSec" Label="Step (sec)" Variant="Variant.Text" Min="0" />*@
<MudTextField Label="Description" @bind-Value="_queryForm.Source.Description" Variant="Variant.Text" InputType="InputType.Search" Lines="3" />
</MudCardContent> </MudCardContent>
<MudCardActions> <MudCardActions>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="MudBlazor.Color.Primary" Class="ml-auto">Submit</MudButton> <MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="MudBlazor.Color.Primary" Class="ml-auto">Submit</MudButton>
@ -51,7 +94,10 @@
<MudItem xs="12" sm="6" md="6" lg="9"> <MudItem xs="12" sm="6" md="6" lg="9">
<MudCard> <MudCard>
<MudCardContent> <MudCardContent>
<TimeSeriesChart Data="@DisplayData" @bind-MinDate=MinDate @bind-MaxDate=MaxDate /> <div hidden="@IsChartShown"><MudProgressCircular Color="MudBlazor.Color.Default" /></div>
<div hidden="@IsChartHidden">
<TimeSeriesChart Data="@DisplayData" @bind-MinDate=MinDate @bind-MaxDate=MaxDate />
</div>
</MudCardContent> </MudCardContent>
</MudCard> </MudCard>
</MudItem> </MudItem>
@ -66,7 +112,7 @@
private class PrometheusForm private class PrometheusForm
{ {
[Required] [Required]
public string? Query { get; set; } = """ec2_cpu_utilization_24ae8d"""; public DataSourceDefinition Source { get; set; } = new();
public DateRange Dates { get; set; } = new DateRange(DateTime.UtcNow.Date - TimeSpan.FromDays(14), DateTime.UtcNow.Date); public DateRange Dates { get; set; } = new DateRange(DateTime.UtcNow.Date - TimeSpan.FromDays(14), DateTime.UtcNow.Date);
@ -82,8 +128,21 @@
} }
private PrometheusForm _queryForm = new(); private PrometheusForm _queryForm = new();
private List<DataSourceDefinition> _dataSources = new()
{
new()
{
Queries =
{
new("""ec2_cpu_utilization_24ae8d""", "#F57F17")
}
}
};
private MudDateRangePicker? _picker; private MudDateRangePicker? _picker;
private bool IsChartHidden => DisplayData == null;
private bool IsChartShown => !IsChartHidden;
private DateTime? MinDate private DateTime? MinDate
{ {
get => _queryForm.Dates.Start?.Date + _queryForm.TimeStart; get => _queryForm.Dates.Start?.Date + _queryForm.TimeStart;
@ -120,11 +179,72 @@
} }
} }
private List<TimeSeries>? DisplayData { get; set; } private TimeSeriesChart.TimeSeriesData? DisplayData { get; set; }
private string[] _palette = new[]
{
"#F44336", "#E91E63", "#9C27B0", "#673AB7", "#3F51B5",
"#FFEBEE", "#FCE4EC", "#F3E5F5", "#EDE7F6", "#E8EAF6",
"#FFCDD2", "#F8BBD0", "#E1BEE7", "#D1C4E9", "#C5CAE9",
"#EF9A9A", "#F48FB1", "#CE93D8", "#B39DDB", "#9FA8DA",
"#E57373", "#F06292", "#BA68C8", "#9575CD", "#7986CB",
"#EF5350", "#EC407A", "#AB47BC", "#7E57C2", "#5C6BC0",
"#E53935", "#D81B60", "#8E24AA", "#5E35B1", "#3949AB"
};
protected override void OnInitialized()
{
base.OnInitialized();
_queryForm.Source = _dataSources[0];
}
private void AddQuery(int pos)
{
pos += 1;
var color = pos < _palette.Length
? _palette[pos]
: "#F44336"
;
_queryForm.Source.Queries.Insert(pos, new("",color));
StateHasChanged();
}
private void DeleteQuery(int pos)
{
_queryForm.Source.Queries.RemoveAt(pos);
StateHasChanged();
}
private void HandleAddSource()
{
_dataSources.Add(new() { Queries = { new("", _palette[0]) } });
_queryForm.Source = _dataSources[^1];
StateHasChanged();
}
private void HandleDeleteSource()
{
if (_dataSources.Count < 2)
{
return;
}
var pos = _dataSources.IndexOf(_queryForm.Source);
if (pos < 0)
{
ShowError("Not found");
return;
}
_dataSources.RemoveAt(pos);
_queryForm.Source = _dataSources[pos<_dataSources.Count ? pos: _dataSources.Count-1];
StateHasChanged();
}
private async Task HandleSubmit() private async Task HandleSubmit()
{ {
if (string.IsNullOrWhiteSpace(_queryForm.Query) || _queryForm.Dates.End == null || _queryForm.Dates.Start == null) if (_queryForm.Source.Queries.Count < 1 || string.IsNullOrWhiteSpace(_queryForm.Source.Queries[0].Query) || _queryForm.Dates.End == null || _queryForm.Dates.Start == null)
return; return;
var startTime = _queryForm.TimeStart var startTime = _queryForm.TimeStart
@ -146,16 +266,8 @@
var startDate = _queryForm.Dates.Start.Value.Date + startTime; var startDate = _queryForm.Dates.Start.Value.Date + startTime;
var endDate = _queryForm.Dates.End.Value.Date + endTime; var endDate = _queryForm.Dates.End.Value.Date + endTime;
try if (!await FormatQueries())
{
var formatted = await Prometheus.FormatQuery(_queryForm.Query);
_queryForm.Query = formatted;
}
catch (Exception e)
{
ShowError(e.Message);
return; return;
}
TimeSpan step; TimeSpan step;
@ -167,36 +279,90 @@
seconds = 1.0; seconds = 1.0;
step = TimeSpan.FromSeconds(seconds); step = TimeSpan.FromSeconds(seconds);
var res = await Prometheus.RangeQuery(_queryForm.Query, startDate, endDate, step, TimeSpan.FromSeconds(2)); var tasks = _queryForm.Source.Queries
.Select(x => Prometheus.RangeQuery(x.Query, startDate, endDate, step, TimeSpan.FromSeconds(2)))
.ToArray();
if (res.Status != StatusType.Success) try
{ {
ShowError(res.Error ?? "Error"); await Task.WhenAll(tasks);
}
catch ( Exception e )
{
ShowError(e.Message);
return; return;
} }
if (res.ResultType != ResultTypeType.Matrix) var data = new List<TimeSeriesChart.TimeSeriesDataSet>();
foreach ( var (res, def) in tasks.Select((x,i) => (x.Result, _queryForm.Source.Queries[i]) ))
{ {
ShowError($"Got {res.ResultType}, but Matrix expected"); if (res.Status != StatusType.Success)
return; {
ShowError(res.Error ?? "Error");
return;
}
if (res.ResultType != ResultTypeType.Matrix)
{
ShowError($"Got {res.ResultType}, but Matrix expected for {def.Query}");
return;
}
var m = res.AsMatrix().Result;
if (m == null || m.Length != 1)
{
ShowError($"No data returned for {def.Query}");
return;
}
data.Add(
new()
{
Name = def.Query,
Color = def.Color.Value,
Data = m[0].Values!.ToList()
}
);
} }
var m = res.AsMatrix().Result; DisplayData = new() { Series = data };
if (m == null || m.Length != 1) await InvokeAsync(StateHasChanged);
{
ShowError("No data returned"); // --------------------- playground -----------------------------
return;
}
var mlContext = new MLContext(); var mlContext = new MLContext();
DisplayData = m[0].Values!.ToList();
var dataView = mlContext.Data.LoadFromEnumerable<MyTimeSeries>(DisplayData.Select(x => new MyTimeSeries(Time: x.TimeStamp, Value: x.Value))); var dataView = mlContext.Data.LoadFromEnumerable<MyTimeSeries>(DisplayData.Series[0].Data.Select(x => new MyTimeSeries(Time: x.TimeStamp, Value: x.Value)));
//DetectSpike(mlContext, dataView, data); //DetectSpike(mlContext, dataView, data);
int period = DetectPeriod(mlContext, dataView); int period = DetectPeriod(mlContext, dataView);
DetectAnomaly(mlContext, dataView, period); DetectAnomaly(mlContext, dataView, period);
await InvokeAsync(StateHasChanged); }
private async Task<bool> FormatQueries()
{
try
{
var formatTasks = _queryForm.Source.Queries
.Select(x => Prometheus.FormatQuery(x.Query))
.ToArray();
await Task.WhenAll(formatTasks);
for ( var i = 0; i < formatTasks.Length; i++ )
{
_queryForm.Source.Queries[i].Query = formatTasks[i].Result;
}
return true;
}
catch (Exception e)
{
ShowError(e.Message);
return false;
}
} }
private record MyTimeSeries(DateTime Time, double Value); private record MyTimeSeries(DateTime Time, double Value);