Коммит
80dafaf912
|
@ -19,10 +19,19 @@
|
|||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
build/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
msbuild.log
|
||||
msbuild.err
|
||||
msbuild.wrn
|
||||
|
||||
*.swp
|
||||
*.*~
|
||||
project.lock.json
|
||||
.DS_Store
|
||||
*.pyc
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
|
@ -232,13 +241,6 @@ FakesAssemblies/
|
|||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
|
@ -286,3 +288,29 @@ __pycache__/
|
|||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
packages
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Other Package manager components
|
||||
jspm_packages
|
||||
bower_components
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# App Specific files
|
||||
**/app/**/*.js
|
||||
**/app/**/*.map
|
||||
|
||||
# Build Location
|
||||
wwwroot/
|
||||
build/
|
||||
dist/
|
||||
aot/
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
language: csharp
|
||||
mono: none
|
||||
dotnet: 2.1.3
|
||||
script:
|
||||
- dotnet build Diagnostics.sln
|
||||
- cd tests
|
||||
- dotnet test
|
|
@ -0,0 +1,123 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27130.2010
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0F59F43E-D06C-4A0F-8D38-9538BA58FC6A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Diagnostics.CompilerHost", "src\Diagnostics.CompilerHost\Diagnostics.CompilerHost.csproj", "{BA2DEE46-11B3-4861-9A4B-A673C02484F6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Diagnostics.DataProviders", "src\Diagnostics.DataProviders\Diagnostics.DataProviders.csproj", "{9664BA76-6EB4-468A-979D-E4D39C17AB03}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Diagnostics.ModelsAndUtils", "src\Diagnostics.ModelsAndUtils\Diagnostics.ModelsAndUtils.csproj", "{3B2A1F91-C97E-45B6-B792-13A9FC863DDC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Diagnostics.RuntimeHost", "src\Diagnostics.RuntimeHost\Diagnostics.RuntimeHost.csproj", "{0080F318-4A06-40E3-BCA6-5C61978CBFE6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Diagnostics.Scripts", "src\Diagnostics.Scripts\Diagnostics.Scripts.csproj", "{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Diagnostics.Tests", "tests\Diagnostics.Tests.csproj", "{CFD34079-6A40-4938-8840-4325B504ACFA}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{2092A19F-7B5C-4B42-BD99-9A91A252F7ED}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
templates\Detector_KustoQuery.csx = templates\Detector_KustoQuery.csx
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C0537983-2503-4D70-B831-27614DB29F81}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BA2DEE46-11B3-4861-9A4B-A673C02484F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BA2DEE46-11B3-4861-9A4B-A673C02484F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BA2DEE46-11B3-4861-9A4B-A673C02484F6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{BA2DEE46-11B3-4861-9A4B-A673C02484F6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{BA2DEE46-11B3-4861-9A4B-A673C02484F6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{BA2DEE46-11B3-4861-9A4B-A673C02484F6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{BA2DEE46-11B3-4861-9A4B-A673C02484F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BA2DEE46-11B3-4861-9A4B-A673C02484F6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BA2DEE46-11B3-4861-9A4B-A673C02484F6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{BA2DEE46-11B3-4861-9A4B-A673C02484F6}.Release|x64.Build.0 = Release|Any CPU
|
||||
{BA2DEE46-11B3-4861-9A4B-A673C02484F6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BA2DEE46-11B3-4861-9A4B-A673C02484F6}.Release|x86.Build.0 = Release|Any CPU
|
||||
{9664BA76-6EB4-468A-979D-E4D39C17AB03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9664BA76-6EB4-468A-979D-E4D39C17AB03}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9664BA76-6EB4-468A-979D-E4D39C17AB03}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{9664BA76-6EB4-468A-979D-E4D39C17AB03}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{9664BA76-6EB4-468A-979D-E4D39C17AB03}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{9664BA76-6EB4-468A-979D-E4D39C17AB03}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{9664BA76-6EB4-468A-979D-E4D39C17AB03}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9664BA76-6EB4-468A-979D-E4D39C17AB03}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9664BA76-6EB4-468A-979D-E4D39C17AB03}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{9664BA76-6EB4-468A-979D-E4D39C17AB03}.Release|x64.Build.0 = Release|Any CPU
|
||||
{9664BA76-6EB4-468A-979D-E4D39C17AB03}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{9664BA76-6EB4-468A-979D-E4D39C17AB03}.Release|x86.Build.0 = Release|Any CPU
|
||||
{3B2A1F91-C97E-45B6-B792-13A9FC863DDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3B2A1F91-C97E-45B6-B792-13A9FC863DDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3B2A1F91-C97E-45B6-B792-13A9FC863DDC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{3B2A1F91-C97E-45B6-B792-13A9FC863DDC}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{3B2A1F91-C97E-45B6-B792-13A9FC863DDC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{3B2A1F91-C97E-45B6-B792-13A9FC863DDC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{3B2A1F91-C97E-45B6-B792-13A9FC863DDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3B2A1F91-C97E-45B6-B792-13A9FC863DDC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3B2A1F91-C97E-45B6-B792-13A9FC863DDC}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{3B2A1F91-C97E-45B6-B792-13A9FC863DDC}.Release|x64.Build.0 = Release|Any CPU
|
||||
{3B2A1F91-C97E-45B6-B792-13A9FC863DDC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{3B2A1F91-C97E-45B6-B792-13A9FC863DDC}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0080F318-4A06-40E3-BCA6-5C61978CBFE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0080F318-4A06-40E3-BCA6-5C61978CBFE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0080F318-4A06-40E3-BCA6-5C61978CBFE6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{0080F318-4A06-40E3-BCA6-5C61978CBFE6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{0080F318-4A06-40E3-BCA6-5C61978CBFE6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0080F318-4A06-40E3-BCA6-5C61978CBFE6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{0080F318-4A06-40E3-BCA6-5C61978CBFE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0080F318-4A06-40E3-BCA6-5C61978CBFE6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0080F318-4A06-40E3-BCA6-5C61978CBFE6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{0080F318-4A06-40E3-BCA6-5C61978CBFE6}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0080F318-4A06-40E3-BCA6-5C61978CBFE6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0080F318-4A06-40E3-BCA6-5C61978CBFE6}.Release|x86.Build.0 = Release|Any CPU
|
||||
{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F}.Release|x86.Build.0 = Release|Any CPU
|
||||
{CFD34079-6A40-4938-8840-4325B504ACFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CFD34079-6A40-4938-8840-4325B504ACFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CFD34079-6A40-4938-8840-4325B504ACFA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{CFD34079-6A40-4938-8840-4325B504ACFA}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{CFD34079-6A40-4938-8840-4325B504ACFA}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{CFD34079-6A40-4938-8840-4325B504ACFA}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{CFD34079-6A40-4938-8840-4325B504ACFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CFD34079-6A40-4938-8840-4325B504ACFA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CFD34079-6A40-4938-8840-4325B504ACFA}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{CFD34079-6A40-4938-8840-4325B504ACFA}.Release|x64.Build.0 = Release|Any CPU
|
||||
{CFD34079-6A40-4938-8840-4325B504ACFA}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{CFD34079-6A40-4938-8840-4325B504ACFA}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{BA2DEE46-11B3-4861-9A4B-A673C02484F6} = {0F59F43E-D06C-4A0F-8D38-9538BA58FC6A}
|
||||
{9664BA76-6EB4-468A-979D-E4D39C17AB03} = {0F59F43E-D06C-4A0F-8D38-9538BA58FC6A}
|
||||
{3B2A1F91-C97E-45B6-B792-13A9FC863DDC} = {0F59F43E-D06C-4A0F-8D38-9538BA58FC6A}
|
||||
{0080F318-4A06-40E3-BCA6-5C61978CBFE6} = {0F59F43E-D06C-4A0F-8D38-9538BA58FC6A}
|
||||
{F23F3EDA-2CD2-4F7B-B605-13DF4A8A632F} = {0F59F43E-D06C-4A0F-8D38-9538BA58FC6A}
|
||||
{CFD34079-6A40-4938-8840-4325B504ACFA} = {C0537983-2503-4D70-B831-27614DB29F81}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {B4A19BFB-9A33-49E2-98AD-955E9472EFAD}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,36 @@
|
|||
@echo off
|
||||
|
||||
:: Delete existing build drop
|
||||
@RD /S /Q "build"
|
||||
|
||||
:: Build all the projects in the solution
|
||||
dotnet build Diagnostics.sln
|
||||
|
||||
IF %ERRORLEVEL% NEQ 0 (
|
||||
echo "Build Failed."
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
echo\
|
||||
|
||||
:: Publish Compiler Host to Build Location
|
||||
echo\
|
||||
echo "------------------- Publishing Compiler Host to build directory -------------------"
|
||||
echo\
|
||||
dotnet publish src\\Diagnostics.CompilerHost\\Diagnostics.CompilerHost.csproj -c Release -o ..\\..\\build\\antares.external.diagnostics.compilerhost.1.0.0
|
||||
|
||||
IF %ERRORLEVEL% NEQ 0 (
|
||||
echo "Diagnostics.CompilerHost Publish Failed."
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
echo\
|
||||
|
||||
:: Publish Runtime Host to Build Location
|
||||
echo\
|
||||
echo "------------------- Publishing Runtime Host to build directory -------------------"
|
||||
echo\
|
||||
dotnet publish src\\Diagnostics.RuntimeHost\\Diagnostics.RuntimeHost.csproj -c Release -o ..\\..\\build\\antares.external.diagnostics.runtimehost.1.0.0
|
||||
|
||||
IF %ERRORLEVEL% NEQ 0 (
|
||||
echo "Diagnostics.RuntimeHost Publish Failed."
|
||||
exit /b %errorlevel%
|
||||
)
|
|
@ -0,0 +1,18 @@
|
|||
private static string GetQuery(OperationContext cxt)
|
||||
{
|
||||
return
|
||||
$@"<YOUR_TABLE_NAME>
|
||||
| where {Utilities.TimeAndTenantFilterQuery(cxt)}
|
||||
| <YOUR_QUERY>";
|
||||
}
|
||||
|
||||
[Definition(Id = "<YOUR_DETECTOR_ID>", Name = "", Description = "")]
|
||||
public async static Task<Response> Run(DataProviders dp, OperationContext cxt, Response res)
|
||||
{
|
||||
res.Dataset.Add(new DiagnosticData()
|
||||
{
|
||||
Table = await dp.Kusto.ExecuteQuery(GetQuery(cxt), cxt.Resource.Stamp)
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Diagnostics.ModelsAndUtils;
|
||||
using Diagnostics.Scripts;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Diagnostics.CompilerHost.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
public class CompilerHostController : Controller
|
||||
{
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Post([FromBody]JToken jsonBody)
|
||||
{
|
||||
if (jsonBody == null)
|
||||
{
|
||||
return BadRequest("Missing body");
|
||||
}
|
||||
|
||||
string script = jsonBody.Value<string>("script");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(script))
|
||||
{
|
||||
return BadRequest("Missing script from body");
|
||||
}
|
||||
|
||||
EntityMetadata metaData = new EntityMetadata(script);
|
||||
CompilerResponse compilerResponse = new CompilerResponse();
|
||||
using (var invoker = new EntityInvoker(metaData, ScriptHelper.GetFrameworkReferences(), ScriptHelper.GetFrameworkImports()))
|
||||
{
|
||||
await invoker.InitializeEntryPointAsync();
|
||||
compilerResponse.CompilationOutput = invoker.CompilationOutput;
|
||||
compilerResponse.CompilationSucceeded = invoker.IsCompilationSuccessful;
|
||||
|
||||
if (compilerResponse.CompilationSucceeded)
|
||||
{
|
||||
Tuple<string, string> asmBytes = await invoker.GetAssemblyBytesAsync();
|
||||
compilerResponse.AssemblyBytes = asmBytes.Item1;
|
||||
compilerResponse.PdbBytes = asmBytes.Item2;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(compilerResponse);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Diagnostics.DataProviders\Diagnostics.DataProviders.csproj" />
|
||||
<ProjectReference Include="..\Diagnostics.ModelsAndUtils\Diagnostics.ModelsAndUtils.csproj" />
|
||||
<ProjectReference Include="..\Diagnostics.Scripts\Diagnostics.Scripts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,27 @@
|
|||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Diagnostics.CompilerHost
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BuildWebHost(args).Run();
|
||||
}
|
||||
|
||||
public static IWebHost BuildWebHost(string[] args)
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddCommandLine(args)
|
||||
.Build();
|
||||
|
||||
return
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseConfiguration(config)
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Diagnostics.CompilerHost
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMvc();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"Debug": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"Console": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
public interface IConfigurationFactory
|
||||
{
|
||||
DataSourcesConfiguration LoadConfigurations();
|
||||
}
|
||||
|
||||
public class AppSettingsDataProviderConfigurationFactory : DataProviderConfigurationFactory
|
||||
{
|
||||
private IConfigurationRoot _configuration;
|
||||
public AppSettingsDataProviderConfigurationFactory()
|
||||
{
|
||||
var builder = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
|
||||
|
||||
_configuration = builder.Build();
|
||||
}
|
||||
|
||||
protected override string GetValue(string prefix, string name)
|
||||
{
|
||||
var section = _configuration.GetSection(prefix);
|
||||
var appSettingStringValue = section[name];
|
||||
return appSettingStringValue;
|
||||
}
|
||||
|
||||
private string GetAppSettingName(string prefix, string name)
|
||||
{
|
||||
return string.Format("{0}_{1}", prefix, name);
|
||||
}
|
||||
}
|
||||
|
||||
public class RegistryDataProviderConfigurationFactory : DataProviderConfigurationFactory
|
||||
{
|
||||
private string _registryPath;
|
||||
|
||||
public RegistryDataProviderConfigurationFactory(string registryPath)
|
||||
{
|
||||
_registryPath = registryPath;
|
||||
}
|
||||
|
||||
protected override string GetValue(string prefix, string name)
|
||||
{
|
||||
string kustoRegistryPath = $@"{_registryPath}\DiagnosticDataProviders\{prefix}";
|
||||
|
||||
return (string)Registry.GetValue(kustoRegistryPath, name, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public class MockDataProviderConfigurationFactory : DataProviderConfigurationFactory
|
||||
{
|
||||
protected override string GetValue(string prefix, string name)
|
||||
{
|
||||
if(prefix == "Kusto" && name == "DBName")
|
||||
{
|
||||
return "Mock";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class DataProviderConfigurationFactory : IConfigurationFactory
|
||||
{
|
||||
public DataSourcesConfiguration LoadConfigurations()
|
||||
{
|
||||
var dataSourcesConfiguration = new DataSourcesConfiguration();
|
||||
var configurationProperties = dataSourcesConfiguration.GetType().GetProperties()
|
||||
.Where(property => {
|
||||
return property.PropertyType.GetInterfaces().Contains(typeof(IDataProviderConfiguration));
|
||||
});
|
||||
|
||||
foreach(var configProperty in configurationProperties)
|
||||
{
|
||||
var instance = Activator.CreateInstance(configProperty.PropertyType) as IDataProviderConfiguration;
|
||||
LoadConfigurationValues(instance);
|
||||
instance.PostInitialize();
|
||||
configProperty.SetValue(dataSourcesConfiguration, instance, null);
|
||||
}
|
||||
|
||||
return dataSourcesConfiguration;
|
||||
}
|
||||
|
||||
private void LoadConfigurationValues(object dataProviderConfiguration)
|
||||
{
|
||||
string prefix = null;
|
||||
DataSourceConfigurationAttribute configurationAttribute = dataProviderConfiguration.GetType()
|
||||
.GetCustomAttribute(typeof(DataSourceConfigurationAttribute)) as DataSourceConfigurationAttribute;
|
||||
|
||||
if (configurationAttribute != null && !string.IsNullOrWhiteSpace(configurationAttribute.Prefix))
|
||||
{
|
||||
prefix = configurationAttribute.Prefix;
|
||||
}
|
||||
|
||||
IEnumerable<PropertyInfo> configurationProperties =
|
||||
dataProviderConfiguration.GetType().GetProperties()
|
||||
.Where(property => Attribute.IsDefined(property, typeof(ConfigurationNameAttribute)));
|
||||
|
||||
foreach (var property in configurationProperties)
|
||||
{
|
||||
ConfigurationNameAttribute attribute =
|
||||
Attribute.GetCustomAttribute(property, typeof(ConfigurationNameAttribute)) as ConfigurationNameAttribute;
|
||||
|
||||
if (attribute != null)
|
||||
{
|
||||
object existingValue = property.GetValue(dataProviderConfiguration, null);
|
||||
|
||||
if (!property.PropertyType.IsValueType && existingValue != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = GetValue(prefix, attribute.Name);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
SetValue(dataProviderConfiguration, property, value, attribute.DefaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract string GetValue(string prefix, string name);
|
||||
|
||||
protected void SetValue(object target, PropertyInfo property, string stringValue, object defaultValue)
|
||||
{
|
||||
object value = null;
|
||||
if (property.PropertyType == typeof(string))
|
||||
{
|
||||
value = Environment.ExpandEnvironmentVariables(stringValue);
|
||||
}
|
||||
else if (property.PropertyType == typeof(int))
|
||||
{
|
||||
int intValue;
|
||||
if (!int.TryParse(stringValue, out intValue) && defaultValue != null)
|
||||
{
|
||||
value = (int)defaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = intValue;
|
||||
}
|
||||
}
|
||||
else if (property.PropertyType == typeof(bool))
|
||||
{
|
||||
bool boolValue;
|
||||
if (!bool.TryParse(stringValue, out boolValue) && defaultValue != null)
|
||||
{
|
||||
value = (bool)defaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = boolValue;
|
||||
}
|
||||
}
|
||||
else if (property.PropertyType == typeof(double))
|
||||
{
|
||||
double doubleValue;
|
||||
if (!double.TryParse(stringValue, out doubleValue) && defaultValue != null)
|
||||
{
|
||||
value = (double)defaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = doubleValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format(
|
||||
"Property {0} with type {1} is not supported.",
|
||||
property.Name,
|
||||
property.PropertyType));
|
||||
}
|
||||
|
||||
property.SetValue(target, value, null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
|
||||
public class ConfigurationNameAttribute : Attribute
|
||||
{
|
||||
public ConfigurationNameAttribute(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public object DefaultValue { get; set; }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class DataSourceConfigurationAttribute : Attribute
|
||||
{
|
||||
public DataSourceConfigurationAttribute(string prefix)
|
||||
{
|
||||
this.Prefix = prefix;
|
||||
}
|
||||
|
||||
public string Prefix
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public object DefaultValue { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
public class OperationDataCache
|
||||
{
|
||||
private Lazy<ConcurrentDictionary<string, CacheMember>> _cacheInitialization = new Lazy<ConcurrentDictionary<string, CacheMember>>(() => new ConcurrentDictionary<string, CacheMember>());
|
||||
|
||||
private ConcurrentDictionary<string, CacheMember> _cache
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cacheInitialization.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<dynamic> GetOrAdd(string key, Func<string, CacheMember> cacheMember)
|
||||
{
|
||||
return _cache.GetOrAdd(key, cacheMember).DataTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class CacheMember
|
||||
{
|
||||
public Task<dynamic> DataTask { get; set; }
|
||||
|
||||
public string MetaData { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
[DataSourceConfiguration("Kusto")]
|
||||
public class KustoDataProviderConfiguration : IDataProviderConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Client Id
|
||||
/// </summary>
|
||||
[ConfigurationName("ClientId")]
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// App Key
|
||||
/// </summary>
|
||||
[ConfigurationName("AppKey")]
|
||||
public string AppKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DB Name
|
||||
/// </summary>
|
||||
[ConfigurationName("DBName")]
|
||||
public string DBName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DB Name
|
||||
/// </summary>
|
||||
[ConfigurationName("KustoRegionGroupings")]
|
||||
public string KustoRegionGroupings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DB Name
|
||||
/// </summary>
|
||||
[ConfigurationName("KustoClusterNameGroupings")]
|
||||
public string KustoClusterNameGroupings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Region Specific Cluster Names.
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<string, string> RegionSpecificClusterNameCollection { get; set; }
|
||||
|
||||
public KustoDataProviderConfiguration()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void PostInitialize()
|
||||
{
|
||||
RegionSpecificClusterNameCollection = new ConcurrentDictionary<string, string>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(KustoRegionGroupings) && string.IsNullOrWhiteSpace(KustoClusterNameGroupings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var separator = new char[] { ',' };
|
||||
var regionGroupingParts = KustoRegionGroupings.Split(separator);
|
||||
var clusterNameGroupingParts = KustoClusterNameGroupings.Split(separator);
|
||||
|
||||
if (regionGroupingParts.Length != clusterNameGroupingParts.Length)
|
||||
{
|
||||
// TODO: Log
|
||||
return;
|
||||
}
|
||||
|
||||
for (int iterator = 0; iterator < regionGroupingParts.Length; iterator++)
|
||||
{
|
||||
var regionParts = regionGroupingParts[iterator].Split(new char[] { ':' });
|
||||
|
||||
foreach (var region in regionParts)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(region))
|
||||
{
|
||||
RegionSpecificClusterNameCollection.TryAdd(region.ToLower(), clusterNameGroupingParts[iterator]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
public class DataProviders
|
||||
{
|
||||
private OperationDataCache _cache = new OperationDataCache();
|
||||
|
||||
public KustoDataProvider Kusto;
|
||||
|
||||
public DataProviders(DataSourcesConfiguration configuration)
|
||||
{
|
||||
Kusto = new KustoDataProvider(_cache, configuration.KustoConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
public class DiagnosticDataProvider
|
||||
{
|
||||
private OperationDataCache _cache;
|
||||
|
||||
public DiagnosticDataProvider(OperationDataCache cache)
|
||||
{
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
protected Task<T> GetOrAddFromCache<T>(string key, Func<string, CacheMember> addFunction)
|
||||
{
|
||||
return Convert.ChangeType(_cache.GetOrAdd(key, addFunction), typeof(Task<T>)) as Task<T>;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using Diagnostics.ModelsAndUtils;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
public class KustoDataProvider: DiagnosticDataProvider, IDiagnosticDataProvider
|
||||
{
|
||||
private KustoDataProviderConfiguration _configuration;
|
||||
private IKustoClient _kustoClient;
|
||||
|
||||
public KustoDataProvider(OperationDataCache cache, KustoDataProviderConfiguration configuration): base(cache)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_kustoClient = KustoClientFactory.GetKustoClient(configuration);
|
||||
}
|
||||
|
||||
public async Task<DataTableResponseObject> ExecuteQuery(string query, string stampName, string requestId = null, string operationName = null)
|
||||
{
|
||||
return await _kustoClient.ExecuteQueryAsync(query, stampName, requestId, operationName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
public class DataSourcesConfiguration
|
||||
{
|
||||
public KustoDataProviderConfiguration KustoConfiguration { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
using Diagnostics.ModelsAndUtils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
public static class DataTableUtility
|
||||
{
|
||||
public static DataTable GetDataTable(DataTableResponseObject dataTableResponse)
|
||||
{
|
||||
if (dataTableResponse == null)
|
||||
{
|
||||
throw new ArgumentNullException("kustoDataTable");
|
||||
}
|
||||
|
||||
var dataTable = new DataTable(dataTableResponse.TableName);
|
||||
|
||||
dataTable.Columns.AddRange(dataTableResponse.Columns.Select(column => new DataColumn(column.ColumnName, GetColumnType(column.ColumnType, column.DataType))).ToArray());
|
||||
|
||||
foreach (var row in dataTableResponse.Rows)
|
||||
{
|
||||
var rowWithCorrectTypes = new List<object>();
|
||||
for (int i = 0; i < dataTable.Columns.Count; i++)
|
||||
{
|
||||
object rowValueWithCorrectType = null;
|
||||
|
||||
if (row[i] != null)
|
||||
{
|
||||
rowValueWithCorrectType = Convert.ChangeType(row[i], dataTable.Columns[i].DataType);
|
||||
}
|
||||
|
||||
rowWithCorrectTypes.Add(rowValueWithCorrectType);
|
||||
}
|
||||
|
||||
dataTable.Rows.Add(rowWithCorrectTypes.ToArray());
|
||||
}
|
||||
|
||||
return dataTable;
|
||||
}
|
||||
|
||||
internal static Type GetColumnType(string type, string datatype)
|
||||
{
|
||||
return Type.GetType($"System.{datatype}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Models\**" />
|
||||
<EmbeddedResource Remove="Models\**" />
|
||||
<None Remove="Models\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="3.19.2" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Diagnostics.ModelsAndUtils\Diagnostics.ModelsAndUtils.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
public interface IDataProviderConfiguration
|
||||
{
|
||||
void PostInitialize();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
public interface IDiagnosticDataProvider
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using Diagnostics.ModelsAndUtils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
public interface IKustoClient
|
||||
{
|
||||
Task<DataTableResponseObject> ExecuteQueryAsync(string query, string stampName, string requestId = null, string operationName = null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
using Diagnostics.ModelsAndUtils;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
public class KustoClient: IKustoClient
|
||||
{
|
||||
private KustoDataProviderConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Kusto Endpoint
|
||||
/// </summary>
|
||||
private const string KustoApiEndpoint = "https://{0}.kusto.windows.net:443/v1/rest/query";
|
||||
|
||||
private readonly Lazy<HttpClient> _client = new Lazy<HttpClient>(() =>
|
||||
{
|
||||
var client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
return client;
|
||||
}
|
||||
);
|
||||
|
||||
private HttpClient _httpClient
|
||||
{
|
||||
get
|
||||
{
|
||||
return _client.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public KustoClient(KustoDataProviderConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public async Task<DataTableResponseObject> ExecuteQueryAsync(string query, string stampName, string requestId = null, string operationName = null)
|
||||
{
|
||||
string appserviceRegion = ParseRegionFromStamp(stampName);
|
||||
|
||||
string kustoClusterName;
|
||||
if (!_configuration.RegionSpecificClusterNameCollection.TryGetValue(appserviceRegion.ToLower(), out kustoClusterName))
|
||||
{
|
||||
// try to use default cluster name.
|
||||
if (!_configuration.RegionSpecificClusterNameCollection.TryGetValue("*", out kustoClusterName))
|
||||
{
|
||||
throw new KeyNotFoundException(String.Format("Kusto Cluster Name not found for Region : {0}", appserviceRegion.ToLower()));
|
||||
}
|
||||
}
|
||||
|
||||
string authorizationToken = await KustoTokenService.Instance.GetAuthorizationTokenAsync();
|
||||
|
||||
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, string.Format(KustoApiEndpoint, kustoClusterName));
|
||||
request.Headers.Add("Authorization", authorizationToken);
|
||||
request.Headers.Add("x-ms-client-request-id", requestId ?? Guid.NewGuid().ToString());
|
||||
|
||||
object requestPayload = new
|
||||
{
|
||||
db = _configuration.DBName,
|
||||
csl = query
|
||||
};
|
||||
|
||||
request.Content = new StringContent(JsonConvert.SerializeObject(requestPayload), Encoding.UTF8, "application/json");
|
||||
|
||||
CancellationTokenSource tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(Constants.DefaultTimeoutInSeconds));
|
||||
HttpResponseMessage responseMsg = await _httpClient.SendAsync(request, tokenSource.Token);
|
||||
string content = await responseMsg.Content.ReadAsStringAsync();
|
||||
|
||||
DataTableResponseObjectCollection dataSet = JsonConvert.DeserializeObject<DataTableResponseObjectCollection>(content);
|
||||
|
||||
return dataSet.Tables.FirstOrDefault();
|
||||
}
|
||||
|
||||
private static string ParseRegionFromStamp(string stampName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(stampName))
|
||||
{
|
||||
throw new ArgumentNullException("stampName");
|
||||
}
|
||||
|
||||
var stampParts = stampName.Split(new char[] { '-' });
|
||||
if (stampParts.Any() && stampParts.Length >= 3)
|
||||
{
|
||||
return stampParts[2];
|
||||
}
|
||||
|
||||
//return * for private stamps if no prod stamps are found
|
||||
return "*";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
internal static class KustoClientFactory
|
||||
{
|
||||
internal static IKustoClient GetKustoClient(KustoDataProviderConfiguration config)
|
||||
{
|
||||
if (config.DBName == "Mock")
|
||||
{
|
||||
return new MockKustoClient();
|
||||
}
|
||||
|
||||
return new KustoClient(config);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
public class KustoTokenService
|
||||
{
|
||||
private AuthenticationContext _authContext;
|
||||
private ClientCredential _clientCredential;
|
||||
private KustoDataProviderConfiguration _configuration;
|
||||
private static readonly Lazy<KustoTokenService> _instance = new Lazy<KustoTokenService>(() => new KustoTokenService());
|
||||
private string _authorizationToken;
|
||||
private bool _tokenAcquiredAtleastOnce;
|
||||
private Task<AuthenticationResult> _acquireTokenTask;
|
||||
|
||||
public static KustoTokenService Instance => _instance.Value;
|
||||
|
||||
public string AuthorizationToken => _authorizationToken;
|
||||
|
||||
private KustoTokenService() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public void Initialize(KustoDataProviderConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_authContext = new AuthenticationContext(Constants.MicrosoftTenantAuthorityUrl);
|
||||
_clientCredential = new ClientCredential(_configuration.ClientId, _configuration.AppKey);
|
||||
_tokenAcquiredAtleastOnce = false;
|
||||
StartTokenRefresh();
|
||||
}
|
||||
|
||||
private async Task StartTokenRefresh()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_acquireTokenTask = _authContext.AcquireTokenAsync(Constants.DefaultKustoEndpoint, _clientCredential);
|
||||
AuthenticationResult authResult = await _acquireTokenTask;
|
||||
_authorizationToken = GetAuthTokenFromAuthenticationResult(authResult);
|
||||
_tokenAcquiredAtleastOnce = true;
|
||||
|
||||
await Task.Delay(Constants.TokenRefreshIntervalInMs);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetAuthTokenFromAuthenticationResult(AuthenticationResult authenticationResult)
|
||||
{
|
||||
return $"{authenticationResult.AccessTokenType} {authenticationResult.AccessToken}";
|
||||
}
|
||||
|
||||
public async Task<string> GetAuthorizationTokenAsync()
|
||||
{
|
||||
if (!_tokenAcquiredAtleastOnce)
|
||||
{
|
||||
var authResult = await _acquireTokenTask;
|
||||
return GetAuthTokenFromAuthenticationResult(authResult);
|
||||
}
|
||||
|
||||
return _authorizationToken;
|
||||
}
|
||||
}
|
||||
|
||||
public class Constants
|
||||
{
|
||||
public const string MicrosoftTenantAuthorityUrl = "https://login.windows.net/microsoft.com";
|
||||
|
||||
public const int TokenRefreshIntervalInMs = 15 * 60 * 1000;
|
||||
|
||||
public const string DefaultKustoEndpoint = "https://wawswus.kusto.windows.net";
|
||||
|
||||
public const string WebHostingRegistryPath = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\IIS Extensions\Web Hosting Framework";
|
||||
|
||||
public const int DefaultTimeoutInSeconds = 60;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
using Diagnostics.ModelsAndUtils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.DataProviders
|
||||
{
|
||||
class MockKustoClient: IKustoClient
|
||||
{
|
||||
public async Task<DataTableResponseObject> ExecuteQueryAsync(string query, string stampName, string requestId = null, string operationName = null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(operationName))
|
||||
{
|
||||
switch (operationName.ToLower())
|
||||
{
|
||||
case "gettenantidforstamp":
|
||||
return await GetFakeTenantIdResults();
|
||||
|
||||
default:
|
||||
return await GetTestA();
|
||||
}
|
||||
}
|
||||
|
||||
switch(query)
|
||||
{
|
||||
case "TestA":
|
||||
return await GetTestA();
|
||||
}
|
||||
|
||||
return new DataTableResponseObject();
|
||||
}
|
||||
|
||||
private Task<DataTableResponseObject> GetFakeTenantIdResults()
|
||||
{
|
||||
var tenantColumn = new DataTableResponseColumn();
|
||||
tenantColumn.ColumnName = "Tenant";
|
||||
tenantColumn.ColumnType = "string";
|
||||
tenantColumn.DataType = "String";
|
||||
|
||||
var publicHostColumn = new DataTableResponseColumn();
|
||||
publicHostColumn.ColumnName = "PublicHost";
|
||||
publicHostColumn.ColumnType = "string";
|
||||
publicHostColumn.DataType = "String";
|
||||
|
||||
var res = new DataTableResponseObject
|
||||
{
|
||||
Columns = new List<DataTableResponseColumn>(new[] { tenantColumn, publicHostColumn })
|
||||
};
|
||||
|
||||
res.Rows = new string[1][];
|
||||
res.Rows[0] = new string[2] { Guid.NewGuid().ToString(), "fakestamp.cloudapp.net" };
|
||||
|
||||
return Task.FromResult(res);
|
||||
}
|
||||
|
||||
private Task<DataTableResponseObject> GetTestA()
|
||||
{
|
||||
var testColumn = new DataTableResponseColumn();
|
||||
testColumn.ColumnName = "TestColumn";
|
||||
testColumn.ColumnType = "System.string";
|
||||
testColumn.DataType = "string";
|
||||
|
||||
var res = new DataTableResponseObject();
|
||||
res.Columns = new List<DataTableResponseColumn>(new[] { testColumn });
|
||||
|
||||
return Task.FromResult(res);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Collections.Immutable" Version="1.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.ModelsAndUtils
|
||||
{
|
||||
public class CompilerResponse
|
||||
{
|
||||
public bool CompilationSucceeded;
|
||||
|
||||
public IEnumerable<string> CompilationOutput;
|
||||
|
||||
public string AssemblyBytes;
|
||||
|
||||
public string PdbBytes;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.ModelsAndUtils
|
||||
{
|
||||
public class DataTableResponseObjectCollection
|
||||
{
|
||||
public IEnumerable<DataTableResponseObject> Tables { get; set; }
|
||||
}
|
||||
|
||||
public class DataTableResponseObject
|
||||
{
|
||||
public string TableName { get; set; }
|
||||
|
||||
public IEnumerable<DataTableResponseColumn> Columns { get; set; }
|
||||
|
||||
public string[][] Rows { get; set; }
|
||||
}
|
||||
|
||||
public class DataTableResponseColumn
|
||||
{
|
||||
public string ColumnName { get; set; }
|
||||
public string DataType { get; set; }
|
||||
public string ColumnType { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.ModelsAndUtils
|
||||
{
|
||||
public class Definition : Attribute, IEquatable<Definition>
|
||||
{
|
||||
[DataMember]
|
||||
public string Id { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public string Description { get; set; }
|
||||
|
||||
public bool Equals(Definition other)
|
||||
{
|
||||
return Id == other.Id;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.ModelsAndUtils
|
||||
{
|
||||
public class OperationContext
|
||||
{
|
||||
public SiteResource Resource;
|
||||
|
||||
public string StartTime;
|
||||
|
||||
public string EndTime;
|
||||
|
||||
public string TimeGrain;
|
||||
|
||||
public OperationContext(SiteResource resource, string startTimeStr, string endTimeStr, string timeGrain = "5")
|
||||
{
|
||||
Resource = resource;
|
||||
StartTime = startTimeStr;
|
||||
EndTime = endTimeStr;
|
||||
TimeGrain = timeGrain;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.ModelsAndUtils
|
||||
{
|
||||
public class QueryResponse<T>
|
||||
{
|
||||
public CompilerResponse CompilationOutput;
|
||||
|
||||
public T InvocationOutput;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.ModelsAndUtils
|
||||
{
|
||||
public interface IResource
|
||||
{
|
||||
}
|
||||
|
||||
public abstract class Resource : IResource
|
||||
{
|
||||
public string SubscriptionId;
|
||||
|
||||
public string ResourceGroup;
|
||||
|
||||
public IEnumerable<string> TenantIdList;
|
||||
}
|
||||
|
||||
public sealed class SiteResource : Resource
|
||||
{
|
||||
public string SiteName;
|
||||
|
||||
public IEnumerable<string> HostNames;
|
||||
|
||||
public string Stamp;
|
||||
|
||||
public string SourceMoniker {
|
||||
get
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(Stamp) ? Stamp.ToUpper().Replace("-", string.Empty) : string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.ModelsAndUtils
|
||||
{
|
||||
public class Response
|
||||
{
|
||||
public Definition Metadata { get; set; }
|
||||
|
||||
public List<DiagnosticData> Dataset { get; set; }
|
||||
|
||||
public Response()
|
||||
{
|
||||
Metadata = new Definition();
|
||||
Dataset = new List<DiagnosticData>();
|
||||
}
|
||||
}
|
||||
|
||||
public class DiagnosticData
|
||||
{
|
||||
public DataTableResponseObject Table { get; set; }
|
||||
|
||||
public Rendering RenderingProperties { get; set; }
|
||||
|
||||
public DiagnosticData()
|
||||
{
|
||||
Table = new DataTableResponseObject();
|
||||
RenderingProperties = new Rendering();
|
||||
}
|
||||
}
|
||||
|
||||
public class Rendering
|
||||
{
|
||||
public GraphType Type { get; set; }
|
||||
|
||||
public Rendering()
|
||||
{
|
||||
Type = GraphType.TimeSeries;
|
||||
}
|
||||
|
||||
public Rendering(GraphType type)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public enum GraphType
|
||||
{
|
||||
NoGraph = 0,
|
||||
Table,
|
||||
TimeSeries
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.ModelsAndUtils
|
||||
{
|
||||
public class ScriptHelper
|
||||
{
|
||||
public static ImmutableArray<string> GetFrameworkReferences() => ImmutableArray.Create(
|
||||
"System.Data",
|
||||
"Diagnostics.DataProviders",
|
||||
"Diagnostics.ModelsAndUtils"
|
||||
);
|
||||
|
||||
public static ImmutableArray<string> GetFrameworkImports() => ImmutableArray.Create(
|
||||
"System.Data",
|
||||
"System.Threading.Tasks",
|
||||
"Diagnostics.DataProviders",
|
||||
"Diagnostics.ModelsAndUtils"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.ModelsAndUtils
|
||||
{
|
||||
public class Utilities
|
||||
{
|
||||
public static string TimeFilterQuery(OperationContext cxt, string timeColumnName = "PreciseTimeStamp")
|
||||
{
|
||||
return $"{timeColumnName} >= datetime({cxt.StartTime}) and {timeColumnName} <= datetime({cxt.EndTime})";
|
||||
}
|
||||
|
||||
public static string TenantFilterQuery(OperationContext cxt)
|
||||
{
|
||||
return $"Tenant in ({string.Join(",", cxt.Resource.TenantIdList.Select(t => $@"""{t}"""))})";
|
||||
}
|
||||
|
||||
public static string TimeAndTenantFilterQuery(OperationContext cxt, string timeColumnName = "PreciseTimeStamp")
|
||||
{
|
||||
return $"{TimeFilterQuery(cxt, timeColumnName)} and {TenantFilterQuery(cxt)}";
|
||||
}
|
||||
|
||||
public static string HostNamesFilterQuery(OperationContext cxt, string hostNameColumn = "Cs_host")
|
||||
{
|
||||
var wildCardHostNames = cxt.Resource.HostNames.Where(p => p.StartsWith("*"));
|
||||
var nonWildCardHostNames = cxt.Resource.HostNames.Where(p => !p.StartsWith("*"));
|
||||
|
||||
string hostNameQuery = string.Empty;
|
||||
|
||||
if (nonWildCardHostNames.Any())
|
||||
{
|
||||
hostNameQuery = $"{hostNameColumn} in ({string.Join(",", nonWildCardHostNames.Select(h => $@"""{h}"""))})";
|
||||
}
|
||||
|
||||
if (wildCardHostNames.Any())
|
||||
{
|
||||
string wildCardQuery = string.Join("or", wildCardHostNames.Select(w => $@"{hostNameColumn} endswith ""{w}"""));
|
||||
hostNameQuery = $"{hostNameQuery} or {wildCardQuery}";
|
||||
}
|
||||
|
||||
return hostNameQuery;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
using Diagnostics.ModelsAndUtils;
|
||||
using Diagnostics.RuntimeHost.Services;
|
||||
using Diagnostics.RuntimeHost.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Controllers
|
||||
{
|
||||
public abstract class ControllerBase : Controller
|
||||
{
|
||||
public ControllerBase()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class SiteControllerBase : ControllerBase
|
||||
{
|
||||
protected IResourceService _resourceService;
|
||||
|
||||
public SiteControllerBase(IResourceService resourceService)
|
||||
{
|
||||
_resourceService = resourceService;
|
||||
}
|
||||
|
||||
protected bool VerifyQueryParams(string[] hostNames, string stampName, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
if (hostNames == null || hostNames.Length <= 0)
|
||||
{
|
||||
reason = "Invalid or empty hostnames";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(stampName))
|
||||
{
|
||||
reason = "Invalid or empty stampName";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected OperationContext PrepareContext(SiteResource resource, DateTime startTime, DateTime endTime)
|
||||
{
|
||||
return new OperationContext(
|
||||
resource,
|
||||
DateTimeHelper.GetDateTimeInUtcFormat(startTime).ToString(HostConstants.KustoTimeFormat),
|
||||
DateTimeHelper.GetDateTimeInUtcFormat(endTime).ToString(HostConstants.KustoTimeFormat)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Diagnostics.RuntimeHost.Services.SourceWatcher;
|
||||
using Diagnostics.RuntimeHost.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
public class ProcessHealthController : Controller
|
||||
{
|
||||
private ISourceWatcherService _sourceWatcherService;
|
||||
|
||||
public ProcessHealthController(ISourceWatcherService sourceWatcherService)
|
||||
{
|
||||
// This dependency is injected for source watcher service to start.
|
||||
_sourceWatcherService = sourceWatcherService;
|
||||
}
|
||||
|
||||
[HttpGet(UriElements.HealthPing)]
|
||||
public IActionResult HealthPing()
|
||||
{
|
||||
return Ok("Server is up and running.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Diagnostics.DataProviders;
|
||||
using Diagnostics.ModelsAndUtils;
|
||||
using Diagnostics.RuntimeHost.Services;
|
||||
using Diagnostics.RuntimeHost.Services.SourceWatcher;
|
||||
using Diagnostics.RuntimeHost.Utilities;
|
||||
using Diagnostics.Scripts;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route(UriElements.SitesResource + UriElements.Diagnostics)]
|
||||
public class SitesController : SiteControllerBase
|
||||
{
|
||||
private ICompilerHostClient _compilerHostClient;
|
||||
private ISourceWatcherService _sourceWatcherService;
|
||||
private ICache<string, EntityInvoker> _invokerCache;
|
||||
private IDataSourcesConfigurationService _dataSourcesConfigService;
|
||||
|
||||
public SitesController(ICompilerHostClient compilerHostClient, ISourceWatcherService sourceWatcherService, ICache<string, EntityInvoker> invokerCache, IResourceService resourceService, IDataSourcesConfigurationService dataSourcesConfigService)
|
||||
: base(resourceService)
|
||||
{
|
||||
_compilerHostClient = compilerHostClient;
|
||||
_sourceWatcherService = sourceWatcherService;
|
||||
_invokerCache = invokerCache;
|
||||
_dataSourcesConfigService = dataSourcesConfigService;
|
||||
}
|
||||
|
||||
[HttpPost(UriElements.Query)]
|
||||
public async Task<IActionResult> Post(string subscriptionId, string resourceGroupName, string siteName, string[] hostNames, string stampName, [FromBody]JToken jsonBody, string startTime = null, string endTime = null, string timeGrain = null)
|
||||
{
|
||||
if (jsonBody == null)
|
||||
{
|
||||
return BadRequest("Missing body");
|
||||
}
|
||||
|
||||
string script = jsonBody.Value<string>("script");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(script))
|
||||
{
|
||||
return BadRequest("Missing script from body");
|
||||
}
|
||||
|
||||
if (!VerifyQueryParams(hostNames, stampName, out string verficationOutput))
|
||||
{
|
||||
return BadRequest(verficationOutput);
|
||||
}
|
||||
|
||||
if (!DateTimeHelper.PrepareStartEndTimeWithTimeGrain(startTime, endTime, timeGrain, out DateTime startTimeUtc, out DateTime endTimeUtc, out TimeSpan timeGrainTimeSpan, out string errorMessage))
|
||||
{
|
||||
return BadRequest(errorMessage);
|
||||
}
|
||||
|
||||
EntityMetadata metaData = new EntityMetadata(script);
|
||||
var dataProviders = new DataProviders.DataProviders(_dataSourcesConfigService.Config);
|
||||
SiteResource resource = await _resourceService.GetSite(subscriptionId, resourceGroupName, siteName, hostNames, stampName, startTimeUtc, endTimeUtc);
|
||||
OperationContext cxt = PrepareContext(resource, startTimeUtc, endTimeUtc);
|
||||
|
||||
QueryResponse<Response> queryRes = new QueryResponse<Response>
|
||||
{
|
||||
InvocationOutput = new Response()
|
||||
};
|
||||
|
||||
Assembly tempAsm = null;
|
||||
var compilerResponse = await _compilerHostClient.GetCompilationResponse(script);
|
||||
|
||||
queryRes.CompilationOutput = compilerResponse;
|
||||
|
||||
if (queryRes.CompilationOutput.CompilationSucceeded)
|
||||
{
|
||||
byte[] asmData = Convert.FromBase64String(compilerResponse.AssemblyBytes);
|
||||
byte[] pdbData = Convert.FromBase64String(compilerResponse.PdbBytes);
|
||||
|
||||
tempAsm = Assembly.Load(asmData, pdbData);
|
||||
|
||||
using (var invoker = new EntityInvoker(metaData, ScriptHelper.GetFrameworkReferences(), ScriptHelper.GetFrameworkImports()))
|
||||
{
|
||||
invoker.InitializeEntryPoint(tempAsm);
|
||||
queryRes.InvocationOutput.Metadata = invoker.EntryPointDefinitionAttribute;
|
||||
queryRes.InvocationOutput = (Response)await invoker.Invoke(new object[] { dataProviders, cxt, queryRes.InvocationOutput });
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(queryRes);
|
||||
}
|
||||
|
||||
[HttpGet(UriElements.Detectors)]
|
||||
public async Task<IActionResult> ListDetectors(string subscriptionId, string resourceGroupName, string siteName)
|
||||
{
|
||||
await _sourceWatcherService.Watcher.WaitForFirstCompletion();
|
||||
IEnumerable<Definition> entityDefinitions = _invokerCache.GetAll().Select(p => p.EntryPointDefinitionAttribute);
|
||||
return Ok(entityDefinitions);
|
||||
}
|
||||
|
||||
[HttpGet(UriElements.Detectors + UriElements.DetectorResource)]
|
||||
public async Task<IActionResult> GetDetectorResource(string subscriptionId, string resourceGroupName, string siteName, string detectorId, string[] hostNames, string stampName, string startTime = null, string endTime = null, string timeGrain = null)
|
||||
{
|
||||
if (!VerifyQueryParams(hostNames, stampName, out string verficationOutput))
|
||||
{
|
||||
return BadRequest(verficationOutput);
|
||||
}
|
||||
|
||||
if (!DateTimeHelper.PrepareStartEndTimeWithTimeGrain(startTime, endTime, timeGrain, out DateTime startTimeUtc, out DateTime endTimeUtc, out TimeSpan timeGrainTimeSpan, out string errorMessage))
|
||||
{
|
||||
return BadRequest(errorMessage);
|
||||
}
|
||||
|
||||
var dataProviders = new DataProviders.DataProviders(_dataSourcesConfigService.Config);
|
||||
SiteResource resource = await _resourceService.GetSite(subscriptionId, resourceGroupName, siteName, hostNames, stampName, startTimeUtc, endTimeUtc);
|
||||
OperationContext cxt = PrepareContext(resource, startTimeUtc, endTimeUtc);
|
||||
|
||||
if (!_invokerCache.TryGetValue(detectorId, out EntityInvoker invoker))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
Response res = new Response
|
||||
{
|
||||
Metadata = invoker.EntryPointDefinitionAttribute
|
||||
};
|
||||
|
||||
res = (Response)await invoker.Invoke(new object[] { dataProviders, cxt, res });
|
||||
|
||||
return Ok(res);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Controllers\ValuesController.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
|
||||
<!--<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.1" />-->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Diagnostics.DataProviders\Diagnostics.DataProviders.csproj" />
|
||||
<ProjectReference Include="..\Diagnostics.ModelsAndUtils\Diagnostics.ModelsAndUtils.csproj" />
|
||||
<ProjectReference Include="..\Diagnostics.Scripts\Diagnostics.Scripts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Diagnostics.RuntimeHost
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BuildWebHost(args).Run();
|
||||
}
|
||||
|
||||
public static IWebHost BuildWebHost(string[] args)
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddCommandLine(args)
|
||||
.Build();
|
||||
|
||||
return
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseConfiguration(config)
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
using Diagnostics.ModelsAndUtils;
|
||||
using Diagnostics.RuntimeHost.Utilities;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Services
|
||||
{
|
||||
public interface ICompilerHostClient
|
||||
{
|
||||
Task<CompilerResponse> GetCompilationResponse(string script);
|
||||
}
|
||||
|
||||
public class CompilerHostClient : ICompilerHostClient, IDisposable
|
||||
{
|
||||
private SemaphoreSlim _semaphoreObject;
|
||||
private IHostingEnvironment _env;
|
||||
private IConfiguration _configuration;
|
||||
private string _compilerHostUrl;
|
||||
private HttpClient _httpClient;
|
||||
private bool _isComplierHostRunning;
|
||||
private int _processId;
|
||||
private string _dotNetProductName;
|
||||
private string _compilerHostBinaryLocation;
|
||||
private string _compilerHostPort;
|
||||
private int _pollingIntervalInSeconds;
|
||||
private long _processMemoryThresholdInMB;
|
||||
|
||||
public CompilerHostClient(IHostingEnvironment env, IConfiguration configuration)
|
||||
{
|
||||
_env = env;
|
||||
_configuration = configuration;
|
||||
_semaphoreObject = new SemaphoreSlim(1, 1);
|
||||
|
||||
_httpClient = new HttpClient
|
||||
{
|
||||
MaxResponseContentBufferSize = Int32.MaxValue
|
||||
};
|
||||
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
_isComplierHostRunning = false;
|
||||
_processId = -1;
|
||||
_dotNetProductName = "dotnet";
|
||||
|
||||
LoadConfigurations();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_compilerHostBinaryLocation))
|
||||
{
|
||||
throw new ArgumentNullException("compilerHostBinaryLocation");
|
||||
}
|
||||
|
||||
_compilerHostUrl = $@"http://localhost:{_compilerHostPort}";
|
||||
|
||||
StartProcessMonitor();
|
||||
}
|
||||
|
||||
public async Task<CompilerResponse> GetCompilationResponse(string script)
|
||||
{
|
||||
await _semaphoreObject.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
if (!_isComplierHostRunning)
|
||||
{
|
||||
await LaunchCompilerHostProcess();
|
||||
}
|
||||
|
||||
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, $"{_compilerHostUrl}/api/compilerhost")
|
||||
{
|
||||
Content = new StringContent(JsonConvert.SerializeObject(PrepareRequestBody(script)), Encoding.UTF8, "application/json")
|
||||
};
|
||||
|
||||
HttpResponseMessage responseMessage = await _httpClient.SendAsync(requestMessage);
|
||||
|
||||
// TODO : Check for 200 and handle errors
|
||||
|
||||
return await responseMessage.Content.ReadAsAsyncCustom<CompilerResponse>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphoreObject.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LaunchCompilerHostProcess()
|
||||
{
|
||||
var proc = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
WorkingDirectory = _compilerHostBinaryLocation,
|
||||
FileName = _dotNetProductName,
|
||||
Arguments = $@"Diagnostics.CompilerHost.dll --urls {_compilerHostUrl}",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
CreateNoWindow = false
|
||||
}
|
||||
};
|
||||
|
||||
proc.Start();
|
||||
// TODO : Remove artificial wait.
|
||||
await Task.Delay(5000);
|
||||
|
||||
if (!proc.HasExited)
|
||||
{
|
||||
_isComplierHostRunning = true;
|
||||
_processId = proc.Id;
|
||||
}
|
||||
}
|
||||
|
||||
private async void StartProcessMonitor()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
Process proc = null;
|
||||
if (_processId != -1)
|
||||
{
|
||||
proc = Process.GetProcessById(_processId);
|
||||
}
|
||||
|
||||
if (proc != null && !proc.HasExited)
|
||||
{
|
||||
if (proc.WorkingSet64 > (_processMemoryThresholdInMB * 1024 * 1024))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _semaphoreObject.WaitAsync();
|
||||
proc.Kill();
|
||||
proc.WaitForExit();
|
||||
_processId = -1;
|
||||
_isComplierHostRunning = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphoreObject.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_processId = -1;
|
||||
_isComplierHostRunning = false;
|
||||
}
|
||||
|
||||
await Task.Delay(_pollingIntervalInSeconds * 1000);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO : Log Exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object PrepareRequestBody(string scriptText)
|
||||
{
|
||||
return new
|
||||
{
|
||||
script = scriptText
|
||||
};
|
||||
}
|
||||
|
||||
private void LoadConfigurations()
|
||||
{
|
||||
// TODO : Probably needs a better way to manage configurations accross various services.
|
||||
if (_env.IsProduction())
|
||||
{
|
||||
_compilerHostBinaryLocation = (string)Registry.GetValue(RegistryConstants.CompilerHostRegistryPath, RegistryConstants.CompilerHostBinaryLocationKey, string.Empty);
|
||||
_compilerHostPort = (string)Registry.GetValue(RegistryConstants.CompilerHostRegistryPath, RegistryConstants.CompilerHostPortKey, string.Empty);
|
||||
_pollingIntervalInSeconds = Convert.ToInt32(Registry.GetValue(RegistryConstants.CompilerHostRegistryPath, RegistryConstants.CompilerHostPollingIntervalKey, 60));
|
||||
_processMemoryThresholdInMB = Convert.ToInt64(Registry.GetValue(RegistryConstants.CompilerHostRegistryPath, RegistryConstants.CompilerHostProcessMemoryThresholdInMBKey, 300));
|
||||
}
|
||||
else
|
||||
{
|
||||
_compilerHostBinaryLocation = (_configuration[$"CompilerHost:{RegistryConstants.CompilerHostBinaryLocationKey}"]).ToString();
|
||||
_compilerHostPort = (_configuration[$"CompilerHost:{RegistryConstants.CompilerHostPortKey}"]).ToString();
|
||||
_pollingIntervalInSeconds = Convert.ToInt32(_configuration[$"CompilerHost:{RegistryConstants.CompilerHostPollingIntervalKey}"]);
|
||||
_processMemoryThresholdInMB = Convert.ToInt64(_configuration[$"CompilerHost:{RegistryConstants.CompilerHostProcessMemoryThresholdInMBKey}"]);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if(_semaphoreObject != null)
|
||||
{
|
||||
_semaphoreObject.Dispose();
|
||||
}
|
||||
|
||||
if(_httpClient != null)
|
||||
{
|
||||
_httpClient.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
using Diagnostics.DataProviders;
|
||||
using Diagnostics.RuntimeHost.Utilities;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Services
|
||||
{
|
||||
public interface IDataSourcesConfigurationService
|
||||
{
|
||||
DataSourcesConfiguration Config { get; }
|
||||
}
|
||||
|
||||
public class DataSourcesConfigurationService : IDataSourcesConfigurationService
|
||||
{
|
||||
private DataSourcesConfiguration _config;
|
||||
|
||||
public DataSourcesConfiguration Config => _config;
|
||||
|
||||
public DataSourcesConfigurationService(IHostingEnvironment env)
|
||||
{
|
||||
IConfigurationFactory factory = GetDataProviderConfigurationFactory(env);
|
||||
_config = factory.LoadConfigurations();
|
||||
}
|
||||
|
||||
public static IConfigurationFactory GetDataProviderConfigurationFactory(IHostingEnvironment env)
|
||||
{
|
||||
if (env.IsProduction())
|
||||
{
|
||||
return new RegistryDataProviderConfigurationFactory(RegistryConstants.RegistryRootPath);
|
||||
}
|
||||
|
||||
switch (env.EnvironmentName.ToLower())
|
||||
{
|
||||
case "mock":
|
||||
return new MockDataProviderConfigurationFactory();
|
||||
default:
|
||||
return new AppSettingsDataProviderConfigurationFactory();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
using Diagnostics.RuntimeHost.Utilities;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Services
|
||||
{
|
||||
public interface IGithubClient : IDisposable
|
||||
{
|
||||
string UserName { get; }
|
||||
|
||||
string RepoName { get; }
|
||||
|
||||
string Branch { get; }
|
||||
|
||||
Task<HttpResponseMessage> Get(string url, string etag = "");
|
||||
|
||||
Task<HttpResponseMessage> Get(HttpRequestMessage request);
|
||||
|
||||
Task DownloadFile(string fileUrl, string destinationPath);
|
||||
}
|
||||
|
||||
public class GithubClient : IGithubClient
|
||||
{
|
||||
private IHostingEnvironment _env;
|
||||
private IConfiguration _config;
|
||||
private string _userName;
|
||||
private string _repoName;
|
||||
private string _branch;
|
||||
private string _accessToken;
|
||||
private HttpClient _httpClient;
|
||||
|
||||
public string UserName => _userName;
|
||||
|
||||
public string RepoName => _repoName;
|
||||
|
||||
public string Branch => _branch;
|
||||
|
||||
public GithubClient(IHostingEnvironment env, IConfiguration configuration)
|
||||
{
|
||||
_env = env;
|
||||
_config = configuration;
|
||||
LoadConfigurations();
|
||||
ValidateConfigurations();
|
||||
InitializeHttpClient();
|
||||
}
|
||||
|
||||
public GithubClient(string userName, string repoName, string branch = "master", string accessToken = "")
|
||||
{
|
||||
_userName = userName;
|
||||
_repoName = repoName;
|
||||
_branch = !string.IsNullOrWhiteSpace(branch) ? branch : "master";
|
||||
_accessToken = accessToken ?? string.Empty;
|
||||
ValidateConfigurations();
|
||||
InitializeHttpClient();
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> Get(string url, string etag = "")
|
||||
{
|
||||
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, AppendQueryStringParams(url));
|
||||
if (!string.IsNullOrWhiteSpace(etag))
|
||||
{
|
||||
request.Headers.Add("If-None-Match", etag);
|
||||
}
|
||||
|
||||
return Get(request);
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> Get(HttpRequestMessage request)
|
||||
{
|
||||
return _httpClient.SendAsync(request);
|
||||
}
|
||||
|
||||
public async Task DownloadFile(string fileUrl, string destinationPath)
|
||||
{
|
||||
using (HttpResponseMessage httpResponse = await Get(fileUrl))
|
||||
{
|
||||
if (!httpResponse.IsSuccessStatusCode)
|
||||
{
|
||||
throw new HttpRequestException($"Failed while getting resource : {fileUrl} . Http Status Code : {httpResponse.StatusCode}");
|
||||
}
|
||||
|
||||
using (Stream srcStream = await httpResponse.Content.ReadAsStreamAsync(),
|
||||
destStream = new FileStream(destinationPath, FileMode.Create))
|
||||
{
|
||||
await srcStream.CopyToAsync(destStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_httpClient != null)
|
||||
{
|
||||
_httpClient.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadConfigurations()
|
||||
{
|
||||
if (_env.IsProduction())
|
||||
{
|
||||
_userName = (string)Registry.GetValue(RegistryConstants.GithubWatcherRegistryPath, RegistryConstants.GithubUserNameKey, string.Empty);
|
||||
_repoName = (string)Registry.GetValue(RegistryConstants.GithubWatcherRegistryPath, RegistryConstants.GithubRepoNameKey, string.Empty);
|
||||
_branch = (string)Registry.GetValue(RegistryConstants.GithubWatcherRegistryPath, RegistryConstants.GithubBranchKey, string.Empty);
|
||||
_accessToken = (string)Registry.GetValue(RegistryConstants.GithubWatcherRegistryPath, RegistryConstants.GithubAccessTokenKey, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
_userName = (_config[$"SourceWatcher:Github:{RegistryConstants.GithubUserNameKey}"]).ToString();
|
||||
_repoName = (_config[$"SourceWatcher:Github:{RegistryConstants.GithubRepoNameKey}"]).ToString();
|
||||
_branch = (_config[$"SourceWatcher:Github:{RegistryConstants.GithubBranchKey}"]).ToString();
|
||||
_accessToken = (_config[$"SourceWatcher:Github:{RegistryConstants.GithubAccessTokenKey}"]).ToString();
|
||||
}
|
||||
|
||||
_branch = !string.IsNullOrWhiteSpace(_branch) ? _branch : "master";
|
||||
_accessToken = _accessToken ?? string.Empty;
|
||||
}
|
||||
|
||||
private void ValidateConfigurations()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_userName))
|
||||
{
|
||||
throw new ArgumentNullException("Github UserName");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_repoName))
|
||||
{
|
||||
throw new ArgumentNullException("Github RepoName");
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeHttpClient()
|
||||
{
|
||||
_httpClient = new HttpClient
|
||||
{
|
||||
MaxResponseContentBufferSize = Int32.MaxValue,
|
||||
Timeout = TimeSpan.FromSeconds(30)
|
||||
};
|
||||
|
||||
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
_httpClient.DefaultRequestHeaders.Add("User-Agent", _userName);
|
||||
}
|
||||
|
||||
private string AppendQueryStringParams(string url)
|
||||
{
|
||||
var uriBuilder = new UriBuilder(url);
|
||||
var queryParams = HttpUtility.ParseQueryString(uriBuilder.Query);
|
||||
queryParams.Add("access_token", _accessToken);
|
||||
uriBuilder.Query = queryParams.ToString();
|
||||
return uriBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Services
|
||||
{
|
||||
public class GithubEntry
|
||||
{
|
||||
public string Name;
|
||||
public string Path;
|
||||
public string Sha;
|
||||
public string Url;
|
||||
public string Download_url;
|
||||
public string Type;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using Diagnostics.Scripts;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Services
|
||||
{
|
||||
public interface ICache<K, V>
|
||||
{
|
||||
void AddOrUpdate(K key, V value);
|
||||
|
||||
bool TryGetValue(K key, out V value);
|
||||
|
||||
bool TryRemoveValue(K key, out V value);
|
||||
|
||||
IEnumerable<V> GetAll();
|
||||
}
|
||||
|
||||
public class InvokerCacheService : ICache<string, EntityInvoker>
|
||||
{
|
||||
private ConcurrentDictionary<string, EntityInvoker> _collection;
|
||||
|
||||
public InvokerCacheService()
|
||||
{
|
||||
_collection = new ConcurrentDictionary<string, EntityInvoker>();
|
||||
}
|
||||
|
||||
public void AddOrUpdate(string key, EntityInvoker value)
|
||||
{
|
||||
_collection.AddOrUpdate(key.ToLower(), value, (existingKey, oldValue) => value);
|
||||
}
|
||||
|
||||
public IEnumerable<EntityInvoker> GetAll()
|
||||
{
|
||||
return _collection.Values;
|
||||
}
|
||||
|
||||
public bool TryRemoveValue(string key, out EntityInvoker value)
|
||||
{
|
||||
return _collection.TryRemove(key.ToLower(), out value);
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out EntityInvoker value)
|
||||
{
|
||||
return _collection.TryGetValue(key.ToLower(), out value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using Diagnostics.ModelsAndUtils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Services
|
||||
{
|
||||
public interface IResourceService
|
||||
{
|
||||
Task<SiteResource> GetSite(string subscriptionId, string resourceGroup, string siteName, IEnumerable<string> hostNames, string stampName, DateTime startTime, DateTime endTime, string requestId = null);
|
||||
}
|
||||
|
||||
public class ResourceService : IResourceService
|
||||
{
|
||||
private ITenantIdService _tenantIdService;
|
||||
|
||||
public ResourceService(ITenantIdService tenantIdService)
|
||||
{
|
||||
_tenantIdService = tenantIdService;
|
||||
}
|
||||
|
||||
public async Task<SiteResource> GetSite(string subscriptionId, string resourceGroup, string siteName, IEnumerable<string> hostNames, string stampName, DateTime startTime, DateTime endTime, string requestId = null)
|
||||
{
|
||||
SiteResource resource = new SiteResource()
|
||||
{
|
||||
SubscriptionId = subscriptionId,
|
||||
ResourceGroup = resourceGroup,
|
||||
SiteName = siteName,
|
||||
HostNames = hostNames,
|
||||
Stamp = stampName
|
||||
};
|
||||
|
||||
resource.TenantIdList = await _tenantIdService.GetTenantIdForStamp(stampName, startTime, endTime, requestId);
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
using Diagnostics.DataProviders;
|
||||
using Diagnostics.ModelsAndUtils;
|
||||
using Diagnostics.RuntimeHost.Utilities;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Services
|
||||
{
|
||||
public interface ITenantIdService
|
||||
{
|
||||
Task<List<string>> GetTenantIdForStamp(string stamp, DateTime startTime, DateTime endTime, string requestId = null);
|
||||
}
|
||||
|
||||
public class TenantIdService : ITenantIdService
|
||||
{
|
||||
private ConcurrentDictionary<string, List<string>> _tenantCache;
|
||||
private string _queryTemplate;
|
||||
private IDataSourcesConfigurationService _dataSourcesConfigService;
|
||||
private DataProviders.DataProviders _dataProviders;
|
||||
|
||||
public TenantIdService(IDataSourcesConfigurationService dataSourcesConfigService)
|
||||
{
|
||||
_dataSourcesConfigService = dataSourcesConfigService;
|
||||
_tenantCache = new ConcurrentDictionary<string, List<string>>();
|
||||
_queryTemplate =
|
||||
@"RoleInstanceHeartbeat
|
||||
| where TIMESTAMP >= datetime({StartTime}) and TIMESTAMP <= datetime({EndTime}) and PublicHost startswith ""{StampName}""
|
||||
| summarize by Tenant, PublicHost";
|
||||
|
||||
_dataProviders = new DataProviders.DataProviders(_dataSourcesConfigService.Config);
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetTenantIdForStamp(string stamp, DateTime startTime, DateTime endTime, string requestId = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(stamp))
|
||||
{
|
||||
throw new ArgumentNullException("stamp cannot be null.");
|
||||
}
|
||||
|
||||
if (_tenantCache.TryGetValue(stamp.ToLower(), out List<string> tenantIds))
|
||||
{
|
||||
return tenantIds;
|
||||
}
|
||||
|
||||
tenantIds = new List<string>();
|
||||
string startTimeStr = DateTimeHelper.GetDateTimeInUtcFormat(startTime).ToString(HostConstants.KustoTimeFormat);
|
||||
string endTimeStr = DateTimeHelper.GetDateTimeInUtcFormat(endTime).ToString(HostConstants.KustoTimeFormat);
|
||||
|
||||
var query = _queryTemplate
|
||||
.Replace("{StartTime}", startTimeStr)
|
||||
.Replace("{EndTime}", endTimeStr)
|
||||
.Replace("{StampName}", stamp);
|
||||
|
||||
DataTableResponseObject response = await _dataProviders.Kusto.ExecuteQuery(query, stamp, requestId, "GetTenantIdForStamp");
|
||||
DataTable tenantIdTable = DataTableUtility.GetDataTable(response);
|
||||
|
||||
if (tenantIdTable != null && tenantIdTable.Rows.Count > 0)
|
||||
{
|
||||
foreach (DataRow row in tenantIdTable.Rows)
|
||||
{
|
||||
tenantIds.Add(row["Tenant"].ToString());
|
||||
}
|
||||
|
||||
_tenantCache.TryAdd(stamp, tenantIds);
|
||||
}
|
||||
|
||||
return tenantIds;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Services.SourceWatcher
|
||||
{
|
||||
public interface ISourceWatcher
|
||||
{
|
||||
void Start();
|
||||
|
||||
Task WaitForFirstCompletion();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Services.SourceWatcher
|
||||
{
|
||||
public interface ISourceWatcherService
|
||||
{
|
||||
ISourceWatcher Watcher { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using Diagnostics.RuntimeHost.Utilities;
|
||||
using Diagnostics.Scripts;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Services.SourceWatcher
|
||||
{
|
||||
public class SourceWatcherService : ISourceWatcherService
|
||||
{
|
||||
private ISourceWatcher _watcher;
|
||||
|
||||
public ISourceWatcher Watcher => _watcher;
|
||||
|
||||
public SourceWatcherService(IHostingEnvironment env, IConfiguration configuration, ICache<string, EntityInvoker> invokerCacheService)
|
||||
{
|
||||
SourceWatcherType watcherType;
|
||||
|
||||
if (env.IsProduction())
|
||||
{
|
||||
string watcherTypeRegistryValue = Registry.GetValue(RegistryConstants.SourceWatcherRegistryPath, RegistryConstants.WatcherTypeKey, 0).ToString();
|
||||
if(!Enum.TryParse<SourceWatcherType>(watcherTypeRegistryValue, out watcherType))
|
||||
{
|
||||
throw new NotSupportedException($"Source Watcher Type : {watcherTypeRegistryValue} not supported.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
watcherType = Enum.Parse<SourceWatcherType>(configuration[$"SourceWatcher:{RegistryConstants.WatcherTypeKey}"]);
|
||||
}
|
||||
|
||||
switch (watcherType)
|
||||
{
|
||||
case SourceWatcherType.LocalFileSystem:
|
||||
_watcher = new LocalFileSystemWatcher(env, configuration, invokerCacheService);
|
||||
break;
|
||||
case SourceWatcherType.Github:
|
||||
IGithubClient githubClient = new GithubClient(env, configuration);
|
||||
_watcher = new GitHubWatcher(env, configuration, invokerCacheService, githubClient);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("Source Watcher Type not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Services.SourceWatcher
|
||||
{
|
||||
public enum SourceWatcherType
|
||||
{
|
||||
LocalFileSystem = 0,
|
||||
Github = 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
using Diagnostics.RuntimeHost.Utilities;
|
||||
using Diagnostics.Scripts;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Services.SourceWatcher
|
||||
{
|
||||
public class GitHubWatcher : ISourceWatcher
|
||||
{
|
||||
private Task _firstTimeCompletionTask;
|
||||
private IHostingEnvironment _env;
|
||||
private IConfiguration _config;
|
||||
private ICache<string, EntityInvoker> _invokerCache;
|
||||
private IGithubClient _githubClient;
|
||||
private string _rootContentApiPath;
|
||||
private string _lastModifiedMarkerName;
|
||||
private string _deleteMarkerName;
|
||||
private string _cacheIdFileName;
|
||||
private string _etagHeaderName;
|
||||
|
||||
private string _destinationCsxPath;
|
||||
private int _pollingIntervalInSeconds;
|
||||
|
||||
public GitHubWatcher(IHostingEnvironment env, IConfiguration configuration, ICache<string, EntityInvoker> invokerCache, IGithubClient githubClient)
|
||||
{
|
||||
_env = env;
|
||||
_config = configuration;
|
||||
_invokerCache = invokerCache;
|
||||
_githubClient = githubClient;
|
||||
|
||||
_lastModifiedMarkerName = "_lastModified.marker";
|
||||
_deleteMarkerName = "_delete.marker";
|
||||
_cacheIdFileName = "cacheId.txt";
|
||||
_etagHeaderName = "ETag";
|
||||
LoadConfigurations();
|
||||
Start();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_firstTimeCompletionTask = StartWatcherInternal();
|
||||
StartPollingForChanges();
|
||||
}
|
||||
|
||||
public Task WaitForFirstCompletion() => _firstTimeCompletionTask;
|
||||
|
||||
private async Task StartWatcherInternal()
|
||||
{
|
||||
try
|
||||
{
|
||||
DirectoryInfo destDirInfo = new DirectoryInfo(_destinationCsxPath);
|
||||
string destLastModifiedMarker = await FileHelper.GetFileContentAsync(destDirInfo.FullName, _lastModifiedMarkerName);
|
||||
HttpResponseMessage response = await _githubClient.Get(_rootContentApiPath, etag: destLastModifiedMarker);
|
||||
|
||||
if (response.StatusCode >= HttpStatusCode.NotFound)
|
||||
{
|
||||
// TODO: log fatal error
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.NotModified)
|
||||
{
|
||||
/*
|
||||
* If No changes detected on Github Root Directory, skip download.
|
||||
* Make Sure this entity is loaded in Invoker cache for runtime.
|
||||
* This codepath will be mostly used when the process restarts or machine reboot (and no changes are done in scripts source).
|
||||
*/
|
||||
|
||||
foreach (DirectoryInfo subDir in destDirInfo.EnumerateDirectories())
|
||||
{
|
||||
await AddInvokerToCacheIfNeeded(subDir);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
string githubRootContentETag = GetHeaderValue(response, _etagHeaderName).Replace("W/", string.Empty);
|
||||
GithubEntry[] githubDirectories = await response.Content.ReadAsAsyncCustom<GithubEntry[]>();
|
||||
|
||||
foreach (GithubEntry gitHubDir in githubDirectories)
|
||||
{
|
||||
if (!gitHubDir.Type.Equals("dir", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
|
||||
DirectoryInfo subDir = new DirectoryInfo(Path.Combine(destDirInfo.FullName, gitHubDir.Name));
|
||||
if (!subDir.Exists)
|
||||
{
|
||||
subDir.Create();
|
||||
}
|
||||
|
||||
FileHelper.DeleteFileIfExists(subDir.FullName, _deleteMarkerName);
|
||||
string subDirModifiedMarker = await FileHelper.GetFileContentAsync(subDir.FullName, _lastModifiedMarkerName);
|
||||
|
||||
if (subDirModifiedMarker == gitHubDir.Sha)
|
||||
{
|
||||
await AddInvokerToCacheIfNeeded(subDir);
|
||||
continue;
|
||||
}
|
||||
|
||||
await DownloadContentAndUpdateInvokerCache(gitHubDir, subDir);
|
||||
await FileHelper.WriteToFileAsync(subDir.FullName, _lastModifiedMarkerName, gitHubDir.Sha);
|
||||
}
|
||||
|
||||
await SyncLocalDirForDeletedEntriesInGitHub(githubDirectories, destDirInfo);
|
||||
|
||||
await FileHelper.WriteToFileAsync(destDirInfo.FullName, _lastModifiedMarkerName, githubRootContentETag);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO : Log and consume the exception
|
||||
}
|
||||
}
|
||||
|
||||
private async void StartPollingForChanges()
|
||||
{
|
||||
await _firstTimeCompletionTask;
|
||||
|
||||
do
|
||||
{
|
||||
await Task.Delay(_pollingIntervalInSeconds * 1000);
|
||||
await StartWatcherInternal();
|
||||
|
||||
} while (true);
|
||||
}
|
||||
|
||||
private async Task AddInvokerToCacheIfNeeded(DirectoryInfo subDir)
|
||||
{
|
||||
string cacheId = await FileHelper.GetFileContentAsync(subDir.FullName, _cacheIdFileName);
|
||||
if (string.IsNullOrWhiteSpace(cacheId) || !_invokerCache.TryGetValue(cacheId, out EntityInvoker invoker))
|
||||
{
|
||||
FileInfo mostRecentAssembly = GetMostRecentFileByExtension(subDir, ".dll");
|
||||
FileInfo csxScriptFile = GetMostRecentFileByExtension(subDir, ".csx");
|
||||
FileInfo deleteMarkerFile = new FileInfo(Path.Combine(subDir.FullName, _deleteMarkerName));
|
||||
|
||||
if (mostRecentAssembly != default(FileInfo) && csxScriptFile != default(FileInfo) && !deleteMarkerFile.Exists)
|
||||
{
|
||||
string scriptText = await FileHelper.GetFileContentAsync(csxScriptFile.FullName);
|
||||
Assembly asm = Assembly.LoadFrom(mostRecentAssembly.FullName);
|
||||
invoker = new EntityInvoker(new EntityMetadata(scriptText));
|
||||
invoker.InitializeEntryPoint(asm);
|
||||
|
||||
if (invoker.EntryPointDefinitionAttribute != null)
|
||||
{
|
||||
_invokerCache.AddOrUpdate(invoker.EntryPointDefinitionAttribute.Id, invoker);
|
||||
await FileHelper.WriteToFileAsync(subDir.FullName, _cacheIdFileName, invoker.EntryPointDefinitionAttribute.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadContentAndUpdateInvokerCache(GithubEntry parentGithubEntry, DirectoryInfo destDir)
|
||||
{
|
||||
string assemblyName = Guid.NewGuid().ToString();
|
||||
string csxFilePath = string.Empty;
|
||||
string lastCacheId = string.Empty;
|
||||
string cacheIdFilePath = Path.Combine(destDir.FullName, _cacheIdFileName);
|
||||
Assembly asm;
|
||||
|
||||
HttpResponseMessage response = await _githubClient.Get(parentGithubEntry.Url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GithubEntry[] githubFiles = await response.Content.ReadAsAsyncCustom<GithubEntry[]>();
|
||||
|
||||
foreach (GithubEntry githubFile in githubFiles)
|
||||
{
|
||||
string fileExtension = githubFile.Name.Split(new char[] { '.' }).LastOrDefault();
|
||||
if (string.IsNullOrWhiteSpace(githubFile.Download_url) || string.IsNullOrWhiteSpace(fileExtension))
|
||||
{
|
||||
// Skip Downloading any directory (with empty download url) or any file without an extension.
|
||||
continue;
|
||||
}
|
||||
|
||||
string downloadFilePath = Path.Combine(destDir.FullName, githubFile.Name);
|
||||
if (fileExtension.ToLower().Equals("csx"))
|
||||
{
|
||||
csxFilePath = downloadFilePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use Guids for Assembly and PDB Names to ensure uniqueness.
|
||||
downloadFilePath = Path.Combine(destDir.FullName, $"{assemblyName}.{fileExtension.ToLower()}");
|
||||
}
|
||||
|
||||
await _githubClient.DownloadFile(githubFile.Download_url, downloadFilePath);
|
||||
}
|
||||
|
||||
string scriptText = await FileHelper.GetFileContentAsync(csxFilePath);
|
||||
asm = Assembly.LoadFrom(Path.Combine(destDir.FullName, $"{assemblyName}.dll"));
|
||||
|
||||
EntityInvoker newInvoker = new EntityInvoker(new EntityMetadata(scriptText));
|
||||
newInvoker.InitializeEntryPoint(asm);
|
||||
|
||||
// Remove the Old Invoker from Cache
|
||||
lastCacheId = await FileHelper.GetFileContentAsync(cacheIdFilePath);
|
||||
if(!string.IsNullOrWhiteSpace(lastCacheId) && _invokerCache.TryRemoveValue(lastCacheId, out EntityInvoker oldInvoker))
|
||||
{
|
||||
oldInvoker.Dispose();
|
||||
}
|
||||
|
||||
// Add new invoker to Cache and update Cache Id File
|
||||
if (newInvoker.EntryPointDefinitionAttribute != null)
|
||||
{
|
||||
_invokerCache.AddOrUpdate(newInvoker.EntryPointDefinitionAttribute.Id, newInvoker);
|
||||
await FileHelper.WriteToFileAsync(cacheIdFilePath, newInvoker.EntryPointDefinitionAttribute.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SyncLocalDirForDeletedEntriesInGitHub(GithubEntry[] githubDirectories, DirectoryInfo destDirInfo)
|
||||
{
|
||||
if (!destDirInfo.Exists) return;
|
||||
|
||||
foreach(DirectoryInfo subDir in destDirInfo.EnumerateDirectories())
|
||||
{
|
||||
bool dirExistsInGithub = githubDirectories.Any(p => p.Name.Equals(subDir.Name));
|
||||
if (!dirExistsInGithub)
|
||||
{
|
||||
// remove the entry from cache and mark the folder for deletion.s
|
||||
string cacheId = await FileHelper.GetFileContentAsync(subDir.FullName, _cacheIdFileName);
|
||||
if(!string.IsNullOrWhiteSpace(cacheId) && _invokerCache.TryRemoveValue(cacheId, out EntityInvoker invoker))
|
||||
{
|
||||
invoker.Dispose();
|
||||
}
|
||||
|
||||
await FileHelper.WriteToFileAsync(subDir.FullName, _deleteMarkerName, "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetHeaderValue(HttpResponseMessage responseMsg, string headerName)
|
||||
{
|
||||
if (responseMsg.Headers.TryGetValues(headerName, out IEnumerable<string> values))
|
||||
{
|
||||
return values.FirstOrDefault() ?? string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private FileInfo GetMostRecentFileByExtension(DirectoryInfo dir, string extension)
|
||||
{
|
||||
return dir.GetFiles().Where(p => (!string.IsNullOrWhiteSpace(p.Extension) && p.Extension.Equals(extension, StringComparison.OrdinalIgnoreCase)))
|
||||
.OrderByDescending(f=>f.LastWriteTimeUtc).FirstOrDefault();
|
||||
}
|
||||
|
||||
private void LoadConfigurations()
|
||||
{
|
||||
_rootContentApiPath = $@"https://api.github.com/repos/{_githubClient.UserName}/{_githubClient.RepoName}/contents?ref={_githubClient.Branch}";
|
||||
string pollingIntervalvalue = string.Empty;
|
||||
if (_env.IsProduction())
|
||||
{
|
||||
_destinationCsxPath = (string)Registry.GetValue(RegistryConstants.GithubWatcherRegistryPath, RegistryConstants.DestinationScriptsPathKey, string.Empty);
|
||||
pollingIntervalvalue = (string)Registry.GetValue(RegistryConstants.SourceWatcherRegistryPath, RegistryConstants.PollingIntervalInSecondsKey, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
_destinationCsxPath = (_config[$"SourceWatcher:Github:{RegistryConstants.DestinationScriptsPathKey}"]).ToString();
|
||||
pollingIntervalvalue = (_config[$"SourceWatcher:{RegistryConstants.PollingIntervalInSecondsKey}"]).ToString();
|
||||
}
|
||||
|
||||
if(!int.TryParse(pollingIntervalvalue, out _pollingIntervalInSeconds))
|
||||
{
|
||||
_pollingIntervalInSeconds = HostConstants.WatcherDefaultPollingIntervalInSeconds;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(_destinationCsxPath))
|
||||
{
|
||||
Directory.CreateDirectory(_destinationCsxPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
using Diagnostics.RuntimeHost.Utilities;
|
||||
using Diagnostics.Scripts;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Services.SourceWatcher
|
||||
{
|
||||
public class LocalFileSystemWatcher : ISourceWatcher
|
||||
{
|
||||
private Task _firstTimeCompletionTask;
|
||||
private IHostingEnvironment _env;
|
||||
private IConfiguration _config;
|
||||
private ICache<string, EntityInvoker> _invokerCache;
|
||||
private string _localScriptsPath;
|
||||
|
||||
public LocalFileSystemWatcher(IHostingEnvironment env, IConfiguration configuration, ICache<string, EntityInvoker> invokerCache)
|
||||
{
|
||||
_env = env;
|
||||
_config = configuration;
|
||||
_invokerCache = invokerCache;
|
||||
LoadConfigurations();
|
||||
Start();
|
||||
}
|
||||
|
||||
public LocalFileSystemWatcher(string localScriptsSourcePath, ICache<string, EntityInvoker> invokerCache)
|
||||
{
|
||||
_localScriptsPath = localScriptsSourcePath;
|
||||
_invokerCache = invokerCache;
|
||||
Start();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_firstTimeCompletionTask = StartWatcherInternal();
|
||||
}
|
||||
|
||||
public Task WaitForFirstCompletion() => _firstTimeCompletionTask;
|
||||
|
||||
private async Task StartWatcherInternal()
|
||||
{
|
||||
DirectoryInfo srcDirectoryInfo = new DirectoryInfo(_localScriptsPath);
|
||||
foreach (DirectoryInfo srcSubDirInfo in srcDirectoryInfo.GetDirectories())
|
||||
{
|
||||
var files = srcSubDirInfo.GetFiles().OrderByDescending(p => p.LastWriteTimeUtc);
|
||||
var csxFile = files.FirstOrDefault(p => p.Extension.Equals(".csx", StringComparison.OrdinalIgnoreCase));
|
||||
var asmFile = files.FirstOrDefault(p => p.Extension.Equals(".dll", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
string scriptText = string.Empty;
|
||||
if(csxFile != default(FileInfo))
|
||||
{
|
||||
scriptText = await File.ReadAllTextAsync(csxFile.FullName);
|
||||
}
|
||||
|
||||
EntityMetadata scriptMetadata = new EntityMetadata(scriptText);
|
||||
EntityInvoker invoker = new EntityInvoker(scriptMetadata);
|
||||
|
||||
if(asmFile == default(FileInfo))
|
||||
{
|
||||
// TODO : Log Error of missing dll
|
||||
continue;
|
||||
}
|
||||
|
||||
Assembly asm = Assembly.LoadFrom(asmFile.FullName);
|
||||
invoker.InitializeEntryPoint(asm);
|
||||
|
||||
_invokerCache.AddOrUpdate(invoker.EntryPointDefinitionAttribute.Id, invoker);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadConfigurations()
|
||||
{
|
||||
if (_env.IsProduction())
|
||||
{
|
||||
_localScriptsPath = (string)Registry.GetValue(RegistryConstants.LocalWatcherRegistryPath, RegistryConstants.LocalScriptsPathKey, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
_localScriptsPath = (_config[$"SourceWatcher:Local:{RegistryConstants.LocalScriptsPathKey}"]).ToString();
|
||||
}
|
||||
|
||||
if (!Directory.Exists(_localScriptsPath))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"Script Source Directory : {_localScriptsPath} not found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Diagnostics.DataProviders;
|
||||
using Diagnostics.RuntimeHost.Services;
|
||||
using Diagnostics.RuntimeHost.Services.SourceWatcher;
|
||||
using Diagnostics.Scripts;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Diagnostics.RuntimeHost
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMvc();
|
||||
|
||||
services.AddSingleton<IDataSourcesConfigurationService, DataSourcesConfigurationService>();
|
||||
services.AddSingleton<ICompilerHostClient, CompilerHostClient>();
|
||||
services.AddSingleton<ISourceWatcherService, SourceWatcherService>();
|
||||
services.AddSingleton<ICache<string, EntityInvoker>, InvokerCacheService>();
|
||||
services.AddSingleton<ITenantIdService, TenantIdService>();
|
||||
services.AddSingleton<IResourceService, ResourceService>();
|
||||
|
||||
// TODO : Not sure what's the right place for the following code piece.
|
||||
#region Custom Start up Code
|
||||
|
||||
var servicesProvider = services.BuildServiceProvider();
|
||||
var dataSourcesConfigService = servicesProvider.GetService<IDataSourcesConfigurationService>();
|
||||
KustoTokenService.Instance.Initialize(dataSourcesConfigService.Config.KustoConfiguration);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Utilities
|
||||
{
|
||||
internal class HostConstants
|
||||
{
|
||||
internal const int WatcherDefaultPollingIntervalInSeconds = 5 * 60;
|
||||
|
||||
// These Configurations probably should be in Data Providers
|
||||
|
||||
public static TimeSpan KustoDataRetentionPeriod = TimeSpan.FromDays(-30);
|
||||
|
||||
public static TimeSpan KustoDataLatencyPeriod = TimeSpan.FromMinutes(15);
|
||||
|
||||
public const int DefaultTimeGrainInMinutes = 5;
|
||||
|
||||
public const string KustoTimeFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
#region Time Grain Constants
|
||||
|
||||
internal static List<Tuple<TimeSpan, TimeSpan, bool>> TimeGrainOptions = new List<Tuple<TimeSpan, TimeSpan, bool>>
|
||||
{
|
||||
// 5 minute grain - max time range 1 day
|
||||
new Tuple<TimeSpan, TimeSpan, bool>(TimeSpan.FromMinutes(5), TimeSpan.FromDays(1), true),
|
||||
|
||||
// 30 minute grain - max time range 3 days
|
||||
new Tuple<TimeSpan, TimeSpan, bool>(TimeSpan.FromMinutes(30), TimeSpan.FromDays(3), false),
|
||||
|
||||
// 1 hour grain - max time range 7 days
|
||||
new Tuple<TimeSpan, TimeSpan, bool>(TimeSpan.FromHours(1), TimeSpan.FromDays(7), false),
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal class RegistryConstants
|
||||
{
|
||||
internal const string RegistryRootPath = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\IIS Extensions\Web Hosting Framework";
|
||||
|
||||
// Compiler Host Registry Settings
|
||||
internal const string CompilerHostRegistryPath = RegistryRootPath + @"\CompilerHost";
|
||||
internal const string CompilerHostBinaryLocationKey = "CompilerHostBinaryLocation";
|
||||
internal const string CompilerHostPortKey = "CompilerHostPort";
|
||||
internal const string CompilerHostPollingIntervalKey = "PollingIntervalInSeconds";
|
||||
internal const string CompilerHostProcessMemoryThresholdInMBKey = "ProcessMemoryThresholdInMB";
|
||||
|
||||
// Source Watcher Registry Settings
|
||||
internal const string SourceWatcherRegistryPath = RegistryRootPath + @"\SourceWatcher";
|
||||
internal const string WatcherTypeKey = "WatcherType";
|
||||
internal const string PollingIntervalInSecondsKey = "PollingIntervalInSeconds";
|
||||
internal const string LocalWatcherRegistryPath = SourceWatcherRegistryPath + @"\Local";
|
||||
internal const string LocalScriptsPathKey = "LocalScriptsPath";
|
||||
internal const string GithubWatcherRegistryPath = SourceWatcherRegistryPath + @"\Github";
|
||||
internal const string GithubAccessTokenKey = "AccessToken";
|
||||
internal const string GithubUserNameKey = "UserName";
|
||||
internal const string GithubRepoNameKey = "RepoName";
|
||||
internal const string GithubBranchKey = "Branch";
|
||||
internal const string DestinationScriptsPathKey = "DestinationScriptsPath";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Utilities
|
||||
{
|
||||
internal class DateTimeHelper
|
||||
{
|
||||
internal static bool PrepareStartEndTimeWithTimeGrain(string startTime, string endTime, string timeGrain, out DateTime startTimeUtc, out DateTime endTimeUtc, out TimeSpan timeGrainTimeSpan, out string errorMessage)
|
||||
{
|
||||
Tuple<TimeSpan, TimeSpan, bool> selectedTimeGrainOption = null;
|
||||
const string timeGrainParameterName = "timeGrain";
|
||||
bool result = true;
|
||||
errorMessage = string.Empty;
|
||||
|
||||
result = PrepareStartEndTimeUtc(startTime, endTime, out startTimeUtc, out endTimeUtc, out errorMessage);
|
||||
|
||||
var defaultTimeGrainOption = HostConstants.TimeGrainOptions.FirstOrDefault(t => t.Item3);
|
||||
timeGrainTimeSpan = defaultTimeGrainOption.Item1;
|
||||
|
||||
if (!string.IsNullOrEmpty(timeGrain))
|
||||
{
|
||||
TimeSpan parsedTimeGrain;
|
||||
var success = ParseXmlDurationParameter(timeGrainParameterName, timeGrain, null, out parsedTimeGrain);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
result = false;
|
||||
errorMessage = string.Concat(errorMessage, "Invalid Time Grain.");
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedTimeGrainOption = HostConstants.TimeGrainOptions.FirstOrDefault(t => t.Item1.Equals(parsedTimeGrain));
|
||||
if (selectedTimeGrainOption == null)
|
||||
{
|
||||
result = false;
|
||||
errorMessage = string.Concat(errorMessage, "Invalid Time Grain.");
|
||||
}
|
||||
else
|
||||
{
|
||||
timeGrainTimeSpan = selectedTimeGrainOption.Item1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startTimeUtc = GetDateTimeInUtcFormat(RoundDownTime(startTimeUtc, timeGrainTimeSpan));
|
||||
endTimeUtc = GetDateTimeInUtcFormat(RoundDownTime(endTimeUtc, timeGrainTimeSpan));
|
||||
|
||||
if (startTimeUtc == endTimeUtc)
|
||||
{
|
||||
endTimeUtc = selectedTimeGrainOption != null ? startTimeUtc.Add(selectedTimeGrainOption.Item2) : startTimeUtc.Add(TimeSpan.FromDays(1));
|
||||
}
|
||||
|
||||
//TODO: Here we could use Item2 in selectedTimeGrainOption to limit the length of time that you can query for each time grain
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static bool PrepareStartEndTimeUtc(string startTime, string endTime, out DateTime startTimeUtc, out DateTime endTimeUtc, out string errorMessage)
|
||||
{
|
||||
//1. no startTime, no endTime => return current time - 24 hours, current time
|
||||
//2. startTime, no endTime => return start time, end time = start time + 24 hours
|
||||
//3. no startTime, endTime => return start time = end time - 24 hours, end time
|
||||
//4. startTime, endTime => return start time, end time
|
||||
|
||||
DateTime currentUtcTime = GetDateTimeInUtcFormat(DateTime.UtcNow);
|
||||
bool result = true;
|
||||
errorMessage = string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(startTime) && string.IsNullOrWhiteSpace(endTime))
|
||||
{
|
||||
endTimeUtc = currentUtcTime;
|
||||
startTimeUtc = endTimeUtc.AddDays(-1);
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(startTime))
|
||||
{
|
||||
result = ParseDateTimeParameter("endTime", endTime, currentUtcTime, out endTimeUtc);
|
||||
startTimeUtc = endTimeUtc.AddDays(-1);
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(endTime))
|
||||
{
|
||||
result = ParseDateTimeParameter("startTime", startTime, currentUtcTime.AddDays(-1), out startTimeUtc);
|
||||
endTimeUtc = startTimeUtc.AddDays(1);
|
||||
if (endTimeUtc > currentUtcTime)
|
||||
{
|
||||
endTimeUtc = currentUtcTime;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ParseDateTimeParameter("endTime", endTime, currentUtcTime, out endTimeUtc);
|
||||
result &= ParseDateTimeParameter("startTime", startTime, currentUtcTime.AddDays(-1), out startTimeUtc);
|
||||
}
|
||||
|
||||
if (result == false)
|
||||
{
|
||||
errorMessage = "Cannot parse invalid date time. Valid Time format is yyyy-mm-ddThh:mm";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (startTimeUtc > endTimeUtc)
|
||||
{
|
||||
errorMessage = "Invalid Start Time and End Time. End Time cannot be earlier than Start Time.";
|
||||
return false;
|
||||
}
|
||||
else if (startTimeUtc > currentUtcTime)
|
||||
{
|
||||
errorMessage = "Invalid Start Time. Start Time cannot be a future date.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (startTimeUtc < DateTime.UtcNow.Add(HostConstants.KustoDataRetentionPeriod))
|
||||
{
|
||||
errorMessage = $"Invalid Start Time. Start Time cannot be earlier than {Math.Abs(HostConstants.KustoDataRetentionPeriod.Days)} days.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (endTimeUtc - startTimeUtc > TimeSpan.FromHours(24))
|
||||
{
|
||||
errorMessage = "Invalid Time Range. Time Range cannot be more than 24 hours.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool ParseDateTimeParameter(string parameterName, string parameterValue, DateTime defaultValue, out DateTime dateObj)
|
||||
{
|
||||
dateObj = defaultValue;
|
||||
if (!string.IsNullOrEmpty(parameterValue))
|
||||
{
|
||||
DateTime temp;
|
||||
bool result = DateTime.TryParse(parameterValue, CultureInfo.InvariantCulture, DateTimeStyles.None, out temp);
|
||||
if (result)
|
||||
{
|
||||
dateObj = GetDateTimeInUtcFormat(temp);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static DateTime GetDateTimeInUtcFormat(DateTime dateTime)
|
||||
{
|
||||
if (dateTime.Kind == DateTimeKind.Unspecified)
|
||||
{
|
||||
return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
return dateTime.ToUniversalTime();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Round down Date time.
|
||||
/// </summary>
|
||||
/// <param name="dateTime">Date Time to round down.</param>
|
||||
/// <param name="roundDownBy">Round down value.</param>
|
||||
/// <returns>Rounded down Date Time.</returns>
|
||||
public static DateTime RoundDownTime(DateTime dateTime, TimeSpan roundDownBy)
|
||||
{
|
||||
return new DateTime((dateTime.Ticks / roundDownBy.Ticks) * roundDownBy.Ticks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Round up Date time.
|
||||
/// </summary>
|
||||
/// <param name="dateTime">Date Time to round up.</param>
|
||||
/// <param name="roundUpBy">Round up value.</param>
|
||||
/// <returns>Rounded up Date Time.</returns>
|
||||
internal static DateTime RoundUpTime(DateTime dateTime, TimeSpan roundUpBy)
|
||||
{
|
||||
return new DateTime(((dateTime.Ticks + roundUpBy.Ticks) / roundUpBy.Ticks) * roundUpBy.Ticks);
|
||||
}
|
||||
|
||||
internal static bool ParseXmlDurationParameter(string parameterName, string parameterValue, TimeSpan? defaultValue, out TimeSpan duration)
|
||||
{
|
||||
duration = new TimeSpan();
|
||||
TimeSpan? ret = defaultValue;
|
||||
bool parseFailed = string.IsNullOrEmpty(parameterValue);
|
||||
if (!parseFailed)
|
||||
{
|
||||
try
|
||||
{
|
||||
ret = System.Xml.XmlConvert.ToTimeSpan(parameterValue);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
parseFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// throw exception if parsing failed and no default value is provided
|
||||
if (parseFailed && !defaultValue.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
duration = (TimeSpan)ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static DateTime EpochTimeToDateTime(long unixTime)
|
||||
{
|
||||
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
return epoch.AddSeconds(unixTime);
|
||||
}
|
||||
|
||||
internal static long DateTimeToEpochTime(DateTime date)
|
||||
{
|
||||
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
return Convert.ToInt64((date - epoch).TotalSeconds);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Utilities
|
||||
{
|
||||
internal class FileHelper
|
||||
{
|
||||
internal static Task<string> GetFileContentAsync(string dir, string fileName)
|
||||
{
|
||||
string filePath = Path.Combine(dir, fileName);
|
||||
return GetFileContentAsync(filePath);
|
||||
}
|
||||
|
||||
internal static async Task<string> GetFileContentAsync(string filePath)
|
||||
{
|
||||
string fileContent = string.Empty;
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
fileContent = await File.ReadAllTextAsync(filePath);
|
||||
}
|
||||
|
||||
return fileContent;
|
||||
}
|
||||
|
||||
internal static Task WriteToFileAsync(string dir, string fileName, string content)
|
||||
{
|
||||
return WriteToFileAsync(Path.Combine(dir, fileName), content);
|
||||
}
|
||||
|
||||
internal static Task WriteToFileAsync(string filePath, string content)
|
||||
{
|
||||
return File.WriteAllTextAsync(filePath, content);
|
||||
}
|
||||
|
||||
internal static void DeleteFileIfExists(string dir, string fileName)
|
||||
{
|
||||
string filePath = Path.Combine(dir, fileName);
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task CopyContentsAsync(DirectoryInfo srcDirInfo, DirectoryInfo destinationDirInfo, string customNameForDllFile = null)
|
||||
{
|
||||
foreach (var fileInfo in srcDirInfo.GetFiles())
|
||||
{
|
||||
string desiredFileName = fileInfo.Name;
|
||||
|
||||
if (IsDllorPdb(fileInfo) && !string.IsNullOrWhiteSpace(customNameForDllFile))
|
||||
{
|
||||
desiredFileName = $"{customNameForDllFile}{fileInfo.Extension}";
|
||||
}
|
||||
|
||||
using (FileStream SourceStream = File.Open(fileInfo.FullName, FileMode.Open))
|
||||
{
|
||||
using (FileStream DestinationStream = File.Create(Path.Combine(destinationDirInfo.FullName, desiredFileName)))
|
||||
{
|
||||
await SourceStream.CopyToAsync(DestinationStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsDllorPdb(FileInfo fileInfo)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(fileInfo.Extension)
|
||||
&& ((fileInfo.Extension.Equals(".dll", StringComparison.OrdinalIgnoreCase)
|
||||
|| fileInfo.Extension.Equals(".pdb", StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Utilities
|
||||
{
|
||||
public static class HttpExtensions
|
||||
{
|
||||
public static async Task<T> ReadAsAsyncCustom<T>(this HttpContent value)
|
||||
{
|
||||
string responseString = await value.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<T>(responseString);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.RuntimeHost.Utilities
|
||||
{
|
||||
public class UriElements
|
||||
{
|
||||
public const string HealthPing = "/healthping";
|
||||
|
||||
public const string WebResourceProviderName = "Microsoft.Web";
|
||||
public const string NetworkResourceProviderName = "Microsoft.Network";
|
||||
|
||||
public const string ResourceRoot = "subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/" + WebResourceProviderName;
|
||||
public const string SitesResource = ResourceRoot + "/sites/{siteName}";
|
||||
public const string Diagnostics = "/diagnostics";
|
||||
|
||||
public const string Query = "query";
|
||||
public const string Detectors = "detectors";
|
||||
public const string DetectorResource = "/{detectorId}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"Debug": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"Console": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CompilerHost": {
|
||||
"CompilerHostBinaryLocation": "",
|
||||
"CompilerHostPort": "7000",
|
||||
"PollingIntervalInSeconds": "60",
|
||||
"ProcessMemoryThresholdInMB": "300"
|
||||
},
|
||||
"SourceWatcher": {
|
||||
"WatcherType": "1",
|
||||
"PollingIntervalInSeconds" : "10",
|
||||
"Local": {
|
||||
"LocalScriptsPath": ""
|
||||
},
|
||||
"Github": {
|
||||
"DestinationScriptsPath": "",
|
||||
"UserName": "",
|
||||
"RepoName": "",
|
||||
"Branch": "master",
|
||||
"AccessToken": ""
|
||||
}
|
||||
},
|
||||
"Kusto": {
|
||||
"ClientId": "",
|
||||
"AppKey": "",
|
||||
"DBName": "",
|
||||
"KustoRegionGroupings": "",
|
||||
"KustoClusterNameGroupings": ""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Diagnostics.Scripts.CompilationService
|
||||
{
|
||||
public class AnalysisCompilation : CompilationBase
|
||||
{
|
||||
private static string _entryPointMethodName = "Run";
|
||||
|
||||
public AnalysisCompilation() : base(EntryPointResolutionType.MethodName, _entryPointMethodName)
|
||||
{
|
||||
}
|
||||
|
||||
public AnalysisCompilation(Compilation compilation) : base(compilation, EntryPointResolutionType.MethodName, _entryPointMethodName)
|
||||
{
|
||||
}
|
||||
protected override ImmutableArray<DiagnosticAnalyzer> GetCodeAnalyzers()
|
||||
{
|
||||
return ImmutableArray.Create<DiagnosticAnalyzer>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using Diagnostics.Scripts.CompilationService.Interfaces;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.Scripts.CompilationService
|
||||
{
|
||||
public class AnalysisCompilationService : CompilationServiceBase
|
||||
{
|
||||
public AnalysisCompilationService(EntityMetadata entityMetadata, ScriptOptions scriptOptions) : base(entityMetadata, scriptOptions)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<ICompilation> CreateCompilationObject(Compilation scriptCompilation) => Task.FromResult<ICompilation>(new AnalysisCompilation(scriptCompilation));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
using Diagnostics.Scripts.CompilationService.Interfaces;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.Scripts.CompilationService
|
||||
{
|
||||
public abstract class CompilationBase : ICompilation
|
||||
{
|
||||
private readonly Compilation _compilation;
|
||||
private readonly EntryPointResolutionType _resolutionType;
|
||||
private readonly string _entryPointName;
|
||||
|
||||
protected abstract ImmutableArray<DiagnosticAnalyzer> GetCodeAnalyzers();
|
||||
|
||||
public CompilationBase(EntryPointResolutionType resolutionType, string entryPointName)
|
||||
{
|
||||
_resolutionType = resolutionType;
|
||||
_entryPointName = entryPointName;
|
||||
}
|
||||
|
||||
public CompilationBase(Compilation compilation, EntryPointResolutionType resolutionType, string entryPointName)
|
||||
{
|
||||
_compilation = compilation;
|
||||
_resolutionType = resolutionType;
|
||||
_entryPointName = entryPointName;
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<Diagnostic>> GetDiagnosticsAsync()
|
||||
{
|
||||
if(_compilation == null)
|
||||
{
|
||||
throw new ArgumentException("Compilation Object not initialized.");
|
||||
}
|
||||
|
||||
ImmutableArray<DiagnosticAnalyzer> analyzers = GetCodeAnalyzers();
|
||||
if (analyzers.IsEmpty)
|
||||
{
|
||||
return Task.Factory.StartNew(() => _compilation.GetDiagnostics());
|
||||
}
|
||||
|
||||
return _compilation.WithAnalyzers(GetCodeAnalyzers()).GetAllDiagnosticsAsync();
|
||||
}
|
||||
|
||||
public EntityMethodSignature GetEntryPointSignature()
|
||||
{
|
||||
if (_compilation == null)
|
||||
{
|
||||
return new EntityMethodSignature(_entryPointName);
|
||||
}
|
||||
|
||||
var methods = _compilation.ScriptClass
|
||||
.GetMembers()
|
||||
.OfType<IMethodSymbol>();
|
||||
|
||||
IMethodSymbol entryPointReference = default(IMethodSymbol);
|
||||
|
||||
switch (_resolutionType)
|
||||
{
|
||||
case EntryPointResolutionType.Attribute:
|
||||
break;
|
||||
case EntryPointResolutionType.MethodName:
|
||||
default:
|
||||
entryPointReference = GetMethodByName(methods, _entryPointName);
|
||||
break;
|
||||
}
|
||||
|
||||
if (entryPointReference == default(IMethodSymbol))
|
||||
{
|
||||
throw new ScriptCompilationException($"No Entry point found. Entry point resoultion type : {_resolutionType} , value : {_entryPointName}");
|
||||
}
|
||||
|
||||
var methodParameters = entryPointReference.Parameters.Select(p => new EntityParameter(p.Name, GetFullTypeName(p.Type), p.IsOptional, p.RefKind));
|
||||
var attributes = entryPointReference.GetAttributes();
|
||||
return new EntityMethodSignature(
|
||||
entryPointReference.ContainingType.ToDisplayString(),
|
||||
entryPointReference.Name,
|
||||
ImmutableArray.CreateRange(methodParameters.ToArray()),
|
||||
GetFullTypeName(entryPointReference.ReturnType),
|
||||
attributes);
|
||||
}
|
||||
|
||||
public Task<Assembly> EmitAssemblyAsync()
|
||||
{
|
||||
if (_compilation == null)
|
||||
{
|
||||
throw new ArgumentException("Compilation Object not initialized.");
|
||||
}
|
||||
|
||||
return Task.Factory.StartNew<Assembly>(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var assemblyStream = new MemoryStream())
|
||||
using (var pdbStream = new MemoryStream())
|
||||
{
|
||||
_compilation.Emit(assemblyStream, pdbStream);
|
||||
return Assembly.Load(assemblyStream.GetBuffer(), pdbStream.GetBuffer());
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO : Need to throw custom exception?
|
||||
throw;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<string> SaveAssemblyAsync(string assemblyPath)
|
||||
{
|
||||
if (_compilation == null)
|
||||
{
|
||||
throw new ArgumentException("Compilation Object not initialized.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(assemblyPath))
|
||||
{
|
||||
throw new ArgumentNullException("assemblyPath");
|
||||
}
|
||||
|
||||
string pdbPath = assemblyPath;
|
||||
if (!assemblyPath.EndsWith(".dll"))
|
||||
{
|
||||
assemblyPath = $"{assemblyPath}.dll";
|
||||
pdbPath = $"{pdbPath}.pdb";
|
||||
}
|
||||
else
|
||||
{
|
||||
pdbPath = assemblyPath.Replace(".dll", ".pdb");
|
||||
}
|
||||
|
||||
|
||||
if (File.Exists(assemblyPath))
|
||||
{
|
||||
throw new IOException($"Assembly File already exists : {assemblyPath}");
|
||||
}
|
||||
|
||||
using (var assemblyStream = new MemoryStream())
|
||||
using (var pdbStream = new MemoryStream())
|
||||
{
|
||||
_compilation.Emit(assemblyStream, pdbStream);
|
||||
|
||||
using (FileStream asmFs = File.Create(assemblyPath))
|
||||
using (FileStream pdbFs = File.Create(pdbPath))
|
||||
{
|
||||
assemblyStream.Position = 0;
|
||||
pdbStream.Position = 0;
|
||||
await assemblyStream.CopyToAsync(asmFs);
|
||||
await pdbStream.CopyToAsync(pdbFs);
|
||||
}
|
||||
}
|
||||
|
||||
return assemblyPath;
|
||||
}
|
||||
|
||||
public Task<Tuple<string, string>> GetAssemblyBytesAsync()
|
||||
{
|
||||
if (_compilation == null)
|
||||
{
|
||||
throw new ArgumentException("Compilation Object not initialized.");
|
||||
}
|
||||
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
Tuple<string, string> asmEncodedBytes;
|
||||
try
|
||||
{
|
||||
using (var assemblyStream = new MemoryStream())
|
||||
using (var pdbStream = new MemoryStream())
|
||||
{
|
||||
_compilation.Emit(assemblyStream, pdbStream);
|
||||
asmEncodedBytes = new Tuple<string, string>(
|
||||
Convert.ToBase64String(assemblyStream.GetBuffer()),
|
||||
Convert.ToBase64String(pdbStream.GetBuffer()));
|
||||
|
||||
return asmEncodedBytes;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO : Need to throw custom exception?
|
||||
throw;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private IMethodSymbol GetMethodByName(IEnumerable<IMethodSymbol> methods, string methodName)
|
||||
{
|
||||
var namedMethods = methods
|
||||
.Where(m => m.DeclaredAccessibility == Accessibility.Public && string.Compare(m.Name, methodName, StringComparison.Ordinal) == 0)
|
||||
.ToList();
|
||||
|
||||
if (namedMethods.Count == 1)
|
||||
{
|
||||
return namedMethods.First();
|
||||
}
|
||||
|
||||
// If we have multiple public methods matching the provided name, throw a compilation exception
|
||||
if (namedMethods.Count > 1)
|
||||
{
|
||||
throw new ScriptCompilationException($"Multiple Entry Point Methods with name {methodName} found.");
|
||||
}
|
||||
|
||||
return default(IMethodSymbol);
|
||||
}
|
||||
|
||||
private string GetFullTypeName(ITypeSymbol type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return type.ContainingAssembly == null
|
||||
? type.ToDisplayString()
|
||||
: string.Format(CultureInfo.InvariantCulture, "{0}, {1}", type.ToDisplayString(), type.ContainingAssembly.ToDisplayString());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using Diagnostics.Scripts.CompilationService.Interfaces;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Scripting;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.Scripts.CompilationService
|
||||
{
|
||||
public abstract class CompilationServiceBase : ICompilationService
|
||||
{
|
||||
private EntityMetadata _entityMetadata;
|
||||
|
||||
private ScriptOptions _scriptOptions;
|
||||
|
||||
public CompilationServiceBase(EntityMetadata entityMetadata, ScriptOptions scriptOptions)
|
||||
{
|
||||
_entityMetadata = entityMetadata;
|
||||
_scriptOptions = scriptOptions;
|
||||
}
|
||||
|
||||
public Task<ICompilation> GetCompilationAsync()
|
||||
{
|
||||
Script<object> script = CSharpScript.Create<object>(_entityMetadata.ScriptText, _scriptOptions);
|
||||
return CreateCompilationObject(GetScriptCompilation(script));
|
||||
}
|
||||
|
||||
private Compilation GetScriptCompilation(Script<object> script)
|
||||
{
|
||||
return script.GetCompilation();
|
||||
}
|
||||
|
||||
protected abstract Task<ICompilation> CreateCompilationObject(Compilation scriptCompilation);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using Diagnostics.Scripts.CompilationService.Interfaces;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using System;
|
||||
|
||||
namespace Diagnostics.Scripts.CompilationService
|
||||
{
|
||||
public static class CompilationServiceFactory
|
||||
{
|
||||
public static ICompilationService CreateService(EntityMetadata metaData, ScriptOptions scriptOptions)
|
||||
{
|
||||
if(metaData == null)
|
||||
{
|
||||
throw new ArgumentNullException("Entity Metadata cannot be null.");
|
||||
}
|
||||
|
||||
if(scriptOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException("ScriptOptions cannot be null.");
|
||||
}
|
||||
|
||||
switch (metaData.Type)
|
||||
{
|
||||
case EntityType.Signal:
|
||||
return new SignalCompilationService(metaData, scriptOptions);
|
||||
case EntityType.Detector:
|
||||
return new DetectorCompilationService(metaData, scriptOptions);
|
||||
case EntityType.Analysis:
|
||||
return new AnalysisCompilationService(metaData, scriptOptions);
|
||||
default:
|
||||
throw new NotSupportedException($"EntityMetaData with type {metaData.Type} is invalid.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Diagnostics.Scripts.CompilationService
|
||||
{
|
||||
public class DetectorCompilation : CompilationBase
|
||||
{
|
||||
private static string _entryPointMethodName = "Run";
|
||||
|
||||
public DetectorCompilation() : base(EntryPointResolutionType.MethodName, _entryPointMethodName)
|
||||
{
|
||||
}
|
||||
|
||||
public DetectorCompilation(Compilation compilation) : base(compilation, EntryPointResolutionType.MethodName, _entryPointMethodName)
|
||||
{
|
||||
}
|
||||
|
||||
protected override ImmutableArray<DiagnosticAnalyzer> GetCodeAnalyzers()
|
||||
{
|
||||
return ImmutableArray.Create<DiagnosticAnalyzer>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using Diagnostics.Scripts.CompilationService.Interfaces;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.Scripts.CompilationService
|
||||
{
|
||||
public class DetectorCompilationService : CompilationServiceBase
|
||||
{
|
||||
public DetectorCompilationService(EntityMetadata entityMetadata, ScriptOptions scriptOptions) : base(entityMetadata, scriptOptions)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<ICompilation> CreateCompilationObject(Compilation scriptCompilation) => Task.FromResult<ICompilation>(new DetectorCompilation(scriptCompilation));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.Scripts.CompilationService.Interfaces
|
||||
{
|
||||
public interface ICompilation
|
||||
{
|
||||
Task<ImmutableArray<Diagnostic>> GetDiagnosticsAsync();
|
||||
|
||||
EntityMethodSignature GetEntryPointSignature();
|
||||
|
||||
Task<Assembly> EmitAssemblyAsync();
|
||||
|
||||
Task<string> SaveAssemblyAsync(string assemblyPath);
|
||||
|
||||
Task<Tuple<string, string>> GetAssemblyBytesAsync();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.Scripts.CompilationService.Interfaces
|
||||
{
|
||||
public interface ICompilationService
|
||||
{
|
||||
Task<ICompilation> GetCompilationAsync();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using Diagnostics.Scripts.CompilationService.Interfaces;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.Scripts.CompilationService
|
||||
{
|
||||
public sealed class SignalCompilationService : CompilationServiceBase
|
||||
{
|
||||
public SignalCompilationService(EntityMetadata entityMetadata, ScriptOptions scriptOptions) : base(entityMetadata, scriptOptions)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<ICompilation> CreateCompilationObject(Compilation scriptCompilation) => Task.FromResult<ICompilation>(new SignalCompilation(scriptCompilation));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Diagnostics.Scripts.CompilationService
|
||||
{
|
||||
public class SignalCompilation : CompilationBase
|
||||
{
|
||||
private static string _entryPointMethodName = "Run";
|
||||
|
||||
public SignalCompilation() : base(EntryPointResolutionType.MethodName, _entryPointMethodName)
|
||||
{
|
||||
}
|
||||
|
||||
public SignalCompilation(Compilation compilation) : base(compilation, EntryPointResolutionType.MethodName, _entryPointMethodName)
|
||||
{
|
||||
}
|
||||
|
||||
protected override ImmutableArray<DiagnosticAnalyzer> GetCodeAnalyzers()
|
||||
{
|
||||
return ImmutableArray.Create<DiagnosticAnalyzer>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.Scripts
|
||||
{
|
||||
public class ScriptCompilationException : Exception
|
||||
{
|
||||
public IEnumerable<string> CompilationOutput;
|
||||
|
||||
public ScriptCompilationException()
|
||||
{
|
||||
CompilationOutput = Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
public ScriptCompilationException(IEnumerable<string> compilationErrors)
|
||||
: base("script compliation failed.")
|
||||
{
|
||||
CompilationOutput = compilationErrors;
|
||||
}
|
||||
|
||||
public ScriptCompilationException(string message)
|
||||
: base(message)
|
||||
{
|
||||
CompilationOutput = Enumerable.Empty<string>();
|
||||
CompilationOutput = CompilationOutput.Concat(new string[] { message });
|
||||
}
|
||||
|
||||
public ScriptCompilationException(string message, Exception inner)
|
||||
: base(message, inner)
|
||||
{
|
||||
CompilationOutput = Enumerable.Empty<string>();
|
||||
CompilationOutput = CompilationOutput.Concat(new string[] { message });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="2.7.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="CompilationService\Analysis\CodeAnalyzers\" />
|
||||
<Folder Include="CompilationService\Detector\CodeAnalyzers\" />
|
||||
<Folder Include="CompilationService\Signal\CodeAnalyzers\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Diagnostics.ModelsAndUtils\Diagnostics.ModelsAndUtils.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,250 @@
|
|||
using Diagnostics.ModelsAndUtils;
|
||||
using Diagnostics.Scripts.CompilationService;
|
||||
using Diagnostics.Scripts.CompilationService.Interfaces;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.Scripts
|
||||
{
|
||||
public sealed class EntityInvoker : IDisposable
|
||||
{
|
||||
private EntityMetadata _entityMetaData;
|
||||
private ImmutableArray<string> _frameworkReferences;
|
||||
private ImmutableArray<string> _frameworkImports;
|
||||
private ICompilation _compilation;
|
||||
private ImmutableArray<Diagnostic> _diagnostics;
|
||||
private MethodInfo _entryPointMethodInfo;
|
||||
private Definition _entryPointDefinitionAttribute;
|
||||
|
||||
public bool IsCompilationSuccessful { get; private set; }
|
||||
|
||||
public IEnumerable<string> CompilationOutput { get; private set; }
|
||||
|
||||
public EntityMetadata EntityMetadata => _entityMetaData;
|
||||
|
||||
public Definition EntryPointDefinitionAttribute => _entryPointDefinitionAttribute;
|
||||
|
||||
public EntityInvoker(EntityMetadata entityMetadata)
|
||||
{
|
||||
_entityMetaData = entityMetadata;
|
||||
_frameworkReferences = ImmutableArray.Create<string>();
|
||||
_frameworkImports = ImmutableArray.Create<string>();
|
||||
CompilationOutput = Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
public EntityInvoker(EntityMetadata entityMetadata, ImmutableArray<string> frameworkReferences)
|
||||
{
|
||||
_entityMetaData = entityMetadata;
|
||||
_frameworkReferences = frameworkReferences;
|
||||
_frameworkImports = ImmutableArray.Create<string>();
|
||||
CompilationOutput = Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
public EntityInvoker(EntityMetadata entityMetadata, ImmutableArray<string> frameworkReferences, ImmutableArray<string> frameworkImports)
|
||||
{
|
||||
_entityMetaData = entityMetadata;
|
||||
_frameworkImports = frameworkImports;
|
||||
_frameworkReferences = frameworkReferences;
|
||||
CompilationOutput = Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the entry point by compiling the script and loading/saving the assembly
|
||||
/// </summary>
|
||||
/// <param name="assemblyInitType"></param>
|
||||
/// <returns></returns>
|
||||
public async Task InitializeEntryPointAsync()
|
||||
{
|
||||
ICompilationService compilationService = CompilationServiceFactory.CreateService(_entityMetaData, GetScriptOptions(_frameworkReferences));
|
||||
_compilation = await compilationService.GetCompilationAsync();
|
||||
_diagnostics = await _compilation.GetDiagnosticsAsync();
|
||||
|
||||
IsCompilationSuccessful = !_diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error);
|
||||
CompilationOutput = _diagnostics.Select(m => m.ToString());
|
||||
|
||||
if (IsCompilationSuccessful)
|
||||
{
|
||||
try
|
||||
{
|
||||
EntityMethodSignature methodSignature = _compilation.GetEntryPointSignature();
|
||||
Assembly assembly = await _compilation.EmitAssemblyAsync();
|
||||
_entryPointMethodInfo = methodSignature.GetMethod(assembly);
|
||||
|
||||
InitializeAttributes();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
if(ex is ScriptCompilationException)
|
||||
{
|
||||
var scriptEx = (ScriptCompilationException)ex;
|
||||
IsCompilationSuccessful = false;
|
||||
|
||||
if (scriptEx.CompilationOutput.Any())
|
||||
{
|
||||
CompilationOutput = CompilationOutput.Concat(scriptEx.CompilationOutput);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the entry point from already loaded assembly.
|
||||
/// </summary>
|
||||
/// <param name="asm">Assembly</param>
|
||||
public void InitializeEntryPoint(Assembly asm)
|
||||
{
|
||||
if (asm == null)
|
||||
{
|
||||
throw new ArgumentNullException("Assembly");
|
||||
}
|
||||
|
||||
// TODO : We might need to create a factory to get compilation object based on type.
|
||||
_compilation = new SignalCompilation();
|
||||
|
||||
// If assembly is present, that means compilation was successful.
|
||||
IsCompilationSuccessful = true;
|
||||
|
||||
try
|
||||
{
|
||||
EntityMethodSignature methodSignature = _compilation.GetEntryPointSignature();
|
||||
_entryPointMethodInfo = methodSignature.GetMethod(asm);
|
||||
InitializeAttributes();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is ScriptCompilationException)
|
||||
{
|
||||
var scriptEx = (ScriptCompilationException)ex;
|
||||
IsCompilationSuccessful = false;
|
||||
|
||||
if (scriptEx.CompilationOutput.Any())
|
||||
{
|
||||
CompilationOutput = CompilationOutput.Concat(scriptEx.CompilationOutput);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<object> Invoke(object[] parameters)
|
||||
{
|
||||
if (!IsCompilationSuccessful)
|
||||
{
|
||||
throw new ScriptCompilationException(CompilationOutput);
|
||||
}
|
||||
|
||||
int actualParameterCount = _entryPointMethodInfo.GetParameters().Length;
|
||||
parameters = parameters.Take(actualParameterCount).ToArray();
|
||||
|
||||
object result = _entryPointMethodInfo.Invoke(null, parameters);
|
||||
|
||||
if (result is Task)
|
||||
{
|
||||
result = await ((Task)result).ContinueWith(t => GetTaskResult(t));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<string> SaveAssemblyToDiskAsync(string assemblyPath)
|
||||
{
|
||||
ICompilationService compilationService = CompilationServiceFactory.CreateService(_entityMetaData, GetScriptOptions(_frameworkReferences));
|
||||
_compilation = await compilationService.GetCompilationAsync();
|
||||
_diagnostics = await _compilation.GetDiagnosticsAsync();
|
||||
|
||||
IsCompilationSuccessful = !_diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error);
|
||||
CompilationOutput = _diagnostics.Select(m => m.ToString());
|
||||
|
||||
if (!IsCompilationSuccessful)
|
||||
{
|
||||
throw new ScriptCompilationException(CompilationOutput);
|
||||
}
|
||||
|
||||
return await _compilation.SaveAssemblyAsync(assemblyPath);
|
||||
}
|
||||
|
||||
public async Task<Tuple<string, string>> GetAssemblyBytesAsync()
|
||||
{
|
||||
ICompilationService compilationService = CompilationServiceFactory.CreateService(_entityMetaData, GetScriptOptions(_frameworkReferences));
|
||||
_compilation = await compilationService.GetCompilationAsync();
|
||||
_diagnostics = await _compilation.GetDiagnosticsAsync();
|
||||
|
||||
IsCompilationSuccessful = !_diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error);
|
||||
CompilationOutput = _diagnostics.Select(m => m.ToString());
|
||||
|
||||
if (!IsCompilationSuccessful)
|
||||
{
|
||||
throw new ScriptCompilationException(CompilationOutput);
|
||||
}
|
||||
|
||||
return await _compilation.GetAssemblyBytesAsync();
|
||||
}
|
||||
|
||||
private void InitializeAttributes()
|
||||
{
|
||||
if(_entryPointMethodInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_entryPointDefinitionAttribute = _entryPointMethodInfo.GetCustomAttribute<Definition>();
|
||||
}
|
||||
|
||||
private ScriptOptions GetScriptOptions(ImmutableArray<string> frameworkReferences)
|
||||
{
|
||||
ScriptOptions scriptOptions = ScriptOptions.Default;
|
||||
|
||||
if (!frameworkReferences.IsDefaultOrEmpty)
|
||||
{
|
||||
scriptOptions = ScriptOptions.Default
|
||||
.WithReferences(frameworkReferences);
|
||||
}
|
||||
|
||||
if (!_frameworkImports.IsDefaultOrEmpty)
|
||||
{
|
||||
scriptOptions = scriptOptions.WithImports(_frameworkImports);
|
||||
}
|
||||
|
||||
return scriptOptions;
|
||||
}
|
||||
|
||||
internal static object GetTaskResult(Task task)
|
||||
{
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
throw task.Exception;
|
||||
}
|
||||
|
||||
Type taskType = task.GetType();
|
||||
|
||||
if (taskType.IsGenericType)
|
||||
{
|
||||
return taskType.GetProperty("Result").GetValue(task);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_compilation = null;
|
||||
_entryPointMethodInfo = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.Scripts.Models
|
||||
{
|
||||
public enum AssemblyInitType
|
||||
{
|
||||
LoadInMemory = 0,
|
||||
SaveToDisk = 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.Scripts.Models
|
||||
{
|
||||
public sealed class EntityMetadata
|
||||
{
|
||||
public EntityType Type;
|
||||
|
||||
public string ScriptText;
|
||||
|
||||
public EntityMetadata()
|
||||
{
|
||||
}
|
||||
|
||||
public EntityMetadata(string scriptText, EntityType type = EntityType.Signal)
|
||||
{
|
||||
ScriptText = scriptText;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Diagnostics.Scripts.Models
|
||||
{
|
||||
public class EntityMethodSignature
|
||||
{
|
||||
private readonly ImmutableArray<EntityParameter> _parameters;
|
||||
private readonly string _parentTypeName;
|
||||
private readonly string _methodName;
|
||||
private readonly string _returnTypeName;
|
||||
private readonly ImmutableArray<AttributeData> _attributes;
|
||||
|
||||
public EntityMethodSignature(string methodName)
|
||||
{
|
||||
_methodName = methodName;
|
||||
}
|
||||
|
||||
public EntityMethodSignature(string parentTypeName, string methodName, ImmutableArray<EntityParameter> parameters, string returnTypeName, ImmutableArray<AttributeData> attributes)
|
||||
{
|
||||
_parameters = parameters;
|
||||
_parentTypeName = parentTypeName;
|
||||
_returnTypeName = returnTypeName;
|
||||
_methodName = methodName;
|
||||
_attributes = attributes;
|
||||
}
|
||||
|
||||
public ImmutableArray<EntityParameter> Parameters => _parameters;
|
||||
|
||||
public string ParentTypeName => _parentTypeName;
|
||||
|
||||
public string MethodName => _methodName;
|
||||
|
||||
public string ReturnTypeName => _returnTypeName;
|
||||
|
||||
public ImmutableArray<AttributeData> Attributes => _attributes;
|
||||
|
||||
public MethodInfo GetMethod(Assembly assembly)
|
||||
{
|
||||
if (assembly == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(assembly));
|
||||
}
|
||||
|
||||
//return assembly.DefinedTypes
|
||||
// .FirstOrDefault(t => string.Compare(t.FullName, ParentTypeName, StringComparison.Ordinal) == 0)
|
||||
// ?.GetMethod(MethodName);
|
||||
|
||||
return assembly.DefinedTypes.FirstOrDefault(t => t.DeclaringType == null)?.GetMethod(MethodName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Diagnostics.Scripts.Models
|
||||
{
|
||||
public class EntityParameter
|
||||
{
|
||||
public EntityParameter(string name, string typeName, bool isOptional, RefKind refkind)
|
||||
{
|
||||
Name = name;
|
||||
TypeName = typeName;
|
||||
IsOptional = isOptional;
|
||||
RefKind = refkind;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string TypeName { get; }
|
||||
|
||||
public bool IsOptional { get; }
|
||||
|
||||
public RefKind RefKind { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace Diagnostics.Scripts.Models
|
||||
{
|
||||
public enum EntityType
|
||||
{
|
||||
Signal = 1,
|
||||
Detector = 2,
|
||||
Analysis = 4
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace Diagnostics.Scripts.Models
|
||||
{
|
||||
public enum EntryPointResolutionType
|
||||
{
|
||||
MethodName = 1,
|
||||
|
||||
Attribute = 2
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using Diagnostics.DataProviders;
|
||||
using Diagnostics.ModelsAndUtils;
|
||||
using Diagnostics.Scripts;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Diagnostics.Tests.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Diagnostics.Tests.DataProviderTests
|
||||
{
|
||||
public class DataProviderTests
|
||||
{
|
||||
private readonly ITestOutputHelper output;
|
||||
|
||||
public DataProviderTests(ITestOutputHelper output)
|
||||
{
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void DataProvders_TestKusto()
|
||||
{
|
||||
EntityMetadata metadata = ScriptTestDataHelper.GetRandomMetadata();
|
||||
metadata.ScriptText = GetDataProviderScript("TestA");
|
||||
|
||||
var configFactory = new MockDataProviderConfigurationFactory();
|
||||
var config = configFactory.LoadConfigurations();
|
||||
|
||||
var dataProviders = new DataProviders.DataProviders(config);
|
||||
|
||||
using (EntityInvoker invoker = new EntityInvoker(metadata, ScriptHelper.GetFrameworkReferences(), ScriptHelper.GetFrameworkImports()))
|
||||
{
|
||||
await invoker.InitializeEntryPointAsync();
|
||||
DataTableResponseObject result = (DataTableResponseObject)await invoker.Invoke(new object[] { dataProviders });
|
||||
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
}
|
||||
|
||||
private void PrintDataTable(DataTable dt)
|
||||
{
|
||||
var cols = new StringBuilder();
|
||||
foreach (DataColumn column in dt.Columns)
|
||||
{
|
||||
cols.Append($"{column.ColumnName}\t");
|
||||
}
|
||||
|
||||
output.WriteLine(cols.ToString());
|
||||
|
||||
foreach (DataRow row in dt.Rows)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (DataColumn column in dt.Columns)
|
||||
{
|
||||
sb.Append($"{row[column.ColumnName]}\t");
|
||||
|
||||
}
|
||||
|
||||
output.WriteLine(sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetDataProviderScript(string test)
|
||||
{
|
||||
switch (test)
|
||||
{
|
||||
case "TestA":
|
||||
default:
|
||||
return @"
|
||||
|
||||
public async static Task<DataTableResponseObject> Run(DataProviders dataProviders) {
|
||||
|
||||
var dt = await dataProviders.Kusto.ExecuteQuery(""TestA"", string.Empty);
|
||||
return dt;
|
||||
}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.6.1" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\src\Diagnostics.CompilerHost\Diagnostics.CompilerHost.csproj" />
|
||||
<ProjectReference Include="..\src\Diagnostics.DataProviders\Diagnostics.DataProviders.csproj" />
|
||||
<ProjectReference Include="..\src\Diagnostics.ModelsAndUtils\Diagnostics.ModelsAndUtils.csproj" />
|
||||
<ProjectReference Include="..\src\Diagnostics.RuntimeHost\Diagnostics.RuntimeHost.csproj" />
|
||||
<ProjectReference Include="..\src\Diagnostics.Scripts\Diagnostics.Scripts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="RuntimeHostTests\" />
|
||||
<Folder Include="TestData\" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="AfterBuildScript" AfterTargets="Build">
|
||||
<Copy SourceFiles="..\data\templates\Detector_KustoQuery.csx" DestinationFolder="$(OutputPath)\templates" ContinueOnError="true" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,10 @@
|
|||
namespace Diagnostics.Tests.Helpers
|
||||
{
|
||||
public enum ScriptErrorType
|
||||
{
|
||||
CompilationError = 1,
|
||||
MissingEntryPoint = 2,
|
||||
DuplicateEntryPoint = 3,
|
||||
RuntimeError = 4
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
using Diagnostics.Scripts.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Diagnostics.Tests.Helpers
|
||||
{
|
||||
public static class ScriptTestDataHelper
|
||||
{
|
||||
|
||||
public static EntityMetadata GetRandomMetadata(EntityType type = EntityType.Signal)
|
||||
{
|
||||
return new EntityMetadata()
|
||||
{
|
||||
ScriptText = GetNumSqaureScript(),
|
||||
Type = type
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetNumSqaureScript()
|
||||
{
|
||||
return @"
|
||||
public static int Run(int x) {
|
||||
x = x * x;
|
||||
return x;
|
||||
}";
|
||||
}
|
||||
|
||||
public static string GetInvalidCsxScript(ScriptErrorType errorType)
|
||||
{
|
||||
switch (errorType)
|
||||
{
|
||||
case ScriptErrorType.MissingEntryPoint:
|
||||
return @"
|
||||
public static string SomeMethod() => ""test string"";
|
||||
";
|
||||
case ScriptErrorType.DuplicateEntryPoint:
|
||||
return @"
|
||||
public static int Run(int x) {
|
||||
return x * x;
|
||||
}
|
||||
public static int Run(int x, int y) {
|
||||
return x + y;
|
||||
}
|
||||
";
|
||||
case ScriptErrorType.CompilationError:
|
||||
default:
|
||||
return @"
|
||||
public static int Run(int x) {
|
||||
return x * x
|
||||
}";
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string> GetDetectorScript(string id, string kustoTableName = "MockTable", string queryPart = "take 1")
|
||||
{
|
||||
string kustoTemplate = await File.ReadAllTextAsync(@"templates/Detector_KustoQuery.csx");
|
||||
return kustoTemplate.Replace("<YOUR_DETECTOR_ID>", id)
|
||||
.Replace("<YOUR_TABLE_NAME>", kustoTableName)
|
||||
.Replace("<YOUR_QUERY>", queryPart);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
using Diagnostics.Scripts;
|
||||
using Diagnostics.Scripts.CompilationService;
|
||||
using Diagnostics.Scripts.CompilationService.Interfaces;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Diagnostics.Tests.Helpers;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace Diagnostics.Tests.ScriptsTests
|
||||
{
|
||||
public class CompilationServiceTests
|
||||
{
|
||||
#region Compilation Service Tests
|
||||
|
||||
[Fact]
|
||||
public async void CompilationService_TestScriptCompilation()
|
||||
{
|
||||
var serviceInstance = CompilationServiceFactory.CreateService(ScriptTestDataHelper.GetRandomMetadata(), ScriptOptions.Default);
|
||||
ICompilation compilation = await serviceInstance.GetCompilationAsync();
|
||||
|
||||
ImmutableArray<Diagnostic> diagnostics = await compilation.GetDiagnosticsAsync();
|
||||
Assert.Empty(diagnostics.Select(d => d.Severity == DiagnosticSeverity.Error));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void CompilationService_TestScriptCompilationFailure()
|
||||
{
|
||||
EntityMetadata metaData = ScriptTestDataHelper.GetRandomMetadata(EntityType.Detector);
|
||||
metaData.ScriptText = ScriptTestDataHelper.GetInvalidCsxScript(ScriptErrorType.CompilationError);
|
||||
|
||||
var serviceInstance = CompilationServiceFactory.CreateService(metaData, ScriptOptions.Default);
|
||||
ICompilation compilation = await serviceInstance.GetCompilationAsync();
|
||||
|
||||
ImmutableArray<Diagnostic> diagnostics = await compilation.GetDiagnosticsAsync();
|
||||
Assert.NotEmpty(diagnostics.Select(d => d.Severity == DiagnosticSeverity.Error));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void CompilationService_TestVaildEntryPointResolution()
|
||||
{
|
||||
var serviceInstance = CompilationServiceFactory.CreateService(ScriptTestDataHelper.GetRandomMetadata(), ScriptOptions.Default);
|
||||
ICompilation compilation = await serviceInstance.GetCompilationAsync();
|
||||
|
||||
Exception ex = Record.Exception(() =>
|
||||
{
|
||||
EntityMethodSignature methodSignature = compilation.GetEntryPointSignature();
|
||||
});
|
||||
|
||||
Assert.Null(ex);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ScriptErrorType.DuplicateEntryPoint)]
|
||||
[InlineData(ScriptErrorType.MissingEntryPoint)]
|
||||
public async void CompilationService_TestDuplicateEntryPoints(ScriptErrorType errorType)
|
||||
{
|
||||
EntityMetadata metadata = ScriptTestDataHelper.GetRandomMetadata();
|
||||
metadata.ScriptText = ScriptTestDataHelper.GetInvalidCsxScript(errorType);
|
||||
|
||||
var serviceInstance = CompilationServiceFactory.CreateService(metadata, ScriptOptions.Default);
|
||||
ICompilation compilation = await serviceInstance.GetCompilationAsync();
|
||||
|
||||
ScriptCompilationException ex = Assert.Throws<ScriptCompilationException>(() =>
|
||||
{
|
||||
EntityMethodSignature methodSignature = compilation.GetEntryPointSignature();
|
||||
});
|
||||
|
||||
Assert.NotEmpty(ex.CompilationOutput);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Compilation Service Factory Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(EntityType.Signal, typeof(SignalCompilationService))]
|
||||
[InlineData(EntityType.Detector, typeof(DetectorCompilationService))]
|
||||
[InlineData(EntityType.Analysis, typeof(AnalysisCompilationService))]
|
||||
public void CompilationServiceFactory_GetServiceBasedOnType(EntityType type, object value)
|
||||
{
|
||||
EntityMetadata metaData = ScriptTestDataHelper.GetRandomMetadata(type);
|
||||
var compilationServiceInstance = CompilationServiceFactory.CreateService(metaData, ScriptOptions.Default);
|
||||
Assert.Equal(compilationServiceInstance.GetType(), value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompilationServiceFactory_TestNullEntityMetadata()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
{
|
||||
var instance = CompilationServiceFactory.CreateService(null, ScriptOptions.Default);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompilationServiceFactory_TestNullScriptOptions()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
{
|
||||
var instance = CompilationServiceFactory.CreateService(ScriptTestDataHelper.GetRandomMetadata(), null);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompilationServiceFactory_TestForUnsupportedEntityType()
|
||||
{
|
||||
Assert.Throws<NotSupportedException>(() =>
|
||||
{
|
||||
var instance = CompilationServiceFactory.CreateService(new EntityMetadata(), ScriptOptions.Default);
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
using Diagnostics.ModelsAndUtils;
|
||||
using Diagnostics.Scripts;
|
||||
using Diagnostics.Scripts.Models;
|
||||
using Diagnostics.Tests.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace Diagnostics.Tests.ScriptsTests
|
||||
{
|
||||
public class EntityInvokerTests
|
||||
{
|
||||
[Fact]
|
||||
public async void EntityInvoker_TestInvokeMethod()
|
||||
{
|
||||
using (EntityInvoker invoker = new EntityInvoker(ScriptTestDataHelper.GetRandomMetadata(), ImmutableArray.Create<string>()))
|
||||
{
|
||||
await invoker.InitializeEntryPointAsync();
|
||||
int result = (int)await invoker.Invoke(new object[] { 3 });
|
||||
Assert.Equal(9, result);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void EntityInvoker_TestDefinitionAttributeResolution()
|
||||
{
|
||||
Definition definitonAttribute = new Definition()
|
||||
{
|
||||
Id = "TestId"
|
||||
};
|
||||
|
||||
EntityMetadata metadata = ScriptTestDataHelper.GetRandomMetadata();
|
||||
metadata.ScriptText = await ScriptTestDataHelper.GetDetectorScript(definitonAttribute.Id);
|
||||
|
||||
using (EntityInvoker invoker = new EntityInvoker(metadata, ScriptHelper.GetFrameworkReferences(), ScriptHelper.GetFrameworkImports()))
|
||||
{
|
||||
await invoker.InitializeEntryPointAsync();
|
||||
Assert.Equal<Definition>(definitonAttribute, invoker.EntryPointDefinitionAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void EntityInvoker_TestGetAssemblyBytes()
|
||||
{
|
||||
Definition definitonAttribute = new Definition()
|
||||
{
|
||||
Id = "TestId"
|
||||
};
|
||||
|
||||
EntityMetadata metadata = ScriptTestDataHelper.GetRandomMetadata();
|
||||
metadata.ScriptText = await ScriptTestDataHelper.GetDetectorScript(definitonAttribute.Id);
|
||||
|
||||
using (EntityInvoker invoker = new EntityInvoker(metadata, ScriptHelper.GetFrameworkReferences(), ScriptHelper.GetFrameworkImports()))
|
||||
{
|
||||
await invoker.InitializeEntryPointAsync();
|
||||
Tuple<string, string> asmPair = await invoker.GetAssemblyBytesAsync();
|
||||
|
||||
string assemblyBytes = asmPair.Item1;
|
||||
string pdbBytes = asmPair.Item2;
|
||||
|
||||
Assert.False(string.IsNullOrWhiteSpace(assemblyBytes));
|
||||
Assert.False(string.IsNullOrWhiteSpace(pdbBytes));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void EntityInvoker_TestSaveAssemblyToDisk()
|
||||
{
|
||||
Definition definitonAttribute = new Definition()
|
||||
{
|
||||
Id = "TestId"
|
||||
};
|
||||
|
||||
string assemblyPath = $@"{Directory.GetCurrentDirectory()}\{Guid.NewGuid().ToString()}";
|
||||
EntityMetadata metadata = ScriptTestDataHelper.GetRandomMetadata();
|
||||
metadata.ScriptText = await ScriptTestDataHelper.GetDetectorScript(definitonAttribute.Id);
|
||||
|
||||
using (EntityInvoker invoker = new EntityInvoker(metadata, ScriptHelper.GetFrameworkReferences(), ScriptHelper.GetFrameworkImports()))
|
||||
{
|
||||
await invoker.InitializeEntryPointAsync();
|
||||
await invoker.SaveAssemblyToDiskAsync(assemblyPath);
|
||||
|
||||
Assert.True(File.Exists($"{assemblyPath}.dll"));
|
||||
Assert.True(File.Exists($"{assemblyPath}.pdb"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void EntityInvoker_TestInitializationUsingAssembly()
|
||||
{
|
||||
// First Create and Save a assembly for test purposes.
|
||||
Definition definitonAttribute = new Definition()
|
||||
{
|
||||
Id = "TestId"
|
||||
};
|
||||
|
||||
string assemblyPath = $@"{Directory.GetCurrentDirectory()}/{Guid.NewGuid().ToString()}";
|
||||
EntityMetadata metadata = ScriptTestDataHelper.GetRandomMetadata();
|
||||
metadata.ScriptText = await ScriptTestDataHelper.GetDetectorScript(definitonAttribute.Id);
|
||||
|
||||
using (EntityInvoker invoker = new EntityInvoker(metadata, ScriptHelper.GetFrameworkReferences(), ScriptHelper.GetFrameworkImports()))
|
||||
{
|
||||
await invoker.InitializeEntryPointAsync();
|
||||
await invoker.SaveAssemblyToDiskAsync(assemblyPath);
|
||||
|
||||
Assert.True(File.Exists($"{assemblyPath}.dll"));
|
||||
Assert.True(File.Exists($"{assemblyPath}.pdb"));
|
||||
}
|
||||
|
||||
// Now test initializing Entry Point of Invoker using assembly
|
||||
Assembly asm = Assembly.LoadFrom($"{assemblyPath}.dll");
|
||||
|
||||
using (EntityInvoker invoker = new EntityInvoker(metadata))
|
||||
{
|
||||
Exception ex = Record.Exception(() =>
|
||||
{
|
||||
invoker.InitializeEntryPoint(asm);
|
||||
});
|
||||
|
||||
Assert.Null(ex);
|
||||
Assert.True(invoker.IsCompilationSuccessful);
|
||||
Assert.Equal(definitonAttribute.Id, invoker.EntryPointDefinitionAttribute.Id);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ScriptErrorType.CompilationError)]
|
||||
[InlineData(ScriptErrorType.DuplicateEntryPoint)]
|
||||
[InlineData(ScriptErrorType.MissingEntryPoint)]
|
||||
public async void EntityInvoker_TestInvokeWithCompilationError(ScriptErrorType errorType)
|
||||
{
|
||||
EntityMetadata metadata = ScriptTestDataHelper.GetRandomMetadata();
|
||||
metadata.ScriptText = ScriptTestDataHelper.GetInvalidCsxScript(errorType);
|
||||
|
||||
using (EntityInvoker invoker = new EntityInvoker(metadata, ImmutableArray.Create<string>()))
|
||||
{
|
||||
ScriptCompilationException ex = await Assert.ThrowsAsync<ScriptCompilationException>(async () =>
|
||||
{
|
||||
await invoker.InitializeEntryPointAsync();
|
||||
int result = (int)await invoker.Invoke(new object[] { 3 });
|
||||
Assert.Equal(9, result);
|
||||
});
|
||||
|
||||
Assert.NotEmpty(ex.CompilationOutput);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact]
|
||||
public async void EntityInvoker_TestSaveAssemblyToInvalidPath()
|
||||
{
|
||||
Definition definitonAttribute = new Definition()
|
||||
{
|
||||
Id = "TestId"
|
||||
};
|
||||
|
||||
string assemblyPath = string.Empty;
|
||||
EntityMetadata metadata = ScriptTestDataHelper.GetRandomMetadata();
|
||||
metadata.ScriptText = await ScriptTestDataHelper.GetDetectorScript(definitonAttribute.Id);
|
||||
|
||||
using (EntityInvoker invoker = new EntityInvoker(metadata, ScriptHelper.GetFrameworkReferences(), ScriptHelper.GetFrameworkImports()))
|
||||
{
|
||||
await invoker.InitializeEntryPointAsync();
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () =>
|
||||
{
|
||||
await invoker.SaveAssemblyToDiskAsync(assemblyPath);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче