diff --git a/DeepTrace/Controllers/DownloadController.cs b/DeepTrace/Controllers/DownloadController.cs index 55b0fff..8fc070e 100644 --- a/DeepTrace/Controllers/DownloadController.cs +++ b/DeepTrace/Controllers/DownloadController.cs @@ -2,30 +2,29 @@ using Microsoft.AspNetCore.Mvc; using System.Text; -namespace DeepTrace.Controllers +namespace DeepTrace.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class DownloadController : Controller { - [ApiController] - [Route("api/[controller]")] - public class DownloadController : Controller + private readonly IModelStorageService _modelService; + + public DownloadController(IModelStorageService modelService) { - private readonly IModelStorageService _modelService; + _modelService = modelService; + } - public DownloadController(IModelStorageService modelService) - { - _modelService = modelService; - } + [HttpGet("mldata/{modelName}")] + public async Task GetMLDataCsv([FromRoute] string modelName) + { + var ModelDefinition = await _modelService.Load(); + var model = ModelDefinition.FirstOrDefault(x=>x.Name==modelName) ?? throw new ApplicationException($"Model {modelName} not found"); - [HttpGet("mldata/{modelName}")] - public async Task GetMLDataCsv([FromRoute] string modelName) - { - var ModelDefinition = await _modelService.Load(); - var model = ModelDefinition.FirstOrDefault(x=>x.Name==modelName) ?? throw new ApplicationException($"Model {modelName} not found"); - - var csv = model.ToCsv(); - return new(Encoding.UTF8.GetBytes(csv),"text/csv") - { - FileDownloadName = modelName+".csv" - }; - } + var csv = model.ToCsv(); + return new(Encoding.UTF8.GetBytes(csv),"text/csv") + { + FileDownloadName = modelName+".csv" + }; } } diff --git a/DeepTrace/Controls/Dialog.razor b/DeepTrace/Controls/Dialog.razor index fb44cc7..05d76ad 100644 --- a/DeepTrace/Controls/Dialog.razor +++ b/DeepTrace/Controls/Dialog.razor @@ -4,31 +4,31 @@ @if( IsYesNoCancel ) + { + Yes + No + Cancel + } + else + { + if (AllowCancel) { - Yes - No Cancel } - else - { - if (AllowCancel) - { - Cancel - } - Ok - } + Ok + } @code { - [CascadingParameter] MudDialogInstance? MudDialog { get; set; } - [Parameter] public bool AllowCancel { get; set; } - [Parameter] public string Text { get; set; } = ""; - [Parameter] public bool IsYesNoCancel { get; set; } = false; +[CascadingParameter] MudDialogInstance? MudDialog { get; set; } +[Parameter] public bool AllowCancel { get; set; } +[Parameter] public string Text { get; set; } = ""; +[Parameter] public bool IsYesNoCancel { get; set; } = false; - void Submit() => MudDialog?.Close(DialogResult.Ok(true)); - - void Cancel() => MudDialog?.Cancel(); - - void Yes() => MudDialog?.Close(DialogResult.Ok(true)); - void No() => MudDialog?.Close(DialogResult.Ok(false)); +void Submit() => MudDialog?.Close(DialogResult.Ok(true)); + +void Cancel() => MudDialog?.Cancel(); + +void Yes() => MudDialog?.Close(DialogResult.Ok(true)); +void No() => MudDialog?.Close(DialogResult.Ok(false)); } \ No newline at end of file diff --git a/DeepTrace/Controls/ModelCard.razor b/DeepTrace/Controls/ModelCard.razor index ec75833..4432ed3 100644 --- a/DeepTrace/Controls/ModelCard.razor +++ b/DeepTrace/Controls/ModelCard.razor @@ -7,10 +7,16 @@ @inject IDialogService DialogService @inject IModelStorageService ModelService @inject ITrainedModelStorageService TrainedModelService -@inject ILogger MLProcessorLogger @inject ILogger Logger +@inject IMLProcessorFactory MlProcessorFactory - + + + @Model?.Name @@ -21,6 +27,7 @@ Current state: @_prediction.PredictedLabel + @_updated.ToString("HH:mm:ss") @@ -29,7 +36,9 @@ public TrainedModelDefinition? Model { get; set; } private ModelDefinition _modelDefinition = new(); - private Prediction _prediction = new(); + private Prediction _prediction = new(); + private IMLProcessor? _mlProcessor; + private DateTime _updated = DateTime.MinValue; protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -38,7 +47,8 @@ return; } _modelDefinition = (await ModelService.Load(Model.Id)) ?? _modelDefinition; - + _mlProcessor = MlProcessorFactory.Create(); + #pragma warning disable CS4014 Task.Run(PredictionLoop); #pragma warning restore CS4014 @@ -87,15 +97,20 @@ await PredictAnomaly(startDate, endDate); startDate = endDate; } - catch(Exception) + catch(Exception e) { - //ignore + Logger.LogError(e, e.Message); } } } private async Task PredictAnomaly(DateTime startDate, DateTime endDate) { + if (Model == null || !Model.IsEnabled) + { + _prediction = new Prediction { PredictedLabel = "Idle" }; + return; + } // use automatic step value to always request 500 elements var seconds = (endDate - startDate).TotalSeconds / 500.0; @@ -150,8 +165,9 @@ ); } - var mlProcessor = new MLProcessor(MLProcessorLogger); - _prediction = await mlProcessor.Predict(Model, _modelDefinition, data); + _prediction = await _mlProcessor!.Predict(Model, _modelDefinition, data); + _updated = DateTime.Now; + await InvokeAsync(StateHasChanged); } private async Task ShowError(string text) diff --git a/DeepTrace/Controls/TimeSeriesChart.razor b/DeepTrace/Controls/TimeSeriesChart.razor index d285361..d6befa7 100644 --- a/DeepTrace/Controls/TimeSeriesChart.razor +++ b/DeepTrace/Controls/TimeSeriesChart.razor @@ -9,8 +9,8 @@ OnZoomed="OnZoomed" > @foreach (var ts in _currentData.Series) - { - - } +} @code { - [CascadingParameter] - protected bool IsDarkMode { get; set; } +[CascadingParameter] +protected bool IsDarkMode { get; set; } - [Parameter] public TimeSeriesData? Data { get; set; } +[Parameter] public TimeSeriesData? Data { get; set; } - [Parameter] public DateTime? MinDate { get; set; } - [Parameter] public DateTime? MaxDate { get; set; } - [Parameter] public EventCallback MinDateChanged { get; set; } - [Parameter] public EventCallback MaxDateChanged { get; set; } +[Parameter] public DateTime? MinDate { get; set; } +[Parameter] public DateTime? MaxDate { get; set; } +[Parameter] public EventCallback MinDateChanged { get; set; } +[Parameter] public EventCallback MaxDateChanged { get; set; } - private ApexChart? _chart; - private ApexChartOptions? _options; - private TimeSeriesData _currentData = new() { Series = { new () } }; +private ApexChart? _chart; +private ApexChartOptions? _options; +private TimeSeriesData _currentData = new() { Series = { new () } }; - protected override void OnInitialized() - { - _options = CreateOptions(); - base.OnInitialized(); - } +protected override void OnInitialized() +{ + _options = CreateOptions(); + base.OnInitialized(); +} - protected override async Task OnParametersSetAsync() - { - Console.WriteLine("OnParametersSet"); +protected override async Task OnParametersSetAsync() +{ + Console.WriteLine("OnParametersSet"); - await UpdateChart(); - await base.OnParametersSetAsync(); - } + await UpdateChart(); + await base.OnParametersSetAsync(); +} - private async Task UpdateChart() - { - if (Data == _currentData) - return; +private async Task UpdateChart() +{ + if (Data == _currentData) + return; - _currentData = Data ?? new() { Series = { new() } }; ; - _options = CreateOptions(); + _currentData = Data ?? new() { Series = { new() } }; ; + _options = CreateOptions(); - if (_chart == null) - return; + if (_chart == null) + return; - //await InvokeAsync(StateHasChanged); - await _chart!.UpdateSeriesAsync(); - await _chart!.UpdateOptionsAsync(true, true, true); - await InvokeAsync(StateHasChanged); - - } - - private ApexChartOptions CreateOptions() - { - var backgroundColor = IsDarkMode ? "var(--mud-palette-surface)" : "#f3f3f3"; - var gridColor = IsDarkMode ? "var(--mud-palette-drawer-background)" : "#f3f3f3"; - var borderColor = IsDarkMode ? "var(--mud-palette-text-primary)" : "#e7e7e7"; - var lineColors = _currentData.Series.Select( x => x.Color).ToList(); - var mode = IsDarkMode - ? Mode.Dark - : Mode.Light - ; - - var options = new ApexChartOptions - { - Chart = new() - { - Background = backgroundColor, - Toolbar = new() - { - Show = true - }, - DropShadow = new() - { - Enabled = false, - Color = "", - Top = 18, - Left = 7, - Blur = 10, - Opacity = 0.2d - } - }, - DataLabels = new() - { - Enabled = false - }, - Tooltip = new ApexCharts.Tooltip - { - Y = new () - { - Formatter = @"function(value, opts) { - if (value === undefined) {return '';} - return Number(value).toLocaleString();}", - }, - X = new () - { - Formatter = @"function(value, opts) { - if (value === undefined) {return '';} - return (new Date(value)).toISOString();}", - } - - }, - Xaxis = new() - { - Type = XAxisType.Datetime - }, - Grid = new() - { - BorderColor = borderColor, - Row = new() - { - Colors = new List { gridColor, "transparent" }, - Opacity = 0.5d - } - }, - Colors = lineColors, - //Markers = new() { Shape = ShapeEnum.Circle, Size = 2, FillOpacity = new Opacity(0.8d) }, - Stroke = new() { Curve = Curve.Straight, Width = 2 }, - Legend = new() - { - Position = LegendPosition.Top, - HorizontalAlign = ApexCharts.Align.Right, - Floating = true, - OffsetX = -5, - OffsetY = -25 - }, - Theme = new() - { - Mode = mode, - Palette = PaletteType.Palette8, - } - }; - - return options; - } - - private void OnZoomed(ZoomedData zoomedData) - { - if (zoomedData.XAxis?.Min == null && zoomedData.XAxis?.Max == null) - return; - - DateTimeOffset xMin; - DateTimeOffset xMax; - - xMin = zoomedData.XAxis?.Min == null - ? _currentData!.Series.First().Data.Min(e => e.TimeStamp.Date) - : DateTimeOffset.FromUnixTimeMilliseconds((long)zoomedData.XAxis.Min) - ; - - xMax = zoomedData.XAxis?.Max == null - ? _currentData!.Series.First().Data.Max(e => e.TimeStamp.Date) - : DateTimeOffset.FromUnixTimeMilliseconds((long)zoomedData.XAxis.Max) - ; - - MinDate = xMin.UtcDateTime; - MinDateChanged.InvokeAsync(MinDate); - - MaxDate = xMax.UtcDateTime; - MaxDateChanged.InvokeAsync(MaxDate); - } + //await InvokeAsync(StateHasChanged); + await _chart!.UpdateSeriesAsync(); + await _chart!.UpdateOptionsAsync(true, true, true); + await InvokeAsync(StateHasChanged); + +} + +private ApexChartOptions CreateOptions() +{ + var backgroundColor = IsDarkMode ? "var(--mud-palette-surface)" : "#f3f3f3"; + var gridColor = IsDarkMode ? "var(--mud-palette-drawer-background)" : "#f3f3f3"; + var borderColor = IsDarkMode ? "var(--mud-palette-text-primary)" : "#e7e7e7"; + var lineColors = _currentData.Series.Select( x => x.Color).ToList(); + var mode = IsDarkMode + ? Mode.Dark + : Mode.Light + ; + + var options = new ApexChartOptions + { + Chart = new() + { + Background = backgroundColor, + Toolbar = new() + { + Show = true + }, + DropShadow = new() + { + Enabled = false, + Color = "", + Top = 18, + Left = 7, + Blur = 10, + Opacity = 0.2d + } + }, + DataLabels = new() + { + Enabled = false + }, + Tooltip = new ApexCharts.Tooltip + { + Y = new () + { + Formatter = @"function(value, opts) { + if (value === undefined) {return '';} + return Number(value).toLocaleString();}", + }, + X = new () + { + Formatter = @"function(value, opts) { + if (value === undefined) {return '';} + return (new Date(value)).toISOString();}", + } + + }, + Xaxis = new() + { + Type = XAxisType.Datetime + }, + Grid = new() + { + BorderColor = borderColor, + Row = new() + { + Colors = new List { gridColor, "transparent" }, + Opacity = 0.5d + } + }, + Colors = lineColors, + //Markers = new() { Shape = ShapeEnum.Circle, Size = 2, FillOpacity = new Opacity(0.8d) }, + Stroke = new() { Curve = Curve.Straight, Width = 2 }, + Legend = new() + { + Position = LegendPosition.Top, + HorizontalAlign = ApexCharts.Align.Right, + Floating = true, + OffsetX = -5, + OffsetY = -25 + }, + Theme = new() + { + Mode = mode, + Palette = PaletteType.Palette8, + } + }; + + return options; +} + +private void OnZoomed(ZoomedData zoomedData) +{ + if (zoomedData.XAxis?.Min == null && zoomedData.XAxis?.Max == null) + return; + + DateTimeOffset xMin; + DateTimeOffset xMax; + + xMin = zoomedData.XAxis?.Min == null + ? _currentData!.Series.First().Data.Min(e => e.TimeStamp.Date) + : DateTimeOffset.FromUnixTimeMilliseconds((long)zoomedData.XAxis.Min) + ; + + xMax = zoomedData.XAxis?.Max == null + ? _currentData!.Series.First().Data.Max(e => e.TimeStamp.Date) + : DateTimeOffset.FromUnixTimeMilliseconds((long)zoomedData.XAxis.Max) + ; + + MinDate = xMin.UtcDateTime; + MinDateChanged.InvokeAsync(MinDate); + + MaxDate = xMax.UtcDateTime; + MaxDateChanged.InvokeAsync(MaxDate); +} } diff --git a/DeepTrace/Controls/TrainingDialog.razor b/DeepTrace/Controls/TrainingDialog.razor index 3342658..febe19d 100644 --- a/DeepTrace/Controls/TrainingDialog.razor +++ b/DeepTrace/Controls/TrainingDialog.razor @@ -14,13 +14,13 @@

@Text

- @if (_isTraining == false) - { - MicroAccuracy: @_evaluationMetrics!.MicroAccuracy.ToString("N2") - MacroAccuracy: @_evaluationMetrics!.MacroAccuracy.ToString("N2") - LogLoss: @_evaluationMetrics!.LogLoss.ToString("N2") - LogLossReduction: @_evaluationMetrics!.LogLossReduction.ToString("N2") - } + @if (_isTraining == false && _evaluationMetrics != null) + { + MicroAccuracy: @_evaluationMetrics.MicroAccuracy.ToString("N6") + MacroAccuracy: @_evaluationMetrics.MacroAccuracy.ToString("N6") + LogLoss: @_evaluationMetrics.LogLoss.ToString("N6") + LogLossReduction: @_evaluationMetrics.LogLossReduction.ToString("N6") + } @@ -29,32 +29,43 @@ @code { - [CascadingParameter] MudDialogInstance? MudDialog { get; set; } - [Parameter] public MLProcessor? Processor { get; set; } - [Parameter] public ModelDefinition? Model { get; set; } - [Parameter] public string Text { get; set; } = ""; +[CascadingParameter] MudDialogInstance? MudDialog { get; set; } +[Parameter] public MLProcessor? Processor { get; set; } +[Parameter] public ModelDefinition? Model { get; set; } +[Parameter] public string Text { get; set; } = ""; - private string _progressText = ""; - private bool _isTraining = true; - private MLEvaluationMetrics? _evaluationMetrics; +private string _progressText = ""; +private bool _isTraining = true; +private MLEvaluationMetrics? _evaluationMetrics; - void Submit() => MudDialog?.Close(DialogResult.Ok(true)); +void Submit() => MudDialog?.Close(DialogResult.Ok(true)); - protected override async Task OnAfterRenderAsync(bool firstRender) +protected override async Task OnAfterRenderAsync(bool firstRender) +{ + if (!firstRender || Processor==null || Model==null) + { + return; + } + + try { - if (!firstRender || Processor==null || Model==null) - { - return; - } _evaluationMetrics = await Processor.Train(Model, UpdateProgress); + } + catch (Exception e) + { + _progressText = "ERROR: " + e.Message; + } + finally + { _isTraining = false; await InvokeAsync(StateHasChanged); } +} - private async void UpdateProgress(string message) - { - _progressText = message; - await InvokeAsync(StateHasChanged); - } +private async void UpdateProgress(string message) +{ + _progressText = message; + await InvokeAsync(StateHasChanged); +} } \ No newline at end of file diff --git a/DeepTrace/Data/DataSourceDefinition.cs b/DeepTrace/Data/DataSourceDefinition.cs index 3f45a2e..22e4599 100644 --- a/DeepTrace/Data/DataSourceDefinition.cs +++ b/DeepTrace/Data/DataSourceDefinition.cs @@ -29,4 +29,55 @@ public class DataSourceDefinition public string Description { get; set; } = string.Empty; public override string ToString() => Name; + + public List GetColumnNames() + { + var measureNames = new[] { "min", "max", "avg", "mean" }; + var columnNames = new List(); + foreach (var item in Queries) + { + columnNames.AddRange(measureNames.Select(x => $"{item.Query}_{x}")); + } + return columnNames; + } + + public static string ConvertToCsv(List source) + { + var data = ""; + for (var i = 0; i < source.Count; i++) + { + + var queryData = source[i]; + var min = queryData.Data.Min(x => x.Value); + var max = queryData.Data.Max(x => x.Value); + var avg = queryData.Data.Average(x => x.Value); + var mean = queryData.Data.Sum(x => x.Value) / queryData.Data.Count; + + data += min + "," + max + "," + avg + "," + mean + ","; + + } + + return data.TrimEnd(','); + } + + public static float[] ToFeatures(List source) + { + var data = new float[source.Count * 4]; + for (var i = 0; i < source.Count; i++) + { + + var queryData = source[i]; + var min = queryData.Data.Min(x => x.Value); + var max = queryData.Data.Max(x => x.Value); + var avg = queryData.Data.Average(x => x.Value); + var mean = queryData.Data.Sum(x => x.Value) / queryData.Data.Count; + + data[i*4 + 0] = min; + data[i*4 + 1] = max; + data[i*4 + 2] = avg; + data[i*4 + 3] = mean; + } + + return data; + } } diff --git a/DeepTrace/Data/IntervalDefinition.cs b/DeepTrace/Data/IntervalDefinition.cs index d6194be..ef16873 100644 --- a/DeepTrace/Data/IntervalDefinition.cs +++ b/DeepTrace/Data/IntervalDefinition.cs @@ -1,22 +1,21 @@ -namespace DeepTrace.Data +namespace DeepTrace.Data; + +public class IntervalDefinition { - public class IntervalDefinition + public IntervalDefinition() { } + public IntervalDefinition(DateTime from, DateTime to, string name) { - public IntervalDefinition() { } - public IntervalDefinition(DateTime from, DateTime to, string name) - { - From = from; - To = to; - Name = name; - } - - public DateTime From { get; set; } = DateTime.MinValue; - - public DateTime To { get; set; } = DateTime.MaxValue; - - public string Name { get; set; } = string.Empty; - - public List Data { get; set; } = new(); - + From = from; + To = to; + Name = name; } + + public DateTime From { get; set; } = DateTime.MinValue; + + public DateTime To { get; set; } = DateTime.MaxValue; + + public string Name { get; set; } = string.Empty; + + public List Data { get; set; } = new(); + } diff --git a/DeepTrace/Data/MLModel1.consumption.cs b/DeepTrace/Data/MLModel1.consumption.cs index 0a11343..f367476 100644 --- a/DeepTrace/Data/MLModel1.consumption.cs +++ b/DeepTrace/Data/MLModel1.consumption.cs @@ -5,185 +5,188 @@ using System; using System.Linq; using System.IO; using System.Collections.Generic; -namespace DeepTrace +namespace DeepTrace; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +public partial class MLModel1 { - public partial class MLModel1 + /// + /// model input class for MLModel1. + /// + #region model input class + public class ModelInput { - /// - /// model input class for MLModel1. - /// - #region model input class - public class ModelInput - { - [ColumnName(@"Q1min")] - public string Q1min { get; set; } + [ColumnName(@"Q1min")] + public string Q1min { get; set; } - [ColumnName(@"Q1max")] - public string Q1max { get; set; } + [ColumnName(@"Q1max")] + public string Q1max { get; set; } - [ColumnName(@"Q1avg")] - public string Q1avg { get; set; } + [ColumnName(@"Q1avg")] + public string Q1avg { get; set; } - [ColumnName(@"Q1mean")] - public string Q1mean { get; set; } + [ColumnName(@"Q1mean")] + public string Q1mean { get; set; } - [ColumnName(@"Q2min")] - public string Q2min { get; set; } + [ColumnName(@"Q2min")] + public string Q2min { get; set; } - [ColumnName(@"Q2max")] - public string Q2max { get; set; } + [ColumnName(@"Q2max")] + public string Q2max { get; set; } - [ColumnName(@"Q2avg")] - public string Q2avg { get; set; } + [ColumnName(@"Q2avg")] + public string Q2avg { get; set; } - [ColumnName(@"Q2mean")] - public string Q2mean { get; set; } + [ColumnName(@"Q2mean")] + public string Q2mean { get; set; } - [ColumnName(@"Q3min")] - public string Q3min { get; set; } + [ColumnName(@"Q3min")] + public string Q3min { get; set; } - [ColumnName(@"Q3max")] - public string Q3max { get; set; } + [ColumnName(@"Q3max")] + public string Q3max { get; set; } - [ColumnName(@"Q3avg")] - public string Q3avg { get; set; } + [ColumnName(@"Q3avg")] + public string Q3avg { get; set; } - [ColumnName(@"Q3mean")] - public string Q3mean { get; set; } + [ColumnName(@"Q3mean")] + public string Q3mean { get; set; } - [ColumnName(@"Q4min")] - public string Q4min { get; set; } + [ColumnName(@"Q4min")] + public string Q4min { get; set; } - [ColumnName(@"Q4max")] - public string Q4max { get; set; } + [ColumnName(@"Q4max")] + public string Q4max { get; set; } - [ColumnName(@"Q4avg")] - public string Q4avg { get; set; } + [ColumnName(@"Q4avg")] + public string Q4avg { get; set; } - [ColumnName(@"Q4mean")] - public string Q4mean { get; set; } + [ColumnName(@"Q4mean")] + public string Q4mean { get; set; } - [ColumnName(@"Q5min")] - public string Q5min { get; set; } + [ColumnName(@"Q5min")] + public string Q5min { get; set; } - [ColumnName(@"Q5max")] - public string Q5max { get; set; } + [ColumnName(@"Q5max")] + public string Q5max { get; set; } - [ColumnName(@"Q5avg")] - public string Q5avg { get; set; } + [ColumnName(@"Q5avg")] + public string Q5avg { get; set; } - [ColumnName(@"Q5mean")] - public string Q5mean { get; set; } + [ColumnName(@"Q5mean")] + public string Q5mean { get; set; } - [ColumnName(@"Name")] - public string Name { get; set; } + [ColumnName(@"Name")] + public string Name { get; set; } - } + } - #endregion + #endregion - /// - /// model output class for MLModel1. - /// - #region model output class - public class ModelOutput - { - [ColumnName(@"Q1min")] - public string Q1min { get; set; } + /// + /// model output class for MLModel1. + /// + #region model output class + public class ModelOutput + { + [ColumnName(@"Q1min")] + public string Q1min { get; set; } - [ColumnName(@"Q1max")] - public float[] Q1max { get; set; } + [ColumnName(@"Q1max")] + public float[] Q1max { get; set; } - [ColumnName(@"Q1avg")] - public float[] Q1avg { get; set; } + [ColumnName(@"Q1avg")] + public float[] Q1avg { get; set; } - [ColumnName(@"Q1mean")] - public float[] Q1mean { get; set; } + [ColumnName(@"Q1mean")] + public float[] Q1mean { get; set; } - [ColumnName(@"Q2min")] - public float[] Q2min { get; set; } + [ColumnName(@"Q2min")] + public float[] Q2min { get; set; } - [ColumnName(@"Q2max")] - public float[] Q2max { get; set; } + [ColumnName(@"Q2max")] + public float[] Q2max { get; set; } - [ColumnName(@"Q2avg")] - public float[] Q2avg { get; set; } + [ColumnName(@"Q2avg")] + public float[] Q2avg { get; set; } - [ColumnName(@"Q2mean")] - public float[] Q2mean { get; set; } + [ColumnName(@"Q2mean")] + public float[] Q2mean { get; set; } - [ColumnName(@"Q3min")] - public float[] Q3min { get; set; } + [ColumnName(@"Q3min")] + public float[] Q3min { get; set; } - [ColumnName(@"Q3max")] - public float[] Q3max { get; set; } + [ColumnName(@"Q3max")] + public float[] Q3max { get; set; } - [ColumnName(@"Q3avg")] - public float[] Q3avg { get; set; } + [ColumnName(@"Q3avg")] + public float[] Q3avg { get; set; } - [ColumnName(@"Q3mean")] - public float[] Q3mean { get; set; } + [ColumnName(@"Q3mean")] + public float[] Q3mean { get; set; } - [ColumnName(@"Q4min")] - public string Q4min { get; set; } + [ColumnName(@"Q4min")] + public string Q4min { get; set; } - [ColumnName(@"Q4max")] - public float[] Q4max { get; set; } + [ColumnName(@"Q4max")] + public float[] Q4max { get; set; } - [ColumnName(@"Q4avg")] - public float[] Q4avg { get; set; } + [ColumnName(@"Q4avg")] + public float[] Q4avg { get; set; } - [ColumnName(@"Q4mean")] - public float[] Q4mean { get; set; } + [ColumnName(@"Q4mean")] + public float[] Q4mean { get; set; } - [ColumnName(@"Q5min")] - public float[] Q5min { get; set; } + [ColumnName(@"Q5min")] + public float[] Q5min { get; set; } - [ColumnName(@"Q5max")] - public float[] Q5max { get; set; } + [ColumnName(@"Q5max")] + public float[] Q5max { get; set; } - [ColumnName(@"Q5avg")] - public float[] Q5avg { get; set; } + [ColumnName(@"Q5avg")] + public float[] Q5avg { get; set; } - [ColumnName(@"Q5mean")] - public float[] Q5mean { get; set; } + [ColumnName(@"Q5mean")] + public float[] Q5mean { get; set; } - [ColumnName(@"Name")] - public uint Name { get; set; } + [ColumnName(@"Name")] + public uint Name { get; set; } - [ColumnName(@"Features")] - public float[] Features { get; set; } + [ColumnName(@"Features")] + public float[] Features { get; set; } - [ColumnName(@"PredictedLabel")] - public string PredictedLabel { get; set; } + [ColumnName(@"PredictedLabel")] + public string PredictedLabel { get; set; } - [ColumnName(@"Score")] - public float[] Score { get; set; } + [ColumnName(@"Score")] + public float[] Score { get; set; } - } + } - #endregion + #endregion - private static string MLNetModelPath = Path.GetFullPath("MLModel1.zip"); +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - public static readonly Lazy> PredictEngine = new Lazy>(() => CreatePredictEngine(), true); + private static string MLNetModelPath = Path.GetFullPath("MLModel1.zip"); - /// - /// Use this method to predict on . - /// - /// model input. - /// - public static ModelOutput Predict(ModelInput input) - { - var predEngine = PredictEngine.Value; - return predEngine.Predict(input); - } + public static readonly Lazy> PredictEngine = new Lazy>(() => CreatePredictEngine(), true); - private static PredictionEngine CreatePredictEngine() - { - var mlContext = new MLContext(); - ITransformer mlModel = mlContext.Model.Load(MLNetModelPath, out var _); - return mlContext.Model.CreatePredictionEngine(mlModel); - } + /// + /// Use this method to predict on . + /// + /// model input. + /// + public static ModelOutput Predict(ModelInput input) + { + var predEngine = PredictEngine.Value; + return predEngine.Predict(input); + } + + private static PredictionEngine CreatePredictEngine() + { + var mlContext = new MLContext(); + ITransformer mlModel = mlContext.Model.Load(MLNetModelPath, out var _); + return mlContext.Model.CreatePredictionEngine(mlModel); } } diff --git a/DeepTrace/Data/MLModel1.training.cs b/DeepTrace/Data/MLModel1.training.cs index 415036a..55e13c1 100644 --- a/DeepTrace/Data/MLModel1.training.cs +++ b/DeepTrace/Data/MLModel1.training.cs @@ -9,56 +9,55 @@ using Microsoft.ML.Trainers.FastTree; using Microsoft.ML.Trainers; using Microsoft.ML; -namespace DeepTrace +namespace DeepTrace; + +public partial class MLModel1 { - public partial class MLModel1 + /// + /// Retrains model using the pipeline generated as part of the training process. For more information on how to load data, see aka.ms/loaddata. + /// + /// + /// + /// + public static ITransformer RetrainPipeline(MLContext mlContext, IDataView trainData) { - /// - /// Retrains model using the pipeline generated as part of the training process. For more information on how to load data, see aka.ms/loaddata. - /// - /// - /// - /// - public static ITransformer RetrainPipeline(MLContext mlContext, IDataView trainData) - { - var pipeline = BuildPipeline(mlContext); - var model = pipeline.Fit(trainData); + var pipeline = BuildPipeline(mlContext); + var model = pipeline.Fit(trainData); - return model; - } + return model; + } - /// - /// build the pipeline that is used from model builder. Use this function to retrain model. - /// - /// - /// - public static IEstimator BuildPipeline(MLContext mlContext) - { - // Data process configuration with pipeline data transformations - var pipeline = mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q1max",outputColumnName:@"Q1max") - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q1avg",outputColumnName:@"Q1avg")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q1mean",outputColumnName:@"Q1mean")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q2min",outputColumnName:@"Q2min")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q2max",outputColumnName:@"Q2max")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q2avg",outputColumnName:@"Q2avg")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q2mean",outputColumnName:@"Q2mean")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q3min",outputColumnName:@"Q3min")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q3max",outputColumnName:@"Q3max")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q3avg",outputColumnName:@"Q3avg")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q3mean",outputColumnName:@"Q3mean")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q4max",outputColumnName:@"Q4max")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q4avg",outputColumnName:@"Q4avg")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q4mean",outputColumnName:@"Q4mean")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q5min",outputColumnName:@"Q5min")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q5max",outputColumnName:@"Q5max")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q5avg",outputColumnName:@"Q5avg")) - .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q5mean",outputColumnName:@"Q5mean")) - .Append(mlContext.Transforms.Concatenate(@"Features", new []{@"Q1max",@"Q1avg",@"Q1mean",@"Q2min",@"Q2max",@"Q2avg",@"Q2mean",@"Q3min",@"Q3max",@"Q3avg",@"Q3mean",@"Q4max",@"Q4avg",@"Q4mean",@"Q5min",@"Q5max",@"Q5avg",@"Q5mean"})) - .Append(mlContext.Transforms.Conversion.MapValueToKey(outputColumnName:@"Name",inputColumnName:@"Name")) - .Append(mlContext.MulticlassClassification.Trainers.OneVersusAll(binaryEstimator:mlContext.BinaryClassification.Trainers.FastTree(new FastTreeBinaryTrainer.Options(){NumberOfLeaves=33,MinimumExampleCountPerLeaf=14,NumberOfTrees=4,MaximumBinCountPerFeature=1022,FeatureFraction=0.99999999,LearningRate=0.757926844134433,LabelColumnName=@"Name",FeatureColumnName=@"Features"}),labelColumnName: @"Name")) - .Append(mlContext.Transforms.Conversion.MapKeyToValue(outputColumnName:@"PredictedLabel",inputColumnName:@"PredictedLabel")); + /// + /// build the pipeline that is used from model builder. Use this function to retrain model. + /// + /// + /// + public static IEstimator BuildPipeline(MLContext mlContext) + { + // Data process configuration with pipeline data transformations + var pipeline = mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q1max",outputColumnName:@"Q1max") + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q1avg",outputColumnName:@"Q1avg")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q1mean",outputColumnName:@"Q1mean")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q2min",outputColumnName:@"Q2min")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q2max",outputColumnName:@"Q2max")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q2avg",outputColumnName:@"Q2avg")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q2mean",outputColumnName:@"Q2mean")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q3min",outputColumnName:@"Q3min")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q3max",outputColumnName:@"Q3max")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q3avg",outputColumnName:@"Q3avg")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q3mean",outputColumnName:@"Q3mean")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q4max",outputColumnName:@"Q4max")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q4avg",outputColumnName:@"Q4avg")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q4mean",outputColumnName:@"Q4mean")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q5min",outputColumnName:@"Q5min")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q5max",outputColumnName:@"Q5max")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q5avg",outputColumnName:@"Q5avg")) + .Append(mlContext.Transforms.Text.FeaturizeText(inputColumnName:@"Q5mean",outputColumnName:@"Q5mean")) + .Append(mlContext.Transforms.Concatenate(@"Features", new []{@"Q1max",@"Q1avg",@"Q1mean",@"Q2min",@"Q2max",@"Q2avg",@"Q2mean",@"Q3min",@"Q3max",@"Q3avg",@"Q3mean",@"Q4max",@"Q4avg",@"Q4mean",@"Q5min",@"Q5max",@"Q5avg",@"Q5mean"})) + .Append(mlContext.Transforms.Conversion.MapValueToKey(outputColumnName:@"Name",inputColumnName:@"Name")) + .Append(mlContext.MulticlassClassification.Trainers.OneVersusAll(binaryEstimator:mlContext.BinaryClassification.Trainers.FastTree(new FastTreeBinaryTrainer.Options(){NumberOfLeaves=33,MinimumExampleCountPerLeaf=14,NumberOfTrees=4,MaximumBinCountPerFeature=1022,FeatureFraction=0.99999999,LearningRate=0.757926844134433,LabelColumnName=@"Name",FeatureColumnName=@"Features"}),labelColumnName: @"Name")) + .Append(mlContext.Transforms.Conversion.MapKeyToValue(outputColumnName:@"PredictedLabel",inputColumnName:@"PredictedLabel")); - return pipeline; - } + return pipeline; } } diff --git a/DeepTrace/Data/ModelDefinition.cs b/DeepTrace/Data/ModelDefinition.cs index dbab651..3f9ce68 100644 --- a/DeepTrace/Data/ModelDefinition.cs +++ b/DeepTrace/Data/ModelDefinition.cs @@ -2,6 +2,7 @@ using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson; using System.Text; +using DeepTrace.ML; namespace DeepTrace.Data; @@ -15,28 +16,21 @@ public class ModelDefinition } [BsonId] - public ObjectId? Id { get; set; } - public string Name { get; set; } - public DataSourceStorage DataSource { get; set; } = new(); - public string AIparameters { get; set; } = string.Empty; + public ObjectId? Id { get; set; } + public string Name { get; set; } + public DataSourceStorage DataSource { get; set; } = new(); + public string AIparameters { get; set; } = string.Empty; public List IntervalDefinitionList { get; set; } = new(); - public List GetColumnNames() - { - var measureNames = new[] { "min", "max", "avg", "mean" }; - var columnNames = new List(); - foreach (var item in DataSource.Queries) - { - columnNames.AddRange(measureNames.Select(x => $"{item.Query}_{x}")); - } - columnNames.Add("Name"); - return columnNames; - } + public List GetColumnNames() => DataSource.GetColumnNames() + .Concat(new[] { "Name" }) + .ToList() + ; public string ToCsv() { var current = IntervalDefinitionList.First(); - var headers = string.Join(",", GetColumnNames().Select(x=>$"\"{x}\"")); + var headers = string.Join(",", GetColumnNames().Select(x => $"\"{x}\"")); var writer = new StringBuilder(); @@ -45,30 +39,24 @@ public class ModelDefinition foreach (var currentInterval in IntervalDefinitionList) { var source = currentInterval.Data; - string data = ConvertToCsv(source); - data += "," + currentInterval.Name; + string data = DataSourceDefinition.ConvertToCsv(source); + data += $",\"{currentInterval.Name}\""; writer.AppendLine(data); } return writer.ToString(); } - public static string ConvertToCsv(List source) + public IEnumerable ToInput() { - var data = ""; - for (var i = 0; i < source.Count; i++) + foreach (var currentInterval in IntervalDefinitionList) { - - var queryData = source[i]; - var min = queryData.Data.Min(x => x.Value); - var max = queryData.Data.Max(x => x.Value); - var avg = queryData.Data.Average(x => x.Value); - var mean = queryData.Data.Sum(x => x.Value) / queryData.Data.Count; - - data += min + "," + max + "," + avg + "," + mean + ","; - + var source = currentInterval.Data; + yield return new MLInputData + { + Features = DataSourceDefinition.ToFeatures(source), + Label = currentInterval.Name + }; } - - return data+"\"ignoreMe\""; } } diff --git a/DeepTrace/Data/Prediction.cs b/DeepTrace/Data/Prediction.cs index 46857cf..e30be1f 100644 --- a/DeepTrace/Data/Prediction.cs +++ b/DeepTrace/Data/Prediction.cs @@ -4,9 +4,9 @@ namespace DeepTrace.Data; public class Prediction { - [ColumnName(@"PredictedLabel")] - public string PredictedLabel { get; set; } + [ColumnName("PredictedLabel")] + public string PredictedLabel { get; set; } = string.Empty; - [ColumnName(@"Score")] - public float[] Score { get; set; } + [ColumnName("Score")] + public float[] Score { get; set; } = Array.Empty(); } diff --git a/DeepTrace/Data/TrainedModelDefinition.cs b/DeepTrace/Data/TrainedModelDefinition.cs index 9585f16..b3fa2a1 100644 --- a/DeepTrace/Data/TrainedModelDefinition.cs +++ b/DeepTrace/Data/TrainedModelDefinition.cs @@ -1,14 +1,14 @@ using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson; -namespace DeepTrace.Data +namespace DeepTrace.Data; + +public class TrainedModelDefinition { - public class TrainedModelDefinition - { - [BsonId] - public ObjectId? Id { get; set; } - public bool IsEnabled { get; set; } = false; - public string Name { get; set; } = string.Empty; - public byte[] Value { get; set; } = Array.Empty(); //base64 - } + [BsonId] + public ObjectId? Id { get; set; } + public bool IsEnabled { get; set; } = false; + public string Name { get; set; } = string.Empty; + public DataSourceDefinition? DataSource{ get; set;} + public byte[] Value { get; set; } = Array.Empty(); //base64 } diff --git a/DeepTrace/ML/EstimatorBuilder.cs b/DeepTrace/ML/EstimatorBuilder.cs index 0a359c4..8075462 100644 --- a/DeepTrace/ML/EstimatorBuilder.cs +++ b/DeepTrace/ML/EstimatorBuilder.cs @@ -1,47 +1,31 @@ using DeepTrace.Data; using Microsoft.ML; +using Microsoft.ML.Data; using Microsoft.ML.Trainers; -namespace DeepTrace.ML +namespace DeepTrace.ML; + +public class EstimatorBuilder : IEstimatorBuilder { - public class EstimatorBuilder : IEstimatorBuilder + public IEstimator BuildPipeline(MLContext mlContext, ModelDefinition model) { - public IEstimator BuildPipeline(MLContext mlContext, ModelDefinition model) - { - IEstimator? pipeline = null; - var ds = model.DataSource; - - var measureNames = new[] { "min", "max", "avg", "mean" }; - var columnNames = new List(); - foreach (var item in ds.Queries) - { - var estimators = measureNames.Select(x => mlContext.Transforms.Text.FeaturizeText(inputColumnName: $"{item.Query}_{x}", outputColumnName: $"{item.Query}_{x}")); - columnNames.AddRange(measureNames.Select(x => $"{item.Query}_{x}")); - - foreach (var e in estimators) - { - if (pipeline == null) + return + mlContext.Transforms.NormalizeMinMax(inputColumnName: nameof(MLInputData.Features),outputColumnName: "Features") + .Append(mlContext.Transforms.Conversion.MapValueToKey(inputColumnName: nameof(MLInputData.Label), outputColumnName: "Label")) +// .AppendCacheCheckpoint(mlContext) + .Append(mlContext.MulticlassClassification.Trainers.OneVersusAll( + binaryEstimator: mlContext.BinaryClassification.Trainers.LbfgsLogisticRegression( + new LbfgsLogisticRegressionBinaryTrainer.Options { - pipeline = e; + L1Regularization = 1F, + L2Regularization = 1F, + LabelColumnName = "Label", + FeatureColumnName = "Features" } - else - { - pipeline = pipeline.Append(e); - } - } + )) + ) + .Append(mlContext.Transforms.Conversion.MapKeyToValue(nameof(MLOutputData.PredictedLabel), inputColumnName: "PredictedLabel")); - } - - pipeline = pipeline! - .Append(mlContext.Transforms.Concatenate(@"Features", columnNames.ToArray())) - .Append(mlContext.Transforms.Conversion.MapValueToKey(outputColumnName: @"Name", inputColumnName: @"Name")) - .Append(mlContext.Transforms.NormalizeMinMax(@"Features", @"Features")) - .Append(mlContext.MulticlassClassification.Trainers.OneVersusAll(binaryEstimator: mlContext.BinaryClassification.Trainers.LbfgsLogisticRegression(new LbfgsLogisticRegressionBinaryTrainer.Options() { L1Regularization = 1F, L2Regularization = 1F, LabelColumnName = @"Name", FeatureColumnName = @"Features" }), labelColumnName: @"Name")) - .Append(mlContext.Transforms.Conversion.MapKeyToValue(outputColumnName: @"PredictedLabel", inputColumnName: @"PredictedLabel")); - - return pipeline; - - } } } diff --git a/DeepTrace/ML/IEstimatorBuilder.cs b/DeepTrace/ML/IEstimatorBuilder.cs index 5e5c960..2bd0186 100644 --- a/DeepTrace/ML/IEstimatorBuilder.cs +++ b/DeepTrace/ML/IEstimatorBuilder.cs @@ -1,10 +1,9 @@ using DeepTrace.Data; using Microsoft.ML; -namespace DeepTrace.ML +namespace DeepTrace.ML; + +public interface IEstimatorBuilder { - public interface IEstimatorBuilder - { - IEstimator BuildPipeline(MLContext mlContext, ModelDefinition model); - } + IEstimator BuildPipeline(MLContext mlContext, ModelDefinition model); } \ No newline at end of file diff --git a/DeepTrace/ML/IMLProcessor.cs b/DeepTrace/ML/IMLProcessor.cs index 6e78127..6e104a3 100644 --- a/DeepTrace/ML/IMLProcessor.cs +++ b/DeepTrace/ML/IMLProcessor.cs @@ -10,3 +10,8 @@ public interface IMLProcessor void Import(byte[] data); Task Predict(TrainedModelDefinition trainedModel, ModelDefinition model, List data); } + +public interface IMLProcessorFactory +{ + IMLProcessor Create(); +} diff --git a/DeepTrace/ML/IMeasure.cs b/DeepTrace/ML/IMeasure.cs index ce497d9..06a15e2 100644 --- a/DeepTrace/ML/IMeasure.cs +++ b/DeepTrace/ML/IMeasure.cs @@ -1,11 +1,10 @@ using PrometheusAPI; -namespace DeepTrace.ML +namespace DeepTrace.ML; + +public interface IMeasure { - public interface IMeasure - { - public string Name { get; } - void Reset(); - float Calculate(IEnumerable data); - } + public string Name { get; } + void Reset(); + float Calculate(IEnumerable data); } diff --git a/DeepTrace/ML/MLEvaluationMetrics.cs b/DeepTrace/ML/MLEvaluationMetrics.cs index 968baf8..2f72bb7 100644 --- a/DeepTrace/ML/MLEvaluationMetrics.cs +++ b/DeepTrace/ML/MLEvaluationMetrics.cs @@ -1,16 +1,15 @@ -namespace DeepTrace.ML +namespace DeepTrace.ML; + +public class MLEvaluationMetrics { - public class MLEvaluationMetrics + public MLEvaluationMetrics() { - public MLEvaluationMetrics() - { - - } - - public double MicroAccuracy { get; set; } - public double MacroAccuracy { get; set; } - public double LogLoss { get; set; } - public double LogLossReduction { get; set; } } + + public double MicroAccuracy { get; set; } + public double MacroAccuracy { get; set; } + public double LogLoss { get; set; } + public double LogLossReduction { get; set; } + } diff --git a/DeepTrace/ML/MLHelpers.cs b/DeepTrace/ML/MLHelpers.cs index bc6a15a..c378b91 100644 --- a/DeepTrace/ML/MLHelpers.cs +++ b/DeepTrace/ML/MLHelpers.cs @@ -7,6 +7,21 @@ namespace DeepTrace.ML; public record ModelRecord(MLContext Context, DataViewSchema Schema, ITransformer Transformer); +public class MLInputData +{ + public string Label { get; set; } = "Normal operation"; + public float[] Features { get; set; } = Array.Empty(); + +} + +public class MLOutputData +{ + public string PredictedLabel { get; set; } = string.Empty; + + public float[] Score { get; set; } = Array.Empty(); +} + + public static class MLHelpers { public static byte[] ExportSingleModel( ModelRecord model) @@ -32,10 +47,22 @@ public static class MLHelpers await File.WriteAllTextAsync(fileName, csv); - return LoadFromCsv(mlContext, model, fileName); + return (LoadFromCsv(mlContext, model, fileName), fileName); } - public static (IDataView View, string FileName) LoadFromCsv(MLContext mlContext, ModelDefinition model, string fileName) + public static Task ToInput(MLContext mlContext, ModelDefinition model) + { + var input = model.ToInput().ToList(); + + // VectorType attribute with dynamic dimension + // https://github.com/dotnet/machinelearning/issues/164 + var schemaDef = SchemaDefinition.Create(typeof(MLInputData)); + schemaDef["Features"].ColumnType = new VectorDataViewType(NumberDataViewType.Single, input.First().Features.Length ); + + return Task.FromResult(mlContext.Data.LoadFromEnumerable(input, schemaDef)); + } + + public static IDataView LoadFromCsv(MLContext mlContext, ModelDefinition model, string fileName) { var columnNames = model.GetColumnNames(); var columns = columnNames @@ -43,8 +70,14 @@ public static class MLHelpers .ToArray() ; - var view = mlContext.Data.LoadFromTextFile(fileName, columns, separatorChar: ',', hasHeader: true, allowQuoting: true, trimWhitespace: true); + var view = mlContext.Data.LoadFromTextFile( + fileName, + columns, + separatorChar: ',', + hasHeader: true, + allowQuoting: true, + trimWhitespace: true); - return (view, fileName); + return view; } } diff --git a/DeepTrace/ML/MLProcessor.cs b/DeepTrace/ML/MLProcessor.cs index b514298..b8a1d20 100644 --- a/DeepTrace/ML/MLProcessor.cs +++ b/DeepTrace/ML/MLProcessor.cs @@ -1,137 +1,155 @@ using DeepTrace.Data; using Microsoft.ML; -using Microsoft.ML.Data; -using PrometheusAPI; using System.Data; -using static DeepTrace.MLModel1; -namespace DeepTrace.ML +namespace DeepTrace.ML; + +internal class MLProcessorFactory : IMLProcessorFactory { - public class MLProcessor : IMLProcessor + private readonly ILogger _logger; + private IEstimatorBuilder _estimatorBuilder; + + public MLProcessorFactory(ILogger logger, IEstimatorBuilder estimatorBuilder) { - private MLContext _mlContext = new MLContext(); - private EstimatorBuilder _estimatorBuilder = new EstimatorBuilder(); - private DataViewSchema? _schema; - private ITransformer? _transformer; - private static string _signature = "DeepTrace-Model-v1-" + typeof(MLProcessor).Name; - private readonly ILogger _logger; + _logger = logger; + _estimatorBuilder = estimatorBuilder; + } - public MLProcessor(ILogger logger) + public IMLProcessor Create() => new MLProcessor(_logger, _estimatorBuilder); +} + +/// +/// Wrapper for ML.NET operations. +/// +public class MLProcessor : IMLProcessor +{ + private readonly ILogger _logger; + private MLContext _mlContext = new MLContext(); + private IEstimatorBuilder _estimatorBuilder; + private DataViewSchema? _schema; + private ITransformer? _transformer; + private static string _signature = "DeepTrace-Model-v1-" + typeof(MLProcessor).Name; + private PredictionEngine? _predictionEngine; + + public MLProcessor(ILogger logger, IEstimatorBuilder estimatorBuilder) + { + _logger = logger; + _estimatorBuilder = estimatorBuilder; + } + + private string Name { get; set; } = "TestModel"; + + public async Task Train(ModelDefinition modelDef, Action log) + { + _logger.LogInformation("Training started"); + + Name = modelDef.Name; + var pipeline = _estimatorBuilder.BuildPipeline(_mlContext, modelDef); + var data = await MLHelpers.ToInput(_mlContext, modelDef); + + DataOperationsCatalog.TrainTestData dataSplit = _mlContext.Data.TrainTestSplit(data, testFraction: 0.2); + + _mlContext.Log += (_,e) => LogEvents(log, e); + try { - _logger = logger; - } + _schema = data.Schema; + _transformer = pipeline.Fit(dataSplit.TrainSet); - private string Name { get; set; } = "TestModel"; - - public async Task Train(ModelDefinition modelDef, Action log) + return Evaluate(dataSplit.TestSet); + } + finally { - var pipeline = _estimatorBuilder.BuildPipeline(_mlContext, modelDef); - var (data, filename) = await MLHelpers.Convert(_mlContext, modelDef); - - DataOperationsCatalog.TrainTestData dataSplit = _mlContext.Data.TrainTestSplit(data, testFraction: 0.2); - - _mlContext.Log += (_,e) => LogEvents(log, e); - try - { - _schema = data.Schema; - _transformer = pipeline.Fit(dataSplit.TrainSet); - return Evaluate(dataSplit.TestSet); - } - finally - { - File.Delete(filename); - } - - } - - private void LogEvents(Action log, LoggingEventArgs e) - { - if(e.Kind.ToString() != "Trace") - { - _logger.LogDebug(e.Message); - log(e.Message); - } - - } - - private MLEvaluationMetrics Evaluate(IDataView testData) - { - var predictions = _transformer!.Transform(testData); - var metrics = _mlContext.MulticlassClassification.Evaluate(predictions, "Name"); - var evaluationMetrics = new MLEvaluationMetrics() - { - MicroAccuracy = metrics.MicroAccuracy, - MacroAccuracy = metrics.MacroAccuracy, - LogLoss = metrics.LogLoss, - LogLossReduction = metrics.LogLossReduction, - }; - return evaluationMetrics; - } - - public byte[] Export() - { - if(_schema == null) - { - throw new ArgumentNullException(nameof (_schema)); - } - - if (_transformer == null) - { - throw new ArgumentNullException(nameof(_transformer)); - } - - using var mem = new MemoryStream(); - mem.WriteString(_signature); - - mem.WriteString(Name); - - var bytes = MLHelpers.ExportSingleModel(new ModelRecord(_mlContext, _schema, _transformer)); - - mem.WriteInt(bytes.Length); - mem.Write(bytes); - - - return mem.ToArray(); - } - - public void Import(byte[] data) - { - var mem = new MemoryStream(data); - var sig = mem.ReadString(); - if (sig != _signature) - throw new ApplicationException($"Wrong data for {GetType().Name}"); - - Name = mem.ReadString(); - var size = mem.ReadInt(); - var bytes = new byte[size]; - - mem.Read(bytes, 0, bytes.Length); - - (_mlContext, _schema, _transformer) = MLHelpers.ImportSingleModel(bytes); - } - - public async Task Predict(TrainedModelDefinition trainedModel, ModelDefinition model, List data) - { - Import(trainedModel.Value); - var headers = string.Join(",", model.GetColumnNames().Select(x => $"\"{x}\"")); - var row = ModelDefinition.ConvertToCsv(data); - - var csv = headers+"\n"+row; - var fileName = Path.GetTempFileName(); - try - { - await File.WriteAllTextAsync(fileName, csv); - - var (dataView, _) = MLHelpers.LoadFromCsv(_mlContext, model, fileName); - - var predictionEngine = _mlContext.Model.CreatePredictionEngine(_transformer); - var prediction = predictionEngine.Predict(dataView); - return prediction; - } - finally - { - File.Delete(fileName); - } + _logger.LogInformation("Training finished"); } } + + private void LogEvents(Action log, LoggingEventArgs e) + { + if(e.Kind.ToString() != "Trace") + { + _logger.LogDebug(e.Message); + log(e.Message); + } + + } + + private MLEvaluationMetrics Evaluate(IDataView testData) + { + // https://learn.microsoft.com/en-us/dotnet/api/microsoft.ml.standardtrainerscatalog.lbfgslogisticregression?view=ml-dotnet + + var predictions = _transformer!.Transform(testData); + var metrics = _mlContext.MulticlassClassification.Evaluate(predictions, nameof(MLInputData.Label)); + var evaluationMetrics = new MLEvaluationMetrics() + { + MicroAccuracy = metrics.MicroAccuracy, + MacroAccuracy = metrics.MacroAccuracy, + LogLoss = metrics.LogLoss, + LogLossReduction = metrics.LogLossReduction, + }; + return evaluationMetrics; + } + + public byte[] Export() + { + if(_schema == null) + { + throw new ArgumentNullException(nameof (_schema)); + } + + if (_transformer == null) + { + throw new ArgumentNullException(nameof(_transformer)); + } + + using var mem = new MemoryStream(); + mem.WriteString(_signature); + + mem.WriteString(Name); + + var bytes = MLHelpers.ExportSingleModel(new ModelRecord(_mlContext, _schema, _transformer)); + + mem.WriteInt(bytes.Length); + mem.Write(bytes); + + + return mem.ToArray(); + } + + public void Import(byte[] data) + { + var mem = new MemoryStream(data); + var sig = mem.ReadString(); + if (sig != _signature) + throw new ApplicationException($"Wrong data for {GetType().Name}"); + + Name = mem.ReadString(); + var size = mem.ReadInt(); + var bytes = new byte[size]; + + mem.Read(bytes, 0, bytes.Length); + + (_mlContext, _schema, _transformer) = MLHelpers.ImportSingleModel(bytes); + } + + public Task Predict(TrainedModelDefinition trainedModel, ModelDefinition model, List data) + { + Name = trainedModel.Name; + + if (_transformer == null ) + Import(trainedModel.Value); + + if (_predictionEngine == null) + { + _predictionEngine = _mlContext.Model.CreatePredictionEngine(_transformer, _schema); + } + + var input = new MLInputData + { + Features = DataSourceDefinition.ToFeatures(data) + }; + + var prediction = _predictionEngine.Predict( input ); + + return Task.FromResult( new Prediction { PredictedLabel = prediction.PredictedLabel, Score = prediction.Score } ); + } } diff --git a/DeepTrace/ML/Measures.cs b/DeepTrace/ML/Measures.cs index 61d548f..ff59f32 100644 --- a/DeepTrace/ML/Measures.cs +++ b/DeepTrace/ML/Measures.cs @@ -1,89 +1,87 @@ using PrometheusAPI; -namespace DeepTrace.ML +namespace DeepTrace.ML; + +public class MeasureMin : IMeasure { - public class MeasureMin : IMeasure - { - public string Name => "Min"; - public float Calculate(IEnumerable data) => - data - .Where(x => x.Value != 0.0f) - .Min( x => x.Value ) - ; + public string Name => "Min"; + public float Calculate(IEnumerable data) => + data + .Where(x => x.Value != 0.0f) + .Min( x => x.Value ) + ; - public void Reset() { } - } + public void Reset() { } +} - public class MeasureMax : IMeasure - { - public string Name => "Max"; - public float Calculate(IEnumerable data) => data.Max(x => x.Value); - public void Reset() { } - } +public class MeasureMax : IMeasure +{ + public string Name => "Max"; + public float Calculate(IEnumerable data) => data.Max(x => x.Value); + public void Reset() { } +} - public class MeasureAvg : IMeasure - { - public string Name => "Avg"; - public float Calculate(IEnumerable data) => data.Average(x => x.Value); - public void Reset() { } - } +public class MeasureAvg : IMeasure +{ + public string Name => "Avg"; + public float Calculate(IEnumerable data) => data.Average(x => x.Value); + public void Reset() { } +} - /// - /// WARNING: Only works with fixed length interval - /// - public class MeasureSum : IMeasure - { - public string Name => "Sum"; - public float Calculate(IEnumerable data) => data.Sum(x => x.Value); - public void Reset() { } - } +/// +/// WARNING: Only works with fixed length interval +/// +public class MeasureSum : IMeasure +{ + public string Name => "Sum"; + public float Calculate(IEnumerable data) => data.Sum(x => x.Value); + public void Reset() { } +} - public class MeasureMedian : IMeasure - { - public string Name => "Median"; +public class MeasureMedian : IMeasure +{ + public string Name => "Median"; - public float Calculate(IEnumerable data) - => MedianHelper.Median(data, x => x.Value); + public float Calculate(IEnumerable data) + => MedianHelper.Median(data, x => x.Value); - public void Reset() { } - - } - - public class MeasureDiff : IMeasure where T : IMeasure, new() - { - private T _measure = new(); - public string Name => "Diff_"+_measure.Name; - - private float _prev = float.NaN; - - public float Calculate(IEnumerable data) - { - var val = _measure.Calculate(data); - if (float.IsNaN(_prev)) - { - _prev = val; - return 0.0f; - } - - val = val - _prev; - _prev = val; - return val; - } - - public void Reset() - { - _measure.Reset(); - _prev = float.NaN; - } - } - - public class MeasureDiffMin : MeasureDiff { } - public class MeasureDiffMax : MeasureDiff { } - public class MeasureDiffAvg : MeasureDiff { } - /// - /// WARNING: Only works with fixed length interval - /// - public class MeasureDiffSum : MeasureDiff { } - public class MeasureDiffMedian : MeasureDiff { } + public void Reset() { } } + +public class MeasureDiff : IMeasure where T : IMeasure, new() +{ + private T _measure = new(); + public string Name => "Diff_"+_measure.Name; + + private float _prev = float.NaN; + + public float Calculate(IEnumerable data) + { + var val = _measure.Calculate(data); + if (float.IsNaN(_prev)) + { + _prev = val; + return 0.0f; + } + + val = val - _prev; + _prev = val; + return val; + } + + public void Reset() + { + _measure.Reset(); + _prev = float.NaN; + } +} + +public class MeasureDiffMin : MeasureDiff { } +public class MeasureDiffMax : MeasureDiff { } +public class MeasureDiffAvg : MeasureDiff { } +/// +/// WARNING: Only works with fixed length interval +/// +public class MeasureDiffSum : MeasureDiff { } +public class MeasureDiffMedian : MeasureDiff { } diff --git a/DeepTrace/Pages/DataSources.razor b/DeepTrace/Pages/DataSources.razor index d5c2db8..ca8a012 100644 --- a/DeepTrace/Pages/DataSources.razor +++ b/DeepTrace/Pages/DataSources.razor @@ -58,8 +58,8 @@ int pos = i; - @**@ - + + @**@ diff --git a/DeepTrace/Pages/Error.cshtml b/DeepTrace/Pages/Error.cshtml index 68e9c57..555162b 100644 --- a/DeepTrace/Pages/Error.cshtml +++ b/DeepTrace/Pages/Error.cshtml @@ -19,11 +19,11 @@

An error occurred while processing your request.

@if (Model.ShowRequestId) - { -

+ { +

Request ID: @Model.RequestId

- } + }

Development Mode

diff --git a/DeepTrace/Pages/Error.cshtml.cs b/DeepTrace/Pages/Error.cshtml.cs index e42f5ce..281b681 100644 --- a/DeepTrace/Pages/Error.cshtml.cs +++ b/DeepTrace/Pages/Error.cshtml.cs @@ -2,26 +2,25 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using System.Diagnostics; -namespace DeepTrace.Pages +namespace DeepTrace.Pages; + +[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] +[IgnoreAntiforgeryToken] +public class ErrorModel : PageModel { - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - [IgnoreAntiforgeryToken] - public class ErrorModel : PageModel + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + private readonly ILogger _logger; + + public ErrorModel(ILogger logger) { - public string? RequestId { get; set; } + _logger = logger; + } - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - - private readonly ILogger _logger; - - public ErrorModel(ILogger logger) - { - _logger = logger; - } - - public void OnGet() - { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; } } \ No newline at end of file diff --git a/DeepTrace/Pages/Index.razor b/DeepTrace/Pages/Index.razor index 318bc1f..942d6c9 100644 --- a/DeepTrace/Pages/Index.razor +++ b/DeepTrace/Pages/Index.razor @@ -14,9 +14,9 @@ Welcome to your new app. @if (_trainedModels != null) { @foreach(TrainedModelDefinition model in _trainedModels) - { - - } +{ + +} } else { Nothing to display @@ -24,13 +24,13 @@ Welcome to your new app. @code{ - private List _trainedModels = new(); +private List _trainedModels = new(); - protected override async Task OnInitializedAsync() - { - base.OnInitialized(); - _trainedModels = await TrainedModelService.Load(); +protected override async Task OnInitializedAsync() +{ + base.OnInitialized(); + _trainedModels = await TrainedModelService.Load(); - } +} } \ No newline at end of file diff --git a/DeepTrace/Pages/Training.razor b/DeepTrace/Pages/Training.razor index f3377a7..3c3166c 100644 --- a/DeepTrace/Pages/Training.razor +++ b/DeepTrace/Pages/Training.razor @@ -18,7 +18,7 @@ @inject IEstimatorBuilder EstimatorBuilder @inject NavigationManager NavManager @inject IJSRuntime Js -@inject ILogger MLProcessorLogger +@inject IMLProcessorFactory MlProcessorFactory Training @@ -531,14 +531,14 @@ private async Task HandleTrain() { - var mlProcessor = new MLProcessor(MLProcessorLogger); - MLProcessorLogger.LogInformation("Training started"); - var options = new DialogOptions { CloseOnEscapeKey = true }; var parameters = new DialogParameters(); + + var mlProcessor = MlProcessorFactory.Create(); + parameters.Add(nameof(Controls.TrainingDialog.Text), _modelForm!.CurrentModel.Name); parameters.Add(nameof(Controls.TrainingDialog.Processor), mlProcessor); parameters.Add(nameof(Controls.TrainingDialog.Model), _modelForm.CurrentModel); @@ -546,7 +546,6 @@ var d = DialogService.Show("Training", parameters, options); var res = await d.Result; - MLProcessorLogger.LogInformation("Training finished"); var bytes = mlProcessor.Export(); //save to Mongo diff --git a/DeepTrace/Program.cs b/DeepTrace/Program.cs index ab23244..462836e 100644 --- a/DeepTrace/Program.cs +++ b/DeepTrace/Program.cs @@ -29,6 +29,7 @@ builder.Services .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() ; var app = builder.Build(); diff --git a/DeepTrace/Services/DataSourceStorageService.cs b/DeepTrace/Services/DataSourceStorageService.cs index e565807..8546aec 100644 --- a/DeepTrace/Services/DataSourceStorageService.cs +++ b/DeepTrace/Services/DataSourceStorageService.cs @@ -1,58 +1,57 @@ using MongoDB.Bson; using MongoDB.Driver; -namespace DeepTrace.Services +namespace DeepTrace.Services; + +public class DataSourceStorageService : IDataSourceStorageService { - public class DataSourceStorageService : IDataSourceStorageService + + private const string MongoDBDatabaseName = "DeepTrace"; + private const string MongoDBCollection = "Sources"; + + private readonly IMongoClient _client; + + public DataSourceStorageService(IMongoClient client) { + _client = client; + } - private const string MongoDBDatabaseName = "DeepTrace"; - private const string MongoDBCollection = "Sources"; + public async Task> Load() + { + var db = _client.GetDatabase(MongoDBDatabaseName); + var collection = db.GetCollection(MongoDBCollection); - private readonly IMongoClient _client; + var res = await (await collection.FindAsync("{}")).ToListAsync(); + return res; + } + public async Task Store(DataSourceStorage source) + { + var db = _client.GetDatabase(MongoDBDatabaseName); + var collection = db.GetCollection(MongoDBCollection); - public DataSourceStorageService(IMongoClient client) + if ( source.Id == null ) + source.Id = ObjectId.GenerateNewId(); + + // use upsert (insert or update) to automatically handle subsequent updates + await collection.ReplaceOneAsync( + filter: new BsonDocument("_id", source.Id), + options: new ReplaceOptions { IsUpsert = true }, + replacement: source + ); + } + + public async Task Delete(DataSourceStorage source, bool ignoreNotStored = false) + { + if ( source.Id == null ) { - _client = client; + if (!ignoreNotStored) + throw new InvalidDataException("Source was not stored yet. There is nothing to delete"); + return; } - public async Task> Load() - { - var db = _client.GetDatabase(MongoDBDatabaseName); - var collection = db.GetCollection(MongoDBCollection); + var db = _client.GetDatabase(MongoDBDatabaseName); + var collection = db.GetCollection(MongoDBCollection); - var res = await (await collection.FindAsync("{}")).ToListAsync(); - return res; - } - public async Task Store(DataSourceStorage source) - { - var db = _client.GetDatabase(MongoDBDatabaseName); - var collection = db.GetCollection(MongoDBCollection); - - if ( source.Id == null ) - source.Id = ObjectId.GenerateNewId(); - - // use upsert (insert or update) to automatically handle subsequent updates - await collection.ReplaceOneAsync( - filter: new BsonDocument("_id", source.Id), - options: new ReplaceOptions { IsUpsert = true }, - replacement: source - ); - } - - public async Task Delete(DataSourceStorage source, bool ignoreNotStored = false) - { - if ( source.Id == null ) - { - if (!ignoreNotStored) - throw new InvalidDataException("Source was not stored yet. There is nothing to delete"); - return; - } - - var db = _client.GetDatabase(MongoDBDatabaseName); - var collection = db.GetCollection(MongoDBCollection); - - await collection.DeleteOneAsync($"_id = {source.Id}"); - } + await collection.DeleteOneAsync($"_id = {source.Id}"); } } diff --git a/DeepTrace/Services/IDataSourceStorageService.cs b/DeepTrace/Services/IDataSourceStorageService.cs index dd61bf3..6f67b89 100644 --- a/DeepTrace/Services/IDataSourceStorageService.cs +++ b/DeepTrace/Services/IDataSourceStorageService.cs @@ -2,37 +2,36 @@ using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson; -namespace DeepTrace.Services +namespace DeepTrace.Services; + +public class DataSourceStorage : DataSourceDefinition, IEquatable { - public class DataSourceStorage : DataSourceDefinition, IEquatable + [BsonId] + public ObjectId? Id { get; set; } + + public override bool Equals(object? obj) { - [BsonId] - public ObjectId? Id { get; set; } - - public override bool Equals(object? obj) + if ( obj is DataSourceStorage other ) { - if ( obj is DataSourceStorage other ) - { - return Id == other.Id; - } - return false; - } - - public bool Equals(DataSourceStorage? other) - { - return Id == other?.Id; - } - - public override int GetHashCode() - { - return Id?.GetHashCode() ?? base.GetHashCode(); + return Id == other.Id; } + return false; } - public interface IDataSourceStorageService + public bool Equals(DataSourceStorage? other) { - Task Delete(DataSourceStorage source, bool ignoreNotStored = false); - Task> Load(); - Task Store(DataSourceStorage source); + return Id == other?.Id; } + + public override int GetHashCode() + { + return Id?.GetHashCode() ?? base.GetHashCode(); + } +} + +public interface IDataSourceStorageService +{ + Task Delete(DataSourceStorage source, bool ignoreNotStored = false); + Task> Load(); + Task Store(DataSourceStorage source); } \ No newline at end of file diff --git a/DeepTrace/Services/IModelStorageService.cs b/DeepTrace/Services/IModelStorageService.cs index ad40edc..1edf735 100644 --- a/DeepTrace/Services/IModelStorageService.cs +++ b/DeepTrace/Services/IModelStorageService.cs @@ -3,14 +3,13 @@ using MongoDB.Bson; using DeepTrace.Data; using System.Text; -namespace DeepTrace.Services -{ +namespace DeepTrace.Services; - public interface IModelStorageService - { - Task Delete(ModelDefinition source, bool ignoreNotStored = false); - Task> Load(); - Task Load(BsonObjectId id); - Task Store(ModelDefinition source); - } + +public interface IModelStorageService +{ + Task Delete(ModelDefinition source, bool ignoreNotStored = false); + Task> Load(); + Task Load(BsonObjectId id); + Task Store(ModelDefinition source); } diff --git a/DeepTrace/Services/ITrainedModelStorageService.cs b/DeepTrace/Services/ITrainedModelStorageService.cs index a719418..64aa7b9 100644 --- a/DeepTrace/Services/ITrainedModelStorageService.cs +++ b/DeepTrace/Services/ITrainedModelStorageService.cs @@ -1,11 +1,10 @@ using DeepTrace.Data; -namespace DeepTrace.Services +namespace DeepTrace.Services; + +public interface ITrainedModelStorageService { - public interface ITrainedModelStorageService - { - Task Delete(TrainedModelDefinition source, bool ignoreNotStored = false); - Task> Load(); - Task Store(TrainedModelDefinition source); - } + Task Delete(TrainedModelDefinition source, bool ignoreNotStored = false); + Task> Load(); + Task Store(TrainedModelDefinition source); } diff --git a/DeepTrace/Services/ModelStorageService.cs b/DeepTrace/Services/ModelStorageService.cs index 6e10239..4653a74 100644 --- a/DeepTrace/Services/ModelStorageService.cs +++ b/DeepTrace/Services/ModelStorageService.cs @@ -2,67 +2,67 @@ using MongoDB.Bson; using MongoDB.Driver; -namespace DeepTrace.Services +namespace DeepTrace.Services; + +public class ModelStorageService : IModelStorageService { - public class ModelStorageService : IModelStorageService + + private const string MongoDBDatabaseName = "DeepTrace"; + private const string MongoDBCollection = "Models"; + + private readonly IMongoClient _client; + + public ModelStorageService(IMongoClient client) { + _client = client; + } - private const string MongoDBDatabaseName = "DeepTrace"; - private const string MongoDBCollection = "Models"; + public async Task> Load() + { + var db = _client.GetDatabase(MongoDBDatabaseName); + var collection = db.GetCollection(MongoDBCollection); - private readonly IMongoClient _client; + var res = await (await collection.FindAsync("{}")).ToListAsync(); + return res; + } - public ModelStorageService(IMongoClient client) + public async Task Load(BsonObjectId id) + { + var db = _client.GetDatabase(MongoDBDatabaseName); + var collection = db.GetCollection(MongoDBCollection); + + var res = await (await collection.FindAsync($"{{ _id : ObjectId(\"{id}\") }}")).ToListAsync(); + return res.FirstOrDefault(); + } + + public async Task Store(ModelDefinition source) + { + var db = _client.GetDatabase(MongoDBDatabaseName); + var collection = db.GetCollection(MongoDBCollection); + + if (source.Id == null) + source.Id = ObjectId.GenerateNewId(); + + // use upsert (insert or update) to automatically handle subsequent updates + await collection.ReplaceOneAsync( + filter: new BsonDocument("_id", source.Id), + options: new ReplaceOptions { IsUpsert = true }, + replacement: source + ); + } + + public async Task Delete(ModelDefinition source, bool ignoreNotStored = false) + { + if (source.Id == null) { - _client = client; + if (!ignoreNotStored) + throw new InvalidDataException("Source was not stored yet. There is nothing to delete"); + return; } - public async Task> Load() - { - var db = _client.GetDatabase(MongoDBDatabaseName); - var collection = db.GetCollection(MongoDBCollection); + var db = _client.GetDatabase(MongoDBDatabaseName); + var collection = db.GetCollection(MongoDBCollection); - var res = await (await collection.FindAsync("{}")).ToListAsync(); - return res; - } - - public async Task Load(BsonObjectId id) - { - var db = _client.GetDatabase(MongoDBDatabaseName); - var collection = db.GetCollection(MongoDBCollection); - var res = (await (await collection.FindAsync($"{{_id:ObjectId(\"{id}\")}}")).ToListAsync()).FirstOrDefault(); - return res; - } - - public async Task Store(ModelDefinition source) - { - var db = _client.GetDatabase(MongoDBDatabaseName); - var collection = db.GetCollection(MongoDBCollection); - - if (source.Id == null) - source.Id = ObjectId.GenerateNewId(); - - // use upsert (insert or update) to automatically handle subsequent updates - await collection.ReplaceOneAsync( - filter: new BsonDocument("_id", source.Id), - options: new ReplaceOptions { IsUpsert = true }, - replacement: source - ); - } - - public async Task Delete(ModelDefinition source, bool ignoreNotStored = false) - { - if (source.Id == null) - { - if (!ignoreNotStored) - throw new InvalidDataException("Source was not stored yet. There is nothing to delete"); - return; - } - - var db = _client.GetDatabase(MongoDBDatabaseName); - var collection = db.GetCollection(MongoDBCollection); - - await collection.DeleteOneAsync(filter: new BsonDocument("_id", source.Id)); - } + await collection.DeleteOneAsync(filter: new BsonDocument("_id", source.Id)); } } diff --git a/DeepTrace/Services/TrainedModelStorageService.cs b/DeepTrace/Services/TrainedModelStorageService.cs index 37ece57..7285796 100644 --- a/DeepTrace/Services/TrainedModelStorageService.cs +++ b/DeepTrace/Services/TrainedModelStorageService.cs @@ -2,57 +2,56 @@ using MongoDB.Bson; using MongoDB.Driver; -namespace DeepTrace.Services +namespace DeepTrace.Services; + +public class TrainedModelStorageService: ITrainedModelStorageService { - public class TrainedModelStorageService: ITrainedModelStorageService + private const string MongoDBDatabaseName = "DeepTrace"; + private const string MongoDBCollection = "TrainedModels"; + + private readonly IMongoClient _client; + + public TrainedModelStorageService(IMongoClient client) { - private const string MongoDBDatabaseName = "DeepTrace"; - private const string MongoDBCollection = "TrainedModels"; + _client = client; + } - private readonly IMongoClient _client; + public async Task> Load() + { + var db = _client.GetDatabase(MongoDBDatabaseName); + var collection = db.GetCollection(MongoDBCollection); - public TrainedModelStorageService(IMongoClient client) + var res = await (await collection.FindAsync("{}")).ToListAsync(); + return res; + } + public async Task Store(TrainedModelDefinition source) + { + var db = _client.GetDatabase(MongoDBDatabaseName); + var collection = db.GetCollection(MongoDBCollection); + + if (source.Id == null) + source.Id = ObjectId.GenerateNewId(); + + // use upsert (insert or update) to automatically handle subsequent updates + await collection.ReplaceOneAsync( + filter: new BsonDocument("_id", source.Id), + options: new ReplaceOptions { IsUpsert = true }, + replacement: source + ); + } + + public async Task Delete(TrainedModelDefinition source, bool ignoreNotStored = false) + { + if (source.Id == null) { - _client = client; + if (!ignoreNotStored) + throw new InvalidDataException("Source was not stored yet. There is nothing to delete"); + return; } - public async Task> Load() - { - var db = _client.GetDatabase(MongoDBDatabaseName); - var collection = db.GetCollection(MongoDBCollection); + var db = _client.GetDatabase(MongoDBDatabaseName); + var collection = db.GetCollection(MongoDBCollection); - var res = await (await collection.FindAsync("{}")).ToListAsync(); - return res; - } - public async Task Store(TrainedModelDefinition source) - { - var db = _client.GetDatabase(MongoDBDatabaseName); - var collection = db.GetCollection(MongoDBCollection); - - if (source.Id == null) - source.Id = ObjectId.GenerateNewId(); - - // use upsert (insert or update) to automatically handle subsequent updates - await collection.ReplaceOneAsync( - filter: new BsonDocument("_id", source.Id), - options: new ReplaceOptions { IsUpsert = true }, - replacement: source - ); - } - - public async Task Delete(TrainedModelDefinition source, bool ignoreNotStored = false) - { - if (source.Id == null) - { - if (!ignoreNotStored) - throw new InvalidDataException("Source was not stored yet. There is nothing to delete"); - return; - } - - var db = _client.GetDatabase(MongoDBDatabaseName); - var collection = db.GetCollection(MongoDBCollection); - - await collection.DeleteOneAsync(filter: new BsonDocument("_id", source.Id)); - } + await collection.DeleteOneAsync(filter: new BsonDocument("_id", source.Id)); } } diff --git a/DeepTrace/Shared/SurveyPrompt.razor b/DeepTrace/Shared/SurveyPrompt.razor index ec64baa..9d53792 100644 --- a/DeepTrace/Shared/SurveyPrompt.razor +++ b/DeepTrace/Shared/SurveyPrompt.razor @@ -10,7 +10,7 @@ @code { - // Demonstrates how a parent component can supply parameters - [Parameter] - public string? Title { get; set; } +// Demonstrates how a parent component can supply parameters +[Parameter] +public string? Title { get; set; } } diff --git a/PrometheusAPI/JsonSetializerSetup.cs b/PrometheusAPI/JsonSetializerSetup.cs index cdc1187..25c8b02 100644 --- a/PrometheusAPI/JsonSetializerSetup.cs +++ b/PrometheusAPI/JsonSetializerSetup.cs @@ -6,20 +6,19 @@ using System.Text.Json.Serialization; using System.Text.Json; using System.Threading.Tasks; -namespace PrometheusAPI -{ - public static class JsonSetializerSetup - { - private static JsonSerializerOptions _options = new JsonSerializerOptions - { - AllowTrailingCommas = true, - ReadCommentHandling = JsonCommentHandling.Skip, - NumberHandling = - JsonNumberHandling.AllowReadingFromString | - JsonNumberHandling.WriteAsString, - PropertyNameCaseInsensitive = true - }; +namespace PrometheusAPI; - public static JsonSerializerOptions Options => _options; - } +public static class JsonSetializerSetup +{ + private static JsonSerializerOptions _options = new JsonSerializerOptions + { + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip, + NumberHandling = + JsonNumberHandling.AllowReadingFromString | + JsonNumberHandling.WriteAsString, + PropertyNameCaseInsensitive = true + }; + + public static JsonSerializerOptions Options => _options; } diff --git a/PrometheusAPI/PrometheusClient.cs b/PrometheusAPI/PrometheusClient.cs index e0c9f50..95ca0cf 100644 --- a/PrometheusAPI/PrometheusClient.cs +++ b/PrometheusAPI/PrometheusClient.cs @@ -1,119 +1,118 @@ using System.Text.Json; -namespace PrometheusAPI +namespace PrometheusAPI; + +public class PrometheusClient { - public class PrometheusClient + private readonly HttpClient _client; + + public PrometheusClient(HttpClient client) { - private readonly HttpClient _client; + _client = client; + } - public PrometheusClient(HttpClient client) + public async Task InstantQuery(string query, DateTime? time = null, CancellationToken token = default) + { + var q = new List> { - _client = client; + new KeyValuePair("query", query) + }; + + if (time != null) + q.Add(new KeyValuePair("time", TimeSeries.DateTimeToUnixTimestamp(time.Value).ToString("F3"))); + + var form = new FormUrlEncodedContent(q); + + var response = await _client.PostAsync("/api/v1/query", form); + + var json = await response.Content.ReadAsStringAsync() + ?? throw new InvalidDataException("Responce is null"); + + var res = JsonSerializer.Deserialize(json, JsonSetializerSetup.Options) + ?? throw new InvalidDataException("Can't convert responce to InstantQueryResponse"); + + return res; + } + + public async Task RangeQuery(string query, DateTime start, DateTime end, TimeSpan step, TimeSpan timeout = default, CancellationToken token = default) + { + var q = new List> + { + new KeyValuePair("query", query), + new KeyValuePair("start", TimeSeries.DateTimeToUnixTimestamp(start).ToString("F3")), + new KeyValuePair("end", TimeSeries.DateTimeToUnixTimestamp(end).ToString("F3")), + new KeyValuePair("step", step.TotalSeconds.ToString("F3")) + }; + + if( timeout != default ) + { + q.Add(new KeyValuePair("timeout", timeout.TotalSeconds.ToString("F3"))); } - public async Task InstantQuery(string query, DateTime? time = null, CancellationToken token = default) + var form = new FormUrlEncodedContent(q); + + var response = await _client.PostAsync("/api/v1/query_range", form); + + var json = await response.Content.ReadAsStringAsync() + ?? throw new InvalidDataException("Responce is null"); + + var res = JsonSerializer.Deserialize(json, JsonSetializerSetup.Options) + ?? throw new InvalidDataException("Can't convert responce to InstantQueryResponse"); + + return res; + } + + public async Task FormatQuery(string query, CancellationToken token = default) + { + var q = new List> { - var q = new List> - { - new KeyValuePair("query", query) - }; - - if (time != null) - q.Add(new KeyValuePair("time", TimeSeries.DateTimeToUnixTimestamp(time.Value).ToString("F3"))); - - var form = new FormUrlEncodedContent(q); - - var response = await _client.PostAsync("/api/v1/query", form); - - var json = await response.Content.ReadAsStringAsync() - ?? throw new InvalidDataException("Responce is null"); - - var res = JsonSerializer.Deserialize(json, JsonSetializerSetup.Options) - ?? throw new InvalidDataException("Can't convert responce to InstantQueryResponse"); - - return res; - } - - public async Task RangeQuery(string query, DateTime start, DateTime end, TimeSpan step, TimeSpan timeout = default, CancellationToken token = default) - { - var q = new List> - { - new KeyValuePair("query", query), - new KeyValuePair("start", TimeSeries.DateTimeToUnixTimestamp(start).ToString("F3")), - new KeyValuePair("end", TimeSeries.DateTimeToUnixTimestamp(end).ToString("F3")), - new KeyValuePair("step", step.TotalSeconds.ToString("F3")) - }; - - if( timeout != default ) - { - q.Add(new KeyValuePair("timeout", timeout.TotalSeconds.ToString("F3"))); - } - - var form = new FormUrlEncodedContent(q); - - var response = await _client.PostAsync("/api/v1/query_range", form); - - var json = await response.Content.ReadAsStringAsync() - ?? throw new InvalidDataException("Responce is null"); - - var res = JsonSerializer.Deserialize(json, JsonSetializerSetup.Options) - ?? throw new InvalidDataException("Can't convert responce to InstantQueryResponse"); - - return res; - } - - public async Task FormatQuery(string query, CancellationToken token = default) - { - var q = new List> - { - new KeyValuePair("query", query), - }; + new KeyValuePair("query", query), + }; - var form = new FormUrlEncodedContent(q); + var form = new FormUrlEncodedContent(q); - var response = await _client.PostAsync("/api/v1/format_query", form); + var response = await _client.PostAsync("/api/v1/format_query", form); - var json = await response.Content.ReadAsStringAsync() - ?? throw new InvalidDataException("Responce is null"); + var json = await response.Content.ReadAsStringAsync() + ?? throw new InvalidDataException("Responce is null"); - var res = JsonSerializer.Deserialize(json, JsonSetializerSetup.Options) - ?? throw new InvalidDataException("Can't convert responce to JsonDocument"); + var res = JsonSerializer.Deserialize(json, JsonSetializerSetup.Options) + ?? throw new InvalidDataException("Can't convert responce to JsonDocument"); - var status = res.RootElement.GetProperty("status").GetString() - ?? throw new InvalidDataException("Can't get status"); + var status = res.RootElement.GetProperty("status").GetString() + ?? throw new InvalidDataException("Can't get status"); - if (!status.Equals("success", StringComparison.OrdinalIgnoreCase) ) - throw new InvalidDataException(res.RootElement.GetProperty("error").GetString()); + if (!status.Equals("success", StringComparison.OrdinalIgnoreCase) ) + throw new InvalidDataException(res.RootElement.GetProperty("error").GetString()); - var data = res.RootElement.GetProperty("data").GetString() - ?? throw new InvalidDataException("Can't get formatted query"); + var data = res.RootElement.GetProperty("data").GetString() + ?? throw new InvalidDataException("Can't get formatted query"); - return data; - } + return data; + } - public async Task GetMetricsNames(CancellationToken token = default) - { - var response = await _client.GetAsync("/api/v1/label/__name__/values"); + public async Task GetMetricsNames(CancellationToken token = default) + { + var response = await _client.GetAsync("/api/v1/label/__name__/values"); - var json = await response.Content.ReadAsStringAsync() - ?? throw new InvalidDataException("Responce is null"); + var json = await response.Content.ReadAsStringAsync() + ?? throw new InvalidDataException("Responce is null"); - var res = JsonSerializer.Deserialize(json, JsonSetializerSetup.Options) - ?? throw new InvalidDataException("Can't convert responce to JsonDocument"); + var res = JsonSerializer.Deserialize(json, JsonSetializerSetup.Options) + ?? throw new InvalidDataException("Can't convert responce to JsonDocument"); - var status = res.RootElement.GetProperty("status").GetString() - ?? throw new InvalidDataException("Can't get status"); + var status = res.RootElement.GetProperty("status").GetString() + ?? throw new InvalidDataException("Can't get status"); - if (!status.Equals("success", StringComparison.OrdinalIgnoreCase)) - throw new InvalidDataException(res.RootElement.GetProperty("error").GetString()); + if (!status.Equals("success", StringComparison.OrdinalIgnoreCase)) + throw new InvalidDataException(res.RootElement.GetProperty("error").GetString()); - var data = res.RootElement.GetProperty("data").EnumerateArray().Select(x => x.GetString()).Where( x => x != null).Cast().ToArray() - ?? throw new InvalidDataException("Can't get formatted query"); + var data = res.RootElement.GetProperty("data").EnumerateArray().Select(x => x.GetString()).Where( x => x != null).Cast().ToArray() + ?? throw new InvalidDataException("Can't get formatted query"); - return data; - - } + return data; } + } diff --git a/PrometheusAPI/TimeSeriesCoverter.cs b/PrometheusAPI/TimeSeriesCoverter.cs index aeebb28..bb1fd1b 100644 --- a/PrometheusAPI/TimeSeriesCoverter.cs +++ b/PrometheusAPI/TimeSeriesCoverter.cs @@ -1,52 +1,51 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace PrometheusAPI +namespace PrometheusAPI; + +internal class TimeSeriesCoverter : JsonConverter { - internal class TimeSeriesCoverter : JsonConverter + public override TimeSeries? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - public override TimeSeries? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + if (reader.TokenType != JsonTokenType.StartArray) + throw new JsonException(); + + reader.Read(); + + if ( reader.TokenType != JsonTokenType.Number) + throw new JsonException(); + + var s = JsonSerializer.Deserialize(ref reader, options); + + reader.Read(); + + double f; + if (reader.TokenType == JsonTokenType.Number) + f = JsonSerializer.Deserialize(ref reader, options); + else if (reader.TokenType == JsonTokenType.String) + f = Convert.ToDouble(JsonSerializer.Deserialize(ref reader, options)); + else + throw new JsonException(); + + reader.Read(); + + if (reader.TokenType != JsonTokenType.EndArray) + throw new JsonException(); + + return new TimeSeries(TimeSeries.UnixTimeStampToDateTime(s), (float)f); + } + + public override void Write(Utf8JsonWriter writer, TimeSeries? value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + + if (value != null) { - if (reader.TokenType != JsonTokenType.StartArray) - throw new JsonException(); - - reader.Read(); - - if ( reader.TokenType != JsonTokenType.Number) - throw new JsonException(); - - var s = JsonSerializer.Deserialize(ref reader, options); - - reader.Read(); - - double f; - if (reader.TokenType == JsonTokenType.Number) - f = JsonSerializer.Deserialize(ref reader, options); - else if (reader.TokenType == JsonTokenType.String) - f = Convert.ToDouble(JsonSerializer.Deserialize(ref reader, options)); - else - throw new JsonException(); - - reader.Read(); - - if (reader.TokenType != JsonTokenType.EndArray) - throw new JsonException(); - - return new TimeSeries(TimeSeries.UnixTimeStampToDateTime(s), (float)f); + writer.WriteNumberValue(TimeSeries.DateTimeToUnixTimestamp(value.TimeStamp)); + writer.WriteNumberValue(value.Value); } - public override void Write(Utf8JsonWriter writer, TimeSeries? value, JsonSerializerOptions options) - { - writer.WriteStartArray(); + writer.WriteEndArray(); - if (value != null) - { - writer.WriteNumberValue(TimeSeries.DateTimeToUnixTimestamp(value.TimeStamp)); - writer.WriteNumberValue(value.Value); - } - - writer.WriteEndArray(); - - } } }