Merge pull request #1 from Azure/shgup

App Service Diagnostics Code
This commit is contained in:
Shekhar Gupta 2018-03-20 07:52:49 +08:00 коммит произвёл GitHub
Родитель ef757d5e06 c8f3c91905
Коммит 80dafaf912
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
90 изменённых файлов: 4708 добавлений и 8 удалений

44
.gitignore поставляемый
Просмотреть файл

@ -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/

7
.travis.yml Normal file
Просмотреть файл

@ -0,0 +1,7 @@
language: csharp
mono: none
dotnet: 2.1.3
script:
- dotnet build Diagnostics.sln
- cd tests
- dotnet test

123
Diagnostics.sln Normal file
Просмотреть файл

@ -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

36
build.cmd Normal file
Просмотреть файл

@ -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);
});
}
}
}
}