├── .gitignore ├── ASPNetCore └── FlightFinder │ ├── .vscode │ ├── launch.json │ └── tasks.json │ ├── FlightFinder.Client │ ├── Components │ │ ├── AirportsList.razor │ │ ├── GreyOutZone.razor │ │ ├── Search.razor │ │ ├── SearchResultFlightSegment.razor │ │ ├── SearchResults.razor │ │ ├── Shortlist.razor │ │ └── ShortlistFlightSegment.razor │ ├── FlightFinder.Client.csproj │ ├── Main.razor │ ├── Program.cs │ ├── Services │ │ └── AppState.cs │ ├── Startup.cs │ ├── _Imports.razor │ └── wwwroot │ │ ├── css │ │ ├── bootstrap │ │ │ └── bootstrap.min.css │ │ ├── site.css │ │ └── site.scss │ │ └── index.html │ ├── FlightFinder.Server │ ├── .vscode │ │ └── tasks.json │ ├── Controllers │ │ ├── AirportsController.cs │ │ ├── FlightSearchController.cs │ │ └── ShortListController.cs │ ├── FlightDataSource.cs │ ├── FlightFinder.Server.csproj │ ├── IDistributedCacheExtensions.cs │ ├── ISessionExtensions.cs │ ├── Program.cs │ ├── SampleData.cs │ └── Startup.cs │ ├── FlightFinder.Shared │ ├── Airport.cs │ ├── FlightFinder.Shared.csproj │ ├── FlightSegment.cs │ ├── Itinerary.cs │ ├── SearchCriteria.cs │ └── TicketClass.cs │ ├── FlightFinder.sln │ └── README.txt ├── BandWidthMonitor ├── App.config ├── BandwidthLogger.cs ├── BandwidthMonitor.csproj ├── BandwidthMonitor.sln ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── LICENSE ├── README.md ├── Redis ├── Java │ └── RedisClientTests │ │ ├── RedisClientTests.iml │ │ ├── lettuce │ │ ├── lettuce.iml │ │ └── pom.xml │ │ └── src │ │ ├── CommandLineArgs.java │ │ ├── IRedisClient.java │ │ ├── IRedisClientFactory.java │ │ ├── ITestScenario.java │ │ ├── IdleConnectionTests.java │ │ ├── JedisClusterClient.java │ │ ├── JedisRedisClient.java │ │ ├── LatencyPercentileTests.java │ │ ├── LettuceRedisClient.java │ │ ├── LettuceRedisClusterClient.java │ │ ├── LoadTests.java │ │ ├── Logging.java │ │ ├── META-INF │ │ └── MANIFEST.MF │ │ ├── PooledRedisClient.java │ │ ├── Program.java │ │ ├── Redis.java │ │ ├── RedisInstance.java │ │ ├── Stopwatch.java │ │ ├── Utils.java │ │ ├── Worker.java │ │ └── WorkerThread.java └── Python │ ├── .vscode │ └── launch.json │ └── Redis-Cluster-Test.py └── ThreadPoolMonitor ├── App.config ├── PerfCounterHelper.cs ├── Program.cs ├── Properties └── AssemblyInfo.cs ├── ThreadPoolLogger.cs ├── ThreadPoolMonitor.csproj └── ThreadPoolMonitor.sln /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # Windows Azure Build Output 85 | csx 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | [Bb]in 93 | [Oo]bj 94 | sql 95 | TestResults 96 | [Tt]est[Rr]esult* 97 | *.Cache 98 | ClientBin 99 | [Ss]tyle[Cc]op.* 100 | ~$* 101 | *.dbmdl 102 | Generated_Code #added for RIA/Silverlight projects 103 | 104 | # Backup & report files from converting an old project file to a newer 105 | # Visual Studio version. Backup files are not needed, because we have git ;-) 106 | _UpgradeReport_Files/ 107 | Backup*/ 108 | UpgradeLog*.XML 109 | *.pyc 110 | Redis/Java/RedisClientTests/.idea/ 111 | Redis/Java/RedisClientTests/lib/ 112 | Redis/Java/RedisClientTests/out/ 113 | Redis/Java/RedisClientTests/RedisInstances.txt 114 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/FlightFinder.Server/bin/Debug/netcoreapp3.0/FlightFinder.Server.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/FlightFinder.Server", 16 | "stopAtEntry": false, 17 | // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser 18 | "serverReadyAction": { 19 | "action": "openExternally", 20 | "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)" 21 | }, 22 | "env": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "sourceFileMap": { 26 | "/Views": "${workspaceFolder}/Views" 27 | } 28 | }, 29 | { 30 | "name": ".NET Core Attach", 31 | "type": "coreclr", 32 | "request": "attach", 33 | "processId": "${command:pickProcess}" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/FlightFinder.Server/FlightFinder.Server.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/FlightFinder.Server/FlightFinder.Server.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/FlightFinder.Server/FlightFinder.Server.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/Components/AirportsList.razor: -------------------------------------------------------------------------------- 1 | @inject HttpClient Http 2 | 3 | 4 | @foreach (var airport in airports) 5 | { 6 | @airport.DisplayName (@airport.Code) 7 | } 8 | 9 | 10 | @code 11 | { 12 | private Airport[] airports = Array.Empty(); 13 | 14 | protected override async Task OnInitializedAsync() 15 | { 16 | airports = await Http.GetJsonAsync("api/airports"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/Components/GreyOutZone.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @ChildContent 5 | 6 | 7 | @code 8 | { 9 | [Parameter] 10 | public RenderFragment ChildContent { get; set; } 11 | 12 | [Parameter] 13 | public bool IsGreyedOut { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/Components/Search.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ✈ From: 8 | 9 | 10 | 11 | ➝ 12 | 13 | 14 | 15 | 16 | ✈ To: 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 🗓 Depart: 28 | 29 | 30 | 31 | ➝ 32 | 33 | 34 | 35 | 36 | 🗓 Return: 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Economy 47 | Premium Economy 48 | Business 49 | First 50 | 51 | 52 | 53 | OnSearch.InvokeAsync(criteria))" type="button" class="btn btn-danger px-5"> 54 | Search ➝ 55 | 56 | 57 | 58 | 59 | 60 | 61 | @code 62 | { 63 | [Parameter] 64 | public EventCallback OnSearch { get; set; } 65 | 66 | private SearchCriteria criteria = new SearchCriteria("LHR", "SEA"); 67 | } 68 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/Components/SearchResultFlightSegment.razor: -------------------------------------------------------------------------------- 1 | 2 | @Symbol 3 | 4 | 5 | 6 | @Flight.Airline 7 | @Flight.TicketClass.ToDisplayString() 8 | 9 | 10 | 11 | @Flight.DepartureTime.ToShortTimeString() 12 | @Flight.DepartureTime.ToString("ddd MMM d") (@Flight.FromAirportCode) 13 | 14 | 15 | ➝ 16 | 17 | 18 | @Flight.ReturnTime.ToShortTimeString() 19 | @Flight.ReturnTime.ToString("ddd MMM d") (@Flight.ToAirportCode) 20 | 21 | 22 | 23 | @Flight.DurationHours hours 24 | 25 | 26 | @code 27 | { 28 | [Parameter] 29 | public string Symbol { get; set; } 30 | 31 | [Parameter] 32 | public FlightSegment Flight { get; set; } 33 | } 34 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/Components/SearchResults.razor: -------------------------------------------------------------------------------- 1 | 2 | @if (Itineraries != null) 3 | { 4 | 5 | @Itineraries.Count results 6 | 7 | Cheapest 8 | Quickest 9 | 10 | 11 | 12 | @foreach (var item in sortedItineraries) 13 | { 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | @item.Price.ToString("$0") 25 | OnAddItinerary.InvokeAsync(item))">Add 26 | 27 | 28 | } 29 | } 30 | 31 | 32 | @code 33 | { 34 | // Parameters 35 | [Parameter] 36 | public IReadOnlyList Itineraries { get; set; } 37 | 38 | [Parameter] 39 | public EventCallback OnAddItinerary { get; set; } 40 | 41 | // Private state 42 | private SortOrder chosenSortOrder; 43 | private IEnumerable sortedItineraries 44 | => chosenSortOrder == SortOrder.Price 45 | ? Itineraries.OrderBy(x => x.Price) 46 | : Itineraries.OrderBy(x => x.TotalDurationHours); 47 | 48 | private enum SortOrder { Price, Duration } 49 | } 50 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/Components/Shortlist.razor: -------------------------------------------------------------------------------- 1 | Shortlist (@Itineraries.Count) 2 | 3 | @foreach (var item in Itineraries) 4 | { 5 | 6 | 7 | 8 | @item.Outbound.FromAirportCode - 9 | @item.Outbound.ToAirportCode 10 | 11 | OnRemoveItinerary.InvokeAsync(item))"> 12 | × 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | @item.AirlineName 23 | @item.Price.ToString("$0") 24 | 25 | 26 | } 27 | 28 | @code 29 | { 30 | [Parameter] 31 | public IReadOnlyList Itineraries { get; set; } 32 | 33 | [Parameter] 34 | public EventCallback OnRemoveItinerary { get; set; } 35 | } 36 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/Components/ShortlistFlightSegment.razor: -------------------------------------------------------------------------------- 1 | 2 | @Flight.DepartureTime.ToString("ddd MMM d") 3 | @Flight.TicketClass.ToDisplayString() 4 | 5 | 6 | 7 | @Flight.DepartureTime.ToShortTimeString() 8 | @Flight.FromAirportCode 9 | 10 | 11 | ➝ 12 | 13 | 14 | @Flight.ReturnTime.ToShortTimeString() 15 | @Flight.ToAirportCode 16 | 17 | 18 | @code 19 | { 20 | [Parameter] 21 | public FlightSegment Flight { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/FlightFinder.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | Exe 6 | 7.3 7 | 3.0 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/Main.razor: -------------------------------------------------------------------------------- 1 | @inject AppState state 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | @code 20 | { 21 | protected override void OnInitialized() 22 | { 23 | state.OnChange += StateHasChanged; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Hosting; 2 | 3 | namespace FlightFinder.Client 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | CreateHostBuilder(args).Build().Run(); 10 | } 11 | 12 | public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) => 13 | BlazorWebAssemblyHost.CreateDefaultBuilder() 14 | .UseBlazorStartup(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/Services/AppState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using FlightFinder.Shared; 6 | using Microsoft.AspNetCore.Components; 7 | 8 | namespace FlightFinder.Client.Services 9 | { 10 | public class AppState 11 | { 12 | // Actual state 13 | public IReadOnlyList SearchResults { get; private set; } 14 | public bool SearchInProgress { get; private set; } 15 | 16 | private IList shortlist = new List(); 17 | public IReadOnlyList Shortlist => (IReadOnlyList)shortlist; 18 | 19 | // Lets components receive change notifications 20 | // Could have whatever granularity you want (more events, hierarchy...) 21 | public event Action OnChange; 22 | 23 | // Receive 'http' instance from DI 24 | private readonly HttpClient http; 25 | public AppState(HttpClient httpInstance) 26 | { 27 | http = httpInstance; 28 | } 29 | 30 | public async Task Search(SearchCriteria criteria) 31 | { 32 | SearchInProgress = true; 33 | NotifyStateChanged(); 34 | 35 | SearchResults = await http.PostJsonAsync("/api/flightsearch", criteria); 36 | SearchInProgress = false; 37 | NotifyStateChanged(); 38 | } 39 | 40 | public async Task LoadShortList() 41 | { 42 | // Haven't yet figured out how to get this to be invoked on page load... 43 | shortlist = (await http.GetJsonAsync>("/api/shortlist")); 44 | NotifyStateChanged(); 45 | } 46 | 47 | public async Task AddToShortlist(Itinerary itinerary) 48 | { 49 | shortlist = (await http.PostJsonAsync>("/api/shortlist/add", itinerary)); 50 | NotifyStateChanged(); 51 | } 52 | 53 | public async Task RemoveFromShortlist(Itinerary itinerary) 54 | { 55 | shortlist = (await http.PostJsonAsync>("/api/shortlist/remove", itinerary)); 56 | NotifyStateChanged(); 57 | } 58 | 59 | private void NotifyStateChanged() => OnChange?.Invoke(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/Startup.cs: -------------------------------------------------------------------------------- 1 | using FlightFinder.Client.Services; 2 | using Microsoft.AspNetCore.Components.Builder; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace FlightFinder.Client 6 | { 7 | public class Startup 8 | { 9 | public void ConfigureServices(IServiceCollection services) 10 | { 11 | services.AddSingleton(); 12 | } 13 | 14 | public void Configure(IComponentsApplicationBuilder app) 15 | { 16 | app.AddComponent("body"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Components.Routing 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Web 5 | @using FlightFinder.Client.Services 6 | @using FlightFinder.Client.Components 7 | @using FlightFinder.Shared 8 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | /* Generated from site.scss. Do not modify directly. */ 2 | /* 3 | The SCSS structure here is not well organised. There's a lot of unnecessary repetition, 4 | and no use of variables to factor out common or easily-changeable values. 5 | 6 | If you're a SCSS expert, please consider submitting a PR to show how well-organised SCSS 7 | should look. Ideally it should be as predictable and industry-standard as possible. 8 | */ 9 | html { 10 | height: 100%; 11 | } 12 | 13 | body { 14 | height: 100%; 15 | display: flex; 16 | } 17 | 18 | #search-and-results-area { 19 | display: flex; 20 | flex: auto; 21 | flex-direction: column; 22 | } 23 | 24 | .arrow { 25 | flex-grow: 0; 26 | font-size: 20px; 27 | color: white; 28 | } 29 | 30 | .form-control { 31 | white-space: nowrap; 32 | } 33 | 34 | .form-control:focus-within { 35 | border-color: #2784ff; 36 | } 37 | 38 | .form-control div { 39 | color: #c4c4c4; 40 | flex: initial; 41 | width: 75px; 42 | } 43 | 44 | .form-control div i { 45 | font-style: normal; 46 | font-family: Arial, Helvetica, sans-serif; 47 | color: #eee; 48 | } 49 | 50 | .form-control input { 51 | border: 0; 52 | flex: auto; 53 | width: 0; 54 | padding-left: 6px; 55 | background: none; 56 | font-weight: 600; 57 | color: white; 58 | } 59 | 60 | .form-control input:focus { 61 | outline: none; 62 | } 63 | 64 | .row { 65 | flex-wrap: nowrap !important; 66 | } 67 | 68 | .greyout { 69 | position: absolute; 70 | width: 100%; 71 | min-height: 100%; 72 | } 73 | 74 | .greyout > .cover { 75 | background: rgba(150, 150, 150, 0.5); 76 | position: absolute; 77 | width: 100%; 78 | height: 100%; 79 | z-index: 1; 80 | } 81 | 82 | #search-area { 83 | flex: initial; 84 | background: linear-gradient(135deg, #cb60b3 0%, #c146a1 30%, #a80077 85%, #db36a4 100%); 85 | } 86 | 87 | #search-area select { 88 | background-color: rgba(0, 0, 0, 0.15); 89 | border: none; 90 | color: white; 91 | font-weight: 600; 92 | } 93 | 94 | #search-area .form-control { 95 | border: none; 96 | padding-bottom: .25rem; 97 | background-color: rgba(0, 0, 0, 0.3); 98 | color: white; 99 | } 100 | 101 | #search-area select:hover { 102 | background-color: rgba(255, 255, 255, 0.1); 103 | } 104 | 105 | #search-area select option { 106 | color: black; 107 | } 108 | 109 | #search-area button { 110 | box-shadow: 0 0 5px 2px rgba(255, 255, 255, 0.4); 111 | background-color: white; 112 | border: none; 113 | font-weight: 600; 114 | color: black; 115 | } 116 | 117 | #results-area { 118 | flex: auto; 119 | background-color: #f0f0f0; 120 | overflow-y: scroll; 121 | position: relative; 122 | } 123 | 124 | #results-area .title { 125 | display: flex; 126 | justify-content: space-between; 127 | align-items: center; 128 | } 129 | 130 | #results-area .title .custom-select { 131 | width: auto; 132 | border: none; 133 | background-color: #d8d8d8; 134 | } 135 | 136 | #results-area .title .custom-select option { 137 | background-color: white; 138 | } 139 | 140 | #results-area h2 { 141 | font-weight: 200; 142 | } 143 | 144 | #results-area ul { 145 | flex: auto; 146 | } 147 | 148 | #results-area h4 { 149 | margin-bottom: 0; 150 | font-size: 1.2rem; 151 | color: black; 152 | } 153 | 154 | #results-area .airline small { 155 | color: #777; 156 | font-size: 0.8rem !important; 157 | display: block; 158 | } 159 | 160 | #results-area .d-flex button { 161 | width: 100px; 162 | flex: initial; 163 | } 164 | 165 | #results-area .d-flex .arrow { 166 | width: 30px; 167 | flex: initial; 168 | color: black; 169 | } 170 | 171 | #results-area .symbol { 172 | width: 30px; 173 | flex: initial; 174 | font-family: Arial, Helvetica, sans-serif; 175 | color: #afafaf; 176 | } 177 | 178 | #results-area .date { 179 | text-align: center; 180 | color: #777; 181 | font-size: 0.8rem !important; 182 | } 183 | 184 | #results-area .departure { 185 | text-align: center; 186 | color: #777; 187 | font-size: 0.8rem !important; 188 | } 189 | 190 | #results-area .arrow { 191 | text-align: center; 192 | } 193 | 194 | #results-area .return { 195 | text-align: center; 196 | color: #777; 197 | font-size: 0.8rem !important; 198 | } 199 | 200 | #results-area .duration { 201 | text-align: center; 202 | } 203 | 204 | #results-area .price { 205 | flex: initial; 206 | width: 120px; 207 | text-align: center; 208 | } 209 | 210 | #results-area button { 211 | background-color: #9d136d; 212 | color: white; 213 | } 214 | 215 | #results-area button:hover, #results-area button:active { 216 | background-color: #e558b4; 217 | } 218 | 219 | #results-area li > div { 220 | flex: 1 0; 221 | } 222 | 223 | #selections-area { 224 | font-size: 0.9rem; 225 | flex: none; 226 | width: 350px; 227 | background: #404040; 228 | box-shadow: inset 20px 0 20px -20px rgba(0, 0, 0, 0.7); 229 | overflow-y: auto; 230 | } 231 | 232 | #selections-area h2 { 233 | color: #9d9c9c; 234 | font-weight: 200; 235 | } 236 | 237 | #selections-area h4 { 238 | margin-bottom: 0; 239 | font-size: 16px; 240 | color: black; 241 | } 242 | 243 | #selections-area ul.mb-4 li:first-child { 244 | background-color: #9d136d; 245 | border-color: #9d136d; 246 | color: white; 247 | position: relative; 248 | } 249 | 250 | #selections-area .date { 251 | flex: auto; 252 | } 253 | 254 | #selections-area .departure, #selections-area .return, #selections-area .price { 255 | text-align: center; 256 | color: #777; 257 | font-size: 0.8rem !important; 258 | } 259 | 260 | #selections-area .airline { 261 | color: #777; 262 | font-size: 0.8rem !important; 263 | } 264 | 265 | #selections-area .arrow { 266 | text-align: center; 267 | width: 40px; 268 | flex: initial; 269 | color: black; 270 | } 271 | 272 | #selections-area .price h2 { 273 | color: black; 274 | font-weight: 400; 275 | } 276 | 277 | #selections-area button { 278 | background-color: #9d136d; 279 | color: white; 280 | margin-left: 12px; 281 | } 282 | 283 | #selections-area button.close { 284 | position: absolute; 285 | right: 12px; 286 | top: 8px; 287 | } 288 | 289 | #selections-area li { 290 | display: flex; 291 | flex: auto; 292 | align-items: center; 293 | } 294 | 295 | #selections-area li > div { 296 | flex: 1 0; 297 | } 298 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/wwwroot/css/site.scss: -------------------------------------------------------------------------------- 1 | /* 2 | The SCSS structure here is not well organised. There's a lot of unnecessary repetition, 3 | and no use of variables to factor out common or easily-changeable values. 4 | 5 | If you're a SCSS expert, please consider submitting a PR to show how well-organised SCSS 6 | should look. Ideally it should be as predictable and industry-standard as possible. 7 | */ 8 | 9 | html { 10 | height: 100%; 11 | } 12 | 13 | body { 14 | height: 100%; 15 | display: flex; 16 | } 17 | 18 | #search-and-results-area { 19 | display: flex; 20 | flex: auto; 21 | flex-direction: column; 22 | } 23 | 24 | .arrow { 25 | flex-grow: 0; 26 | font-size: 20px; 27 | color: white; 28 | } 29 | 30 | .form-control { 31 | white-space: nowrap; 32 | 33 | &:focus-within { 34 | border-color: #2784ff; 35 | } 36 | 37 | div { 38 | color: #c4c4c4; 39 | flex: initial; 40 | width: 75px; 41 | 42 | i { 43 | font-style: normal; 44 | font-family: Arial, Helvetica, sans-serif; 45 | color: #eee; 46 | } 47 | } 48 | 49 | input { 50 | border: 0; 51 | flex: auto; 52 | width: 0; 53 | padding-left: 6px; 54 | background: none; 55 | font-weight: 600; 56 | color: white; 57 | 58 | &:focus { 59 | outline: none; 60 | } 61 | } 62 | } 63 | 64 | .row { 65 | flex-wrap: nowrap !important; 66 | } 67 | 68 | .greyout { 69 | position: absolute; 70 | width: 100%; 71 | min-height: 100%; 72 | 73 | > .cover { 74 | background: rgba(150, 150, 150, 0.5); 75 | position: absolute; 76 | width: 100%; 77 | height: 100%; 78 | z-index: 1; 79 | } 80 | } 81 | 82 | #search-area { 83 | flex: initial; 84 | background: linear-gradient(135deg, #cb60b3 0%, #c146a1 30%, #a80077 85%, #db36a4 100%); 85 | 86 | select { 87 | background-color: rgba(0, 0, 0, 0.15); 88 | border: none; 89 | color: white; 90 | font-weight: 600; 91 | } 92 | 93 | .form-control { 94 | border: none; 95 | padding-bottom: .25rem; 96 | background-color: rgba(0, 0, 0, 0.3); 97 | color: white; 98 | } 99 | 100 | select { 101 | &:hover { 102 | background-color: rgba(255, 255, 255, 0.1); 103 | } 104 | 105 | option { 106 | color: black; 107 | } 108 | } 109 | 110 | button { 111 | box-shadow: 0 0 5px 2px rgba(255, 255, 255, 0.4); 112 | background-color: white; 113 | border: none; 114 | font-weight: 600; 115 | color: black; 116 | } 117 | } 118 | 119 | #results-area { 120 | flex: auto; 121 | background-color: #f0f0f0; 122 | overflow-y: scroll; 123 | position: relative; 124 | 125 | .title { 126 | display: flex; 127 | justify-content: space-between; 128 | align-items: center; 129 | 130 | .custom-select { 131 | width: auto; 132 | border: none; 133 | background-color: #d8d8d8; 134 | 135 | option { 136 | background-color: white; 137 | } 138 | } 139 | } 140 | 141 | h2 { 142 | font-weight: 200; 143 | } 144 | 145 | ul { 146 | flex: auto; 147 | } 148 | 149 | h4 { 150 | margin-bottom: 0; 151 | font-size: 1.2rem; 152 | color: black; 153 | } 154 | 155 | .airline small { 156 | color: #777; 157 | font-size: 0.8rem !important; 158 | display: block; 159 | } 160 | 161 | .d-flex { 162 | button { 163 | width: 100px; 164 | flex: initial; 165 | } 166 | 167 | .arrow { 168 | width: 30px; 169 | flex: initial; 170 | color: black; 171 | } 172 | } 173 | 174 | .symbol { 175 | width: 30px; 176 | flex: initial; 177 | font-family: Arial, Helvetica, sans-serif; 178 | color: #afafaf; 179 | } 180 | 181 | .date { 182 | text-align: center; 183 | color: #777; 184 | font-size: 0.8rem !important; 185 | } 186 | 187 | .departure { 188 | text-align: center; 189 | color: #777; 190 | font-size: 0.8rem !important; 191 | } 192 | 193 | .arrow { 194 | text-align: center; 195 | } 196 | 197 | .return { 198 | text-align: center; 199 | color: #777; 200 | font-size: 0.8rem !important; 201 | } 202 | 203 | .duration { 204 | text-align: center; 205 | } 206 | 207 | .price { 208 | flex: initial; 209 | width: 120px; 210 | text-align: center; 211 | } 212 | 213 | button { 214 | background-color: #9d136d; 215 | color: white; 216 | 217 | &:hover, &:active { 218 | background-color: #e558b4; 219 | } 220 | } 221 | 222 | li > div { 223 | flex: 1 0; 224 | } 225 | } 226 | 227 | #selections-area { 228 | font-size: 0.9rem; 229 | flex: none; 230 | width: 350px; 231 | background: #404040; 232 | box-shadow: inset 20px 0 20px -20px rgba(0, 0, 0, 0.7); 233 | overflow-y: auto; 234 | 235 | h2 { 236 | color: #9d9c9c; 237 | font-weight: 200; 238 | } 239 | 240 | h4 { 241 | margin-bottom: 0; 242 | font-size: 16px; 243 | color: black; 244 | } 245 | 246 | ul.mb-4 li:first-child { 247 | background-color: #9d136d; 248 | border-color: #9d136d; 249 | color: white; 250 | position: relative; 251 | } 252 | 253 | .date { 254 | flex: auto; 255 | } 256 | 257 | .departure, .return, .price { 258 | text-align: center; 259 | color: #777; 260 | font-size: 0.8rem !important; 261 | } 262 | 263 | .airline { 264 | color: #777; 265 | font-size: 0.8rem !important; 266 | } 267 | 268 | .arrow { 269 | text-align: center; 270 | width: 40px; 271 | flex: initial; 272 | color: black; 273 | } 274 | 275 | .price h2 { 276 | color: black; 277 | font-weight: 400; 278 | } 279 | 280 | button { 281 | background-color: #9d136d; 282 | color: white; 283 | margin-left: 12px; 284 | 285 | &.close { 286 | position: absolute; 287 | right: 12px; 288 | top: 8px; 289 | } 290 | } 291 | 292 | li { 293 | display: flex; 294 | flex: auto; 295 | align-items: center; 296 | 297 | > div { 298 | flex: 1 0; 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Client/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FlightFinder 6 | 7 | 8 | 9 | 10 | 11 | Loading... 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Server/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "type": "shell", 9 | "command": "msbuild", 10 | "args": [ 11 | // Ask msbuild to generate full paths for file names. 12 | "/property:GenerateFullPaths=true", 13 | "/t:build", 14 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel 15 | "/consoleloggerparameters:NoSummary" 16 | ], 17 | "group": "build", 18 | "presentation": { 19 | // Reveal the output only if unrecognized errors occur. 20 | "reveal": "silent" 21 | }, 22 | // Use the standard MS compiler pattern to detect errors, warnings and infos 23 | "problemMatcher": "$msCompile" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Server/Controllers/AirportsController.cs: -------------------------------------------------------------------------------- 1 | using FlightFinder.Shared; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System.Collections.Generic; 4 | 5 | namespace FlightFinder.Server.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | public class AirportsController : Controller 9 | { 10 | public IEnumerable Airports() 11 | { 12 | return SampleData.Airports; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Server/Controllers/FlightSearchController.cs: -------------------------------------------------------------------------------- 1 | using FlightFinder.Shared; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Caching.Distributed; 8 | 9 | namespace FlightFinder.Server.Controllers 10 | { 11 | [Route("api/[controller]")] 12 | public class FlightSearchController 13 | { 14 | private readonly IDistributedCache cache; 15 | public FlightSearchController(IDistributedCache c) 16 | { 17 | cache = c; 18 | } 19 | 20 | // public async Task> Search([FromBody] SearchCriteria criteria) 21 | // { 22 | // var flights = await FlightDataSource.FindFlightsAsync(criteria); 23 | // return flights; 24 | // } 25 | 26 | public async Task> Search([FromBody] SearchCriteria criteria) 27 | { 28 | // returns something like "FlightFinder/Search/LHR/SEA/2020-07-02.00:00:00Z/2020-07-09.00:00:00Z/Economy" 29 | var searchId = criteria.GetSearchId(); 30 | 31 | var flights = await cache.GetSearchResultsAsync(searchId); 32 | 33 | if (flights == null) 34 | { 35 | flights = await FlightDataSource.FindFlightsAsync(criteria); 36 | 37 | await cache.AddSearchResultsAsync(searchId, flights); 38 | } 39 | 40 | return flights; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Server/Controllers/ShortListController.cs: -------------------------------------------------------------------------------- 1 | using FlightFinder.Shared; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace FlightFinder.Server.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | public class ShortList : Controller 11 | { 12 | [Route("get")] 13 | public IList Get() 14 | { 15 | var result = this.HttpContext.Session.GetShortList(); 16 | return result; 17 | } 18 | 19 | [Route("add")] 20 | public async Task> Add([FromBody]Itinerary item) 21 | { 22 | var list = this.HttpContext.Session.GetShortList(); 23 | 24 | list.Add(item); 25 | 26 | await this.HttpContext.Session.SetShortList(list); 27 | 28 | return list; 29 | } 30 | 31 | [Route("remove")] 32 | public async Task> Remove([FromBody]Itinerary item) 33 | { 34 | var list = this.HttpContext.Session.GetShortList(); 35 | 36 | foreach(var i in list) 37 | { 38 | // Real app would need better logic here. 39 | if (i.AirlineName == item.AirlineName 40 | && i.Price == item.Price) 41 | { 42 | list.Remove(i); 43 | break; 44 | } 45 | } 46 | 47 | await this.HttpContext.Session.SetShortList(list); 48 | return Get(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Server/FlightDataSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FlightFinder.Shared; 6 | 7 | 8 | namespace FlightFinder.Server 9 | { 10 | public static class FlightDataSource 11 | { 12 | public static async Task> FindFlightsAsync(SearchCriteria criteria) 13 | { 14 | await Task.Delay(500);//look busy 15 | var rng = new Random(); 16 | return Enumerable.Range(0, rng.Next(2, 5)).Select(_ => new Itinerary 17 | { 18 | Price = rng.Next(100, 2000), 19 | Outbound = new FlightSegment 20 | { 21 | Airline = RandomAirline(), 22 | FromAirportCode = criteria.FromAirport, 23 | ToAirportCode = criteria.ToAirport, 24 | DepartureTime = criteria.OutboundDate.AddHours(rng.Next(24)).AddMinutes(5 * rng.Next(12)), 25 | ReturnTime = criteria.OutboundDate.AddHours(rng.Next(24)).AddMinutes(5 * rng.Next(12)), 26 | DurationHours = 2 + rng.Next(10), 27 | TicketClass = criteria.TicketClass 28 | }, 29 | Return = new FlightSegment 30 | { 31 | Airline = RandomAirline(), 32 | FromAirportCode = criteria.ToAirport, 33 | ToAirportCode = criteria.FromAirport, 34 | DepartureTime = criteria.ReturnDate.AddHours(rng.Next(24)).AddMinutes(5 * rng.Next(12)), 35 | ReturnTime = criteria.ReturnDate.AddHours(rng.Next(24)).AddMinutes(5 * rng.Next(12)), 36 | DurationHours = 2 + rng.Next(10), 37 | TicketClass = criteria.TicketClass 38 | }, 39 | }) 40 | .OrderBy(e => e.Price) 41 | .ToList(); 42 | } 43 | 44 | private static string RandomAirline() 45 | => SampleData.Airlines[new Random().Next(SampleData.Airlines.Length)]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Server/FlightFinder.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 7.3 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Server/IDistributedCacheExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using Microsoft.Extensions.Caching.Distributed; 4 | using System.Threading.Tasks; 5 | using System.Collections.Generic; 6 | using FlightFinder.Shared; 7 | 8 | namespace Microsoft.Extensions.Caching.Distributed 9 | { 10 | public static class IDistributedCacheExtensions 11 | { 12 | public static async Task> GetSearchResultsAsync(this IDistributedCache cache, string searchId) 13 | { 14 | return await cache.GetAsync>(searchId); 15 | } 16 | 17 | public static async Task AddSearchResultsAsync(this IDistributedCache cache, string searchId, IList flights) 18 | { 19 | var options = new DistributedCacheEntryOptions(); 20 | options.SlidingExpiration = TimeSpan.FromSeconds(10); 21 | options.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30); 22 | await cache.SetAsync(searchId, flights, options); 23 | } 24 | 25 | public static async Task GetAsync(this IDistributedCache cache, string key) where T : class 26 | { 27 | var json = await cache.GetStringAsync(key); 28 | if (json == null) 29 | return null; 30 | 31 | return JsonConvert.DeserializeObject(json); 32 | } 33 | 34 | public static async Task SetAsync(this IDistributedCache cache, string key, T value, DistributedCacheEntryOptions options) where T : class 35 | { 36 | var json = JsonConvert.SerializeObject(value); 37 | await cache.SetStringAsync(key, json, options); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Server/ISessionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Newtonsoft.Json; 4 | using System.Collections.Generic; 5 | using FlightFinder.Shared; 6 | 7 | namespace Microsoft.AspNetCore.Http 8 | { 9 | public static class ISessionExtensions 10 | { 11 | const string shortlistKey = "shortlist"; 12 | public static IList GetShortList(this ISession session) 13 | { 14 | if (session.IsAvailable) 15 | { 16 | var json = session.GetString(shortlistKey); 17 | 18 | if (json != null) 19 | { 20 | return JsonConvert.DeserializeObject>(json); 21 | } 22 | } 23 | 24 | // nothing in the cache, so return empty list 25 | return new List(); 26 | } 27 | 28 | public static async Task SetShortList(this ISession session, IList list) 29 | { 30 | if (list == null || list.Count == 0) 31 | { 32 | session.Remove(shortlistKey); 33 | return; 34 | } 35 | 36 | var json = JsonConvert.SerializeObject(list); 37 | 38 | session.SetString(shortlistKey, json); 39 | 40 | await session.CommitAsync(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Server/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.AspNetCore; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | 8 | namespace FlightFinder.Server 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | BuildWebHost(args).Run(); 15 | } 16 | 17 | public static IWebHost BuildWebHost(string[] args) => 18 | WebHost.CreateDefaultBuilder(args) 19 | .UseConfiguration(new ConfigurationBuilder() 20 | .AddCommandLine(args) 21 | .Build()) 22 | .UseStartup() 23 | .Build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Server/SampleData.cs: -------------------------------------------------------------------------------- 1 | using FlightFinder.Shared; 2 | 3 | namespace FlightFinder.Server 4 | { 5 | public class SampleData 6 | { 7 | public readonly static Airport[] Airports = new[] 8 | { 9 | new Airport { Code = "ATL", DisplayName = "Hartsfield–Jackson Atlanta International" }, 10 | new Airport { Code = "PEK", DisplayName = "Beijing Capital International" }, 11 | new Airport { Code = "DXB", DisplayName = "Dubai International" }, 12 | new Airport { Code = "LAX", DisplayName = "Los Angeles International" }, 13 | new Airport { Code = "HND", DisplayName = "Tokyo Haneda International" }, 14 | new Airport { Code = "ORD", DisplayName = "O'Hare International" }, 15 | new Airport { Code = "LHR", DisplayName = "London Heathrow" }, 16 | new Airport { Code = "HKG", DisplayName = "Hong Kong International" }, 17 | new Airport { Code = "PVG", DisplayName = "Shanghai Pudong International" }, 18 | new Airport { Code = "CDG", DisplayName = "Charles de Gaulle" }, 19 | new Airport { Code = "DFW", DisplayName = "Dallas/Fort Worth International" }, 20 | new Airport { Code = "AMS", DisplayName = "Amsterdam Schiphol" }, 21 | new Airport { Code = "FRA", DisplayName = "Frankfurt" }, 22 | new Airport { Code = "IST", DisplayName = "Istanbul Atatürk" }, 23 | new Airport { Code = "CAN", DisplayName = "Guangzhou Baiyun International" }, 24 | new Airport { Code = "JFK", DisplayName = "John F. Kennedy International" }, 25 | new Airport { Code = "SIN", DisplayName = "Singapore Changi" }, 26 | new Airport { Code = "DEN", DisplayName = "Denver International" }, 27 | new Airport { Code = "ICN", DisplayName = "Seoul Incheon International" }, 28 | new Airport { Code = "BKK", DisplayName = "Suvarnabhumi" }, 29 | new Airport { Code = "DEL", DisplayName = "Indira Gandhi International" }, 30 | new Airport { Code = "CGK", DisplayName = "Soekarno–Hatta International" }, 31 | new Airport { Code = "SFO", DisplayName = "San Francisco International" }, 32 | new Airport { Code = "KUL", DisplayName = "Kuala Lumpur International" }, 33 | new Airport { Code = "MAD", DisplayName = "Madrid Barajas" }, 34 | new Airport { Code = "LAS", DisplayName = "McCarran International" }, 35 | new Airport { Code = "CTU", DisplayName = "Chengdu Shuangliu International" }, 36 | new Airport { Code = "SEA", DisplayName = "Seattle-Tacoma International" }, 37 | new Airport { Code = "BOM", DisplayName = "Chhatrapati Shivaji International" }, 38 | new Airport { Code = "MIA", DisplayName = "Miami International" }, 39 | new Airport { Code = "CLT", DisplayName = "Charlotte Douglas International" }, 40 | new Airport { Code = "YYZ", DisplayName = "Toronto Pearson International" }, 41 | new Airport { Code = "BCN", DisplayName = "Barcelona–El Prat" }, 42 | new Airport { Code = "PHX", DisplayName = "Phoenix Sky Harbor International" }, 43 | new Airport { Code = "LGW", DisplayName = "London Gatwick" }, 44 | new Airport { Code = "TPE", DisplayName = "Taiwan Taoyuan International" }, 45 | new Airport { Code = "MUC", DisplayName = "Munich" }, 46 | new Airport { Code = "SYD", DisplayName = "Sydney Kingsford-Smith" }, 47 | new Airport { Code = "KMG", DisplayName = "Kunming Changshui International" }, 48 | new Airport { Code = "SZX", DisplayName = "Shenzhen Bao'an International" }, 49 | new Airport { Code = "MCO", DisplayName = "Orlando International" }, 50 | new Airport { Code = "FCO", DisplayName = "Leonardo da Vinci–Fiumicino" }, 51 | new Airport { Code = "IAH", DisplayName = "George Bush Intercontinental" }, 52 | new Airport { Code = "MEX", DisplayName = "Benito Juárez International" }, 53 | new Airport { Code = "SHA", DisplayName = "Shanghai Hongqiao International" }, 54 | new Airport { Code = "EWR", DisplayName = "Newark Liberty International" }, 55 | new Airport { Code = "MNL", DisplayName = "Ninoy Aquino International" }, 56 | new Airport { Code = "NRT", DisplayName = "Narita International" }, 57 | new Airport { Code = "MSP", DisplayName = "Minneapolis/St Paul International" }, 58 | new Airport { Code = "DOH", DisplayName = "Hamad International" }, 59 | }; 60 | 61 | public readonly static string[] Airlines = new[] 62 | { 63 | "American Airlines", 64 | "British Airways", 65 | "Delta", 66 | "Emirates", 67 | "Etihad", 68 | "JetBlue", 69 | "KLM", 70 | "Singapore Airways", 71 | "Qantas", 72 | "Virgin Atlantic", 73 | }; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Server/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.ResponseCompression; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using System.Linq; 10 | using System; 11 | 12 | namespace FlightFinder.Server 13 | { 14 | public class Startup 15 | { 16 | // This method gets called by the runtime. Use this method to add services to the container. 17 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 18 | public void ConfigureServices(IServiceCollection services) 19 | { 20 | services.AddMvc(); 21 | 22 | // Add IDistributedCache instance to dependency injection 23 | services.AddStackExchangeRedisCache(options => 24 | { 25 | options.Configuration = "localhost"; 26 | options.InstanceName = "FlightFinder/"; // For App Isolation - Prefix automatically added to any key written to the cache 27 | }); 28 | 29 | 30 | 31 | 32 | 33 | 34 | services.AddSession(options => 35 | { 36 | // Set a short timeout for easy testing. 37 | options.IdleTimeout = TimeSpan.FromMinutes(2); 38 | options.Cookie.HttpOnly = true; 39 | // Make the session cookie essential 40 | options.Cookie.IsEssential = true; 41 | }); 42 | 43 | 44 | 45 | services.AddResponseCompression(opts => 46 | { 47 | opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat( 48 | new[] { "application/octet-stream" }); 49 | }); 50 | } 51 | 52 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 53 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 54 | { 55 | app.UseResponseCompression(); 56 | 57 | if (env.IsDevelopment()) 58 | { 59 | app.UseDeveloperExceptionPage(); 60 | app.UseBlazorDebugging(); 61 | } 62 | 63 | app.UseStaticFiles(); 64 | app.UseClientSideBlazorFiles(); 65 | 66 | app.UseSession(); 67 | 68 | app.UseRouting(); 69 | 70 | app.UseEndpoints(endpoints => 71 | { 72 | endpoints.MapDefaultControllerRoute(); 73 | endpoints.MapFallbackToClientSideBlazor("index.html"); 74 | }); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Shared/Airport.cs: -------------------------------------------------------------------------------- 1 | namespace FlightFinder.Shared 2 | { 3 | public class Airport 4 | { 5 | public string Code { get; set; } 6 | public string DisplayName { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Shared/FlightFinder.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Shared/FlightSegment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FlightFinder.Shared 4 | { 5 | public class FlightSegment 6 | { 7 | public string Airline { get; set; } 8 | public string FromAirportCode { get; set; } 9 | public string ToAirportCode { get; set; } 10 | public DateTime DepartureTime { get; set; } 11 | public DateTime ReturnTime { get; set; } 12 | public double DurationHours { get; set; } 13 | public TicketClass TicketClass { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Shared/Itinerary.cs: -------------------------------------------------------------------------------- 1 | namespace FlightFinder.Shared 2 | { 3 | public class Itinerary 4 | { 5 | public FlightSegment Outbound { get; set; } 6 | public FlightSegment Return { get; set; } 7 | public decimal Price { get; set; } 8 | 9 | public double TotalDurationHours 10 | => Outbound.DurationHours + Return.DurationHours; 11 | 12 | public string AirlineName => 13 | (Outbound.Airline == Return.Airline) ? Outbound.Airline : "Multiple airlines"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Shared/SearchCriteria.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FlightFinder.Shared 4 | { 5 | public class SearchCriteria 6 | { 7 | public string FromAirport { get; set; } 8 | public string ToAirport { get; set; } 9 | public DateTime OutboundDate { get; set; } 10 | public DateTime ReturnDate { get; set; } 11 | public TicketClass TicketClass { get; set; } 12 | 13 | public SearchCriteria() 14 | { 15 | } 16 | 17 | public SearchCriteria(string fromAirport, string toAirport) : this() 18 | { 19 | FromAirport = fromAirport; 20 | ToAirport = toAirport; 21 | OutboundDate = new DateTime(2020, 7, 2).Date; 22 | ReturnDate = OutboundDate.AddDays(7); 23 | } 24 | 25 | public string GetSearchId() 26 | { 27 | return $"Search/{FromAirport}/{ToAirport}/{FormatDate(OutboundDate)}/{FormatDate(ReturnDate)}/{TicketClass}"; 28 | } 29 | 30 | private static string FormatDate(DateTime d) => d.ToString("u").Replace(" ", "."); 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.Shared/TicketClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FlightFinder.Shared 4 | { 5 | public enum TicketClass : int 6 | { 7 | Economy = 0, 8 | PremiumEconomy = 1, 9 | Business = 2, 10 | First = 3, 11 | } 12 | 13 | public static class TicketClassExtensions 14 | { 15 | public static string ToDisplayString(this TicketClass ticketClass) 16 | { 17 | switch (ticketClass) 18 | { 19 | case TicketClass.Economy: return "Economy"; 20 | case TicketClass.PremiumEconomy: return "Premium Economy"; 21 | case TicketClass.Business: return "Business"; 22 | case TicketClass.First: return "First"; 23 | default: throw new ArgumentException("Unknown ticket class: " + ticketClass.ToString()); 24 | } 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/FlightFinder.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.27130.2027 4 | MinimumVisualStudioVersion = 15.0.26124.0 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlightFinder.Server", "FlightFinder.Server\FlightFinder.Server.csproj", "{5020A8E9-95DC-4ABB-B8ED-5188DD147D91}" 6 | EndProject 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlightFinder.Client", "FlightFinder.Client\FlightFinder.Client.csproj", "{AF2BD0ED-DB6A-418E-AB88-8A9FCF7A0890}" 8 | EndProject 9 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlightFinder.Shared", "FlightFinder.Shared\FlightFinder.Shared.csproj", "{39613A2D-88E6-452E-9396-11EE410BAB78}" 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug|Any CPU = Debug|Any CPU 14 | Debug|x64 = Debug|x64 15 | Debug|x86 = Debug|x86 16 | Release|Any CPU = Release|Any CPU 17 | Release|x64 = Release|x64 18 | Release|x86 = Release|x86 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {5020A8E9-95DC-4ABB-B8ED-5188DD147D91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {5020A8E9-95DC-4ABB-B8ED-5188DD147D91}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {5020A8E9-95DC-4ABB-B8ED-5188DD147D91}.Debug|x64.ActiveCfg = Debug|Any CPU 24 | {5020A8E9-95DC-4ABB-B8ED-5188DD147D91}.Debug|x64.Build.0 = Debug|Any CPU 25 | {5020A8E9-95DC-4ABB-B8ED-5188DD147D91}.Debug|x86.ActiveCfg = Debug|Any CPU 26 | {5020A8E9-95DC-4ABB-B8ED-5188DD147D91}.Debug|x86.Build.0 = Debug|Any CPU 27 | {5020A8E9-95DC-4ABB-B8ED-5188DD147D91}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {5020A8E9-95DC-4ABB-B8ED-5188DD147D91}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {5020A8E9-95DC-4ABB-B8ED-5188DD147D91}.Release|x64.ActiveCfg = Release|Any CPU 30 | {5020A8E9-95DC-4ABB-B8ED-5188DD147D91}.Release|x64.Build.0 = Release|Any CPU 31 | {5020A8E9-95DC-4ABB-B8ED-5188DD147D91}.Release|x86.ActiveCfg = Release|Any CPU 32 | {5020A8E9-95DC-4ABB-B8ED-5188DD147D91}.Release|x86.Build.0 = Release|Any CPU 33 | {AF2BD0ED-DB6A-418E-AB88-8A9FCF7A0890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {AF2BD0ED-DB6A-418E-AB88-8A9FCF7A0890}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {AF2BD0ED-DB6A-418E-AB88-8A9FCF7A0890}.Debug|x64.ActiveCfg = Debug|Any CPU 36 | {AF2BD0ED-DB6A-418E-AB88-8A9FCF7A0890}.Debug|x64.Build.0 = Debug|Any CPU 37 | {AF2BD0ED-DB6A-418E-AB88-8A9FCF7A0890}.Debug|x86.ActiveCfg = Debug|Any CPU 38 | {AF2BD0ED-DB6A-418E-AB88-8A9FCF7A0890}.Debug|x86.Build.0 = Debug|Any CPU 39 | {AF2BD0ED-DB6A-418E-AB88-8A9FCF7A0890}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {AF2BD0ED-DB6A-418E-AB88-8A9FCF7A0890}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {AF2BD0ED-DB6A-418E-AB88-8A9FCF7A0890}.Release|x64.ActiveCfg = Release|Any CPU 42 | {AF2BD0ED-DB6A-418E-AB88-8A9FCF7A0890}.Release|x64.Build.0 = Release|Any CPU 43 | {AF2BD0ED-DB6A-418E-AB88-8A9FCF7A0890}.Release|x86.ActiveCfg = Release|Any CPU 44 | {AF2BD0ED-DB6A-418E-AB88-8A9FCF7A0890}.Release|x86.Build.0 = Release|Any CPU 45 | {39613A2D-88E6-452E-9396-11EE410BAB78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {39613A2D-88E6-452E-9396-11EE410BAB78}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {39613A2D-88E6-452E-9396-11EE410BAB78}.Debug|x64.ActiveCfg = Debug|Any CPU 48 | {39613A2D-88E6-452E-9396-11EE410BAB78}.Debug|x64.Build.0 = Debug|Any CPU 49 | {39613A2D-88E6-452E-9396-11EE410BAB78}.Debug|x86.ActiveCfg = Debug|Any CPU 50 | {39613A2D-88E6-452E-9396-11EE410BAB78}.Debug|x86.Build.0 = Debug|Any CPU 51 | {39613A2D-88E6-452E-9396-11EE410BAB78}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {39613A2D-88E6-452E-9396-11EE410BAB78}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {39613A2D-88E6-452E-9396-11EE410BAB78}.Release|x64.ActiveCfg = Release|Any CPU 54 | {39613A2D-88E6-452E-9396-11EE410BAB78}.Release|x64.Build.0 = Release|Any CPU 55 | {39613A2D-88E6-452E-9396-11EE410BAB78}.Release|x86.ActiveCfg = Release|Any CPU 56 | {39613A2D-88E6-452E-9396-11EE410BAB78}.Release|x86.Build.0 = Release|Any CPU 57 | EndGlobalSection 58 | GlobalSection(SolutionProperties) = preSolution 59 | HideSolutionNode = FALSE 60 | EndGlobalSection 61 | GlobalSection(ExtensibilityGlobals) = postSolution 62 | SolutionGuid = {49BF2623-E92E-4D4A-9954-6844AE36DE07} 63 | EndGlobalSection 64 | EndGlobal 65 | -------------------------------------------------------------------------------- /ASPNetCore/FlightFinder/README.txt: -------------------------------------------------------------------------------- 1 | This sample is a modified version of the code shared here: https://github.com/aspnet/samples/tree/master/samples/aspnetcore/blazor/FlightFinder. 2 | It has been modified to enable Redis as an IDistributedCache service as well as turning on Session State for the Short List functionality in this sample. 3 | This was for a demo at Redis Day Seattle in January 2020. 4 | 5 | For those that are interested: In the recorded demo, I forgot to change the code in AppState.cs to call into 6 | the new ShortList controller, which is why things weren't working at the tail end of the talk. 7 | 8 | NOTE: This code assumes that a Redis server instance is running on localhost on port 6379 (Redis' default port). If you wish to point it 9 | to a different server, you will need to change the Redis configuration in Starup.cs in the Server project. 10 | -------------------------------------------------------------------------------- /BandWidthMonitor/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /BandWidthMonitor/BandwidthLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.NetworkInformation; 3 | using System.Threading.Tasks; 4 | 5 | namespace BandwidthMonitor 6 | { 7 | class BandwidthLogger : IDisposable 8 | { 9 | private TimeSpan _logFrequency; 10 | private bool _disposed; 11 | private long _previousReadBytes; 12 | private long _previousWriteBytes; 13 | DateTimeOffset _previousComputeTime; 14 | 15 | public BandwidthLogger(TimeSpan logFrequency) 16 | { 17 | if (logFrequency <= TimeSpan.Zero) 18 | { 19 | throw new ArgumentOutOfRangeException("logFrequency"); 20 | } 21 | 22 | _logFrequency = logFrequency; 23 | _previousComputeTime = DateTimeOffset.UtcNow; 24 | GetNetworkUsage(out _previousReadBytes, out _previousWriteBytes); 25 | StartLogging(); 26 | } 27 | 28 | private async void StartLogging() 29 | { 30 | try 31 | { 32 | const long bitsPerByte = 8; 33 | const double oneMeg = 1024 * 1024; 34 | 35 | 36 | while (!_disposed) 37 | { 38 | await Task.Delay(_logFrequency); 39 | 40 | long bytesRead; 41 | long bytesWrite; 42 | GetNetworkUsage(out bytesRead, out bytesWrite); 43 | 44 | DateTimeOffset currentTime = DateTimeOffset.UtcNow; 45 | TimeSpan elapsed = currentTime - _previousComputeTime; 46 | 47 | long readDelta = (bytesRead - _previousReadBytes); 48 | long writeDelta = (bytesWrite - _previousWriteBytes); 49 | 50 | _previousReadBytes = bytesRead; 51 | _previousWriteBytes = bytesWrite; 52 | _previousComputeTime = currentTime; 53 | 54 | double mbitsReadPerSecond = readDelta <= 0 ? 0 : ((readDelta * bitsPerByte) / oneMeg) / elapsed.TotalSeconds; 55 | double mbitsWritePerSecond = writeDelta <= 0 ? 0 : ((writeDelta * bitsPerByte) / oneMeg) / elapsed.TotalSeconds; 56 | 57 | LogUsage(mbitsReadPerSecond, mbitsWritePerSecond); 58 | } 59 | } 60 | catch(Exception) 61 | { 62 | 63 | } 64 | } 65 | 66 | protected virtual void LogUsage(double mbitsReadPerSecond, double mbitsWritePerSecond) 67 | { 68 | Console.WriteLine("[{0}] BandWidth Usage ==> READ: {1} MBits/Sec, WRITE: {2} MBits/Sec", 69 | DateTimeOffset.UtcNow.ToString("u"), 70 | Math.Round(mbitsReadPerSecond, 2), 71 | Math.Round(mbitsWritePerSecond, 2) 72 | ); 73 | } 74 | 75 | private static void GetNetworkUsage(out long bytesRead, out long bytesWrite) 76 | { 77 | bytesRead = 0L; 78 | bytesWrite = 0L; 79 | try 80 | { 81 | var nics = NetworkInterface.GetAllNetworkInterfaces(); 82 | 83 | foreach (var nic in nics) 84 | { 85 | long nicbytesRead = nic.GetIPStatistics().BytesReceived; 86 | long nicbytesWrite = nic.GetIPStatistics().BytesSent; 87 | bytesRead += nicbytesRead; 88 | bytesWrite += nicbytesWrite; 89 | } 90 | } 91 | catch(Exception) 92 | { 93 | 94 | } 95 | } 96 | 97 | public void Dispose() 98 | { 99 | _disposed = true; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /BandWidthMonitor/BandwidthMonitor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {704D6A6C-711C-4332-9248-5F7009C1E071} 8 | Exe 9 | Properties 10 | BandwidthMonitor 11 | BandwidthMonitor 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 59 | -------------------------------------------------------------------------------- /BandWidthMonitor/BandwidthMonitor.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30723.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BandwidthMonitor", "BandwidthMonitor.csproj", "{704D6A6C-711C-4332-9248-5F7009C1E071}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {704D6A6C-711C-4332-9248-5F7009C1E071}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {704D6A6C-711C-4332-9248-5F7009C1E071}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {704D6A6C-711C-4332-9248-5F7009C1E071}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {704D6A6C-711C-4332-9248-5F7009C1E071}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /BandWidthMonitor/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BandwidthMonitor 4 | { 5 | class Program 6 | { 7 | static void Main(string[] args) 8 | { 9 | using (var bandwidthLogger = new BandwidthLogger(TimeSpan.FromSeconds(2))) 10 | { 11 | Console.WriteLine("Press Enter to close application"); 12 | Console.ReadLine(); 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BandWidthMonitor/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("BandwidthMonitor")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("BandwidthMonitor")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("afc8fb62-bc66-4120-923a-18a43b7b7a10")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jon Cole 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SampleCode 2 | Miscellaneous Sample Code 3 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/RedisClientTests.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/lettuce/lettuce.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/lettuce/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | biz.paluch.redis 8 | lettuce 9 | 4.3.3 10 | 11 | 12 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/CommandLineArgs.java: -------------------------------------------------------------------------------- 1 | import com.sun.istack.internal.NotNull; 2 | import sun.tools.jar.CommandLine; 3 | 4 | import java.util.ArrayList; 5 | 6 | public class CommandLineArgs { 7 | private int threadCount = 4; 8 | private int iterationCount = 20000; 9 | private String scenario = "latency"; 10 | private String client = "lettuce"; 11 | 12 | public static CommandLineArgs parse(String [] args) 13 | { 14 | CommandLineArgs result = new CommandLineArgs(); 15 | 16 | for(int i = 0; i < args.length; i++) 17 | { 18 | String currentArg = args[i]; 19 | String [] tokens = currentArg.split(":", 2); 20 | String key = tokens[0]; 21 | String value = tokens.length > 1 ? tokens[1] : null; 22 | 23 | switch(key) 24 | { 25 | case "-t": 26 | result.threadCount = parseInt(value); 27 | break; 28 | case "-i": 29 | result.iterationCount = parseInt(value); 30 | break; 31 | case "-s": 32 | result.scenario = value; 33 | break; 34 | case "-c": 35 | result.client = value; 36 | break; 37 | default: 38 | throw new IllegalArgumentException("Illegal command line entry: " + key); 39 | } 40 | } 41 | 42 | return result; 43 | } 44 | 45 | public int getThreadCount() { return threadCount; } 46 | public int getIterationCount() { return iterationCount; } 47 | public String getScenario() { return scenario; } 48 | 49 | public IRedisClient getClient() { 50 | IRedisClient result = null; 51 | 52 | switch(client.toLowerCase()) 53 | { 54 | case "j": 55 | case "jedis": 56 | result = new JedisRedisClient(RedisInstance.StandardC1()); 57 | break; 58 | case "j-p": //jedis with premium sku server 59 | result = new JedisRedisClient(RedisInstance.PremiumNonClustered()); 60 | break; 61 | case "l": 62 | case "lettuce": 63 | result = new LettuceRedisClient(RedisInstance.StandardC1()); 64 | break; 65 | case "l-p": //Lettuce with premium sku server 66 | result = new LettuceRedisClient(RedisInstance.PremiumNonClustered()); 67 | break; 68 | case "lc": 69 | result = new LettuceRedisClusterClient(RedisInstance.Clustered()); 70 | break; 71 | case "lp": 72 | result = getPooledClient(5, () -> new LettuceRedisClient(RedisInstance.StandardC1())); 73 | break; 74 | case "jc": 75 | result = new JedisClusterClient(RedisInstance.Clustered()); 76 | break; 77 | default: 78 | throw new IllegalArgumentException("client unknown: " + client); 79 | } 80 | 81 | if (result == null) { 82 | result = new LettuceRedisClient(RedisInstance.StandardC1()); 83 | //client = new LettuceRedisClusterClient(RedisInstance.Clustered); 84 | //client = new JedisRedisClient(RedisInstance.StandardC1); 85 | //client = getPooledClient(10, () -> new LettuceRedisClient(RedisInstance.StandardC1)); 86 | //client = getPooledClient(10, () -> new JedisRedisClient(RedisInstance.StandardC1)); 87 | Logging.writeLine("************ Using Default client: " + client.getClass().getSimpleName() + "************"); 88 | } 89 | 90 | return result; 91 | } 92 | 93 | public static int parseInt(@NotNull String s) { 94 | if ( s.length() == 0 || s.compareToIgnoreCase("max") == 0) 95 | return Integer.MAX_VALUE; 96 | return Integer.parseInt(s); 97 | } 98 | 99 | private static IRedisClient getPooledClient(int size, @NotNull IRedisClientFactory factory) { 100 | ArrayList list = new ArrayList<>(); 101 | for(int i = 0; i < size; i++) 102 | list.add(factory.create()); 103 | 104 | return new PooledRedisClient(list); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/IRedisClient.java: -------------------------------------------------------------------------------- 1 | public interface IRedisClient { 2 | String get(String key); 3 | void set(String key, String value); 4 | void ping(); 5 | String info(); 6 | String getHostName(); 7 | } 8 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/IRedisClientFactory.java: -------------------------------------------------------------------------------- 1 | public interface IRedisClientFactory { 2 | IRedisClient create( ); 3 | } 4 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/ITestScenario.java: -------------------------------------------------------------------------------- 1 | public interface ITestScenario { 2 | void run(CommandLineArgs options); 3 | } 4 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/IdleConnectionTests.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | 3 | public class IdleConnectionTests { 4 | 5 | public static void Run(int idleSeconds) { 6 | int concurrency = 10; 7 | ArrayList list = new ArrayList(); 8 | 9 | try { 10 | for (int i = 0; i < concurrency; i++) { 11 | list.add(LoadTests.startLoadTest()); 12 | } 13 | 14 | Redis.startPingTimer(60); 15 | 16 | Thread.sleep(10000); 17 | 18 | // Close all of the worker threads so that the connections go idle 19 | while(list.size() >0) { 20 | list.get(0).getWorker().stop(); 21 | Thread.sleep(100); 22 | list.remove(0); 23 | } 24 | 25 | // Wake up periodically and use the connection to see if it succeeds. 26 | while(true) 27 | { 28 | double minutes = idleSeconds / 60.0; 29 | String logEntry = String.format("\r\n%s Sleeping for %s minute(s).", Logging.getPrefix(), Double.toString(minutes)); 30 | Logging.writeLine(logEntry); 31 | Thread.sleep(idleSeconds * 1000); 32 | Logging.write("^"); 33 | String result = Redis.getClient().get("foo"); 34 | Logging.write(String.format("foo=%s", result)); 35 | idleSeconds += idleSeconds; 36 | } 37 | } 38 | catch(Exception ex) { 39 | 40 | Logging.logException(ex); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/JedisClusterClient.java: -------------------------------------------------------------------------------- 1 | import redis.clients.jedis.*; 2 | import redis.clients.jedis.exceptions.JedisBusyException; 3 | import redis.clients.jedis.exceptions.JedisConnectionException; 4 | import redis.clients.jedis.exceptions.JedisException; 5 | 6 | import java.net.SocketTimeoutException; 7 | 8 | public class JedisClusterClient implements IRedisClient { 9 | 10 | private RedisInstance redisInstance; 11 | private Object lock = new Object(); 12 | private JedisCluster cluster; 13 | private JedisPoolConfig config; 14 | private int connectTimeout = 5000; 15 | private int operationTimeout = 5000; 16 | private int port = 6379; 17 | private int maxConnections = 400; 18 | 19 | public JedisClusterClient(RedisInstance instance) 20 | { 21 | redisInstance = instance; 22 | } 23 | 24 | public String getHostName() {return redisInstance.getHostname(); } 25 | 26 | @Override 27 | public String get(String key) { 28 | try 29 | { 30 | return getInstance().get(key); 31 | } catch (Exception ex) { 32 | JedisRedisClient.LogError(ex); 33 | } 34 | return null; 35 | } 36 | 37 | @Override 38 | public void ping() { 39 | try 40 | { 41 | getInstance().ping(); 42 | } catch (Exception ex) { 43 | JedisRedisClient.LogError(ex); 44 | } 45 | } 46 | 47 | public String info() 48 | { 49 | try 50 | { 51 | return getInstance().info(); 52 | } catch (Exception ex) { 53 | JedisRedisClient.LogError(ex); 54 | } 55 | 56 | return ""; 57 | } 58 | 59 | @Override 60 | public void set(String key, String value) { 61 | 62 | try 63 | { 64 | getInstance().set(key, value); 65 | //Logging.write("+"); 66 | } catch (Exception ex) { 67 | JedisRedisClient.LogError(ex); 68 | } 69 | } 70 | 71 | public JedisCluster getInstance() { 72 | if (cluster == null) { // avoid synchronization lock if initialization has already happened 73 | synchronized(lock) { 74 | if (cluster == null) { // don't re-initialize if another thread beat us to it. 75 | 76 | HostAndPort node = new HostAndPort(redisInstance.getHostname(), port); 77 | int maxAttempts = 3; // Max retries due to redirects... 78 | JedisPoolConfig poolConfig = getPoolConfig(); 79 | //String clientName = Program.AppName + ":Jedis"; 80 | cluster = new JedisCluster(node, connectTimeout, operationTimeout, maxAttempts, redisInstance.getPassword(), poolConfig); 81 | } 82 | } 83 | } 84 | return cluster; 85 | } 86 | 87 | private JedisPoolConfig getPoolConfig() { 88 | if (config == null) { 89 | JedisPoolConfig poolConfig = new JedisPoolConfig(); 90 | 91 | // Each thread trying to access Redis needs its own Jedis instance from the pool. 92 | // Using too small a value here can lead to performance problems, too big and you have wasted resources. 93 | 94 | poolConfig.setMaxTotal(maxConnections); 95 | poolConfig.setMaxIdle(maxConnections); 96 | 97 | // Using "false" here will make it easier to debug when your maxTotal/minIdle/etc settings need adjusting. 98 | // Setting it to "true" will result better behavior when unexpected load hits in production 99 | poolConfig.setBlockWhenExhausted(true); 100 | 101 | // How long to wait before throwing when pool is exhausted 102 | poolConfig.setMaxWaitMillis(5000); 103 | 104 | // This controls the number of connections that should be maintained for bursts of load. 105 | // Increase this value when you see pool.getResource() taking a long time to complete under burst scenarios 106 | poolConfig.setMinIdle(50); 107 | 108 | config = poolConfig; 109 | } 110 | 111 | return config; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/JedisRedisClient.java: -------------------------------------------------------------------------------- 1 | import org.springframework.beans.FatalBeanException; 2 | import redis.clients.jedis.*; 3 | import redis.clients.jedis.exceptions.JedisBusyException; 4 | import redis.clients.jedis.exceptions.JedisConnectionException; 5 | import redis.clients.jedis.exceptions.JedisException; 6 | 7 | import javax.net.ssl.*; 8 | import java.io.IOException; 9 | import java.net.SocketTimeoutException; 10 | 11 | public class JedisRedisClient implements IRedisClient { 12 | 13 | private RedisInstance redisInstance; 14 | private Object lock = new Object(); 15 | private JedisPool pool; 16 | private JedisPoolConfig config; 17 | private int connectTimeout = 5000; 18 | private int operationTimeout = 5000; 19 | private int port = 6380; 20 | private int maxConnections = 400; 21 | 22 | public JedisRedisClient(RedisInstance instance) 23 | { 24 | redisInstance = instance; 25 | } 26 | 27 | public String getHostName() {return redisInstance.getHostname(); } 28 | 29 | @Override 30 | public String get(String key) { 31 | try(Jedis jedis = getPoolInstance().getResource()) 32 | { 33 | return jedis.get(key); 34 | } catch (Exception ex) { 35 | LogError(ex); 36 | } 37 | return null; 38 | } 39 | 40 | @Override 41 | public void ping() { 42 | try(Jedis jedis = getPoolInstance().getResource()) 43 | { 44 | jedis.ping(); 45 | } catch (Exception ex) { 46 | LogError(ex); 47 | } 48 | } 49 | 50 | public String info() 51 | { 52 | try(Jedis jedis = getPoolInstance().getResource()) 53 | { 54 | return jedis.info(); 55 | } catch (Exception ex) { 56 | LogError(ex); 57 | } 58 | 59 | return ""; 60 | } 61 | 62 | @Override 63 | public void set(String key, String value) { 64 | try(Jedis jedis = getPoolInstance().getResource()) 65 | { 66 | jedis.set(key, value); 67 | } catch (Exception ex) { 68 | LogError(ex); 69 | } 70 | } 71 | 72 | public JedisPool getPoolInstance() { 73 | if (pool == null) { // avoid synchronization lock if initialization has already happened 74 | synchronized(lock) { 75 | if (pool == null) { // don't re-initialize if another thread beat us to it. 76 | JedisPoolConfig poolConfig = getPoolConfig(); 77 | boolean useSsl = port == 6380 ? true : false; 78 | int db = 0; 79 | String clientName = Program.AppName + ":Jedis"; 80 | SSLSocketFactory sslSocketFactory = null; // null means use default 81 | SSLParameters sslParameters = null; // null means use default 82 | HostnameVerifier hostnameVerifier = new SimpleHostNameVerifier(redisInstance.getHostname()); 83 | pool = new JedisPool(poolConfig, redisInstance.getHostname(), port, connectTimeout, operationTimeout, redisInstance.getPassword(), db, 84 | clientName, useSsl, sslSocketFactory, sslParameters, hostnameVerifier); 85 | } 86 | } 87 | } 88 | return pool; 89 | } 90 | 91 | private JedisPoolConfig getPoolConfig() { 92 | if (config == null) { 93 | JedisPoolConfig poolConfig = new JedisPoolConfig(); 94 | 95 | // Each thread trying to access Redis needs its own Jedis instance from the pool. 96 | // Using too small a value here can lead to performance problems, too big and you have wasted resources. 97 | 98 | poolConfig.setMaxTotal(maxConnections); 99 | poolConfig.setMaxIdle(maxConnections); 100 | 101 | // Using "false" here will make it easier to debug when your maxTotal/minIdle/etc settings need adjusting. 102 | // Setting it to "true" will result better behavior when unexpected load hits in production 103 | poolConfig.setBlockWhenExhausted(true); 104 | 105 | // How long to wait before throwing when pool is exhausted 106 | poolConfig.setMaxWaitMillis(5000); 107 | 108 | // This controls the number of connections that should be maintained for bursts of load. 109 | // Increase this value when you see pool.getResource() taking a long time to complete under burst scenarios 110 | poolConfig.setMinIdle(50); 111 | 112 | config = poolConfig; 113 | } 114 | 115 | return config; 116 | } 117 | 118 | public static void LogError(Exception ex) 119 | { 120 | if (ex instanceof JedisConnectionException) { 121 | Throwable cause = ex.getCause(); 122 | if (cause != null && cause instanceof SocketTimeoutException) 123 | Logging.write("T"); 124 | else 125 | Logging.write("C"); 126 | } else if (ex instanceof JedisBusyException) { 127 | Logging.write("B"); 128 | } else if (ex instanceof JedisException) { 129 | Logging.write("E"); 130 | } else if (ex instanceof IOException){ 131 | Logging.write("C"); 132 | } else { 133 | Logging.logException(ex); 134 | throw new FatalBeanException(ex.getMessage(), ex); // unexpected exception type, so abort test for investigation 135 | } 136 | } 137 | private static class SimpleHostNameVerifier implements HostnameVerifier { 138 | 139 | private String exactCN; 140 | private String wildCardCN; 141 | public SimpleHostNameVerifier(String cacheHostname) 142 | { 143 | exactCN = "CN=" + cacheHostname; 144 | wildCardCN = "CN=*" + cacheHostname.substring(cacheHostname.indexOf('.')); 145 | } 146 | 147 | public boolean verify(String s, SSLSession sslSession) { 148 | try { 149 | String cn = sslSession.getPeerPrincipal().getName(); 150 | return cn.equalsIgnoreCase(wildCardCN) || cn.equalsIgnoreCase(exactCN); 151 | } catch (SSLPeerUnverifiedException ex) { 152 | return false; 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/LatencyPercentileTests.java: -------------------------------------------------------------------------------- 1 | import com.sun.istack.internal.NotNull; 2 | 3 | import java.text.DecimalFormat; 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.Date; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | public class LatencyPercentileTests implements ITestScenario{ 10 | 11 | private AtomicInteger threadCounter = new AtomicInteger(0); 12 | private ArrayList aggregatedData = new ArrayList(); 13 | private static DecimalFormat dblFormat = new DecimalFormat("###.#"); 14 | 15 | public void run(@NotNull CommandLineArgs options) { 16 | 17 | int threads = options.getThreadCount(); 18 | int iterations = options.getIterationCount(); 19 | 20 | ArrayList list = new ArrayList(); 21 | 22 | try { 23 | Logging.writeLine("Starting %d thread(s), %d iteration(s), using client %s", threads, iterations, Redis.getClient().getClass().getSimpleName()); 24 | 25 | for(int i = 0; i < threads; i++) 26 | list.add(startLoadTest(iterations)); 27 | 28 | Thread.sleep(1000); 29 | Logging.writeLine("\r\nwaiting for iterations to complete..."); 30 | 31 | while(list.size() > 0) 32 | { 33 | WorkerThread t = list.remove(0); 34 | while (t.isAlive()) 35 | { 36 | Logging.write("."); 37 | Thread.sleep(1000); 38 | } 39 | } 40 | 41 | Logging.writeLine("\r\nResult aggregation complete..."); 42 | 43 | printResults(); 44 | 45 | } 46 | catch(Exception ex) { 47 | 48 | Logging.logException(ex); 49 | } 50 | } 51 | 52 | private void printResults() { 53 | 54 | Logging.writeLine( "--------------------------------------------------"); 55 | Collections.sort(aggregatedData); 56 | 57 | int count = aggregatedData.size(); 58 | 59 | Logging.writeLine(String.format("Request Count: %d", count)); 60 | 61 | Logging.writeLine(getPercentile(50.0)); 62 | Logging.writeLine(getPercentile(80.0)); 63 | Logging.writeLine(getPercentile(90.0)); 64 | Logging.writeLine(getPercentile(95.0)); 65 | Logging.writeLine(getPercentile(98.0)); 66 | Logging.writeLine(getPercentile(99.0)); 67 | Logging.writeLine(getPercentile(99.5)); 68 | Logging.writeLine(getPercentile(99.9)); 69 | Logging.writeLine(getPercentile(100.0)); 70 | Logging.writeLine( "--------------------------------------------------"); 71 | } 72 | 73 | 74 | private String getPercentile(Double percentile) 75 | { 76 | int count = aggregatedData.size(); 77 | int index = (int)Math.floor((percentile/100) * count); 78 | 79 | if (index == count) 80 | index = count - 1; 81 | 82 | Double val = aggregatedData.get(index); 83 | return String.format("%5sth Percentile : %sms", dblFormat.format(percentile), dblFormat.format(val)); 84 | } 85 | 86 | private WorkerThread startLoadTest(int iterations) 87 | { 88 | Worker work = new Worker(){ 89 | @Override 90 | public void run() { 91 | 92 | ArrayList times = new ArrayList(); 93 | int threadId = threadCounter.incrementAndGet(); 94 | Logging.write("|" + threadId); 95 | String key = "foo:" + Program.AppName + ":" + Integer.toString(threadId); 96 | String value = new Date().toString(); 97 | 98 | int warmUpCount = Math.max(iterations / 10, 300); 99 | 100 | Redis.getClient().set(key, value); 101 | 102 | //Logging.writeLine("\r\nStarting warmup with %d iterations.", warmUpCount); 103 | for(int i = 0; i < warmUpCount; i ++) 104 | Redis.getClient().get(key); 105 | 106 | 107 | //Logging.writeLine("\r\nStarting real latency tests..."); 108 | for(int i = 0; i < iterations; i++){ 109 | 110 | if (i % 1000 == 0) 111 | Logging.write(String.format("[%d/%d]", i, iterations)); 112 | IRedisClient client = Redis.getClient(); 113 | long start = Stopwatch.startNew(); 114 | client.get(key); 115 | Double duration = Stopwatch.elapsedMillis(start); 116 | times.add(duration); 117 | if (getStopRequested()) 118 | break; 119 | } 120 | 121 | Logging.write("#"); 122 | AggregateResults(times); 123 | } 124 | }; 125 | 126 | return WorkerThread.start(work); 127 | } 128 | 129 | private synchronized void AggregateResults(ArrayList threadResults) 130 | { 131 | aggregatedData.addAll(threadResults); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/LettuceRedisClient.java: -------------------------------------------------------------------------------- 1 | import com.lambdaworks.redis.*; 2 | import com.lambdaworks.redis.api.StatefulRedisConnection; 3 | import com.lambdaworks.redis.api.sync.RedisCommands; 4 | import com.lambdaworks.redis.event.EventPublisherOptions; 5 | import com.lambdaworks.redis.metrics.CommandLatencyCollector; 6 | import com.lambdaworks.redis.metrics.CommandLatencyCollectorOptions; 7 | import com.lambdaworks.redis.metrics.CommandLatencyId; 8 | import com.lambdaworks.redis.metrics.CommandMetrics; 9 | import com.lambdaworks.redis.protocol.ProtocolKeyword; 10 | import com.lambdaworks.redis.resource.DefaultClientResources; 11 | import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory; 12 | import org.springframework.beans.FatalBeanException; 13 | 14 | import java.io.*; 15 | import java.net.SocketAddress; 16 | import java.util.Map; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | public class LettuceRedisClient implements IRedisClient { 20 | 21 | private RedisInstance redisInstance; 22 | private StatefulRedisConnection connection; 23 | private Object syncObject = new Object(); 24 | private RedisCommands commands; 25 | 26 | public LettuceRedisClient(RedisInstance instance) 27 | { 28 | redisInstance = instance; 29 | } 30 | 31 | public String getHostName() {return redisInstance.getHostname(); } 32 | 33 | public String get(String key) 34 | { 35 | try { 36 | return getCommands().get(key); 37 | } catch (Exception ex) { 38 | LogError(ex); 39 | } 40 | return null; 41 | } 42 | 43 | public String info() 44 | { 45 | try { 46 | return getCommands().info(); 47 | } catch(Exception ex) { 48 | LogError(ex); 49 | } 50 | return ""; 51 | } 52 | 53 | public void set(String key, String value) 54 | { 55 | try { 56 | getCommands().set(key, value); 57 | } catch(Exception ex) { 58 | LogError(ex); 59 | } 60 | } 61 | 62 | public static void LogError(Exception ex) 63 | { 64 | if (ex instanceof RedisCommandTimeoutException) { 65 | Logging.write("T"); 66 | } else if (ex instanceof RedisCommandExecutionException) { 67 | Logging.write("E"); 68 | } else if (ex instanceof RedisConnectionException) { 69 | Logging.write("C"); 70 | } else if (ex instanceof IOException){ 71 | Logging.write("C"); 72 | } else { 73 | Logging.logException(ex); 74 | throw new FatalBeanException(ex.getMessage(), ex); // unexpected exception type, so abort test for investigation 75 | } 76 | } 77 | 78 | public static DefaultClientResources getClientResources() 79 | { 80 | int threadPoolSize = Runtime.getRuntime().availableProcessors(); 81 | threadPoolSize *= 2; 82 | 83 | DefaultClientResources resources = DefaultClientResources.builder() 84 | .ioThreadPoolSize(threadPoolSize) 85 | .computationThreadPoolSize(threadPoolSize) 86 | .build(); 87 | Logging.writeLine("DefaultClientResources - ioThreads: %d, computeThreads: %d", resources.ioThreadPoolSize(), resources.computationThreadPoolSize()); 88 | return resources; 89 | } 90 | 91 | public void ping() 92 | { 93 | try{ 94 | getCommands().ping(); 95 | } catch(Exception ex) { 96 | LogError(ex); 97 | } 98 | } 99 | 100 | private RedisClient getRedisClient() 101 | { 102 | 103 | 104 | RedisURI uri = RedisURI.builder() 105 | .withHost(redisInstance.getHostname()) 106 | .withPassword(redisInstance.getPassword()) 107 | .withPort(6380).withSsl(true) 108 | //.withPort(6379).withSsl(false) 109 | .withDatabase(0) 110 | .build(); 111 | return RedisClient.create(getClientResources(), uri); 112 | } 113 | 114 | protected StatefulRedisConnection getConnection() 115 | { 116 | if (connection == null) { 117 | synchronized (syncObject) 118 | { 119 | if (connection == null) 120 | { 121 | Logging.write("$"); 122 | RedisClient client = getRedisClient(); 123 | connection = client.connect(); 124 | TrySetClientName(); 125 | } 126 | } 127 | } 128 | 129 | return connection; 130 | } 131 | 132 | private RedisCommands getCommands() 133 | { 134 | if (commands == null) 135 | commands = getConnection().sync(); 136 | return commands; 137 | } 138 | 139 | private void TrySetClientName() 140 | { 141 | try { 142 | getCommands().clientSetname(Program.AppName + ":Lettuce"); 143 | } catch(Exception ex){ 144 | LogError(ex); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/LettuceRedisClusterClient.java: -------------------------------------------------------------------------------- 1 | import com.lambdaworks.redis.*; 2 | import com.lambdaworks.redis.cluster.*; 3 | import com.lambdaworks.redis.cluster.api.*; 4 | import com.lambdaworks.redis.cluster.api.sync.*; 5 | 6 | public class LettuceRedisClusterClient implements IRedisClient { 7 | 8 | private RedisInstance redisInstance; 9 | private StatefulRedisClusterConnection connection; 10 | private Object syncObject = new Object(); 11 | 12 | public LettuceRedisClusterClient(RedisInstance instance) 13 | { 14 | redisInstance = instance; 15 | } 16 | 17 | public String getHostName() {return redisInstance.getHostname(); } 18 | 19 | public String get(String key) 20 | { 21 | try { 22 | return commands().get(key); 23 | } catch (Exception ex) { 24 | LettuceRedisClient.LogError(ex); 25 | } 26 | return null; 27 | } 28 | 29 | public void set(String key, String value) 30 | { 31 | try { 32 | commands().set(key, value); 33 | } catch (Exception ex) { 34 | LettuceRedisClient.LogError(ex); 35 | } 36 | } 37 | 38 | public String info() 39 | { 40 | try { 41 | return commands().info(); 42 | } catch (Exception ex) { 43 | LettuceRedisClient.LogError(ex); 44 | } 45 | return ""; 46 | } 47 | 48 | public void ping() 49 | { 50 | try{ 51 | commands().ping(); 52 | } catch (Exception ex) { 53 | LettuceRedisClient.LogError(ex); 54 | } 55 | } 56 | 57 | private RedisClusterClient getRedisClient() 58 | { 59 | 60 | RedisURI uri = RedisURI.builder() 61 | .withHost(redisInstance.getHostname()) 62 | .withPassword(redisInstance.getPassword()) 63 | .withPort(6380) 64 | .withSsl(true) 65 | .withDatabase(0) 66 | .build(); 67 | RedisClusterClient client = RedisClusterClient.create(LettuceRedisClient.getClientResources(), uri); 68 | 69 | ClusterClientOptions options = ClusterClientOptions.builder() 70 | .validateClusterNodeMembership(false) 71 | .autoReconnect(true) 72 | .build(); 73 | client.setOptions(options); 74 | return client; 75 | } 76 | 77 | protected StatefulRedisClusterConnection getConnection() 78 | { 79 | if (connection == null) { 80 | synchronized (syncObject) 81 | { 82 | if (connection == null) 83 | { 84 | RedisClusterClient client = getRedisClient(); 85 | connection = client.connect(); 86 | TrySetClientName(); 87 | } 88 | } 89 | } 90 | 91 | return connection; 92 | } 93 | 94 | private RedisClusterCommands commands() 95 | { 96 | return getConnection().sync(); 97 | } 98 | 99 | private void TrySetClientName() 100 | { 101 | try { 102 | commands().clientSetname(Program.AppName + ":LettuceCluster"); 103 | } catch(Exception ex){ 104 | Logging.logException(ex); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/LoadTests.java: -------------------------------------------------------------------------------- 1 | import com.sun.istack.internal.NotNull; 2 | import java.util.ArrayList; 3 | import java.util.Date; 4 | import java.util.Random; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | public class LoadTests implements ITestScenario { 8 | private static AtomicInteger threadCounter = new AtomicInteger(0); 9 | private static int writePercent = 10; 10 | 11 | public void run(@NotNull CommandLineArgs options) { 12 | 13 | int threadCount = options.getThreadCount(); 14 | int iterationCount = options.getIterationCount(); 15 | 16 | ArrayList list = new ArrayList(); 17 | 18 | try { 19 | for (int i = 0; i < threadCount; i++) { 20 | list.add(startLoadTest(iterationCount)); 21 | } 22 | 23 | while(true) 24 | { 25 | Thread.sleep(1000); 26 | boolean done = true; 27 | 28 | for(WorkerThread t : list) 29 | { 30 | done &= !t.isAlive(); 31 | } 32 | if (done) 33 | return; 34 | } 35 | } 36 | catch(Exception ex) { 37 | 38 | Logging.logException(ex); 39 | } 40 | } 41 | 42 | public static WorkerThread startLoadTest() 43 | { 44 | return startLoadTest(Integer.MAX_VALUE); 45 | } 46 | 47 | private static WorkerThread startLoadTest(int iterationCount) 48 | { 49 | Worker work = new Worker(){ 50 | @Override 51 | public void run() { 52 | Random rand = new Random(); 53 | int count = 0; 54 | int threadId = threadCounter.incrementAndGet(); 55 | Logging.write("|" ); 56 | 57 | String key = "foo:" + Program.AppName + ":" + Integer.toString(threadId); 58 | String value = new Date().toString(); 59 | 60 | while(true) { 61 | 62 | if (count < 2 || rand.nextInt(100) < writePercent) { 63 | value = new Date().toString(); 64 | Redis.getClient().set(key, value); 65 | Logging.write("-"); 66 | if (iterationCount < 0) 67 | count = 10; //for infinite case, stop setting value for every request. 68 | } 69 | 70 | if (iterationCount > 0) 71 | count++; 72 | 73 | String valueFromCache = Redis.getClient().get(key); 74 | if (valueFromCache == null || !valueFromCache.equals(value)) 75 | { 76 | Logging.write("M"); 77 | } 78 | else 79 | Logging.write("."); 80 | 81 | 82 | if (getStopRequested() || 83 | (iterationCount > 0 && count >= iterationCount)) 84 | { 85 | Logging.write("#"); 86 | return; 87 | } 88 | 89 | 90 | } 91 | } 92 | }; 93 | 94 | return WorkerThread.start(work); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/Logging.java: -------------------------------------------------------------------------------- 1 | 2 | import java.text.SimpleDateFormat; 3 | import java.util.Date; 4 | import java.util.TimeZone; 5 | 6 | public class Logging { 7 | 8 | private static SimpleDateFormat sdf = createSDF(); 9 | 10 | public static void writeLine() 11 | { 12 | writeLine(""); 13 | } 14 | 15 | public static void writeLine(String str) 16 | { 17 | System.out.println(str); 18 | } 19 | 20 | public static void writeLine(String str, Object...args) 21 | { 22 | System.out.println(String.format(str, args)); 23 | } 24 | 25 | public static void write(String str) 26 | { 27 | System.out.print(str); 28 | } 29 | 30 | public static void logException(Exception ex) 31 | { 32 | ex.printStackTrace(System.out); 33 | } 34 | 35 | public static String getPrefix() 36 | { 37 | String now = sdf.format(new Date()); 38 | return String.format("[%s UTC] ", now); 39 | } 40 | 41 | private static SimpleDateFormat createSDF() 42 | { 43 | SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS"); 44 | s.setTimeZone(TimeZone.getTimeZone("UTC")); 45 | return s; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: Program 3 | 4 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/PooledRedisClient.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.util.concurrent.atomic.*; 3 | 4 | public class PooledRedisClient implements IRedisClient{ 5 | 6 | ArrayList clients; 7 | AtomicInteger roundRobin = new AtomicInteger(0); 8 | 9 | public PooledRedisClient(ArrayList clients) { 10 | this.clients = clients; 11 | } 12 | 13 | public String getHostName() {return clients.get(0).getHostName(); } 14 | 15 | @Override 16 | public void set(String key, String value) { 17 | getClient().set(key, value); 18 | } 19 | 20 | @Override 21 | public String get(String key) { 22 | return getClient().get(key); 23 | } 24 | 25 | @Override 26 | public void ping() { 27 | getClient().ping(); 28 | } 29 | 30 | public String info() { return getClient().info(); } 31 | 32 | private IRedisClient getClient() 33 | { 34 | int index = roundRobin.incrementAndGet() % clients.size(); 35 | 36 | //Logging.writeLine(Integer.toString(index)); 37 | 38 | if (index > 100000) 39 | roundRobin.set(0); 40 | 41 | return clients.get(index); 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/Program.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | import java.lang.management.ManagementFactory; 3 | import java.nio.file.*; 4 | import java.util.Date; 5 | 6 | public class Program { 7 | 8 | public static String AppName = ManagementFactory.getRuntimeMXBean().getName(); 9 | public static void main(String[] args) { 10 | 11 | try { 12 | printSystemInfo(); 13 | 14 | 15 | Path filePath = Paths.get(System.getProperty("user.dir"), "RedisInstances.txt"); 16 | RedisInstance.loadFromFile(filePath); 17 | 18 | CommandLineArgs options = CommandLineArgs.parse(args); 19 | 20 | IRedisClient client = options.getClient(); 21 | 22 | Redis.initialize(client); 23 | 24 | Logging.writeLine("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); 25 | Logging.writeLine("Starting Scenario: %s, threads=%d, iterations=%d, host=%s", 26 | options.getScenario(), 27 | options.getThreadCount(), 28 | options.getIterationCount(), 29 | client.getHostName()); 30 | Logging.writeLine("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); 31 | 32 | ITestScenario scenario = getTestScenario(options.getScenario()); 33 | 34 | scenario.run(options); 35 | 36 | } 37 | catch( Exception ex) 38 | { 39 | Logging.logException(ex); 40 | } 41 | } 42 | 43 | private static void printSystemInfo() { 44 | Logging.writeLine("Working Dir: %s", System.getProperty("user.dir")); 45 | Logging.writeLine("Available Processors: %d", Runtime.getRuntime().availableProcessors()); 46 | } 47 | 48 | public static ITestScenario getTestScenario(String scenarioName) 49 | { 50 | ITestScenario result; 51 | switch(scenarioName.toLowerCase()) { 52 | case "load": 53 | result = new LoadTests(); 54 | break; 55 | case "latency": 56 | result = new LatencyPercentileTests(); 57 | break; 58 | default: 59 | throw new IllegalArgumentException("Unknown Scenario: " + scenarioName); 60 | } 61 | //IdleConnectionTests.run(11*60); 62 | //simpleGetTest(); 63 | //RedisConsole(); 64 | 65 | return result; 66 | } 67 | 68 | public static void simpleGetTest(){ 69 | Logging.writeLine("Connecting..."); 70 | Redis.getClient(); 71 | String value = new Date().toString(); 72 | Logging.writeLine("Setting initial value=" + value); 73 | Redis.getClient().set("foo", value); 74 | Logging.writeLine("done"); 75 | String result = Redis.getClient().get("foo"); 76 | 77 | Logging.writeLine("Returned from cache: " + result); 78 | } 79 | 80 | private static void RedisConsole() { 81 | BufferedReader console = new BufferedReader(new InputStreamReader(System.in)); 82 | 83 | try { 84 | while (true) { 85 | Logging.writeLine(""); 86 | Logging.write(".> "); 87 | String line = console.readLine(); 88 | String[] tokens = line.split(" "); 89 | 90 | if (tokens.length < 1) 91 | return; 92 | switch (tokens[0].toLowerCase()) { 93 | case "set": 94 | if (tokens.length != 3) 95 | continue; 96 | Redis.getClient().set(tokens[1], tokens[2]); 97 | break; 98 | case "get": 99 | if (tokens.length < 2) 100 | continue; 101 | Logging.writeLine( " " + Redis.getClient().get(tokens[1])); 102 | break; 103 | case "info": 104 | if (tokens.length > 1) 105 | continue; 106 | Logging.writeLine( " " + Redis.getClient().info()); 107 | break; 108 | default: 109 | Logging.writeLine(String.format("Unknown command %s", tokens[0])); 110 | } 111 | } 112 | } catch ( IOException ex) { 113 | Logging.logException(ex); 114 | } 115 | } 116 | } 117 | 118 | 119 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/Redis.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.util.concurrent.atomic.AtomicInteger; 3 | 4 | public class Redis { 5 | 6 | private static IRedisClient client; 7 | 8 | public static void initialize(IRedisClient client) 9 | { 10 | Redis.client = client; 11 | } 12 | 13 | public static IRedisClient getClient() 14 | { 15 | return client; 16 | } 17 | 18 | public static Timer startPingTimer(int secondsFrequency) 19 | { 20 | Timer timer = new Timer(); 21 | timer.schedule(new TimerTask() { 22 | @Override 23 | public void run() { 24 | try { 25 | Logging.write("P"); 26 | getClient().ping(); 27 | } catch (Exception ex) { 28 | //do nothing. Non-fatal if a ping command errors out. 29 | } 30 | } 31 | }, 0, secondsFrequency*1000); 32 | return timer; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/RedisInstance.java: -------------------------------------------------------------------------------- 1 | import org.springframework.beans.FatalBeanException; 2 | 3 | import java.io.*; 4 | import java.nio.file.Path; 5 | import java.util.*; 6 | 7 | public class RedisInstance { 8 | static Dictionary map = new Hashtable<>(); 9 | private String hostname; 10 | private String key; 11 | 12 | private RedisInstance(String h, String k) 13 | { 14 | hostname = h; 15 | key = k; 16 | } 17 | 18 | public String getHostname() 19 | { 20 | return hostname; 21 | } 22 | 23 | public String getPassword() 24 | { 25 | return key; 26 | } 27 | 28 | public static RedisInstance GeoReplicated() { return map.get("GeoReplicated"); } 29 | public static RedisInstance Clustered() { return map.get("Clustered"); } 30 | public static RedisInstance StandardC1() { return map.get("StandardC1"); } 31 | public static RedisInstance PremiumNonClustered() { return map.get("PremiumNonClustered"); } 32 | 33 | public static void loadFromFile(Path filePath) 34 | { 35 | try( BufferedReader reader = new BufferedReader(new FileReader(filePath.toString()))) { 36 | 37 | String line; 38 | while ((line = reader.readLine()) != null) { 39 | if (line.length() > 0) { 40 | String[] tokens = line.split(":", 3); 41 | map.put(tokens[0], new RedisInstance(tokens[1], tokens[2])); 42 | } 43 | } 44 | } 45 | catch( IOException ex){ 46 | Logging.writeLine("Error while reading file %s", filePath); 47 | Logging.logException(ex); 48 | throw new FatalBeanException(ex.getMessage()); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/Stopwatch.java: -------------------------------------------------------------------------------- 1 | public class Stopwatch { 2 | public static long startNew() 3 | { 4 | return System.nanoTime(); 5 | } 6 | 7 | public static Double elapsedMillis(long startNS) 8 | { 9 | Double nanoToMillis = 0.000001; 10 | return (System.nanoTime() - startNS) * nanoToMillis; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/Utils.java: -------------------------------------------------------------------------------- 1 | public class Utils { 2 | public static String GenerateValue(int size) 3 | { 4 | StringBuffer value = new StringBuffer(); 5 | for(int i = 0; i < size; i++) 6 | value.append("a"); 7 | 8 | return value.toString(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/Worker.java: -------------------------------------------------------------------------------- 1 | 2 | public abstract class Worker implements Runnable 3 | { 4 | private boolean stopRequested; 5 | public boolean getStopRequested() 6 | { 7 | return stopRequested; 8 | } 9 | public void stop() 10 | { 11 | stopRequested = true; 12 | } 13 | } -------------------------------------------------------------------------------- /Redis/Java/RedisClientTests/src/WorkerThread.java: -------------------------------------------------------------------------------- 1 | import com.sun.istack.internal.NotNull; 2 | 3 | public class WorkerThread extends Thread 4 | { 5 | private Worker doWork; 6 | 7 | public Worker getWorker() 8 | { 9 | return doWork; 10 | } 11 | 12 | public WorkerThread (@NotNull Worker work) 13 | { 14 | doWork = work; 15 | } 16 | 17 | @Override 18 | public void run() { 19 | doWork.run(); 20 | } 21 | 22 | public static WorkerThread start(Worker work) 23 | { 24 | WorkerThread t = new WorkerThread(work); 25 | t.start(); 26 | return t; 27 | } 28 | } 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Redis/Python/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python", 6 | "type": "python", 7 | "request": "launch", 8 | "stopOnEntry": true, 9 | "pythonPath": "${config.python.pythonPath}", 10 | "program": "${file}", 11 | "cwd": "${workspaceRoot}", 12 | "debugOptions": [ 13 | "WaitOnAbnormalExit", 14 | "WaitOnNormalExit", 15 | "RedirectOutput" 16 | ] 17 | }, 18 | { 19 | "name": "PySpark", 20 | "type": "python", 21 | "request": "launch", 22 | "stopOnEntry": true, 23 | "osx": { 24 | "pythonPath": "${env.SPARK_HOME}/bin/spark-submit" 25 | }, 26 | "windows": { 27 | "pythonPath": "${env.SPARK_HOME}/bin/spark-submit.cmd" 28 | }, 29 | "linux": { 30 | "pythonPath": "${env.SPARK_HOME}/bin/spark-submit" 31 | }, 32 | "program": "${file}", 33 | "cwd": "${workspaceRoot}", 34 | "debugOptions": [ 35 | "WaitOnAbnormalExit", 36 | "WaitOnNormalExit", 37 | "RedirectOutput" 38 | ] 39 | }, 40 | { 41 | "name": "Python Module", 42 | "type": "python", 43 | "request": "launch", 44 | "stopOnEntry": true, 45 | "pythonPath": "${config.python.pythonPath}", 46 | "module": "module.name", 47 | "cwd": "${workspaceRoot}", 48 | "debugOptions": [ 49 | "WaitOnAbnormalExit", 50 | "WaitOnNormalExit", 51 | "RedirectOutput" 52 | ] 53 | }, 54 | { 55 | "name": "Integrated Terminal/Console", 56 | "type": "python", 57 | "request": "launch", 58 | "stopOnEntry": true, 59 | "pythonPath": "${config.python.pythonPath}", 60 | "program": "${file}", 61 | "cwd": "null", 62 | "console": "integratedTerminal", 63 | "debugOptions": [ 64 | "WaitOnAbnormalExit", 65 | "WaitOnNormalExit" 66 | ] 67 | }, 68 | { 69 | "name": "External Terminal/Console", 70 | "type": "python", 71 | "request": "launch", 72 | "stopOnEntry": true, 73 | "pythonPath": "${config.python.pythonPath}", 74 | "program": "${file}", 75 | "cwd": "null", 76 | "console": "externalTerminal", 77 | "debugOptions": [ 78 | "WaitOnAbnormalExit", 79 | "WaitOnNormalExit" 80 | ] 81 | }, 82 | { 83 | "name": "Django", 84 | "type": "python", 85 | "request": "launch", 86 | "stopOnEntry": true, 87 | "pythonPath": "${config.python.pythonPath}", 88 | "program": "${workspaceRoot}/manage.py", 89 | "cwd": "${workspaceRoot}", 90 | "args": [ 91 | "runserver", 92 | "--noreload" 93 | ], 94 | "debugOptions": [ 95 | "WaitOnAbnormalExit", 96 | "WaitOnNormalExit", 97 | "RedirectOutput", 98 | "DjangoDebugging" 99 | ] 100 | }, 101 | { 102 | "name": "Flask", 103 | "type": "python", 104 | "request": "launch", 105 | "stopOnEntry": false, 106 | "pythonPath": "${config.python.pythonPath}", 107 | "program": "fully qualified path fo 'flask' executable. Generally located along with python interpreter", 108 | "cwd": "${workspaceRoot}", 109 | "env": { 110 | "FLASK_APP": "${workspaceRoot}/quickstart/app.py" 111 | }, 112 | "args": [ 113 | "run", 114 | "--no-debugger", 115 | "--no-reload" 116 | ], 117 | "debugOptions": [ 118 | "WaitOnAbnormalExit", 119 | "WaitOnNormalExit", 120 | "RedirectOutput" 121 | ] 122 | }, 123 | { 124 | "name": "Flask (old)", 125 | "type": "python", 126 | "request": "launch", 127 | "stopOnEntry": false, 128 | "pythonPath": "${config.python.pythonPath}", 129 | "program": "${workspaceRoot}/run.py", 130 | "cwd": "${workspaceRoot}", 131 | "args": [], 132 | "debugOptions": [ 133 | "WaitOnAbnormalExit", 134 | "WaitOnNormalExit", 135 | "RedirectOutput" 136 | ] 137 | }, 138 | { 139 | "name": "Watson", 140 | "type": "python", 141 | "request": "launch", 142 | "stopOnEntry": true, 143 | "pythonPath": "${config.python.pythonPath}", 144 | "program": "${workspaceRoot}/console.py", 145 | "cwd": "${workspaceRoot}", 146 | "args": [ 147 | "dev", 148 | "runserver", 149 | "--noreload=True" 150 | ], 151 | "debugOptions": [ 152 | "WaitOnAbnormalExit", 153 | "WaitOnNormalExit", 154 | "RedirectOutput" 155 | ] 156 | }, 157 | { 158 | "name": "Attach (Remote Debug)", 159 | "type": "python", 160 | "request": "attach", 161 | "localRoot": "${workspaceRoot}", 162 | "remoteRoot": "${workspaceRoot}", 163 | "port": 3000, 164 | "secret": "my_secret", 165 | "host": "localhost" 166 | } 167 | ] 168 | } -------------------------------------------------------------------------------- /Redis/Python/Redis-Cluster-Test.py: -------------------------------------------------------------------------------- 1 | 2 | # python -m pip install redis-py-cluster 3 | 4 | import time, random, string 5 | from rediscluster import StrictRedisCluster 6 | from rediscluster.client import ClusterConnectionPool 7 | 8 | host = "XXX" 9 | password = "" 10 | shardMap = None 11 | numShards = 2 12 | maxSlots = 16384 13 | numKeys = 400 14 | 15 | def randomword(length): 16 | return ''.join(random.choice(string.ascii_lowercase) for i in range(length)) 17 | 18 | def generateKeys(count): 19 | keys = [] 20 | for i in range(count): 21 | keys.append(randomword(10)) 22 | return keys 23 | 24 | def getShardFromSlot(slot, shardMap): 25 | if shardMap is None: 26 | shardMap = [] 27 | slotsPerShard = int(maxSlots / numShards) 28 | currentShardMaxSlot = slotsPerShard 29 | for i in range(numShards): 30 | if i == numShards-1: 31 | shardMap.append(maxSlots) 32 | else: 33 | shardMap.append(currentShardMaxSlot) 34 | currentShardMaxSlot += slotsPerShard 35 | for i in range(len(shardMap)): 36 | if slot <= shardMap[i]: 37 | return i 38 | 39 | redisSettings = { 40 | 'socket_timeout': 1, 41 | 'socket_connect_timeout': 30, 42 | 'socket_keepalive': True, 43 | 'password': password, 44 | 'skip_full_coverage_check': True, 45 | } 46 | 47 | port = 6379 48 | ssl = False 49 | 50 | if ssl == True: 51 | redisSettings["ssl"] = True 52 | port = 6380 53 | 54 | startup_nodes = [{'host': host, 'port': port}] 55 | 56 | print("Creating StrictRedisCluster for " + host + ", port=" + str(port) + " with connectTimout of " + str(redisSettings['socket_connect_timeout'])) 57 | 58 | pool = ClusterConnectionPool(startup_nodes=startup_nodes, max_connections_per_node=10, **redisSettings) 59 | client = StrictRedisCluster(connection_pool=pool) 60 | #client = StrictRedisCluster(startup_nodes=startup_nodes, **redisSettings) 61 | 62 | keys = generateKeys(numKeys) 63 | 64 | #print(keys) 65 | 66 | shardCounts = [0, 0] 67 | opCount = 0 68 | for k in keys: 69 | client.set(k, random.choice(keys)) 70 | slot = client.cluster_keyslot(k) 71 | shardId = getShardFromSlot(slot, shardMap) 72 | shardCounts[shardId] += 1 73 | opCount += 1 74 | shardPercent = int(round(shardCounts[shardId] / opCount, 2) * 100) 75 | 76 | print("calling Redis.Set(" + k + ") => shard:" + str(shardId) + ", ShardPercent:" + str(shardPercent) + "%, slot:" + str(slot)) 77 | 78 | 79 | shardCounts = [0, 0] 80 | opCount = 0 81 | while True: 82 | try: 83 | k = random.choice(keys) 84 | slot = client.cluster_keyslot(k) 85 | shardId = getShardFromSlot(slot, shardMap) 86 | shardCounts[shardId] += 1 87 | opCount += 1 88 | shardPercent = int(round(shardCounts[shardId] / opCount, 2) * 100) 89 | print("calling Redis.Get(" + k + ") => shard:" + str(shardId) + ", ShardPercent:" + str(shardPercent) + "%, slot:" + str(slot)) 90 | client.get(k) 91 | except Exception as ex: 92 | print("Error: Failed to get key " + k + ". **********************************" + ex) 93 | 94 | 95 | #print("Sleeping...") 96 | #time.sleep(2) 97 | print("") 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /ThreadPoolMonitor/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ThreadPoolMonitor/PerfCounterHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace ThreadPoolMonitor 7 | { 8 | public static class PerfCounterHelper 9 | { 10 | static object staticLock = new object(); 11 | static volatile PerformanceCounter _cpu; 12 | static volatile bool _disabled; 13 | 14 | public static string GetSystemCPU() 15 | { 16 | string cpu = "unavailable"; 17 | 18 | float systemCPU; 19 | if (PerfCounterHelper.TryGetSystemCPU(out systemCPU)) 20 | { 21 | cpu = Math.Round(systemCPU, 2) + "%"; 22 | } 23 | return cpu; 24 | } 25 | 26 | private static bool TryGetSystemCPU(out float value) 27 | { 28 | value = -1; 29 | 30 | try 31 | { 32 | if (!_disabled && _cpu == null) 33 | { 34 | lock (staticLock) 35 | { 36 | if (_cpu == null) 37 | { 38 | _cpu = new PerformanceCounter("Processor", "% Processor Time", "_Total"); 39 | 40 | // First call always returns 0, so get that out of the way. 41 | _cpu.NextValue(); 42 | } 43 | } 44 | } 45 | } 46 | catch (UnauthorizedAccessException) 47 | { 48 | // Some environments don't allow access to Performance Counters, so stop trying. 49 | _disabled = true; 50 | } 51 | catch (Exception) 52 | { 53 | // this shouldn't happen, but just being safe... 54 | } 55 | 56 | if (!_disabled && _cpu != null) 57 | { 58 | value = _cpu.NextValue(); 59 | return true; 60 | } 61 | 62 | return false; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ThreadPoolMonitor/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace ThreadPoolMonitor 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | using (var logger = new ThreadPoolLogger(TimeSpan.FromSeconds(2))) 11 | { 12 | Console.WriteLine("Monitoring ThreadPool statistics for {0}.exe", Assembly.GetExecutingAssembly().GetName().Name); 13 | Console.WriteLine("Press Enter to close application"); 14 | Console.ReadLine(); 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ThreadPoolMonitor/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ThreadPoolMonitor")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ThreadPoolMonitor")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6FED0B2C-A23E-46E4-8351-FF4EC87EE26D")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ThreadPoolMonitor/ThreadPoolLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace ThreadPoolMonitor 6 | { 7 | class ThreadPoolLogger : IDisposable 8 | { 9 | private TimeSpan _logFrequency; 10 | private bool _disposed; 11 | 12 | public ThreadPoolLogger(TimeSpan logFrequency) 13 | { 14 | if (logFrequency <= TimeSpan.Zero) 15 | { 16 | throw new ArgumentOutOfRangeException("logFrequency"); 17 | } 18 | 19 | _logFrequency = logFrequency; 20 | StartLogging(); 21 | } 22 | 23 | private async void StartLogging() 24 | { 25 | try 26 | { 27 | while (!_disposed) 28 | { 29 | await Task.Delay(_logFrequency); 30 | 31 | var stats = GetThreadPoolStats(); 32 | 33 | LogUsage(stats); 34 | } 35 | } 36 | catch(Exception) 37 | { 38 | 39 | } 40 | } 41 | 42 | protected virtual void LogUsage(ThreadPoolUsageStats stats) 43 | { 44 | string message = string.Format("[{0}] IOCP:(Busy={1},Min={2},Max={3}), WORKER:(Busy={4},Min={5},Max={6}), Local CPU: {7}", 45 | DateTimeOffset.UtcNow.ToString("u"), 46 | stats.BusyIoThreads, stats.MinIoThreads, stats.MaxIoThreads, 47 | stats.BusyWorkerThreads, stats.MinWorkerThreads, stats.MaxWorkerThreads, 48 | PerfCounterHelper.GetSystemCPU() 49 | ); 50 | 51 | Console.WriteLine(message); 52 | } 53 | 54 | /// 55 | /// Returns the current thread pool usage statistics for the CURRENT AppDomain/Process 56 | /// 57 | public static ThreadPoolUsageStats GetThreadPoolStats() 58 | { 59 | //BusyThreads = TP.GetMaxThreads() –TP.GetAVailable(); 60 | //If BusyThreads >= TP.GetMinThreads(), then threadpool growth throttling is possible. 61 | 62 | int maxIoThreads, maxWorkerThreads; 63 | ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxIoThreads); 64 | 65 | int freeIoThreads, freeWorkerThreads; 66 | ThreadPool.GetAvailableThreads(out freeWorkerThreads, out freeIoThreads); 67 | 68 | int minIoThreads, minWorkerThreads; 69 | ThreadPool.GetMinThreads(out minWorkerThreads, out minIoThreads); 70 | 71 | int busyIoThreads = maxIoThreads - freeIoThreads; 72 | int busyWorkerThreads = maxWorkerThreads - freeWorkerThreads; 73 | 74 | return new ThreadPoolUsageStats 75 | { 76 | BusyIoThreads = busyIoThreads, 77 | MinIoThreads = minIoThreads, 78 | MaxIoThreads = maxIoThreads, 79 | BusyWorkerThreads = busyWorkerThreads, 80 | MinWorkerThreads = minWorkerThreads, 81 | MaxWorkerThreads = maxWorkerThreads, 82 | }; 83 | } 84 | 85 | public void Dispose() 86 | { 87 | _disposed = true; 88 | } 89 | } 90 | 91 | public struct ThreadPoolUsageStats 92 | { 93 | public int BusyIoThreads { get; set; } 94 | 95 | public int MinIoThreads { get; set; } 96 | 97 | public int MaxIoThreads { get; set; } 98 | 99 | public int BusyWorkerThreads { get; set; } 100 | 101 | public int MinWorkerThreads { get; set; } 102 | 103 | public int MaxWorkerThreads { get; set; } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /ThreadPoolMonitor/ThreadPoolMonitor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {704D6A6C-711C-4332-9248-5F7009C1E071} 8 | Exe 9 | Properties 10 | ThreadPoolMonitor 11 | ThreadPoolMonitor 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 60 | -------------------------------------------------------------------------------- /ThreadPoolMonitor/ThreadPoolMonitor.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30723.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThreadPoolMonitor", "ThreadPoolMonitor.csproj", "{704D6A6C-711C-4332-9248-5F7009C1E071}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {704D6A6C-711C-4332-9248-5F7009C1E071}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {704D6A6C-711C-4332-9248-5F7009C1E071}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {704D6A6C-711C-4332-9248-5F7009C1E071}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {704D6A6C-711C-4332-9248-5F7009C1E071}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | --------------------------------------------------------------------------------