|
| 1 | +/*---------------------------------------------------------------------------------- |
| 2 | +Copyright (c) Microsoft Corporation. All rights reserved. |
| 3 | + |
| 4 | +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, |
| 5 | +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES |
| 6 | +OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. |
| 7 | +---------------------------------------------------------------------------------- |
| 8 | +The example companies, organizations, products, domain names, |
| 9 | +e-mail addresses, logos, people, places, and events depicted |
| 10 | +herein are fictitious. No association with any real company, |
| 11 | +organization, product, domain name, email address, logo, person, |
| 12 | +places, or events is intended or should be inferred. |
| 13 | +
|
| 14 | +*/ |
| 15 | + |
| 16 | +using System; |
| 17 | +using System.IO; |
| 18 | +using DataGenerator; |
| 19 | +using System.Configuration; |
| 20 | +using System.Timers; |
| 21 | +using System.Diagnostics; |
| 22 | +using System.Collections.Generic; |
| 23 | +using System.Linq; |
| 24 | +using System.Text; |
| 25 | +using System.Threading.Tasks; |
| 26 | + |
| 27 | +/*---------------------------------------------------------------------------------- |
| 28 | +High Level Scenario: |
| 29 | +This code sample demonstrates how a SQL Server 2016 (or higher) memory optimized database could be used to ingest a very high input data rate |
| 30 | +and ultimately help improve the performance of applications with this scenario. The code simulates an IoT Smart Grid scenario where multiple |
| 31 | +IoT power meters are constantly sending electricity usage measurements to the database. |
| 32 | +
|
| 33 | +Details: |
| 34 | +This code sample simulates an IoT Smart Grid scenario where multiple IoT power meters are sending electricity usage measurements to a SQL Server memory optimized database. |
| 35 | +The Data Generator, that can be started either from the Console or the Windows Form client, produces a data generated spike to simulate a |
| 36 | +shock absorber scenario: https://blogs.technet.microsoft.com/dataplatforminsider/2013/09/19/in-memory-oltp-common-design-pattern-high-data-input-rateshock-absorber/. |
| 37 | +Every async task in the Data Generator produces a batch of records with random values in order to simulate the data of an IoT power meter. |
| 38 | +It then calls a natively compiled stored procedure, that accepts an memory optimized table valued parameter (TVP), to insert the data into an memory optimized SQL Server table. |
| 39 | +In addition to the in-memory features, the sample is leveraging System-Versioned Temporal Tables: https://msdn.microsoft.com/en-us/library/dn935015.aspx for building version history, |
| 40 | +Clustered Columnstore Index: https://msdn.microsoft.com/en-us/library/dn817827.aspx) for enabling real time operational analytics, and |
| 41 | +Power BI: https://powerbi.microsoft.com/en-us/desktop/ for data visualization. |
| 42 | +*/ |
| 43 | +namespace ConsoleClient |
| 44 | +{ |
| 45 | + class Program |
| 46 | + { |
| 47 | + static SqlDataGenerator dataGenerator; |
| 48 | + static string connection; |
| 49 | + static string spName; |
| 50 | + static string logFileName; |
| 51 | + static string powerBIDesktopPath; |
| 52 | + static int tasks; |
| 53 | + static int meters; |
| 54 | + static int batchSize; |
| 55 | + static int delay; |
| 56 | + static int commandTimeout; |
| 57 | + static int shockFrequency; |
| 58 | + static int shockDuration; |
| 59 | + static int rpsFrequency; |
| 60 | + static int enableShock; |
| 61 | + static Timer mainTimer = new Timer(); |
| 62 | + static Timer rpsTimer = new Timer(); |
| 63 | + static Timer shockTimer = new Timer(); |
| 64 | + |
| 65 | + static void Main(string[] args) |
| 66 | + { |
| 67 | + Init(); |
| 68 | + dataGenerator = new SqlDataGenerator(connection, spName, commandTimeout, meters, tasks, delay, batchSize, ExceptionCallback); |
| 69 | + |
| 70 | + mainTimer.Elapsed += mainTimer_Tick; |
| 71 | + rpsTimer.Elapsed += rpsTimer_Tick; |
| 72 | + shockTimer.Elapsed += shockTimer_Tick; |
| 73 | + |
| 74 | + string commandString = string.Empty; |
| 75 | + Console.ForegroundColor = ConsoleColor.White; |
| 76 | + Console.WriteLine("***********************************************************"); |
| 77 | + Console.WriteLine("* Data Generator *"); |
| 78 | + Console.WriteLine("* *"); |
| 79 | + Console.WriteLine("* Type commands to get started *"); |
| 80 | + Console.WriteLine("* *"); |
| 81 | + Console.WriteLine("***********************************************************"); |
| 82 | + Console.WriteLine(""); |
| 83 | + |
| 84 | + // main command cycle |
| 85 | + while (!commandString.Equals("Exit")) |
| 86 | + { |
| 87 | + Console.ResetColor(); |
| 88 | + Console.WriteLine("Enter command (start | stop | help | report | exit) >"); |
| 89 | + commandString = Console.ReadLine(); |
| 90 | + |
| 91 | + switch (commandString.ToUpper()) |
| 92 | + { |
| 93 | + case "START": |
| 94 | + Start(); |
| 95 | + break; |
| 96 | + case "STOP": |
| 97 | + Stop(); |
| 98 | + break; |
| 99 | + case "HELP": |
| 100 | + Help(); |
| 101 | + break; |
| 102 | + case "REPORT": |
| 103 | + Report(); |
| 104 | + break; |
| 105 | + case "EXIT": |
| 106 | + Console.WriteLine("Bye!"); |
| 107 | + return; |
| 108 | + default: |
| 109 | + Console.ForegroundColor = ConsoleColor.Red; |
| 110 | + Console.WriteLine("Invalid command."); |
| 111 | + break; |
| 112 | + } |
| 113 | + } |
| 114 | + } |
| 115 | + static void ExceptionCallback(int taskId, Exception exception) |
| 116 | + { |
| 117 | + HandleException(exception, taskId); |
| 118 | + } |
| 119 | + static void HandleException(Exception exception, int? taskId = null) |
| 120 | + { |
| 121 | + string ex = taskId?.ToString() + " - " + exception.Message + (exception.InnerException != null ? "\n\nInner Exception\n" + exception.InnerException : ""); |
| 122 | + |
| 123 | + Console.WriteLine(ex); |
| 124 | + using (StreamWriter w = File.AppendText(logFileName)) { w.WriteLine("\r\n{0}: {1}", DateTime.Now, ex); } |
| 125 | + } |
| 126 | + static async void Start() |
| 127 | + { |
| 128 | + try |
| 129 | + { |
| 130 | + if (!dataGenerator.IsRunning) |
| 131 | + { |
| 132 | + if(enableShock == 1) mainTimer.Start(); |
| 133 | + rpsTimer.Start(); |
| 134 | + |
| 135 | + await dataGenerator.RunAsync(); |
| 136 | + } |
| 137 | + } |
| 138 | + catch (Exception exception) { HandleException(exception); } |
| 139 | + } |
| 140 | + |
| 141 | + static async void Stop() |
| 142 | + { |
| 143 | + try |
| 144 | + { |
| 145 | + if (dataGenerator.IsRunning) |
| 146 | + { |
| 147 | + if (enableShock == 1) mainTimer.Stop(); |
| 148 | + rpsTimer.Stop(); |
| 149 | + if (enableShock == 1) shockTimer.Stop(); |
| 150 | + |
| 151 | + await dataGenerator.StopAsync(); |
| 152 | + dataGenerator.RpsReset(); |
| 153 | + } |
| 154 | + } |
| 155 | + catch (Exception exception) { HandleException(exception); } |
| 156 | + } |
| 157 | + |
| 158 | + static void Report() |
| 159 | + { |
| 160 | + ProcessStartInfo psi = new ProcessStartInfo(); |
| 161 | + psi.FileName = powerBIDesktopPath; |
| 162 | + psi.Arguments = @"Reports\PowerDashboard.pbix"; |
| 163 | + Process.Start(psi); |
| 164 | + } |
| 165 | + static void Help() |
| 166 | + { |
| 167 | + Console.ForegroundColor = ConsoleColor.Green; |
| 168 | + Console.WriteLine(""); |
| 169 | + Console.WriteLine("START - Starts the DataGenerator"); |
| 170 | + Console.WriteLine("STOP - Stops the DataGenerator"); |
| 171 | + Console.WriteLine("HELP - Displays this page"); |
| 172 | + Console.WriteLine("REPORT - Launches the Power BI Report"); |
| 173 | + Console.WriteLine("EXIT - Closes this program"); |
| 174 | + Console.WriteLine(""); |
| 175 | + } |
| 176 | + static void Init() |
| 177 | + { |
| 178 | + try |
| 179 | + { |
| 180 | + // Read Config Settings |
| 181 | + connection = ConfigurationManager.ConnectionStrings["Db"].ConnectionString; |
| 182 | + spName = ConfigurationManager.AppSettings["insertSPName"]; |
| 183 | + logFileName = ConfigurationManager.AppSettings["logFileName"]; |
| 184 | + powerBIDesktopPath = ConfigurationManager.AppSettings["powerBIDesktopPath"]; |
| 185 | + tasks = int.Parse(ConfigurationManager.AppSettings["numberOfTasks"]); |
| 186 | + meters = int.Parse(ConfigurationManager.AppSettings["numberOfMeters"]); |
| 187 | + batchSize = int.Parse(ConfigurationManager.AppSettings["batchSize"]); |
| 188 | + delay = int.Parse(ConfigurationManager.AppSettings["commandDelay"]); |
| 189 | + commandTimeout = int.Parse(ConfigurationManager.AppSettings["commandTimeout"]); |
| 190 | + shockFrequency = int.Parse(ConfigurationManager.AppSettings["shockFrequency"]); |
| 191 | + shockDuration = int.Parse(ConfigurationManager.AppSettings["shockDuration"]); |
| 192 | + enableShock = int.Parse(ConfigurationManager.AppSettings["enableShock"]); |
| 193 | + |
| 194 | + rpsFrequency = int.Parse(ConfigurationManager.AppSettings["rpsFrequency"]); |
| 195 | + |
| 196 | + // Initialize Timers |
| 197 | + mainTimer.Interval = shockFrequency; |
| 198 | + shockTimer.Interval = shockDuration; |
| 199 | + rpsTimer.Interval = rpsFrequency; |
| 200 | + |
| 201 | + if (batchSize <= 0) throw new SqlDataGeneratorException("The Batch Size cannot be less or equal to zero."); |
| 202 | + |
| 203 | + if (tasks <= 0) throw new SqlDataGeneratorException("Number Of Tasks cannot be less or equal to zero."); |
| 204 | + |
| 205 | + if (delay < 0) throw new SqlDataGeneratorException("Delay cannot be less than zero"); |
| 206 | + |
| 207 | + if (meters <= 0) throw new SqlDataGeneratorException("Number Of Meters cannot be less than zero"); |
| 208 | + |
| 209 | + if (meters < batchSize * tasks) throw new SqlDataGeneratorException("Number Of Meters cannot be less than (Tasks * BatchSize)."); |
| 210 | + } |
| 211 | + catch (Exception exception) { HandleException(exception); } |
| 212 | + } |
| 213 | + static void mainTimer_Tick(object sender, ElapsedEventArgs e) |
| 214 | + { |
| 215 | + if (dataGenerator.IsRunning) |
| 216 | + { |
| 217 | + dataGenerator.Delay = 0; |
| 218 | + shockTimer.Start(); |
| 219 | + } |
| 220 | + } |
| 221 | + static void rpsTimer_Tick(object sender, ElapsedEventArgs e) |
| 222 | + { |
| 223 | + try |
| 224 | + { |
| 225 | + double rps = dataGenerator.Rps; |
| 226 | + if (dataGenerator.IsRunning) |
| 227 | + { |
| 228 | + if (dataGenerator.RunningTasks == 0) return; |
| 229 | + |
| 230 | + if (rps > 0) |
| 231 | + { |
| 232 | + Console.SetCursorPosition(0, Console.CursorTop); |
| 233 | + Console.Write(string.Format("Rows Per Second (RPS):{0:#,#} ", rps).ToString()); |
| 234 | + } |
| 235 | + } |
| 236 | + } |
| 237 | + catch (Exception exception) { HandleException(exception); } |
| 238 | + } |
| 239 | + static void shockTimer_Tick(object sender, ElapsedEventArgs e) |
| 240 | + { |
| 241 | + Random rand = new Random(); |
| 242 | + dataGenerator.Delay = rand.Next(1500, 3000); |
| 243 | + shockTimer.Stop(); |
| 244 | + } |
| 245 | + } |
| 246 | +} |
0 commit comments