Merge pull request #222 from JocaPC/master

Automatic tuning FLGP demo
This commit is contained in:
Jovan Popovic (MSFT)
2017-04-25 11:45:17 +02:00
committed by GitHub
24 changed files with 782 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

@@ -0,0 +1,10 @@
*.xproj.user
.vs/*
.vscode/*
bin/*
obj/*
*.sln
*.log
Properties/PublishProfiles/*
*.development.json
*.lock.json
@@ -0,0 +1,66 @@
using Belgrade.SqlClient;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
namespace FlgpWwiDemo.Controllers
{
[Route("api/[controller]")]
public class DemoController : Controller
{
IQueryMapper queryMapper = null;
public DemoController(IQueryMapper queryMapper)
{
this.queryMapper = queryMapper;
}
// GET api/demo
[HttpGet]
[Produces("application/json")]
public async Task<string> Get()
{
decimal result = 0;
string status = "OK";
long start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
long end = 0;
await this.queryMapper
.OnError(ex=> status = ex.Message)
.ExecuteReader("EXEC dbo.report 7", reader => {
result = reader.GetDecimal(0);
end = DateTimeOffset.Now.ToUnixTimeMilliseconds();
});
return "{\"x\":\"" + DateTime.Now.ToUniversalTime().ToString() + "\",\"y\":" + (end-start) + ",\"start\":" + start + ",\"end\":" + end + ",\"result\":" + result +",\"status\":\"" + status + "\"}";
}
// GET api/demo/init
[HttpGet("init")]
public async Task Init()
{
await this.queryMapper.ExecuteReader("EXEC dbo.[initialize]", _ => { });
}
// GET api/demo/regression
[HttpGet("regression")]
public async Task Regression()
{
await this.queryMapper.ExecuteReader("EXEC dbo.regression", _ => { });
}
// GET api/demo/on
[HttpGet("on")]
public async Task On()
{
await this.queryMapper.ExecuteReader("EXEC dbo.auto_tuning_on", _ => { });
}
// GET api/demo/off
[HttpGet("off")]
public async Task Off()
{
await this.queryMapper.ExecuteReader("EXEC dbo.auto_tuning_off", _ => { });
}
}
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>ca0088d0-bcb5-4485-89ab-860e23bcf9b7</ProjectGuid>
<RootNamespace>FlgpWwiDemo</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;
namespace FlgpWwiDemo
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
@@ -0,0 +1,28 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:57485/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "index.html",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"FlgpWwiDemo": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:5000/index.html",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,149 @@
# Forcing last good plan
This code sample demonstrates how [Automatic tuning in SQL Server 2017 CTP2.0+](https://docs.microsoft.com/sql/relational-databases/automatic-tuning/automatic-tuning) can identify and automatically fix performance problems in your workload.
### Contents
[About this sample](#about-this-sample)<br/>
[Before you begin](#before-you-begin)<br/>
[Run this sample](#run-this-sample)<br/>
[Sample details](#sample-details)<br/>
[Disclaimers](#disclaimers)<br/>
[Related links](#related-links)<br/>
<a name=about-this-sample></a>
## About this sample
1. **Applies to:** SQL Server 2017 (or higher) Enterprise / Developer / Evaluation Edition
2. **Key features:**
- Automatic tuning / forcing last good plan
- Query Store
3. **Workload:** Single analytic query executed on [WideWorldImporters](../../../databases/wide-world-importers) database
4. **Programming Language:** T-SQL, .NET C#, JavaScript
5. **Author:** Jovan Popovic [jovanpop-msft]
There are two ways to demonstrate the feature using this sample:
- Level 300 demo: T-SQL code that simulates workload using T-SQL commands and shows results using dynamic management views and Query Store UI in SQL Server Management Studio.
- Level 100-200 demo: ASP.NET application that simulates workload using AJAX requests sent to web server and shows results in the web page. Optionally use Query Store UI in SQL Server Management Studio to show performance regressions.
<a name=before-you-begin></a>
## Before you begin
To run this sample, you need the following prerequisites.
**Software prerequisites:**
1. SQL Server 2017 CTP2.0 (or higher)
2. ASP.NET Core 1.0.1 installed (only if you want to use ASP.NET sample). Optionally Visual Studio Code or Visual Studio 2015 U3 (or higher)
<a name=run-this-sample></a>
## Run this sample
### Setup code
1. Download [two T-SQL script files in sql-scripts](sql-scripts) folder if you want to use just T-SQL sample. Optionally you can clone this repository using [Git for Windows](http://www.git-scm.com/), or download the zip file.
2. Download the [WideWorldImporters](../../../databases/wide-world-importers) database and restore it on your server.
3. Execute setup.sql script on your [WideWorldImporters](../../../databases/wide-world-importers) database that will add necessary stored procedures and indexes.
### Configure ASP.NET sample (Only for ASP.NET Sample)
1. Clone this repository using [Git for Windows](http://www.git-scm.com/), or download the zip file, if you have not done it.
2. Open appsettings.json file in the root of the folder and change server, database, username, and password in the connection string.
3. From the project root folder open command prompt and run `dotnet update`, `dotnet build`, and `dotnet run`. These commands will update NuGet packages, build project, and run web app. As an alternative,
open project using Visual Studio 2015 U3, or Visual Studio Code, compile and run sample.
<a name=sample-details></a>
## Sample Details
This sample demonstrates how SQL Server 2017 analyzes workload, keep track about the last good
plan that successfully executed the query in the past, and reverts regressed plan if it is worse that the last known good plan.
The following query is used to demonstrate plan regression and correction:
```
select avg([UnitPrice]*[Quantity])
from Sales.OrderLines
where PackageTypeID = @packagetypeid
```
This query is executed against [WideWorldImporters](../../../databases/wide-world-importers) database. The query can be executed using SQL plan that has **"Hash aggregate"** operator, which provides results quickly. However, sometime Query Optimizer might choose a plan with **"Stream aggregate"** operator that will cause the performance regression. This sample shows how Automatic tuning feature detects this kind of regression and automatically fix the problem.
### T-SQL Sample
Open **demo-full.sql** and follow the comments in the code. Here is the short explanation of the scenario:
#### Part I - regression detection
- Execute query `EXEC dbo.report 7` 30-300 times. SQL Database will collect statistics about the query. Number of queries that should be executed might vary depending on performance of your server (30 would be enough, but you might need to increase this number).
- Execute query `EXEC dbo.regression` to cause the regression.
- Execute query `EXEC dbo.report 7` 20 times and verify that the execution is slower.
- Query `sys.dm_db_tuning_recommendations` and verify that regression is detected and that
the correction script is in the view. Since some information are formatted as JSON docuemnts, you can use the following query to extract relevant information:
```
SELECT planForceDetails.query_id, reason, score,
JSON_VALUE(details, '$.implementationDetails.script') script,
planForceDetails.[new plan_id], planForceDetails.[recommended plan_id]
FROM sys.dm_db_tuning_recommendations
CROSS APPLY OPENJSON (Details, '$.planForceDetails')
WITH ( [query_id] int '$.queryId',
[new plan_id] int '$.regressedPlanId',
[recommended plan_id] int '$.forcedPlanId'
) as planForceDetails;
```
- Open Query Store UI in SSMS (e.g. "Top Resource Consuming Queries") and find the query. Verify that there are two plans - one faster with **Hash Aggregate** and another slower with **Stream Aggregate**, similar to the following figures:
![Last good plan](../../../../media/features/automatic-tuning/flgp-query-store-ui-last-good-plan.png "Last good plan")
Fig. 1. Optimal plan with "Hash Aggregate".
![Regressed plan](../../../../media/features/automatic-tuning/flgp-query-store-ui-regressed-plan.png "Regressed plan")
Fig. 2. Regressed plan with "Stream Aggregate".
- Take the correction script from the `sys.dm_db_tuning_recommendations` view and force the recommended plan.
- Execute query `EXEC dbo.report 7` 20 times and verify that the execution is faster. Open Query Store UI in SSMS (e.g. "Top Resource Consuming Queries"), find the query, verify that the plan is forced and that the regression is fixed.
#### Part II - Automatic tuning
- Reset the database state by executing `EXEC dbo.initialize` procedure, and enable automatic tuning on database.
- Execute query `EXEC dbo.report 7` 30-300 times.
- Execute query `EXEC dbo.regression` to cause the regression.
- Execute query `EXEC dbo.report 7` 20 times and verify that the execution is slower.
- Query `sys.dm_db_tuning_recommendations` and verify that regression is detected and that
recommendation is in **Verifying** state.
- Execute query `EXEC dbo.report 7` 30-50 times and verify that the execution is faster.
- Open Query Store UI in SSMS (e.g. "Top Resource Consuming Queries") and find the query. Verify that there are two plans - one with **Hash Aggregate** and another with **Stream Aggregate**. Better plan should be forced, and you should see that the forced plan has better performance than regressed plan.
### ASP.NET Core Sample
This code sample contains a simple web page that periodically sends HTTP requests to the Web server. Web server executes T-SQL query against the database, returns query result, and calculates query elapsed time in each iteration.
Web page collects response from the web server, calculates expected throughput based on the
last 10 T-SQL request durations, and displays how many requests per second can be executed.
Open index.html page that will show number of requests per second that can be executed. Turn-on
**Automatic tuning** using the **ON** button. You should see something like to following page:
![Web app](../../../../media/features/automatic-tuning/flgp-web-ui.png "Demo web app")
Fig. 3. Number of requests per seconds.
> Number of requests per second might be different in your environment so, gauge needle might
> be on left or right side. You can change the scale of the gauge if you manually edit index.html
> file and change value 150 in the line 96: `var gauge = new GraphVizGauge("svg", { to: 150 });`
> You can refresh the page after this change and the gauge will be re-scaled.
You can press **Regression** button to cause SQL plan choice regression in database layer. On the web page will be shown decreased number of requests per seconds that can be served.
![Web app](../../../../media/features/automatic-tuning/flgp-web-ui-regression.png "Demo web app")
Fig. 4. Number of requests per seconds after regression.
After some time, you will notice that the regression will be automatically corrected. Pressing the **Regression** button again will not cause any regression.
<a name=disclaimers></a>
## Disclaimers
The code included in this sample is not intended to be a set of best practices on how to build scalable enterprise grade applications. This is beyond the scope of this sample.
<a name=related-links></a>
## Related Links
- [Automatic tuning in SQL Server 2017 CTP2.0+](https://docs.microsoft.com/sql/relational-databases/automatic-tuning/automatic-tuning)
- [sys.dm_db_tuning_recommendations view (Transact-SQL)](https://docs.microsoft.com/sql/relational-databases/system-dynamic-management-views/sys-dm-db-tuning-recommendations-transact-sql)
- [Monitoring Performance By Using the Query Store](https://docs.microsoft.com/en-us/sql/relational-databases/performance/monitoring-performance-by-using-the-query-store)
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Threading.Tasks;
using Belgrade.SqlClient;
using Belgrade.SqlClient.SqlDb;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace FlgpWwiDemo
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
string ConnString = Configuration["ConnectionStrings:Wwi"];
// Adding data access services/components.
services.AddTransient<IQueryMapper>(
sp => new QueryMapper(new SqlConnection(ConnString))
);
// Add framework 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, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseStaticFiles();
app.UseMvc();
}
}
}
@@ -0,0 +1,13 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"ConnectionStrings": {
"Wwi": "Server=.;Database=WideWorldImporters;Integrated Security=true"
}
}
@@ -0,0 +1,59 @@
{
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.1",
"type": "platform"
},
"Microsoft.AspNetCore.Mvc": "1.0.1",
"Microsoft.AspNetCore.Routing": "1.0.1",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Logging": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
"System.Data.SqlClient": "4.3.0",
"Belgrade.Sql.Client": "0.7.0",
"Microsoft.AspNetCore.StaticFiles": "1.1.0"
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
]
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"publishOptions": {
"include": [
"wwwroot",
"**/*.cshtml",
"appsettings.json",
"web.config"
]
},
"scripts": {
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
}
@@ -0,0 +1,119 @@
/********************************************************
* SETUP - clear everything
********************************************************/
EXEC [dbo].[initialize]
/********************************************************
* PART I
* Plan regression identification.
********************************************************/
-- 1. Start workload - execute procedure 30 times:
begin
declare @packagetypeid int = 7;
exec dbo.report @packagetypeid
end
go 300
-- Queries should be fast
-- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Hash Aggregate)
-- 2. Execute procedure that causes plan regression
-- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Stream Aggregate)
exec dbo.regression
-- 3. Start workload again - verify that is slower.
begin
declare @packagetypeid int = 7;
exec dbo.report @packagetypeid
end
go 20
-- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Stream Aggregate)
-- 4. Find recommendation recommended by database:
SELECT planForceDetails.query_id, reason, score,
JSON_VALUE(details, '$.implementationDetails.script') script,
planForceDetails.[new plan_id], planForceDetails.[recommended plan_id]
FROM sys.dm_db_tuning_recommendations
CROSS APPLY OPENJSON (Details, '$.planForceDetails')
WITH ( [query_id] int '$.queryId',
[new plan_id] int '$.regressedPlanId',
[recommended plan_id] int '$.forcedPlanId'
) as planForceDetails;
-- Note: User can apply script and force the recommended plan to correct the error.
<<Insert T-SQL from the script column here and execute the script>>
-- e.g.: exec sp_query_store_force_plan @query_id = 3, @plan_id = 1
-- 5. Start workload again - verify that is faster.
begin
declare @packagetypeid int = 7;
exec dbo.report @packagetypeid
end
go 20
-- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Hash Aggregate again)
-- In part II will be shown better approach - automatic tuning.
/********************************************************
* PART II
* Automatic tuning
********************************************************/
/********************************************************
* RESET - clear everything
********************************************************/
EXEC [dbo].[initialize]
-- Enable automatic tuning on the database:
ALTER DATABASE current
SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = ON);
-- Verify that actual state on FLGP is ON:
SELECT name, desired_state_desc, actual_state_desc, reason_desc
FROM sys.database_automatic_tuning_options;
-- 1. Start workload - execute procedure 20 times like in the phase I
begin
declare @packagetypeid int = 7;
exec dbo.report @packagetypeid
end
go 300
-- 2. Execute the procedure that causes plan regression
exec dbo.regression
-- 3. Start workload again - verify that it is slower.
begin
declare @packagetypeid int = 7;
exec dbo.report @packagetypeid
end
go 20
-- 4. Find recommendation that returns query perf regression
-- and check is it in Verifying state:
SELECT reason, score,
JSON_VALUE(state, '$.currentValue') state,
JSON_VALUE(state, '$.reason') state_transition_reason,
JSON_VALUE(details, '$.implementationDetails.script') script,
planForceDetails.*
FROM sys.dm_db_tuning_recommendations
CROSS APPLY OPENJSON (Details, '$.planForceDetails')
WITH ( [query_id] int '$.queryId',
[new plan_id] int '$.regressedPlanId',
[recommended plan_id] int '$.forcedPlanId'
) as planForceDetails;
-- 5. Wait until recommendation is applied and start workload again - verify that it is faster.
begin
declare @packagetypeid int = 7;
exec dbo.report @packagetypeid
end
go 30
-- Open Query Store/"Top Resource Consuming Queries" dialog in SSMS and show that better plan is forced.
@@ -0,0 +1,68 @@
DROP INDEX IF EXISTS [NCCX_Sales_OrderLines] ON [Sales].[OrderLines]
/****** Object: Index [NCCX_Sales_OrderLines] Script Date: 4/20/2017 11:27:27 AM ******/
CREATE NONCLUSTERED COLUMNSTORE INDEX [NCCX_Sales_OrderLines] ON [Sales].[OrderLines]
(
[OrderID],
[StockItemID],
[Description],
[Quantity],
[UnitPrice],
[PickedQuantity],
[PackageTypeID] -- adding package type id for demo purpose
)WITH (DROP_EXISTING = OFF, COMPRESSION_DELAY = 0) ON [USERDATA]
GO
CREATE procedure [dbo].[initialize]
as begin
DBCC FREEPROCCACHE;
ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = OFF);
end
GO
CREATE procedure [dbo].[report] (@packagetypeid int)
as begin
select avg([UnitPrice]*[Quantity])
from Sales.OrderLines
where PackageTypeID = @packagetypeid
end
GO
CREATE procedure [dbo].[regression]
as begin
DBCC FREEPROCCACHE;
begin
declare @packagetypeid int = 1;
exec report @packagetypeid
end
end
GO
CREATE procedure [dbo].[auto_tuning_on]
as begin
ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = ON);
DBCC FREEPROCCACHE;
ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
end
GO
CREATE procedure [dbo].[auto_tuning_off]
as begin
ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = OFF);
DBCC FREEPROCCACHE;
ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
end
GO
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!--
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
-->
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
</system.webServer>
</configuration>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,101 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
h1 { margin-top: 0px !important; }
.label {
font-size: 22.5px;
fill: #ffffff;
text-anchor: middle;
alignment-baseline: middle;
}
.face {
stroke: #c8c8c8;
stroke-width: 2;
}
.minorTicks {
stroke-width: 2;
stroke: white;
}
.majorTicks {
stroke: white;
stroke-width: 3;
}
body {
margin: auto;
}
</style>
<link href="media/bootstrap.min.css" rel="stylesheet" />
</head>
<body class="container-fluid">
<div class="row">
<div class="col-md-4">
<h1>Automatic tuning</h1>
</div>
<div class="col-md-3">
<div class="btn-group pull-right align-bottom" data-toggle="buttons">
<label class="btn btn-default active">
<input type="radio" name="options" id="off" autocomplete="off" checked> OFF
</label>
<label class="btn btn-default">
<input type="radio" name="options" id="on" autocomplete="off"> ON
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<span id="speed">0</span> requests per second.
</div>
<div class="col-md-3">
<button id="regression" type="button" class="btn btn-danger pull-right">Regression</button>
</div>
</div>
<div class="row">
<svg width="500" height="500"></svg>
</div>
<script src="media/d3.v3.min.js"></script>
<script src="media/viz.v1.0.0.min.js"></script>
<script src="media/jquery-3.2.1.min.js"></script>
<script src="media/bootstrap.min.js"></script>
<script src="media/GraphVizGauge.js"></script>
<script src="/api/demo/init"></script>
<script>
var gauge = new GraphVizGauge("svg", { to: 150 });
var perfData = [];
setInterval(function () {
$.ajax({
url: "/api/demo",
success: function (result) {
(perfData.length == 10) && perfData.shift();
perfData.push(result.y);
var total = 0;
for (i=0; i<perfData.length; i++) { total += perfData[i]; }
var perf = Math.round(10000 / (total / 10.0)) / 10.0;
gauge.Data(perf);
$("#speed").text(perf);
}
});
}, 200);
$("button#regression").on("click", function () {
$.ajax("/api/demo/regression");
});
$("#on").on("change", function () {
$.ajax("/api/demo/on");
});
$("#off").on("change", function () {
$.ajax("/api/demo/off");
});
</script>
<script src="/api/demo/init"></script>
</body>
</html>
@@ -0,0 +1,35 @@
/*
* D3 Gauge Control.
* Encapsulated example from: http://bl.ocks.org/NPashaP/59c2c7483fb61070486835d15c807941
* Licence: GNU General Public License, version 3.
* Authors: Pasha, Jovan Popovic
**************************************************************************/
var GraphVizGauge = function (target, options) {
options = options || {from:0, to: 10};
this.options = options;
var svg = d3.select(target);
try{
options.size = svg[0][0].clientWidth || 600;
} catch (ex) {options.size = 600; }
var g = svg.append("g").attr("transform", "translate("+options.size/2+","+options.size/2+")");
var domain = [options.from || 0, options.to || 10];
var gg = viz.gg()
.domain(domain)
.ticks(d3.range(domain[0], domain[1] + 1, options.tick || 1))
.outerRadius(options.outerRadius || options.size / 2)
.innerRadius(options.innerRadius || 30)
.value(0)
.duration(options.duration || 1000);
gg.defs(svg);
g.call(gg);
this.Data = function (data) {
gg.setNeedle(Math.min(data, options.to));
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long