├── .gitignore ├── LICENSE ├── PingTest ├── .gitignore ├── PingTest │ ├── App.config │ ├── CommandLineArgsForm.Designer.cs │ ├── CommandLineArgsForm.cs │ ├── CommandLineArgsForm.resx │ ├── Extensions.cs │ ├── GraphScalingMethod.cs │ ├── MainForm.Designer.cs │ ├── MainForm.cs │ ├── MainForm.resx │ ├── OptionsForm.Designer.cs │ ├── OptionsForm.cs │ ├── OptionsForm.resx │ ├── PingGraphControl.Designer.cs │ ├── PingGraphControl.cs │ ├── PingGraphControl.resx │ ├── PingLog.cs │ ├── PingTracer.csproj │ ├── PingTracer.csproj.vspscc │ ├── Program.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── Settings.Designer.cs │ │ └── Settings.settings │ ├── ScreenCapture.cs │ ├── SerializableObjectBase.cs │ ├── Settings.cs │ ├── StartupOptions.cs │ ├── TestForm.Designer.cs │ ├── TestForm.cs │ ├── TestForm.resx │ ├── Throttle.cs │ ├── TraceRoute │ │ ├── PathChangedEventArgs.cs │ │ ├── PathTracer.cs │ │ ├── PingResponseEventArgs.cs │ │ ├── RouteTracerMethodA.cs │ │ ├── RouteTracerMethodB.cs │ │ ├── RouteTracerMethodC.cs │ │ └── TraceRouteHostResult.cs │ ├── Tracer │ │ ├── HostSettings.cs │ │ └── PingController.cs │ ├── Tracert.cs │ ├── TracertEntry.cs │ ├── Util │ │ ├── PingInstancePool.cs │ │ └── SimpleThreadPool.cs │ ├── WindowParams.cs │ ├── include │ │ ├── SmartPingF.dll │ │ ├── Tracer.Designer.cs │ │ ├── Tracer.cs │ │ └── Tracer.resx │ └── pingtracer.ico ├── PingTracer.sln └── PingTracer.vssscc └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 bp2008 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 | -------------------------------------------------------------------------------- /PingTest/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | #*.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /PingTest/PingTest/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /PingTest/PingTest/CommandLineArgsForm.Designer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace PingTracer 3 | { 4 | partial class CommandLineArgsForm 5 | { 6 | /// 7 | /// Required designer variable. 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// Clean up any resources being used. 13 | /// 14 | /// true if managed resources should be disposed; otherwise, false. 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Windows Form Designer generated code 25 | 26 | /// 27 | /// Required method for Designer support - do not modify 28 | /// the contents of this method with the code editor. 29 | /// 30 | private void InitializeComponent() 31 | { 32 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CommandLineArgsForm)); 33 | this.txtDocumentation = new System.Windows.Forms.TextBox(); 34 | this.label1 = new System.Windows.Forms.Label(); 35 | this.txtArgs = new System.Windows.Forms.TextBox(); 36 | this.SuspendLayout(); 37 | // 38 | // txtDocumentation 39 | // 40 | this.txtDocumentation.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 41 | | System.Windows.Forms.AnchorStyles.Left) 42 | | System.Windows.Forms.AnchorStyles.Right))); 43 | this.txtDocumentation.BackColor = System.Drawing.SystemColors.Window; 44 | this.txtDocumentation.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 45 | this.txtDocumentation.Location = new System.Drawing.Point(12, 12); 46 | this.txtDocumentation.Multiline = true; 47 | this.txtDocumentation.Name = "txtDocumentation"; 48 | this.txtDocumentation.ReadOnly = true; 49 | this.txtDocumentation.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; 50 | this.txtDocumentation.Size = new System.Drawing.Size(664, 331); 51 | this.txtDocumentation.TabIndex = 2; 52 | this.txtDocumentation.Text = resources.GetString("txtDocumentation.Text"); 53 | // 54 | // label1 55 | // 56 | this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 57 | this.label1.AutoSize = true; 58 | this.label1.Location = new System.Drawing.Point(12, 346); 59 | this.label1.Name = "label1"; 60 | this.label1.Size = new System.Drawing.Size(168, 13); 61 | this.label1.TabIndex = 1; 62 | this.label1.Text = "Arguments to start in current state:"; 63 | // 64 | // txtArgs 65 | // 66 | this.txtArgs.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 67 | | System.Windows.Forms.AnchorStyles.Right))); 68 | this.txtArgs.BackColor = System.Drawing.SystemColors.Window; 69 | this.txtArgs.Location = new System.Drawing.Point(12, 362); 70 | this.txtArgs.Name = "txtArgs"; 71 | this.txtArgs.ReadOnly = true; 72 | this.txtArgs.Size = new System.Drawing.Size(664, 20); 73 | this.txtArgs.TabIndex = 0; 74 | // 75 | // CommandLineArgsForm 76 | // 77 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 78 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 79 | this.ClientSize = new System.Drawing.Size(688, 394); 80 | this.Controls.Add(this.txtArgs); 81 | this.Controls.Add(this.label1); 82 | this.Controls.Add(this.txtDocumentation); 83 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; 84 | this.Name = "CommandLineArgsForm"; 85 | this.Text = "CommandLineArgsForm"; 86 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.CommandLineArgsForm_FormClosing); 87 | this.Load += new System.EventHandler(this.CommandLineArgsForm_Load); 88 | this.ResumeLayout(false); 89 | this.PerformLayout(); 90 | 91 | } 92 | 93 | #endregion 94 | 95 | private System.Windows.Forms.TextBox txtDocumentation; 96 | private System.Windows.Forms.Label label1; 97 | private System.Windows.Forms.TextBox txtArgs; 98 | } 99 | } -------------------------------------------------------------------------------- /PingTest/PingTest/CommandLineArgsForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace PingTracer 12 | { 13 | public partial class CommandLineArgsForm : Form 14 | { 15 | MainForm mainForm; 16 | StartupOptions options = new StartupOptions(); 17 | public CommandLineArgsForm(MainForm mainForm) 18 | { 19 | this.mainForm = mainForm; 20 | 21 | InitializeComponent(); 22 | 23 | this.SetLocationNearMouse(); 24 | 25 | mainForm.Move += SetCommandLineArgs; 26 | mainForm.Resize += SetCommandLineArgs; 27 | mainForm.StartedPinging += SetCommandLineArgs; 28 | mainForm.StoppedPinging += SetCommandLineArgs; 29 | mainForm.SelectedHostChanged += SetCommandLineArgs; 30 | mainForm.MaximizeGraphsChanged += SetCommandLineArgs; 31 | 32 | SetCommandLineArgs(); 33 | } 34 | 35 | private void CommandLineArgsForm_Load(object sender, EventArgs e) 36 | { 37 | txtDocumentation.Select(0, 0); 38 | txtArgs.Select(0, 0); 39 | txtArgs.Focus(); 40 | txtArgs.SelectAll(); 41 | } 42 | 43 | private void SetCommandLineArgs(object sender, EventArgs e) 44 | { 45 | SetCommandLineArgs(); 46 | } 47 | int mT => mainForm.settings.osWindowTopMargin; 48 | int mL => mainForm.settings.osWindowLeftMargin; 49 | int mR => mainForm.settings.osWindowRightMargin; 50 | int mB => mainForm.settings.osWindowBottomMargin; 51 | private void SetCommandLineArgs() 52 | { 53 | options.WindowLocation = new WindowParams(mainForm.Location.X + mL, 54 | mainForm.Location.Y + mT, 55 | mainForm.Size.Width - (mL + mR), 56 | mainForm.Size.Height - (mT + mB)); 57 | 58 | options.StartupHostName = mainForm.txtDisplayName.Text; 59 | if (string.IsNullOrWhiteSpace(options.StartupHostName)) 60 | options.StartupHostName = mainForm.txtHost.Text; 61 | if (string.IsNullOrWhiteSpace(options.StartupHostName)) 62 | options.StartupHostName = null; 63 | 64 | options.StartPinging = mainForm.isRunning; 65 | 66 | options.MaximizeGraphs = mainForm.graphsMaximized; 67 | 68 | options.PreferIPv6 = mainForm.cbPreferIpv4.Checked ? BoolOverride.False : BoolOverride.True; 69 | 70 | options.TraceRoute = mainForm.cbTraceroute.Checked ? BoolOverride.True : BoolOverride.False; 71 | 72 | txtArgs.Text = options.ToString(); 73 | } 74 | 75 | private void CommandLineArgsForm_FormClosing(object sender, FormClosingEventArgs e) 76 | { 77 | mainForm.Move -= SetCommandLineArgs; 78 | mainForm.Resize -= SetCommandLineArgs; 79 | mainForm.StartedPinging -= SetCommandLineArgs; 80 | mainForm.StoppedPinging -= SetCommandLineArgs; 81 | mainForm.SelectedHostChanged -= SetCommandLineArgs; 82 | mainForm.MaximizeGraphsChanged -= SetCommandLineArgs; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /PingTest/PingTest/CommandLineArgsForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Arguments: 122 | -h <value> 123 | Load a saved configuration with Display Name or Host field matching <value>. 124 | If a matching configuration is not found, one will be created. 125 | -4 126 | (Use with -h) This indicates the "Prefer IPv4" checkbox must be checked. 127 | -6 128 | (Use with -h) This indicates the "Prefer IPv4" checkbox must be unchecked. 129 | -t0 130 | (Use with -h) This indicates the "Trace Route" checkbox must be unchecked. 131 | -t1 132 | (Use with -h) This indicates the "Trace Route" checkbox must be checked. 133 | -l x,y,w,h 134 | The window will be moved to the specified location and size. 135 | "w" and "h" parameters are optional. 136 | -s 137 | Pinging will begin automatically. 138 | -m 139 | The ping graphs will be maximized. 140 | 141 | -------------------------------------------------------------------------------- /PingTest/PingTest/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Forms; 8 | 9 | namespace PingTracer 10 | { 11 | public static class Extensions 12 | { 13 | /// 14 | /// Sets the location of the form to be near the mouse pointer, preferably not directly on top of the mouse pointer, but entirely on-screen if possible. 15 | /// 16 | /// The form. 17 | public static void SetLocationNearMouse(this Form form) 18 | { 19 | int offset = 10; 20 | int x = 0, y = 0; 21 | Point cursor = Cursor.Position; 22 | Screen screen = Screen.FromPoint(cursor); 23 | Rectangle workspace = screen.WorkingArea; 24 | Point centerScreen = new Point(workspace.X + (workspace.Width / 2), workspace.Y + (workspace.Height / 2)); 25 | 26 | // Position the form near the cursor, extending away from the cursor toward the center of the screen. 27 | if (cursor.X <= centerScreen.X) 28 | { 29 | if (cursor.Y <= centerScreen.Y) 30 | { 31 | // Upper-left quadrant 32 | x = cursor.X + offset; 33 | y = cursor.Y + offset; 34 | } 35 | else 36 | { 37 | // Lower-left quadrant 38 | x = cursor.X + offset; 39 | y = cursor.Y - offset - form.Height; 40 | } 41 | } 42 | else 43 | { 44 | if (cursor.Y <= centerScreen.Y) 45 | { 46 | // Upper-right quadrant 47 | x = cursor.X - offset - form.Width; 48 | y = cursor.Y + offset; 49 | } 50 | else 51 | { 52 | // Lower-right quadrant 53 | x = cursor.X - offset - form.Width; 54 | y = cursor.Y - offset - form.Height; 55 | } 56 | } 57 | 58 | // Screen bounds check. Keep form entirely within this screen if possible, but ensure that the top left corner is visible if all else fails. 59 | if (x >= workspace.X + (workspace.Width - form.Width)) 60 | x = workspace.X + (workspace.Width - form.Width); 61 | if (x < workspace.X) 62 | x = workspace.X; 63 | if (y >= workspace.Y + (workspace.Height - form.Height)) 64 | y = workspace.Y + (workspace.Height - form.Height); 65 | if (y < workspace.Y) 66 | y = workspace.Y; 67 | 68 | // Assign location 69 | form.StartPosition = FormStartPosition.Manual; 70 | form.Location = new Point(x, y); 71 | } 72 | /// 73 | /// If the center of the form is not visible on any of the screens, moves the form to the screen whose center is closest to the form in its old position. 74 | /// 75 | /// The form to move. 76 | public static void MoveOnscreenIfOffscreen(this Form form) 77 | { 78 | // Check if the center of the form is visible on any of the screens 79 | bool formIsVisible = false; 80 | Point formCenter = new Point(form.Left + form.Width / 2, form.Top + form.Height / 2); 81 | foreach (Screen screen in Screen.AllScreens) 82 | { 83 | if (screen.WorkingArea.Contains(formCenter)) 84 | { 85 | formIsVisible = true; 86 | break; 87 | } 88 | } 89 | 90 | // If the center of the form is not visible, move it to the screen whose center is closest to the form in its old position 91 | if (!formIsVisible) 92 | { 93 | Screen closestScreen = Screen.AllScreens[0]; 94 | double minDistance = double.MaxValue; 95 | foreach (Screen screen in Screen.AllScreens) 96 | { 97 | Point screenCenter = new Point(screen.WorkingArea.Left + screen.WorkingArea.Width / 2, screen.WorkingArea.Top + screen.WorkingArea.Height / 2); 98 | double distance = Math.Sqrt(Math.Pow(screenCenter.X - formCenter.X, 2) + Math.Pow(screenCenter.Y - formCenter.Y, 2)); 99 | if (distance < minDistance) 100 | { 101 | minDistance = distance; 102 | closestScreen = screen; 103 | } 104 | } 105 | int x = closestScreen.WorkingArea.Left + (closestScreen.WorkingArea.Width - form.Width) / 2; 106 | int y = closestScreen.WorkingArea.Top + (closestScreen.WorkingArea.Height - form.Height) / 2; 107 | form.Location = new Point(x, y); 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /PingTest/PingTest/GraphScalingMethod.cs: -------------------------------------------------------------------------------- 1 | namespace PingTracer 2 | { 3 | /// 4 | /// Ping graph scaling methods. 5 | /// 6 | public enum GraphScalingMethod 7 | { 8 | /// 9 | /// The Classic method prefers to treat 1 pixel as 1 millisecond, but can zoom out if needed to show higher response time values correctly. 10 | /// 11 | Classic = 0, 12 | /// 13 | /// The Zoom Unlimited method zooms in or out to closely fit the data but will not zoom out beyond the user's specified limits, possibly causing clipping. 14 | /// 15 | Zoom = 1, 16 | /// 17 | /// The Zoom Unlimited method zooms in or out to closely fit the data. 18 | /// 19 | Zoom_Unlimited = 2, 20 | /// 21 | /// The Fixed method shows exactly the user-specified response time range and does not zoom to fit the data. 22 | /// 23 | Fixed = 3, 24 | } 25 | } -------------------------------------------------------------------------------- /PingTest/PingTest/OptionsForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace PingTracer 2 | { 3 | partial class OptionsForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.components = new System.ComponentModel.Container(); 32 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(OptionsForm)); 33 | this.cbLogToFile = new System.Windows.Forms.CheckBox(); 34 | this.cbDelayMostRecentPing = new System.Windows.Forms.CheckBox(); 35 | this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); 36 | this.cbWarnGraphNotLive = new System.Windows.Forms.CheckBox(); 37 | this.label1 = new System.Windows.Forms.Label(); 38 | this.nudPingResponsesToCache = new System.Windows.Forms.NumericUpDown(); 39 | this.label2 = new System.Windows.Forms.Label(); 40 | this.cbFastRefreshScrollingGraphs = new System.Windows.Forms.CheckBox(); 41 | this.label3 = new System.Windows.Forms.Label(); 42 | this.nudGraphScrollMultiplier = new System.Windows.Forms.NumericUpDown(); 43 | this.cbShowDateInCorner = new System.Windows.Forms.CheckBox(); 44 | this.label4 = new System.Windows.Forms.Label(); 45 | this.txtCustomTimeString = new System.Windows.Forms.TextBox(); 46 | this.customTimeStringHelp = new System.Windows.Forms.LinkLabel(); 47 | this.panel1 = new System.Windows.Forms.Panel(); 48 | this.groupBoxFormMargins = new System.Windows.Forms.GroupBox(); 49 | this.nudBottomMargin = new System.Windows.Forms.NumericUpDown(); 50 | this.nudRightMargin = new System.Windows.Forms.NumericUpDown(); 51 | this.nudLeftMargin = new System.Windows.Forms.NumericUpDown(); 52 | this.nudTopMargin = new System.Windows.Forms.NumericUpDown(); 53 | this.label5 = new System.Windows.Forms.Label(); 54 | this.nudPingTimeoutRedLineHeight = new System.Windows.Forms.NumericUpDown(); 55 | this.label6 = new System.Windows.Forms.Label(); 56 | this.label7 = new System.Windows.Forms.Label(); 57 | ((System.ComponentModel.ISupportInitialize)(this.nudPingResponsesToCache)).BeginInit(); 58 | ((System.ComponentModel.ISupportInitialize)(this.nudGraphScrollMultiplier)).BeginInit(); 59 | this.groupBoxFormMargins.SuspendLayout(); 60 | ((System.ComponentModel.ISupportInitialize)(this.nudBottomMargin)).BeginInit(); 61 | ((System.ComponentModel.ISupportInitialize)(this.nudRightMargin)).BeginInit(); 62 | ((System.ComponentModel.ISupportInitialize)(this.nudLeftMargin)).BeginInit(); 63 | ((System.ComponentModel.ISupportInitialize)(this.nudTopMargin)).BeginInit(); 64 | ((System.ComponentModel.ISupportInitialize)(this.nudPingTimeoutRedLineHeight)).BeginInit(); 65 | this.SuspendLayout(); 66 | // 67 | // cbLogToFile 68 | // 69 | this.cbLogToFile.AutoSize = true; 70 | this.cbLogToFile.Checked = true; 71 | this.cbLogToFile.CheckState = System.Windows.Forms.CheckState.Checked; 72 | this.cbLogToFile.Location = new System.Drawing.Point(12, 12); 73 | this.cbLogToFile.Name = "cbLogToFile"; 74 | this.cbLogToFile.Size = new System.Drawing.Size(125, 17); 75 | this.cbLogToFile.TabIndex = 1; 76 | this.cbLogToFile.Text = "Log text output to file"; 77 | this.toolTip1.SetToolTip(this.cbLogToFile, "Output goes to PingTracer_Output.txt in the current working directory."); 78 | this.cbLogToFile.UseVisualStyleBackColor = true; 79 | this.cbLogToFile.CheckedChanged += new System.EventHandler(this.cbLogToFile_CheckedChanged); 80 | // 81 | // cbDelayMostRecentPing 82 | // 83 | this.cbDelayMostRecentPing.Checked = true; 84 | this.cbDelayMostRecentPing.CheckState = System.Windows.Forms.CheckState.Checked; 85 | this.cbDelayMostRecentPing.Location = new System.Drawing.Point(12, 35); 86 | this.cbDelayMostRecentPing.Name = "cbDelayMostRecentPing"; 87 | this.cbDelayMostRecentPing.Size = new System.Drawing.Size(259, 34); 88 | this.cbDelayMostRecentPing.TabIndex = 2; 89 | this.cbDelayMostRecentPing.Text = "Delay ping graphing by one ping interval\r\n(reduces visual flickering)"; 90 | this.toolTip1.SetToolTip(this.cbDelayMostRecentPing, "(Checked by default)\r\n\r\nIf unchecked, each wave of pings will appear early, \r\nlik" + 91 | "ely before the ping response has arrived, causing \r\na visual flickering effect w" + 92 | "hen the response arrives."); 93 | this.cbDelayMostRecentPing.UseVisualStyleBackColor = true; 94 | this.cbDelayMostRecentPing.CheckedChanged += new System.EventHandler(this.cbDelayMostRecentPing_CheckedChanged); 95 | // 96 | // toolTip1 97 | // 98 | this.toolTip1.AutomaticDelay = 250; 99 | this.toolTip1.AutoPopDelay = 10000; 100 | this.toolTip1.InitialDelay = 250; 101 | this.toolTip1.ReshowDelay = 50; 102 | // 103 | // cbWarnGraphNotLive 104 | // 105 | this.cbWarnGraphNotLive.AutoSize = true; 106 | this.cbWarnGraphNotLive.Checked = true; 107 | this.cbWarnGraphNotLive.CheckState = System.Windows.Forms.CheckState.Checked; 108 | this.cbWarnGraphNotLive.Location = new System.Drawing.Point(12, 75); 109 | this.cbWarnGraphNotLive.Name = "cbWarnGraphNotLive"; 110 | this.cbWarnGraphNotLive.Size = new System.Drawing.Size(290, 17); 111 | this.cbWarnGraphNotLive.TabIndex = 3; 112 | this.cbWarnGraphNotLive.Text = "Warn when graph has been scrolled and is \"NOT LIVE\""; 113 | this.toolTip1.SetToolTip(this.cbWarnGraphNotLive, "(Checked by default)\r\n\r\nIf checked, \"NOT LIVE\" text will appear \r\nwhen you scroll" + 114 | " the graph to the side."); 115 | this.cbWarnGraphNotLive.UseVisualStyleBackColor = true; 116 | this.cbWarnGraphNotLive.CheckedChanged += new System.EventHandler(this.cbWarnGraphNotLive_CheckedChanged); 117 | // 118 | // label1 119 | // 120 | this.label1.AutoSize = true; 121 | this.label1.Location = new System.Drawing.Point(12, 181); 122 | this.label1.Name = "label1"; 123 | this.label1.Size = new System.Drawing.Size(293, 13); 124 | this.label1.TabIndex = 11; 125 | this.label1.Text = "Number of ping responses to cache in memory for each host:"; 126 | this.toolTip1.SetToolTip(this.label1, resources.GetString("label1.ToolTip")); 127 | // 128 | // nudPingResponsesToCache 129 | // 130 | this.nudPingResponsesToCache.Location = new System.Drawing.Point(12, 200); 131 | this.nudPingResponsesToCache.Maximum = new decimal(new int[] { 132 | 10000000, 133 | 0, 134 | 0, 135 | 0}); 136 | this.nudPingResponsesToCache.Minimum = new decimal(new int[] { 137 | 10000, 138 | 0, 139 | 0, 140 | 0}); 141 | this.nudPingResponsesToCache.Name = "nudPingResponsesToCache"; 142 | this.nudPingResponsesToCache.Size = new System.Drawing.Size(102, 20); 143 | this.nudPingResponsesToCache.TabIndex = 7; 144 | this.toolTip1.SetToolTip(this.nudPingResponsesToCache, resources.GetString("nudPingResponsesToCache.ToolTip")); 145 | this.nudPingResponsesToCache.Value = new decimal(new int[] { 146 | 360000, 147 | 0, 148 | 0, 149 | 0}); 150 | this.nudPingResponsesToCache.ValueChanged += new System.EventHandler(this.nudPingResponsesToCache_ValueChanged); 151 | // 152 | // label2 153 | // 154 | this.label2.AutoSize = true; 155 | this.label2.Location = new System.Drawing.Point(120, 202); 156 | this.label2.Name = "label2"; 157 | this.label2.Size = new System.Drawing.Size(227, 13); 158 | this.label2.TabIndex = 13; 159 | this.label2.Text = "Takes effect when ping monitoring is restarted."; 160 | this.toolTip1.SetToolTip(this.label2, resources.GetString("label2.ToolTip")); 161 | // 162 | // cbFastRefreshScrollingGraphs 163 | // 164 | this.cbFastRefreshScrollingGraphs.AutoSize = true; 165 | this.cbFastRefreshScrollingGraphs.Checked = true; 166 | this.cbFastRefreshScrollingGraphs.CheckState = System.Windows.Forms.CheckState.Checked; 167 | this.cbFastRefreshScrollingGraphs.Location = new System.Drawing.Point(12, 98); 168 | this.cbFastRefreshScrollingGraphs.Name = "cbFastRefreshScrollingGraphs"; 169 | this.cbFastRefreshScrollingGraphs.Size = new System.Drawing.Size(212, 17); 170 | this.cbFastRefreshScrollingGraphs.TabIndex = 4; 171 | this.cbFastRefreshScrollingGraphs.Text = "Accelerate graph redraw when scrolling"; 172 | this.toolTip1.SetToolTip(this.cbFastRefreshScrollingGraphs, "(Checked by default)\r\n\r\nIf checked, graphs will update faster while being scrolle" + 173 | "d,\r\nat the cost of increased CPU usage."); 174 | this.cbFastRefreshScrollingGraphs.UseVisualStyleBackColor = true; 175 | this.cbFastRefreshScrollingGraphs.CheckedChanged += new System.EventHandler(this.cbFastRefreshScrollingGraphs_CheckedChanged); 176 | // 177 | // label3 178 | // 179 | this.label3.AutoSize = true; 180 | this.label3.Location = new System.Drawing.Point(9, 150); 181 | this.label3.Name = "label3"; 182 | this.label3.Size = new System.Drawing.Size(126, 13); 183 | this.label3.TabIndex = 15; 184 | this.label3.Text = "Graph scrolling multiplier: "; 185 | this.toolTip1.SetToolTip(this.label3, "(Default: 50)\r\n\r\nWhen you click and drag a ping graph horizontally,\r\nit scrolls. " + 186 | " If you increase this value, it will scroll faster.\r\n\r\nIf you set this value to " + 187 | "0, graph scrolling will be disabled."); 188 | // 189 | // nudGraphScrollMultiplier 190 | // 191 | this.nudGraphScrollMultiplier.Location = new System.Drawing.Point(141, 148); 192 | this.nudGraphScrollMultiplier.Maximum = new decimal(new int[] { 193 | 10000, 194 | 0, 195 | 0, 196 | 0}); 197 | this.nudGraphScrollMultiplier.Name = "nudGraphScrollMultiplier"; 198 | this.nudGraphScrollMultiplier.Size = new System.Drawing.Size(102, 20); 199 | this.nudGraphScrollMultiplier.TabIndex = 6; 200 | this.toolTip1.SetToolTip(this.nudGraphScrollMultiplier, "(Default: 50)\r\n\r\nWhen you click and drag a ping graph horizontally,\r\nit scrolls. " + 201 | " If you increase this value, it will scroll faster.\r\n\r\nIf you set this value to " + 202 | "0, graph scrolling will be disabled."); 203 | this.nudGraphScrollMultiplier.Value = new decimal(new int[] { 204 | 1, 205 | 0, 206 | 0, 207 | 0}); 208 | this.nudGraphScrollMultiplier.ValueChanged += new System.EventHandler(this.nudGraphScrollMultiplier_ValueChanged); 209 | // 210 | // cbShowDateInCorner 211 | // 212 | this.cbShowDateInCorner.AutoSize = true; 213 | this.cbShowDateInCorner.Checked = true; 214 | this.cbShowDateInCorner.CheckState = System.Windows.Forms.CheckState.Checked; 215 | this.cbShowDateInCorner.Location = new System.Drawing.Point(12, 121); 216 | this.cbShowDateInCorner.Name = "cbShowDateInCorner"; 217 | this.cbShowDateInCorner.Size = new System.Drawing.Size(310, 17); 218 | this.cbShowDateInCorner.TabIndex = 5; 219 | this.cbShowDateInCorner.Text = "Show the current date in the bottom left corner of the graphs"; 220 | this.toolTip1.SetToolTip(this.cbShowDateInCorner, "(Checked by default)\r\n\r\nIf checked, the associated date will overlap the bottom\r\n" + 221 | "left corner of the timeline below the graphs."); 222 | this.cbShowDateInCorner.UseVisualStyleBackColor = true; 223 | this.cbShowDateInCorner.CheckedChanged += new System.EventHandler(this.cbShowDateInCorner_CheckedChanged); 224 | // 225 | // label4 226 | // 227 | this.label4.AutoSize = true; 228 | this.label4.Location = new System.Drawing.Point(12, 234); 229 | this.label4.Name = "label4"; 230 | this.label4.Size = new System.Drawing.Size(142, 13); 231 | this.label4.TabIndex = 16; 232 | this.label4.Text = "Custom Time String for Logs:"; 233 | // 234 | // txtCustomTimeString 235 | // 236 | this.txtCustomTimeString.Location = new System.Drawing.Point(160, 231); 237 | this.txtCustomTimeString.Name = "txtCustomTimeString"; 238 | this.txtCustomTimeString.Size = new System.Drawing.Size(147, 20); 239 | this.txtCustomTimeString.TabIndex = 17; 240 | this.txtCustomTimeString.TextChanged += new System.EventHandler(this.txtCustomTimeStringGraphs_TextChanged); 241 | // 242 | // customTimeStringHelp 243 | // 244 | this.customTimeStringHelp.AutoSize = true; 245 | this.customTimeStringHelp.Location = new System.Drawing.Point(313, 234); 246 | this.customTimeStringHelp.Name = "customTimeStringHelp"; 247 | this.customTimeStringHelp.Size = new System.Drawing.Size(33, 13); 248 | this.customTimeStringHelp.TabIndex = 18; 249 | this.customTimeStringHelp.TabStop = true; 250 | this.customTimeStringHelp.Text = "(help)"; 251 | this.customTimeStringHelp.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.customTimeStringHelp_LinkClicked); 252 | // 253 | // panel1 254 | // 255 | this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 256 | this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; 257 | this.panel1.Location = new System.Drawing.Point(228, 42); 258 | this.panel1.Name = "panel1"; 259 | this.panel1.Size = new System.Drawing.Size(53, 49); 260 | this.panel1.TabIndex = 19; 261 | // 262 | // groupBoxFormMargins 263 | // 264 | this.groupBoxFormMargins.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 265 | | System.Windows.Forms.AnchorStyles.Left) 266 | | System.Windows.Forms.AnchorStyles.Right))); 267 | this.groupBoxFormMargins.Controls.Add(this.nudBottomMargin); 268 | this.groupBoxFormMargins.Controls.Add(this.nudRightMargin); 269 | this.groupBoxFormMargins.Controls.Add(this.nudLeftMargin); 270 | this.groupBoxFormMargins.Controls.Add(this.nudTopMargin); 271 | this.groupBoxFormMargins.Controls.Add(this.label5); 272 | this.groupBoxFormMargins.Controls.Add(this.panel1); 273 | this.groupBoxFormMargins.Location = new System.Drawing.Point(12, 277); 274 | this.groupBoxFormMargins.Name = "groupBoxFormMargins"; 275 | this.groupBoxFormMargins.Size = new System.Drawing.Size(346, 131); 276 | this.groupBoxFormMargins.TabIndex = 20; 277 | this.groupBoxFormMargins.TabStop = false; 278 | this.groupBoxFormMargins.Text = "Window Margins"; 279 | // 280 | // nudBottomMargin 281 | // 282 | this.nudBottomMargin.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 283 | this.nudBottomMargin.Location = new System.Drawing.Point(228, 97); 284 | this.nudBottomMargin.Maximum = new decimal(new int[] { 285 | 30, 286 | 0, 287 | 0, 288 | 0}); 289 | this.nudBottomMargin.Minimum = new decimal(new int[] { 290 | 30, 291 | 0, 292 | 0, 293 | -2147483648}); 294 | this.nudBottomMargin.Name = "nudBottomMargin"; 295 | this.nudBottomMargin.Size = new System.Drawing.Size(53, 20); 296 | this.nudBottomMargin.TabIndex = 24; 297 | this.nudBottomMargin.ValueChanged += new System.EventHandler(this.nudBottomMargin_ValueChanged); 298 | // 299 | // nudRightMargin 300 | // 301 | this.nudRightMargin.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 302 | this.nudRightMargin.Location = new System.Drawing.Point(287, 56); 303 | this.nudRightMargin.Maximum = new decimal(new int[] { 304 | 30, 305 | 0, 306 | 0, 307 | 0}); 308 | this.nudRightMargin.Minimum = new decimal(new int[] { 309 | 30, 310 | 0, 311 | 0, 312 | -2147483648}); 313 | this.nudRightMargin.Name = "nudRightMargin"; 314 | this.nudRightMargin.Size = new System.Drawing.Size(53, 20); 315 | this.nudRightMargin.TabIndex = 23; 316 | this.nudRightMargin.ValueChanged += new System.EventHandler(this.nudRightMargin_ValueChanged); 317 | // 318 | // nudLeftMargin 319 | // 320 | this.nudLeftMargin.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 321 | this.nudLeftMargin.Location = new System.Drawing.Point(169, 56); 322 | this.nudLeftMargin.Maximum = new decimal(new int[] { 323 | 30, 324 | 0, 325 | 0, 326 | 0}); 327 | this.nudLeftMargin.Minimum = new decimal(new int[] { 328 | 30, 329 | 0, 330 | 0, 331 | -2147483648}); 332 | this.nudLeftMargin.Name = "nudLeftMargin"; 333 | this.nudLeftMargin.Size = new System.Drawing.Size(53, 20); 334 | this.nudLeftMargin.TabIndex = 22; 335 | this.nudLeftMargin.ValueChanged += new System.EventHandler(this.nudLeftMargin_ValueChanged); 336 | // 337 | // nudTopMargin 338 | // 339 | this.nudTopMargin.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 340 | this.nudTopMargin.Location = new System.Drawing.Point(228, 16); 341 | this.nudTopMargin.Maximum = new decimal(new int[] { 342 | 30, 343 | 0, 344 | 0, 345 | 0}); 346 | this.nudTopMargin.Minimum = new decimal(new int[] { 347 | 30, 348 | 0, 349 | 0, 350 | -2147483648}); 351 | this.nudTopMargin.Name = "nudTopMargin"; 352 | this.nudTopMargin.Size = new System.Drawing.Size(53, 20); 353 | this.nudTopMargin.TabIndex = 21; 354 | this.nudTopMargin.ValueChanged += new System.EventHandler(this.nudTopMargin_ValueChanged); 355 | // 356 | // label5 357 | // 358 | this.label5.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 359 | | System.Windows.Forms.AnchorStyles.Left) 360 | | System.Windows.Forms.AnchorStyles.Right))); 361 | this.label5.Location = new System.Drawing.Point(6, 18); 362 | this.label5.Name = "label5"; 363 | this.label5.Size = new System.Drawing.Size(157, 110); 364 | this.label5.TabIndex = 20; 365 | this.label5.Text = "When maximizing the ping graphs, they may appear larger or smaller than the regul" + 366 | "ar program window. To correct this, the following margins will be subtracted fr" + 367 | "om the maximized graph view."; 368 | // 369 | // nudPingTimeoutRedLineHeight 370 | // 371 | this.nudPingTimeoutRedLineHeight.Location = new System.Drawing.Point(160, 257); 372 | this.nudPingTimeoutRedLineHeight.Maximum = new decimal(new int[] { 373 | 10000, 374 | 0, 375 | 0, 376 | 0}); 377 | this.nudPingTimeoutRedLineHeight.Minimum = new decimal(new int[] { 378 | 1, 379 | 0, 380 | 0, 381 | 0}); 382 | this.nudPingTimeoutRedLineHeight.Name = "nudPingTimeoutRedLineHeight"; 383 | this.nudPingTimeoutRedLineHeight.Size = new System.Drawing.Size(64, 20); 384 | this.nudPingTimeoutRedLineHeight.TabIndex = 21; 385 | this.toolTip1.SetToolTip(this.nudPingTimeoutRedLineHeight, "When a ping times out (gets no response), a red line is drawn \r\nup to this many p" + 386 | "ixels tall in the graph. You can reduce this \r\nvalue to shrink the line that is " + 387 | "drawn."); 388 | this.nudPingTimeoutRedLineHeight.Value = new decimal(new int[] { 389 | 10000, 390 | 0, 391 | 0, 392 | 0}); 393 | this.nudPingTimeoutRedLineHeight.ValueChanged += new System.EventHandler(this.nudPingTimeoutRedLineHeight_ValueChanged); 394 | // 395 | // label6 396 | // 397 | this.label6.AutoSize = true; 398 | this.label6.Location = new System.Drawing.Point(12, 259); 399 | this.label6.Name = "label6"; 400 | this.label6.Size = new System.Drawing.Size(137, 13); 401 | this.label6.TabIndex = 22; 402 | this.label6.Text = "Ping timeout red line height:"; 403 | this.toolTip1.SetToolTip(this.label6, "When a ping times out (gets no response), a red line is drawn \r\nup to this many p" + 404 | "ixels tall in the graph. You can reduce this \r\nvalue to shrink the line that is " + 405 | "drawn."); 406 | // 407 | // label7 408 | // 409 | this.label7.AutoSize = true; 410 | this.label7.Location = new System.Drawing.Point(232, 259); 411 | this.label7.Name = "label7"; 412 | this.label7.Size = new System.Drawing.Size(39, 13); 413 | this.label7.TabIndex = 23; 414 | this.label7.Text = "(pixels)"; 415 | this.toolTip1.SetToolTip(this.label7, "When a ping times out (gets no response), a red line is drawn \r\nup to this many p" + 416 | "ixels tall in the graph. You can reduce this \r\nvalue to shrink the line that is " + 417 | "drawn."); 418 | // 419 | // OptionsForm 420 | // 421 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 422 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 423 | this.ClientSize = new System.Drawing.Size(370, 415); 424 | this.Controls.Add(this.label7); 425 | this.Controls.Add(this.nudPingTimeoutRedLineHeight); 426 | this.Controls.Add(this.label6); 427 | this.Controls.Add(this.groupBoxFormMargins); 428 | this.Controls.Add(this.customTimeStringHelp); 429 | this.Controls.Add(this.txtCustomTimeString); 430 | this.Controls.Add(this.label4); 431 | this.Controls.Add(this.cbShowDateInCorner); 432 | this.Controls.Add(this.nudGraphScrollMultiplier); 433 | this.Controls.Add(this.label3); 434 | this.Controls.Add(this.cbFastRefreshScrollingGraphs); 435 | this.Controls.Add(this.label2); 436 | this.Controls.Add(this.nudPingResponsesToCache); 437 | this.Controls.Add(this.label1); 438 | this.Controls.Add(this.cbWarnGraphNotLive); 439 | this.Controls.Add(this.cbDelayMostRecentPing); 440 | this.Controls.Add(this.cbLogToFile); 441 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 442 | this.Name = "OptionsForm"; 443 | this.Text = "Ping Tracer Options"; 444 | ((System.ComponentModel.ISupportInitialize)(this.nudPingResponsesToCache)).EndInit(); 445 | ((System.ComponentModel.ISupportInitialize)(this.nudGraphScrollMultiplier)).EndInit(); 446 | this.groupBoxFormMargins.ResumeLayout(false); 447 | ((System.ComponentModel.ISupportInitialize)(this.nudBottomMargin)).EndInit(); 448 | ((System.ComponentModel.ISupportInitialize)(this.nudRightMargin)).EndInit(); 449 | ((System.ComponentModel.ISupportInitialize)(this.nudLeftMargin)).EndInit(); 450 | ((System.ComponentModel.ISupportInitialize)(this.nudTopMargin)).EndInit(); 451 | ((System.ComponentModel.ISupportInitialize)(this.nudPingTimeoutRedLineHeight)).EndInit(); 452 | this.ResumeLayout(false); 453 | this.PerformLayout(); 454 | 455 | } 456 | 457 | #endregion 458 | 459 | private System.Windows.Forms.CheckBox cbLogToFile; 460 | private System.Windows.Forms.CheckBox cbDelayMostRecentPing; 461 | private System.Windows.Forms.ToolTip toolTip1; 462 | private System.Windows.Forms.CheckBox cbWarnGraphNotLive; 463 | private System.Windows.Forms.Label label1; 464 | private System.Windows.Forms.NumericUpDown nudPingResponsesToCache; 465 | private System.Windows.Forms.Label label2; 466 | private System.Windows.Forms.CheckBox cbFastRefreshScrollingGraphs; 467 | private System.Windows.Forms.Label label3; 468 | private System.Windows.Forms.NumericUpDown nudGraphScrollMultiplier; 469 | private System.Windows.Forms.CheckBox cbShowDateInCorner; 470 | private System.Windows.Forms.Label label4; 471 | private System.Windows.Forms.TextBox txtCustomTimeString; 472 | private System.Windows.Forms.LinkLabel customTimeStringHelp; 473 | private System.Windows.Forms.Panel panel1; 474 | private System.Windows.Forms.GroupBox groupBoxFormMargins; 475 | private System.Windows.Forms.NumericUpDown nudTopMargin; 476 | private System.Windows.Forms.Label label5; 477 | private System.Windows.Forms.NumericUpDown nudBottomMargin; 478 | private System.Windows.Forms.NumericUpDown nudRightMargin; 479 | private System.Windows.Forms.NumericUpDown nudLeftMargin; 480 | private System.Windows.Forms.NumericUpDown nudPingTimeoutRedLineHeight; 481 | private System.Windows.Forms.Label label6; 482 | private System.Windows.Forms.Label label7; 483 | } 484 | } -------------------------------------------------------------------------------- /PingTest/PingTest/OptionsForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Diagnostics; 6 | using System.Drawing; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Windows.Forms; 10 | 11 | namespace PingTracer 12 | { 13 | public partial class OptionsForm : Form 14 | { 15 | private MainForm mainForm; 16 | 17 | public OptionsForm() 18 | { 19 | InitializeComponent(); 20 | } 21 | 22 | public OptionsForm(MainForm mainForm) 23 | { 24 | this.mainForm = mainForm; 25 | InitializeComponent(); 26 | cbLogToFile.Checked = mainForm.settings.logTextOutputToFile; 27 | cbDelayMostRecentPing.Checked = mainForm.settings.delayMostRecentPing; 28 | cbWarnGraphNotLive.Checked = mainForm.settings.warnGraphNotLive; 29 | nudPingResponsesToCache.Value = mainForm.settings.cacheSize; 30 | cbFastRefreshScrollingGraphs.Checked = mainForm.settings.fastRefreshScrollingGraphs; 31 | nudGraphScrollMultiplier.Value = mainForm.settings.graphScrollMultiplier; 32 | cbShowDateInCorner.Checked = mainForm.settings.showDateOnGraphTimeline; 33 | nudPingTimeoutRedLineHeight.Value = mainForm.settings.maxHeightOfPingTimeoutLine; 34 | txtCustomTimeString.Text = mainForm.settings.customTimeStr; 35 | nudTopMargin.Value = mainForm.settings.osWindowTopMargin; 36 | nudLeftMargin.Value = mainForm.settings.osWindowLeftMargin; 37 | nudRightMargin.Value = mainForm.settings.osWindowRightMargin; 38 | nudBottomMargin.Value = mainForm.settings.osWindowBottomMargin; 39 | } 40 | 41 | private void cbLogToFile_CheckedChanged(object sender, EventArgs e) 42 | { 43 | mainForm.settings.logTextOutputToFile = cbLogToFile.Checked; 44 | mainForm.settings.Save(); 45 | } 46 | 47 | private void cbDelayMostRecentPing_CheckedChanged(object sender, EventArgs e) 48 | { 49 | mainForm.settings.delayMostRecentPing = cbDelayMostRecentPing.Checked; 50 | mainForm.settings.Save(); 51 | } 52 | 53 | private void cbWarnGraphNotLive_CheckedChanged(object sender, EventArgs e) 54 | { 55 | mainForm.settings.warnGraphNotLive = cbWarnGraphNotLive.Checked; 56 | mainForm.settings.Save(); 57 | } 58 | 59 | private void nudPingResponsesToCache_ValueChanged(object sender, EventArgs e) 60 | { 61 | mainForm.settings.cacheSize = (int)nudPingResponsesToCache.Value; 62 | mainForm.settings.Save(); 63 | } 64 | 65 | private void cbFastRefreshScrollingGraphs_CheckedChanged(object sender, EventArgs e) 66 | { 67 | mainForm.settings.fastRefreshScrollingGraphs = cbFastRefreshScrollingGraphs.Checked; 68 | mainForm.settings.Save(); 69 | } 70 | 71 | private void nudGraphScrollMultiplier_ValueChanged(object sender, EventArgs e) 72 | { 73 | mainForm.settings.graphScrollMultiplier = (int)nudGraphScrollMultiplier.Value; 74 | mainForm.settings.Save(); 75 | } 76 | 77 | private void cbShowDateInCorner_CheckedChanged(object sender, EventArgs e) 78 | { 79 | mainForm.settings.showDateOnGraphTimeline = cbShowDateInCorner.Checked; 80 | mainForm.settings.Save(); 81 | } 82 | 83 | private void customTimeStringHelp_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 84 | { 85 | Process.Start("https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings"); 86 | } 87 | 88 | private void txtCustomTimeStringGraphs_TextChanged(object sender, EventArgs e) 89 | { 90 | mainForm.settings.customTimeStr = txtCustomTimeString.Text; 91 | mainForm.settings.Save(); 92 | } 93 | 94 | private void nudPingTimeoutRedLineHeight_ValueChanged(object sender, EventArgs e) 95 | { 96 | mainForm.settings.maxHeightOfPingTimeoutLine = (int)nudPingTimeoutRedLineHeight.Value; 97 | mainForm.settings.Save(); 98 | } 99 | 100 | private void nudTopMargin_ValueChanged(object sender, EventArgs e) 101 | { 102 | mainForm.settings.osWindowTopMargin = (int)nudTopMargin.Value; 103 | mainForm.settings.Save(); 104 | } 105 | 106 | private void nudLeftMargin_ValueChanged(object sender, EventArgs e) 107 | { 108 | mainForm.settings.osWindowLeftMargin = (int)nudLeftMargin.Value; 109 | mainForm.settings.Save(); 110 | } 111 | 112 | private void nudRightMargin_ValueChanged(object sender, EventArgs e) 113 | { 114 | mainForm.settings.osWindowRightMargin = (int)nudRightMargin.Value; 115 | mainForm.settings.Save(); 116 | } 117 | 118 | private void nudBottomMargin_ValueChanged(object sender, EventArgs e) 119 | { 120 | mainForm.settings.osWindowBottomMargin = (int)nudBottomMargin.Value; 121 | mainForm.settings.Save(); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /PingTest/PingTest/PingGraphControl.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace PingTracer 2 | { 3 | partial class PingGraphControl 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.SuspendLayout(); 32 | // 33 | // PingGraphControl 34 | // 35 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 36 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 37 | this.Cursor = System.Windows.Forms.Cursors.Default; 38 | this.DoubleBuffered = true; 39 | this.Name = "PingGraphControl"; 40 | this.Size = new System.Drawing.Size(610, 110); 41 | this.Paint += new System.Windows.Forms.PaintEventHandler(this.PingGraphControl_Paint); 42 | this.MouseLeave += new System.EventHandler(this.PingGraphControl_MouseLeave); 43 | this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.PingGraphControl_MouseMove); 44 | this.Resize += new System.EventHandler(this.PingGraphControl_Resize); 45 | this.ResumeLayout(false); 46 | 47 | } 48 | 49 | #endregion 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /PingTest/PingTest/PingGraphControl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Drawing; 5 | using System.Data; 6 | using System.Text; 7 | using System.Windows.Forms; 8 | using System.Threading; 9 | using System.Net.NetworkInformation; 10 | using System.Linq; 11 | using System.Net; 12 | 13 | namespace PingTracer 14 | { 15 | public partial class PingGraphControl : UserControl 16 | { 17 | #region Fields and Properties 18 | private Settings settings = new Settings(); 19 | 20 | public static Brush brushSuccess = new SolidBrush(Color.FromArgb(64, 128, 64)); 21 | public static Pen penSuccess = new Pen(brushSuccess, 1); 22 | public static Brush brushSuccessBad = new SolidBrush(Color.FromArgb(128, 128, 0)); 23 | public static Pen penSuccessBad = new Pen(brushSuccessBad, 1); 24 | public static Brush brushSuccessWorse = new SolidBrush(Color.FromArgb(255, 255, 0)); 25 | public static Pen penSuccessWorse = new Pen(brushSuccessWorse, 1); 26 | public static Brush brushFailure = new SolidBrush(Color.FromArgb(255, 0, 0)); 27 | public static Pen penFailure = new Pen(brushFailure, 1); 28 | public static Brush brushText = new SolidBrush(Color.FromArgb(255, 255, 255)); 29 | public static Color colorBackground = Color.FromArgb(0, 0, 0); 30 | public static Brush brushBackgroundBad = new SolidBrush(Color.FromArgb(35, 35, 0)); 31 | public static Brush brushBackgroundWorse = new SolidBrush(Color.FromArgb(40, 0, 0)); 32 | public static Brush brushBackgroundTimestamps = new SolidBrush(Color.FromArgb(0, 0, 0)); 33 | public static Brush brushTimestampsText = new SolidBrush(Color.FromArgb(200, 200, 200)); 34 | public static Pen penTimestampsMark = new Pen(Color.FromArgb(128, 128, 128), 1); 35 | public static Pen penTimestampsBorder = new Pen(Color.FromArgb(128, 128, 128), 1); 36 | public static Font textFont = new Font(FontFamily.GenericSansSerif, 8.25f); 37 | /// 38 | /// Text that is displayed in the upper left corner of the graph. 39 | /// 40 | public string DisplayName = ""; 41 | /// 42 | /// A buffer to store information for the most recent pings. 43 | /// 44 | private PingLog[] pings; 45 | /// 46 | /// Gets the number of pings currently cached within the graph control (slow). 47 | /// 48 | public int cachedPings 49 | { 50 | get 51 | { 52 | return pings.Count(x => x != null); 53 | } 54 | } 55 | /// 56 | /// The scroll X offset as a negative number, optionally offset by 1 if the "delay most recent ping" setting is set, in order to delay rendering of the most recent ping. 57 | /// 58 | private int countOffset 59 | { 60 | get 61 | { 62 | return (settings.delayMostRecentPing ? 0 : 1) - scrollXOffset; 63 | } 64 | } 65 | /// 66 | /// The start index to read from the buffer. 67 | /// 68 | private int StartIndex 69 | { 70 | get 71 | { 72 | long currentOffset = Interlocked.Read(ref _nextIndexOffset); 73 | if (currentOffset == -1) 74 | return 0; 75 | return (int)(((currentOffset + countOffset) - DisplayableCount) % pings.Length); 76 | } 77 | } 78 | private int BufferedCount 79 | { 80 | get 81 | { 82 | long currentOffset = Interlocked.Read(ref _nextIndexOffset); 83 | if (currentOffset == -1) 84 | return 0; 85 | else 86 | { 87 | return (int)Math.Min(currentOffset + countOffset, pings.Length); 88 | } 89 | } 90 | } 91 | /// 92 | /// Gets the number of pings we have data for in the current graph viewport. (the number of pings you should interate over, relatiev to StartIndex, when painting) 93 | /// 94 | private int DisplayableCount 95 | { 96 | get 97 | { 98 | return Math.Min(BufferedCount, this.Width); 99 | } 100 | } 101 | /// 102 | /// Counter for tracking the next index in the circular buffer. 103 | /// 104 | private long _nextIndexOffset = -1; 105 | /// 106 | /// The amount of pixels the graph has been scrolled to the left. Scroll position is clamped between 0 and the buffer size. 107 | /// 108 | private int scrollXOffset = 0; 109 | /// 110 | /// Gets or sets the amount of pixels the graph has been scrolled to the left. Scroll position is clamped between 0 and the buffer size. 111 | /// 112 | public int ScrollXOffset 113 | { 114 | get 115 | { 116 | return scrollXOffset; 117 | } 118 | set 119 | { 120 | int v = value; 121 | if (v > pings.Length - this.Width) 122 | v = pings.Length - this.Width; 123 | if (v < 0) 124 | v = 0; 125 | scrollXOffset = v; 126 | if (scrollXOffset == 0) 127 | setLiveAtTime = Environment.TickCount; 128 | } 129 | } 130 | /// 131 | /// Remembers the TickCount (in milliseconds) when the graph was scrolled to the live position, so we can show a message for a short time. 132 | /// 133 | private int setLiveAtTime = 0; 134 | #endregion 135 | public PingGraphControl(Settings settings, IPAddress ipAddress, string hostName, bool reverseDnsLookup) 136 | { 137 | this.settings = settings; 138 | pings = new PingLog[settings.cacheSize]; 139 | this.DisplayName = ipAddress.ToString(); 140 | if (string.IsNullOrWhiteSpace(hostName)) 141 | { 142 | if (reverseDnsLookup) 143 | ThreadPool.QueueUserWorkItem(LookupHostname, ipAddress); 144 | } 145 | else 146 | ConsumeHostName(hostName); 147 | InitializeComponent(); 148 | } 149 | 150 | private void LookupHostname(object arg) 151 | { 152 | ConsumeHostName(GetIpHostname((IPAddress)arg)); 153 | } 154 | 155 | private string GetIpHostname(IPAddress ip) 156 | { 157 | try 158 | { 159 | return Dns.GetHostEntry(ip).HostName; 160 | } 161 | catch (Exception) 162 | { 163 | } 164 | return string.Empty; 165 | } 166 | 167 | private void ConsumeHostName(string hostName) 168 | { 169 | if (string.IsNullOrWhiteSpace(hostName)) 170 | return; 171 | this.DisplayName = hostName + " [" + this.DisplayName + "]"; 172 | } 173 | 174 | public void AddPingLog(PingLog pingLog) 175 | { 176 | long newOffset = Interlocked.Increment(ref _nextIndexOffset); 177 | pings[newOffset % pings.Length] = pingLog; 178 | this.Invalidate(); 179 | } 180 | public void AddPingLogToSpecificOffset(long offset, PingLog pingLog) 181 | { 182 | pings[offset % pings.Length] = pingLog; 183 | this.Invalidate(); 184 | } 185 | /// 186 | /// I'm not sure this function is safe to call. 187 | /// 188 | /// 189 | public void ClearSpecificOffset(long offset) 190 | { 191 | pings[offset % pings.Length] = null; 192 | Interlocked.Exchange(ref _nextIndexOffset, offset); 193 | this.Invalidate(); 194 | } 195 | public long ClearNextOffset() 196 | { 197 | long newOffset = Interlocked.Increment(ref _nextIndexOffset); 198 | pings[newOffset % pings.Length] = null; 199 | this.Invalidate(); 200 | return newOffset; 201 | } 202 | public void ClearAll() 203 | { 204 | pings = new PingLog[pings.Length]; 205 | Interlocked.Exchange(ref _nextIndexOffset, -1); 206 | this.Invalidate(); 207 | } 208 | 209 | int timestampsHeight = 13; 210 | public int TimestampsHeight 211 | { 212 | get 213 | { 214 | return timestampsHeight; 215 | } 216 | } 217 | private bool isInvalidatedSync = false; 218 | /// 219 | /// True if the InvalidateSync method has been called and the Paint operation has not yet been performed. 220 | /// 221 | public bool IsInvalidatedSync 222 | { 223 | get 224 | { 225 | return isInvalidatedSync; 226 | } 227 | } 228 | int min = 0, avg = 0, max = 0, last = 0, height = short.MaxValue; 229 | int mouseX = -1; 230 | int mouseY = -1; 231 | /// 232 | /// This number defines the top of the graph. Any response time greater than or equal to this MUST yield a full line on the graph. Any response time less than this MAY yield a less-than-full line on the graph. 233 | /// 234 | int upperLimitDraw = 0; 235 | /// 236 | /// This number defines the bottom of the graph. A ping response time must be greater than this number of milliseconds in order to appear on the graph. For response times of 0ms to appear on the graph, this number must be negative. 237 | /// 238 | int lowerLimitDraw = 0; 239 | /// 240 | /// Gets the number of milliseconds of time covered by the Y axis. 241 | /// 242 | int drawHeight => upperLimitDraw - lowerLimitDraw; 243 | double vScale = 0; 244 | public bool AlwaysShowServerNames = false; 245 | public int Threshold_Bad = 100; 246 | public int Threshold_Worse = 100; 247 | public int upperLimit = 0; 248 | public int lowerLimit = 0; 249 | public bool ShowLastPing = false; 250 | public bool ShowAverage = false; 251 | public bool ShowJitter = false; 252 | public bool ShowMinMax = false; 253 | public bool ShowPacketLoss = false; 254 | public bool ShowTimestamps = true; 255 | public bool DrawLimitText = false; 256 | public GraphScalingMethod ScalingMethod = GraphScalingMethod.Classic; 257 | 258 | private void PingGraphControl_Paint(object sender, PaintEventArgs e) 259 | { 260 | isInvalidatedSync = false; 261 | e.Graphics.Clear(colorBackground); 262 | 263 | bool showTimestampsThisTime = ShowTimestamps; 264 | height = Math.Min(this.Height - (showTimestampsThisTime ? timestampsHeight : 0), short.MaxValue); 265 | 266 | int start = StartIndex; 267 | if (start == -1) 268 | return; 269 | 270 | int count = DisplayableCount; 271 | 272 | max = int.MinValue; 273 | min = int.MaxValue; 274 | int sum = 0; 275 | int successCount = 0; 276 | 277 | // Loop through pings to calculate min, max, and average values 278 | for (int i = 0; i < count; i++) 279 | { 280 | int idx = (start + i) % pings.Length; 281 | PingLog p = pings[idx]; 282 | if (p != null && p.result == IPStatus.Success) 283 | { 284 | successCount++; 285 | last = p.pingTime; 286 | sum += last; 287 | max = Math.Max(max, last); 288 | min = Math.Min(min, last); 289 | } 290 | } 291 | 292 | decimal packetLoss = 0; 293 | if (count > 0) 294 | packetLoss = ((count - successCount) / (decimal)count) * 100; 295 | 296 | avg = sum == 0 || successCount == 0 ? 0 : (int)((double)sum / (double)successCount); 297 | if (min == int.MaxValue) 298 | min = 0; 299 | if (max == int.MinValue) 300 | max = 0; 301 | 302 | // Decide how to scale the Y-axis based on configuration and available data. 303 | switch (ScalingMethod) 304 | { 305 | case GraphScalingMethod.Classic: 306 | { 307 | // Zoom out if necessary to fit the response time data, otherwise prefer to draw at an exact ratio of 1px : 1ms. 308 | lowerLimitDraw = lowerLimit; 309 | upperLimitDraw = lowerLimit + height; 310 | if (max > upperLimitDraw) 311 | { 312 | // max value is too high to draw at 1px : 1ms ratio, so zoom out to fit 313 | upperLimitDraw = (int)(max * 1.1); 314 | } 315 | if (upperLimitDraw > upperLimit) 316 | upperLimitDraw = upperLimit; 317 | } 318 | break; 319 | case GraphScalingMethod.Zoom: 320 | { 321 | // Zoom in or out to fit the response time data, but adhere to user-specified limits. 322 | lowerLimitDraw = Math.Max(min - 1, lowerLimit); 323 | upperLimitDraw = Math.Min(max + 1, upperLimit); 324 | } 325 | break; 326 | case GraphScalingMethod.Zoom_Unlimited: 327 | { 328 | // Zoom in or out to fit the response time data. 329 | lowerLimitDraw = min - 1; 330 | upperLimitDraw = max + 1; 331 | } 332 | break; 333 | case GraphScalingMethod.Fixed: 334 | { 335 | // Adhere to user-specified limits, do not dynamically zoom based on the response time data. 336 | lowerLimitDraw = Math.Max(lowerLimit, 0); 337 | upperLimitDraw = Math.Max(upperLimit, 1); 338 | } 339 | break; 340 | } 341 | if (upperLimitDraw <= lowerLimitDraw) 342 | upperLimitDraw = lowerLimitDraw + 1; // Prevent vScale becoming "Infinity" which yields exception. 343 | 344 | vScale = (double)height / (upperLimitDraw - lowerLimitDraw); 345 | 346 | int scaledBadLine = (int)((Threshold_Bad - lowerLimitDraw) * vScale); 347 | int scaledWorseLine = (int)((Threshold_Worse - lowerLimitDraw) * vScale); 348 | 349 | if (scaledWorseLine < height) 350 | e.Graphics.FillRectangle(brushBackgroundWorse, new Rectangle(0, 0, this.Width, height - scaledWorseLine)); 351 | if (scaledBadLine < height) 352 | e.Graphics.FillRectangle(brushBackgroundBad, new Rectangle(0, height - scaledWorseLine, this.Width, scaledWorseLine - scaledBadLine)); 353 | 354 | if (showTimestampsThisTime) 355 | e.Graphics.DrawLine(penTimestampsBorder, new Point(0, height), new Point(this.Width - 1, height)); 356 | // e.Graphics.FillRectangle(brushBackgroundTimestamps, new Rectangle(0, this.Height - timestampsHeight, this.Width, timestampsHeight)); 357 | 358 | Point pStart = new Point(this.Width - count, height - 1); 359 | Point pEnd = new Point(this.Width - count, height - 1); 360 | Point pTimestampMarkStart = new Point(this.Width - count, height + 1); 361 | Point pTimestampMarkEnd = new Point(this.Width - count, this.Height - 1); 362 | 363 | int lastStampedMinute = -1; 364 | string timelineOverlayString = ""; 365 | 366 | Pen linePen = penSuccess; 367 | Brush lineBrush = brushSuccess; 368 | for (int i = 0; i < count; i++) 369 | { 370 | try 371 | { 372 | int idx = (start + i) % pings.Length; 373 | PingLog p = pings[idx]; 374 | if (p == null) 375 | continue; 376 | 377 | bool drawMyLine = false; 378 | if (p.result == IPStatus.Success) 379 | { 380 | if (p.pingTime > lowerLimitDraw) 381 | { 382 | drawMyLine = true; 383 | if (p.pingTime < Threshold_Bad) 384 | { 385 | lineBrush = brushSuccess; 386 | linePen = penSuccess; 387 | } 388 | else if (p.pingTime < Threshold_Worse) 389 | { 390 | lineBrush = brushSuccessBad; 391 | linePen = penSuccessBad; 392 | } 393 | else 394 | { 395 | lineBrush = brushSuccessWorse; 396 | linePen = penSuccessWorse; 397 | } 398 | 399 | pStart.Y = (int)(height - ((p.pingTime - lowerLimitDraw) * vScale)); 400 | } 401 | } 402 | else 403 | { 404 | drawMyLine = true; 405 | lineBrush = brushFailure; 406 | linePen = penFailure; 407 | if (height > settings.maxHeightOfPingTimeoutLine) 408 | pStart.Y = height - settings.maxHeightOfPingTimeoutLine; 409 | else 410 | pStart.Y = 0; 411 | } 412 | 413 | if (drawMyLine) 414 | { 415 | if (pStart == pEnd) 416 | e.Graphics.FillRectangle(lineBrush, pStart.X, pStart.Y, 1, 1); 417 | else 418 | e.Graphics.DrawLine(linePen, pStart, pEnd); 419 | } 420 | 421 | // Timestamp drawing logic 422 | if (showTimestampsThisTime) 423 | { 424 | if (lastStampedMinute == -1 || p.startTime.Minute != lastStampedMinute) 425 | { 426 | if (settings.showDateOnGraphTimeline && lastStampedMinute == -1) 427 | timelineOverlayString += p.startTime.ToString("yyyy-M-d "); 428 | if (p.startTime.Second < 2) // Only draw the line if this is close to the actual moment the minute struck. 429 | e.Graphics.DrawLine(penTimestampsMark, pTimestampMarkStart, pTimestampMarkEnd); 430 | 431 | string stamp = p.startTime.ToString("t"); 432 | SizeF strSize = e.Graphics.MeasureString(stamp, textFont); 433 | e.Graphics.FillRectangle(brushBackgroundTimestamps, new Rectangle(pTimestampMarkStart.X + 1, pTimestampMarkStart.Y, (int)strSize.Width - 1, (int)timestampsHeight - 1)); 434 | e.Graphics.DrawString(stamp, textFont, brushTimestampsText, pTimestampMarkStart.X, pTimestampMarkStart.Y - 1); 435 | 436 | lastStampedMinute = p.startTime.Minute; 437 | } 438 | } 439 | } 440 | finally 441 | { 442 | pStart.X++; 443 | pEnd.X++; 444 | pTimestampMarkStart.X++; 445 | pTimestampMarkEnd.X++; 446 | } 447 | } 448 | 449 | if (count <= 0 && Interlocked.Read(ref _nextIndexOffset) != -1) 450 | timelineOverlayString += "The graph begins " + -count + " lines to the right. "; 451 | else if (scrollXOffset == 0 && Math.Abs(setLiveAtTime - Environment.TickCount) < 1000) 452 | timelineOverlayString += "The graph is now displaying live data. "; 453 | 454 | if (timelineOverlayString.Length > 0) 455 | { 456 | SizeF strSize = e.Graphics.MeasureString(timelineOverlayString, textFont); 457 | e.Graphics.FillRectangle(brushBackgroundTimestamps, new Rectangle(0, pTimestampMarkStart.Y, (int)strSize.Width - 1, (int)timestampsHeight - 1)); 458 | e.Graphics.DrawString(timelineOverlayString, textFont, brushTimestampsText, 0, pTimestampMarkStart.Y - 1); 459 | } 460 | 461 | if (DrawLimitText) 462 | { 463 | // Add lower and upper limit labels on the right side 464 | string lowerLimitLabel = lowerLimitDraw.ToString(); 465 | string upperLimitLabel = upperLimitDraw.ToString(); 466 | SizeF lowerLimitSize = e.Graphics.MeasureString(lowerLimitLabel, textFont); 467 | SizeF upperLimitSize = e.Graphics.MeasureString(upperLimitLabel, textFont); 468 | e.Graphics.DrawString(lowerLimitLabel, textFont, brushText, this.Width - lowerLimitSize.Width, height - lowerLimitSize.Height); 469 | e.Graphics.DrawString(upperLimitLabel, textFont, brushText, this.Width - upperLimitSize.Width, 0); 470 | } 471 | 472 | string statusStr = ""; 473 | 474 | if (scrollXOffset != 0 && settings.warnGraphNotLive) 475 | statusStr += "NOT LIVE -" + scrollXOffset + ": "; 476 | if (ShowPacketLoss) 477 | statusStr += packetLoss.ToString("0.00") + "% "; 478 | 479 | List intVals = new List(); 480 | if (ShowLastPing) 481 | intVals.Add(last); 482 | if (ShowAverage) 483 | intVals.Add(avg); 484 | if (ShowJitter) 485 | intVals.Add(Math.Abs(max - min)); 486 | if (ShowMinMax) 487 | { 488 | intVals.Add(min); 489 | intVals.Add(max); 490 | } 491 | 492 | if (intVals.Count > 0) 493 | statusStr += "[" + string.Join(",", intVals) + "] "; 494 | 495 | string MouseHintText = GetMouseoverHintText(); 496 | if (!string.IsNullOrEmpty(MouseHintText)) 497 | { 498 | if (!string.IsNullOrEmpty(DisplayName)) 499 | statusStr += DisplayName + " "; 500 | statusStr += MouseHintText + " "; 501 | } 502 | else if (AlwaysShowServerNames && !string.IsNullOrEmpty(DisplayName)) 503 | statusStr += DisplayName + " "; 504 | 505 | e.Graphics.DrawString(statusStr, textFont, brushText, 1, 1); 506 | //SizeF measuredSize = e.Graphics.MeasureString(statusStr, textFont); 507 | //e.Graphics.DrawString(statusStr, textFont, brushText, (this.Width - measuredSize.Width) - 15, 1); 508 | } 509 | 510 | private void PingGraphControl_Resize(object sender, EventArgs e) 511 | { 512 | this.Invalidate(); 513 | } 514 | 515 | private string GetTimestamp(DateTime time) 516 | { 517 | if (!string.IsNullOrWhiteSpace(settings.customTimeStr)) 518 | { 519 | try 520 | { 521 | return time.ToString(settings.customTimeStr); 522 | } 523 | catch { } 524 | } 525 | return time.ToString("h:mm:ss tt"); 526 | } 527 | /// 528 | /// Invalidates the control, causing a redraw, and marking the IsInvalidatedSync flag = true 529 | /// 530 | public void InvalidateSync() 531 | { 532 | isInvalidatedSync = true; 533 | base.Invalidate(); 534 | } 535 | #region Mouseover Hint 536 | private void PingGraphControl_MouseMove(object sender, MouseEventArgs e) 537 | { 538 | mouseX = e.X; 539 | mouseY = e.Y; 540 | this.Invalidate(); 541 | } 542 | 543 | private void PingGraphControl_MouseLeave(object sender, EventArgs e) 544 | { 545 | mouseX = mouseY = -1; 546 | this.Invalidate(); 547 | } 548 | public string GetMouseoverHintText() 549 | { 550 | if (mouseX < 0 || mouseY < 0) 551 | return ""; 552 | int mouseMs = GetScaledHeightValue(height - mouseY); 553 | try 554 | { 555 | int offset = mouseX - this.Width; 556 | int start = StartIndex + DisplayableCount; 557 | int i = (start + offset) % pings.Length; 558 | if (offset <= -pings.Length) 559 | return "Out of bounds, Mouse ms: " + mouseMs; 560 | else if (i < 0) 561 | return "No Data Yet, Mouse ms: " + mouseMs; 562 | PingLog pingLog = pings[i]; 563 | if (pingLog == null) 564 | return "Waiting for response, Mouse ms: " + mouseMs; 565 | if (pingLog.result != IPStatus.Success) 566 | return GetTimestamp(pingLog.startTime) + ": " + pingLog.result.ToString() + ", Mouse ms: " + mouseMs; 567 | return GetTimestamp(pingLog.startTime) + ": " + pingLog.pingTime + " ms, Mouse ms: " + mouseMs; 568 | } 569 | catch (Exception) 570 | { 571 | return "Error, Mouse ms: " + mouseMs; 572 | } 573 | } 574 | private int GetScaledHeightValue(int v) 575 | { 576 | if (vScale == 0) 577 | return 0; 578 | double posRelative = (double)v / (double)height; 579 | int posMs = (int)(posRelative * drawHeight); 580 | return posMs + lowerLimitDraw; 581 | } 582 | #endregion 583 | } 584 | } 585 | -------------------------------------------------------------------------------- /PingTest/PingTest/PingGraphControl.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /PingTest/PingTest/PingLog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Net.NetworkInformation; 5 | 6 | namespace PingTracer 7 | { 8 | public class PingLog 9 | { 10 | public DateTime startTime; 11 | public short pingTime; 12 | public IPStatus result; 13 | public PingLog(DateTime startTime, short pingTime, IPStatus result) 14 | { 15 | this.startTime = startTime; 16 | this.pingTime = pingTime; 17 | this.result = result; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /PingTest/PingTest/PingTracer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {854FD525-4946-412E-9B3F-69412F505C30} 8 | WinExe 9 | Properties 10 | PingTracer 11 | PingTracer 12 | v4.6.2 13 | 512 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | AnyCPU 27 | true 28 | full 29 | false 30 | bin\Debug\ 31 | DEBUG;TRACE 32 | prompt 33 | 4 34 | false 35 | 36 | 37 | AnyCPU 38 | pdbonly 39 | true 40 | bin\Release\ 41 | TRACE 42 | prompt 43 | 4 44 | false 45 | 46 | 47 | pingtracer.ico 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | Form 64 | 65 | 66 | CommandLineArgsForm.cs 67 | 68 | 69 | 70 | 71 | Form 72 | 73 | 74 | Tracer.cs 75 | 76 | 77 | Form 78 | 79 | 80 | MainForm.cs 81 | 82 | 83 | Form 84 | 85 | 86 | OptionsForm.cs 87 | 88 | 89 | UserControl 90 | 91 | 92 | PingGraphControl.cs 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | Form 108 | 109 | 110 | TestForm.cs 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | CommandLineArgsForm.cs 124 | Designer 125 | 126 | 127 | Tracer.cs 128 | 129 | 130 | MainForm.cs 131 | 132 | 133 | OptionsForm.cs 134 | 135 | 136 | PingGraphControl.cs 137 | 138 | 139 | ResXFileCodeGenerator 140 | Resources.Designer.cs 141 | Designer 142 | 143 | 144 | True 145 | Resources.resx 146 | True 147 | 148 | 149 | TestForm.cs 150 | 151 | 152 | SettingsSingleFileGenerator 153 | Settings.Designer.cs 154 | 155 | 156 | True 157 | Settings.settings 158 | True 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 177 | -------------------------------------------------------------------------------- /PingTest/PingTest/PingTracer.csproj.vspscc: -------------------------------------------------------------------------------- 1 | "" 2 | { 3 | "FILE_VERSION" = "9237" 4 | "ENLISTMENT_CHOICE" = "NEVER" 5 | "PROJECT_FILE_RELATIVE_PATH" = "" 6 | "NUMBER_OF_EXCLUDED_FILES" = "0" 7 | "ORIGINAL_PROJECT_FILE_PATH" = "" 8 | "NUMBER_OF_NESTED_PROJECTS" = "0" 9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" 10 | } 11 | -------------------------------------------------------------------------------- /PingTest/PingTest/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows.Forms; 4 | 5 | namespace PingTracer 6 | { 7 | static class Program 8 | { 9 | /// 10 | /// The main entry point for the application. 11 | /// 12 | [STAThread] 13 | static void Main(string[] args) 14 | { 15 | Application.EnableVisualStyles(); 16 | Application.SetCompatibleTextRenderingDefault(false); 17 | Application.Run(new MainForm(args)); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /PingTest/PingTest/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("PingTracer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PingTracer")] 13 | [assembly: AssemblyCopyright("Copyright © 2024")] 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("9355c82d-5ce5-4cfd-a921-b1343b4c6850")] 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.19.1.0")] 36 | [assembly: AssemblyFileVersion("1.19.1.0")] 37 | -------------------------------------------------------------------------------- /PingTest/PingTest/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace PingTracer.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PingTracer.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /PingTest/PingTest/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /PingTest/PingTest/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace PingTracer.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /PingTest/PingTest/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PingTest/PingTest/ScreenCapture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Drawing; 4 | using System.Drawing.Imaging; 5 | 6 | namespace PingTracer 7 | { 8 | /// 9 | /// Provides functions to capture the entire screen, or a particular window, and save it to a file. 10 | /// 11 | /// FROM http://www.developerfusion.com/code/4630/capture-a-screen-shot/ 12 | /// 13 | public class ScreenCapture 14 | { 15 | /// 16 | /// Creates an Image object containing a screen shot of the entire desktop 17 | /// 18 | /// 19 | public Image CaptureScreen() 20 | { 21 | return CaptureWindow(User32.GetDesktopWindow()); 22 | } 23 | 24 | /// 25 | /// Creates an Image object containing a screen shot of a specific window 26 | /// 27 | /// The handle to the window. (In windows forms, this is obtained by the Handle property) 28 | /// 29 | public Image CaptureWindow(IntPtr handle) 30 | { 31 | // get te hDC of the target window 32 | IntPtr hdcSrc = User32.GetWindowDC(handle); 33 | // get the size 34 | User32.RECT windowRect = new User32.RECT(); 35 | User32.GetWindowRect(handle, ref windowRect); 36 | int width = windowRect.right - windowRect.left; 37 | int height = windowRect.bottom - windowRect.top; 38 | // create a device context we can copy to 39 | IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc); 40 | // create a bitmap we can copy it to, 41 | // using GetDeviceCaps to get the width/height 42 | IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height); 43 | // select the bitmap object 44 | IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap); 45 | // bitblt over 46 | GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.SRCCOPY); 47 | // restore selection 48 | GDI32.SelectObject(hdcDest, hOld); 49 | // clean up 50 | GDI32.DeleteDC(hdcDest); 51 | User32.ReleaseDC(handle, hdcSrc); 52 | 53 | // get a .NET image object for it 54 | Image img = Image.FromHbitmap(hBitmap); 55 | // free up the Bitmap object 56 | GDI32.DeleteObject(hBitmap); 57 | 58 | return img; 59 | } 60 | 61 | /// 62 | /// Captures a screen shot of a specific window, and saves it to a file 63 | /// 64 | /// 65 | /// 66 | /// 67 | public void CaptureWindowToFile(IntPtr handle, string filename, ImageFormat format) 68 | { 69 | Image img = CaptureWindow(handle); 70 | img.Save(filename, format); 71 | } 72 | 73 | /// 74 | /// Captures a screen shot of the entire desktop, and saves it to a file 75 | /// 76 | /// 77 | /// 78 | public void CaptureScreenToFile(string filename, ImageFormat format) 79 | { 80 | Image img = CaptureScreen(); 81 | img.Save(filename, format); 82 | } 83 | 84 | /// 85 | /// Helper class containing Gdi32 API functions 86 | /// 87 | private class GDI32 88 | { 89 | 90 | public const int SRCCOPY = 0x00CC0020; // BitBlt dwRop parameter 91 | 92 | [DllImport("gdi32.dll")] 93 | public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, 94 | int nWidth, int nHeight, IntPtr hObjectSource, 95 | int nXSrc, int nYSrc, int dwRop); 96 | [DllImport("gdi32.dll")] 97 | public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth, 98 | int nHeight); 99 | [DllImport("gdi32.dll")] 100 | public static extern IntPtr CreateCompatibleDC(IntPtr hDC); 101 | [DllImport("gdi32.dll")] 102 | public static extern bool DeleteDC(IntPtr hDC); 103 | [DllImport("gdi32.dll")] 104 | public static extern bool DeleteObject(IntPtr hObject); 105 | [DllImport("gdi32.dll")] 106 | public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject); 107 | } 108 | 109 | /// 110 | /// Helper class containing User32 API functions 111 | /// 112 | private class User32 113 | { 114 | [StructLayout(LayoutKind.Sequential)] 115 | public struct RECT 116 | { 117 | public int left; 118 | public int top; 119 | public int right; 120 | public int bottom; 121 | } 122 | 123 | [DllImport("user32.dll")] 124 | public static extern IntPtr GetDesktopWindow(); 125 | [DllImport("user32.dll")] 126 | public static extern IntPtr GetWindowDC(IntPtr hWnd); 127 | [DllImport("user32.dll")] 128 | public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC); 129 | [DllImport("user32.dll")] 130 | public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect); 131 | 132 | } 133 | 134 | 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /PingTest/PingTest/SerializableObjectBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading; 9 | 10 | namespace PingTracer 11 | { 12 | /// 13 | /// Any class inheriting from this may be loaded and saved from file easily. Uses . 14 | /// Note that strings stored via this class will have '\r' characters removed by the xml writer. 15 | /// 16 | public abstract class SerializableObjectBase 17 | { 18 | private static ConcurrentDictionary fileLocks = new ConcurrentDictionary(); 19 | private static object MakeLockKey(string filePath) 20 | { 21 | return filePath; 22 | } 23 | /// 24 | /// Saves this instance to file. Returns true if successful. 25 | /// 26 | /// Optional file path. If null, the default file path is used. 27 | /// 28 | public virtual bool Save(string filePath = null) 29 | { 30 | int tries = 0; 31 | while (tries++ < 5) 32 | try 33 | { 34 | if (filePath == null) 35 | filePath = GetDefaultFilePath(); 36 | object lockObj = fileLocks.GetOrAdd(filePath.ToLower(), MakeLockKey); 37 | lock (lockObj) 38 | { 39 | FileInfo fi = new FileInfo(filePath); 40 | if (!fi.Exists) 41 | { 42 | if (!fi.Directory.Exists) 43 | Directory.CreateDirectory(fi.Directory.FullName); 44 | } 45 | using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read)) 46 | SerializeObject(this, fs); 47 | } 48 | return true; 49 | } 50 | catch (ThreadAbortException) { throw; } 51 | catch (Exception ex) 52 | { 53 | if (tries >= 5) 54 | { 55 | } 56 | else 57 | Thread.Sleep(1); 58 | } 59 | return false; 60 | } 61 | /// 62 | /// Loads this instance from file. Returns true if successful. May throw an exception if the file format is invalid. 63 | /// 64 | /// Optional file path. If null, the default file path is used. 65 | /// 66 | public virtual bool Load(string filePath = null) 67 | { 68 | int tries = 0; 69 | while (tries++ < 5) 70 | try 71 | { 72 | Type thistype = this.GetType(); 73 | if (filePath == null) 74 | filePath = GetDefaultFilePath(); 75 | object lockObj = fileLocks.GetOrAdd(filePath.ToLower(), MakeLockKey); 76 | lock (lockObj) 77 | { 78 | if (!File.Exists(filePath)) 79 | return false; 80 | object obj; 81 | using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 82 | obj = DeserializeObject(fs); 83 | if (obj != null) 84 | { 85 | foreach (FieldInfo sourceField in obj.GetType().GetFields()) 86 | { 87 | try 88 | { 89 | FieldInfo targetField = thistype.GetField(sourceField.Name); 90 | if (targetField != null && targetField.MemberType == sourceField.MemberType) 91 | targetField.SetValue(this, sourceField.GetValue(obj)); 92 | } 93 | catch (ThreadAbortException) { throw; } 94 | catch (Exception) { } 95 | } 96 | if (obj.GetType().GetCustomAttributes(typeof(SerializeProperties), false).FirstOrDefault() != null) 97 | { 98 | foreach (PropertyInfo sourceProperty in obj.GetType().GetProperties()) 99 | { 100 | try 101 | { 102 | PropertyInfo targetProperty = thistype.GetProperty(sourceProperty.Name); 103 | if (targetProperty != null && targetProperty.MemberType == sourceProperty.MemberType) 104 | targetProperty.SetValue(this, sourceProperty.GetValue(obj)); 105 | } 106 | catch (ThreadAbortException) { throw; } 107 | catch (Exception) { } 108 | } 109 | } 110 | } 111 | } 112 | return true; 113 | } 114 | catch (ThreadAbortException) { throw; } 115 | catch (Exception ex) 116 | { 117 | if (tries >= 5) 118 | { 119 | } 120 | else 121 | Thread.Sleep(1); 122 | } 123 | return false; 124 | } 125 | /// 126 | /// (Thread-)Safely checks if the settings file exists, and returns true if it does. 127 | /// 128 | /// Optional file path. If null, the default file path is used. 129 | public virtual bool FileExists(string filePath = null) 130 | { 131 | if (filePath == null) 132 | filePath = GetDefaultFilePath(); 133 | object lockObj = fileLocks.GetOrAdd(filePath.ToLower(), MakeLockKey); 134 | lock (lockObj) 135 | return File.Exists(filePath); 136 | } 137 | /// 138 | /// (Thread-)Safely checks if the settings file exists, and if not, saves the current instance. Returns true if a file was saved. 139 | /// 140 | /// Optional file path. If null, the default file path is used. 141 | public virtual bool SaveIfNoExist(string filePath = null) 142 | { 143 | if (filePath == null) 144 | filePath = GetDefaultFilePath(); 145 | object lockObj = fileLocks.GetOrAdd(filePath.ToLower(), MakeLockKey); 146 | lock (lockObj) 147 | { 148 | if (!File.Exists(filePath)) 149 | return Save(filePath); 150 | } 151 | return false; 152 | } 153 | 154 | /// 155 | /// Gets the default settings file path, which is typically a relative path (to the current working directory). 156 | /// 157 | /// 158 | public virtual string GetDefaultFilePath() 159 | { 160 | return this.GetType().Name + ".cfg"; 161 | } 162 | 163 | /// 164 | /// Writes the object to a FileStream. The default implementation in SerializableObjectBase uses XML. 165 | /// 166 | protected virtual void SerializeObject(object obj, FileStream stream) 167 | { 168 | SerializeObjectXml(obj, stream); 169 | } 170 | /// 171 | /// Reads the object from a FileStream. The default implementation in SerializableObjectBase uses XML. 172 | /// Must return a type inheriting from SerializableObjectBase. 173 | /// 174 | protected virtual SerializableObjectBase DeserializeObject(FileStream stream) 175 | { 176 | return DeserializeObjectXml(stream); 177 | } 178 | /// 179 | /// Writes the object to a FileStream using XML. 180 | /// 181 | protected void SerializeObjectXml(object obj, FileStream stream) 182 | { 183 | System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(this.GetType()); 184 | x.Serialize(stream, this); 185 | } 186 | /// 187 | /// Reads the object from a FileStream using XML. 188 | /// Must return a type inheriting from SerializableObjectBase. 189 | /// 190 | protected SerializableObjectBase DeserializeObjectXml(FileStream stream) 191 | { 192 | System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(this.GetType()); 193 | object obj = x.Deserialize(stream); 194 | return (SerializableObjectBase)obj; 195 | } 196 | } 197 | /// 198 | /// Annotate the serializable object with this in order to load serialized properties (otherwise only fields are loaded from file). 199 | /// 200 | public class SerializeProperties : Attribute 201 | { 202 | public SerializeProperties() { } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /PingTest/PingTest/Settings.cs: -------------------------------------------------------------------------------- 1 | using PingTracer.Tracer; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Xml; 9 | using System.Xml.Serialization; 10 | 11 | namespace PingTracer 12 | { 13 | public class Settings : SerializableObjectBase 14 | { 15 | public bool logTextOutputToFile = true; 16 | public bool delayMostRecentPing = true; 17 | public bool warnGraphNotLive = true; 18 | public List hostHistory = new List(); 19 | public int cacheSize = 360000; 20 | public bool fastRefreshScrollingGraphs = true; 21 | public int graphScrollMultiplier = 50; 22 | public bool showDateOnGraphTimeline = true; 23 | public string customTimeStr; 24 | public WindowParams lastWindowParams = null; 25 | public int osWindowTopMargin = 0; 26 | public int osWindowLeftMargin = 7; 27 | public int osWindowRightMargin = 7; 28 | public int osWindowBottomMargin = 7; 29 | public int maxHeightOfPingTimeoutLine = 10000; 30 | 31 | public bool Save() 32 | { 33 | lock (hostHistory) 34 | { 35 | return Save(settingsFilePath); 36 | } 37 | } 38 | public bool Load() 39 | { 40 | return Load(settingsFilePath); 41 | } 42 | /// 43 | /// Gets the absolute path to the settings file. 44 | /// 45 | private static string settingsFilePath 46 | { 47 | get 48 | { 49 | return settingsFolderPath + "settings.cfg"; 50 | } 51 | } 52 | /// 53 | /// Gets the absolute path to the settings folder. 54 | /// 55 | private static string settingsFolderPath 56 | { 57 | get 58 | { 59 | string path = Environment.CurrentDirectory.TrimEnd('/', '\\') + '/'; 60 | if (!File.Exists(path + "settings.cfg")) 61 | { 62 | path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData).TrimEnd('/', '\\') + "/PingTracer/"; 63 | } 64 | return path; 65 | } 66 | } 67 | /// 68 | /// In Explorer, opens the folder containing the settings file. 69 | /// 70 | public void OpenSettingsFolder() 71 | { 72 | Process.Start(settingsFolderPath); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /PingTest/PingTest/StartupOptions.cs: -------------------------------------------------------------------------------- 1 | using PingTracer.Tracer; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Windows.Forms; 7 | 8 | namespace PingTracer 9 | { 10 | /// 11 | /// Specifies how the application will behave upon startup. 12 | /// 13 | public partial class StartupOptions 14 | { 15 | /// 16 | /// If not null, pings will start to the stored configuration with this display name or host field value upon application startup. 17 | /// 18 | public string StartupHostName = null; 19 | /// 20 | /// If true, the StartupHostName argument will prefer to match a stored configuration that is configured to prefer IPv6. If false, then IPv4 will be preferred. 21 | /// 22 | public BoolOverride PreferIPv6 = BoolOverride.Inherit; 23 | /// 24 | /// If not null, the window will be positioned here upon application startup. Width or Height values less than 1 will be ignored. 25 | /// 26 | public WindowParams WindowLocation = null; 27 | /// 28 | /// If true, pings will be started automatically upon application startup. 29 | /// 30 | public bool StartPinging = false; 31 | /// 32 | /// If true, the graphs will be maximized automatically upon application startup. 33 | /// 34 | public bool MaximizeGraphs = false; 35 | /// 36 | /// If true, the StartupHostName argument will prefer to match a stored configuration that is configured with the Trace Route option checked. 37 | /// 38 | public BoolOverride TraceRoute = BoolOverride.Inherit; 39 | 40 | /// 41 | /// Constructs an empty StartupOptions. 42 | /// 43 | public StartupOptions() { } 44 | /// 45 | /// Constructs a StartupOptions from an args string. 46 | /// 47 | /// 48 | public StartupOptions(string[] args) 49 | { 50 | HashSet flagKeysWithNoValue = new HashSet(new string[] { "-s", "-m", "-4", "-6", "-t0", "-t1" }); 51 | Dictionary flags = new Dictionary(); 52 | string key = null; 53 | for (int i = 0; i < args.Length; i++) 54 | { 55 | string arg = args[i]; 56 | if (key != null) 57 | { 58 | flags[key] = arg; 59 | key = null; 60 | } 61 | else if (flagKeysWithNoValue.Contains(arg)) 62 | flags[arg] = arg; 63 | else 64 | key = arg; 65 | } 66 | if (flags.TryGetValue("-h", out string startupHostName)) 67 | this.StartupHostName = startupHostName; 68 | if (flags.TryGetValue("-l", out string startupLocation)) 69 | { 70 | try 71 | { 72 | string[] parts = startupLocation.Split(' ', ',', '.'); 73 | int[] ints = parts.Select(int.Parse).ToArray(); 74 | if (ints.Length == 2) 75 | this.WindowLocation = new WindowParams(ints[0], ints[1], 0, 0); 76 | else if (ints.Length == 3) 77 | this.WindowLocation = new WindowParams(ints[0], ints[1], ints[2], 0); 78 | else if (ints.Length >= 4) 79 | this.WindowLocation = new WindowParams(ints[0], ints[1], ints[2], ints[3]); 80 | } 81 | catch (Exception ex) 82 | { 83 | MessageBox.Show("Unable to load window startup position: \"" + startupLocation + "\" because of error: " + ex.Message); 84 | } 85 | } 86 | if (flags.ContainsKey("-s")) 87 | this.StartPinging = true; 88 | if (flags.ContainsKey("-m")) 89 | this.MaximizeGraphs = true; 90 | if (flags.ContainsKey("-4")) 91 | this.PreferIPv6 = BoolOverride.False; 92 | if (flags.ContainsKey("-6")) 93 | this.PreferIPv6 = BoolOverride.True; 94 | if (flags.ContainsKey("-t1")) 95 | this.TraceRoute = BoolOverride.True; 96 | if (flags.ContainsKey("-t0")) 97 | this.TraceRoute = BoolOverride.False; 98 | } 99 | 100 | /// 101 | /// Returns the command line text that would produce this StartupOptions. 102 | /// 103 | /// 104 | public override string ToString() 105 | { 106 | StringBuilder sb = new StringBuilder(); 107 | string[] args = GetArgs(); 108 | for (int i = 0; i < args.Length; i++) 109 | { 110 | sb.Append(EscapeCommandLineArgument(args[i], args[i].Contains(' '))); 111 | if (i + 1 < args.Length) 112 | sb.Append(' '); 113 | } 114 | return sb.ToString(); 115 | } 116 | 117 | private string[] GetArgs() 118 | { 119 | List args = new List(); 120 | if (StartupHostName != null) 121 | { 122 | args.Add("-h"); 123 | args.Add(StartupHostName); 124 | } 125 | if (PreferIPv6 == BoolOverride.False) 126 | args.Add("-4"); 127 | if (PreferIPv6 == BoolOverride.True) 128 | args.Add("-6"); 129 | if (WindowLocation != null) 130 | { 131 | args.Add("-l"); 132 | args.Add(WindowLocation.X + "," + WindowLocation.Y + "," + WindowLocation.W + "," + WindowLocation.H); 133 | } 134 | if (StartPinging) 135 | args.Add("-s"); 136 | if (MaximizeGraphs) 137 | args.Add("-m"); 138 | if (TraceRoute == BoolOverride.True) 139 | args.Add("-t1"); 140 | if (TraceRoute == BoolOverride.False) 141 | args.Add("-t0"); 142 | return args.ToArray(); 143 | } 144 | 145 | /// 146 | /// Escapes backslashes and double-quotation marks by prepending backslashes. 147 | /// 148 | /// Unescaped string. 149 | /// If true, the return value will be wrapped in double quotes. 150 | /// A string suitable to be used as a command line argument. 151 | private static string EscapeCommandLineArgument(string str, bool wrapInDoubleQuotes = false) 152 | { 153 | string dqWrap = wrapInDoubleQuotes ? "\"" : ""; 154 | return dqWrap + str.Replace("\\", "\\\\").Replace("\"", "\\\"") + dqWrap; 155 | } 156 | } 157 | public enum BoolOverride 158 | { 159 | Inherit, 160 | False, 161 | True 162 | } 163 | } -------------------------------------------------------------------------------- /PingTest/PingTest/TestForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace PingTracer 2 | { 3 | partial class TestForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.panel_Graphs = new System.Windows.Forms.Panel(); 32 | this.txtOut = new System.Windows.Forms.TextBox(); 33 | this.panel_Graphs.SuspendLayout(); 34 | this.SuspendLayout(); 35 | // 36 | // panel_Graphs 37 | // 38 | this.panel_Graphs.Controls.Add(this.txtOut); 39 | this.panel_Graphs.Dock = System.Windows.Forms.DockStyle.Fill; 40 | this.panel_Graphs.Location = new System.Drawing.Point(0, 0); 41 | this.panel_Graphs.Name = "panel_Graphs"; 42 | this.panel_Graphs.Size = new System.Drawing.Size(800, 450); 43 | this.panel_Graphs.TabIndex = 0; 44 | // 45 | // txtOut 46 | // 47 | this.txtOut.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 48 | | System.Windows.Forms.AnchorStyles.Left) 49 | | System.Windows.Forms.AnchorStyles.Right))); 50 | this.txtOut.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 51 | this.txtOut.Location = new System.Drawing.Point(3, 3); 52 | this.txtOut.Multiline = true; 53 | this.txtOut.Name = "txtOut"; 54 | this.txtOut.Size = new System.Drawing.Size(794, 444); 55 | this.txtOut.TabIndex = 0; 56 | // 57 | // TestForm 58 | // 59 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 60 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 61 | this.ClientSize = new System.Drawing.Size(800, 450); 62 | this.Controls.Add(this.panel_Graphs); 63 | this.Name = "TestForm"; 64 | this.Text = "TestForm"; 65 | this.Load += new System.EventHandler(this.TestForm_Load); 66 | this.panel_Graphs.ResumeLayout(false); 67 | this.panel_Graphs.PerformLayout(); 68 | this.ResumeLayout(false); 69 | 70 | } 71 | 72 | #endregion 73 | 74 | private System.Windows.Forms.Panel panel_Graphs; 75 | private System.Windows.Forms.TextBox txtOut; 76 | } 77 | } -------------------------------------------------------------------------------- /PingTest/PingTest/TestForm.cs: -------------------------------------------------------------------------------- 1 | using PingTracer.TraceRoute; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Data; 6 | using System.Drawing; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Text; 10 | using System.Windows.Forms; 11 | 12 | namespace PingTracer 13 | { 14 | public partial class TestForm : Form 15 | { 16 | const byte maxTtl = 64; 17 | SimpleThreadPool threadPool = new SimpleThreadPool("Ping Test", maxTtl, maxTtl); 18 | List results = new List(); 19 | IPAddress addr; 20 | public TestForm(IPAddress addr) 21 | { 22 | this.addr = addr; 23 | InitializeComponent(); 24 | } 25 | 26 | private void TestForm_Load(object sender, EventArgs e) 27 | { 28 | try 29 | { 30 | results = new List(); 31 | //RouteTracerMethodA.TraceRoute(null, addr, maxTtl, HandlePingResponse); 32 | //RouteTracerMethodB.TraceRoute(null, addr, maxTtl, HandlePingResponse); 33 | RouteTracerMethodC.TraceRoute(threadPool, null, addr, maxTtl, HandlePingResponse); 34 | } 35 | catch (Exception ex) 36 | { 37 | MessageBox.Show(ex.ToString()); 38 | } 39 | } 40 | private void HandlePingResponse(TraceRouteHostResult r) 41 | { 42 | lock (this) 43 | { 44 | results.Add(r); 45 | //WriteLine("Got result " + results.Count); 46 | if (results.Count >= maxTtl) 47 | { 48 | results.Sort((a, b) => 49 | { 50 | return a.ttl.CompareTo(b.ttl); 51 | }); 52 | foreach (TraceRouteHostResult result in results) 53 | { 54 | WriteLine(result.ttl.ToString().PadLeft(2, ' ') 55 | + " [" + result.roundTripTime.ToString().PadLeft(4, ' ') + "ms]: " 56 | + result.replyFrom.ToString() + (result.success ? "" : " (failed)")); 57 | } 58 | } 59 | } 60 | } 61 | 62 | private void WriteLine(string str) 63 | { 64 | if (txtOut.InvokeRequired) 65 | txtOut.Invoke((Action)WriteLine, str); 66 | else 67 | { 68 | txtOut.AppendText(str + Environment.NewLine); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /PingTest/PingTest/TestForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /PingTest/PingTest/Throttle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Timer = System.Threading.Timer; 5 | 6 | namespace PingTracer 7 | { 8 | /// 9 | /// Allows the creation of "throttled" actions. A throttled action can be called many times, but the underlying action will be called at most once per time interval. If the throttled action is called at a time when the underlying action is on cooldown, the underlying action will be called as soon as the cooldown expires. See also for different behavior. 10 | /// 11 | public static class Throttle 12 | { 13 | /// 14 | /// Creates a thread-safe throttled version of the specified action that, when invoked repeatedly, will only actually call the original action at most once per every [wait] milliseconds. 15 | /// When you invoke the action returned by this method, the underlying action will always complete asynchronously. 16 | /// 17 | /// The action to throttle. 18 | /// The number of milliseconds to wait before calling the action again. 19 | /// If the action throws an exception, the exception will be passed to this handler. If the handler is null, the exception will be swallowed. 20 | /// A new action that is a throttled version of the original action. 21 | public static Action Create(Action action, int wait, Action errorHandler) 22 | { 23 | object syncLock = new object(); 24 | Timer timer = null; 25 | bool pendingExecution = false; 26 | 27 | Action throttledAction = null; 28 | throttledAction = () => 29 | { 30 | lock (syncLock) 31 | { 32 | if (timer != null) 33 | { 34 | // We're on cooldown. Schedule the next invokation. 35 | pendingExecution = true; 36 | } 37 | else 38 | { 39 | pendingExecution = false; 40 | // Not on cooldown. Invoke synchronously. 41 | try 42 | { 43 | Task.Run(action); 44 | } 45 | catch (Exception ex) 46 | { 47 | errorHandler?.Invoke(ex); 48 | } 49 | 50 | // Then start the cooldown. 51 | timer = new Timer(_ => 52 | { 53 | lock (syncLock) 54 | { 55 | timer.Dispose(); 56 | timer = null; 57 | // Cooldown expired. Run the action again if required. 58 | if (pendingExecution) 59 | throttledAction(); 60 | } 61 | }, null, wait, Timeout.Infinite); 62 | } 63 | 64 | } 65 | }; 66 | return throttledAction; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /PingTest/PingTest/TraceRoute/PathChangedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PingTracer.TraceRoute 7 | { 8 | public class PathChangedEventArgs : EventArgs 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PingTest/PingTest/TraceRoute/PathTracer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | namespace PingTracer.TraceRoute 8 | { 9 | /// 10 | /// A class which handles monitoring the network path to a host. 11 | /// 12 | public class PathTracer : IDisposable 13 | { 14 | public TimeSpan pingInterval { get; private set; } = TimeSpan.FromSeconds(1); 15 | public readonly string host; 16 | private Timer timer; 17 | /// 18 | /// An event which is raised when the path to a host has changed. 19 | /// 20 | public event EventHandler PathChanged = delegate { }; 21 | /// 22 | /// An event which is raised when a ping response is received. 23 | /// 24 | public event EventHandler PingResponse = delegate { }; 25 | 26 | public PathTracer(string host, TimeSpan pingInterval) 27 | { 28 | this.host = host; 29 | this.pingInterval = pingInterval; 30 | timer = new Timer(TimerElapsed, null, TimeSpan.Zero, pingInterval); 31 | } 32 | 33 | private void TimerElapsed(object state) 34 | { 35 | } 36 | 37 | #region IDisposable Support 38 | private bool disposedValue = false; // To detect redundant calls 39 | 40 | protected virtual void Dispose(bool disposing) 41 | { 42 | if (!disposedValue) 43 | { 44 | if (disposing) 45 | { 46 | // dispose managed state (managed objects). 47 | } 48 | 49 | // free unmanaged resources (unmanaged objects) and override a finalizer below. 50 | // set large fields to null. 51 | 52 | disposedValue = true; 53 | } 54 | } 55 | 56 | // override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. 57 | // ~PathTracer() { 58 | // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. 59 | // Dispose(false); 60 | // } 61 | 62 | // This code added to correctly implement the disposable pattern. 63 | public void Dispose() 64 | { 65 | // Do not change this code. Put cleanup code in Dispose(bool disposing) above. 66 | Dispose(true); 67 | // uncomment the following line if the finalizer is overridden above. 68 | // GC.SuppressFinalize(this); 69 | } 70 | #endregion 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /PingTest/PingTest/TraceRoute/PingResponseEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PingTracer.TraceRoute 7 | { 8 | public class PingResponseEventArgs : EventArgs 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /PingTest/PingTest/TraceRoute/RouteTracerMethodA.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.NetworkInformation; 7 | using System.Text; 8 | 9 | namespace PingTracer.TraceRoute 10 | { 11 | public static class RouteTracerMethodA 12 | { 13 | /// 14 | /// Performs an asynchronous, multi-threaded traceroute operation. 15 | /// 16 | /// An object which should be passed through to the OnHostResult method. 17 | /// The target host to ping. 18 | /// The maximum number of hops to try. 19 | /// Callback method which will be called with the result of each individual ping. 20 | /// Timeout in milliseconds after which the ping should be considered unsuccessful. 21 | public static void TraceRoute(object token, IPAddress Target, byte MaxHops, Action OnHostResult, int PingTimeoutMs = 5000) 22 | { 23 | byte[] buffer = new byte[0]; 24 | for (byte ttl = 1; ttl <= MaxHops; ttl++) 25 | { 26 | PingOptions opt = new PingOptions(ttl, true); 27 | Ping ping = PingInstancePool.Get(); 28 | ping.PingCompleted += Ping_PingCompleted; 29 | Stopwatch sw = new Stopwatch(); 30 | sw.Start(); 31 | ping.SendAsync(Target, PingTimeoutMs, buffer, opt, new 32 | { 33 | sw, 34 | token, 35 | ttl, 36 | Target, 37 | MaxHops, 38 | OnHostResult, 39 | PingTimeoutMs 40 | }); 41 | } 42 | } 43 | 44 | private static void Ping_PingCompleted(object sender, PingCompletedEventArgs e) 45 | { 46 | dynamic state = e.UserState; 47 | Stopwatch sw = state.sw; 48 | sw.Stop(); 49 | object token = state.token; 50 | byte ttl = state.ttl; 51 | IPAddress Target = state.Target; 52 | byte MaxHops = state.MaxHops; 53 | Action OnHostResult = state.OnHostResult; 54 | int PingTimeoutMs = state.PingTimeoutMs; 55 | 56 | bool Success = !e.Cancelled && (e.Reply.Status == IPStatus.Success || e.Reply.Status == IPStatus.TtlExpired); 57 | long RoundTripTime = e.Reply.RoundtripTime; 58 | IPAddress ReplyFrom = Success ? e.Reply.Address : IPAddress.Any; 59 | 60 | ((Ping)sender).PingCompleted -= Ping_PingCompleted; 61 | PingInstancePool.Recycle((Ping)sender); 62 | 63 | TraceRouteHostResult result = new TraceRouteHostResult(token, Success, RoundTripTime, ReplyFrom, ttl, Target, MaxHops, PingTimeoutMs); 64 | OnHostResult(result); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /PingTest/PingTest/TraceRoute/RouteTracerMethodB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.NetworkInformation; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace PingTracer.TraceRoute 11 | { 12 | public static class RouteTracerMethodB 13 | { 14 | /// 15 | /// Performs an asynchronous, multi-threaded traceroute operation. 16 | /// 17 | /// An object which should be passed through to the OnHostResult method. 18 | /// The target host to ping. 19 | /// The maximum number of hops to try. 20 | /// Callback method which will be called with the result of each individual ping. 21 | /// Timeout in milliseconds after which the ping should be considered unsuccessful. 22 | public static void TraceRoute(object token, IPAddress Target, byte MaxHops, Action OnHostResult, int PingTimeoutMs = 5000) 23 | { 24 | List tasks = new List(); 25 | byte[] buffer = new byte[0]; 26 | for (byte ttl = 1; ttl <= MaxHops; ttl++) 27 | { 28 | tasks.Add(PingAsync(new 29 | { 30 | token, 31 | ttl, 32 | Target, 33 | MaxHops, 34 | OnHostResult, 35 | PingTimeoutMs, 36 | buffer 37 | })); 38 | } 39 | Task.WaitAll(tasks.ToArray()); 40 | } 41 | 42 | private static async Task PingAsync(dynamic state) 43 | { 44 | object token = state.token; 45 | byte ttl = state.ttl; 46 | IPAddress Target = state.Target; 47 | byte MaxHops = state.MaxHops; 48 | Action OnHostResult = state.OnHostResult; 49 | int PingTimeoutMs = state.PingTimeoutMs; 50 | byte[] buffer = state.buffer; 51 | 52 | PingOptions opt = new PingOptions(ttl, true); 53 | Ping ping = PingInstancePool.Get(); 54 | Stopwatch sw = new Stopwatch(); 55 | sw.Start(); 56 | PingReply reply = await ping.SendPingAsync(Target, PingTimeoutMs, buffer, opt).ConfigureAwait(false); 57 | sw.Stop(); 58 | 59 | bool Success = reply.Status == IPStatus.Success || reply.Status == IPStatus.TtlExpired; 60 | long RoundTripTime = reply.RoundtripTime; 61 | IPAddress ReplyFrom = Success ? reply.Address : IPAddress.Any; 62 | 63 | PingInstancePool.Recycle(ping); 64 | 65 | TraceRouteHostResult result = new TraceRouteHostResult(token, Success, RoundTripTime, ReplyFrom, ttl, Target, MaxHops, PingTimeoutMs); 66 | OnHostResult(result); 67 | } 68 | private class PingTask 69 | { 70 | public object State; 71 | public Task Task; 72 | 73 | public PingTask(object state, Task task) 74 | { 75 | State = state; 76 | Task = task; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /PingTest/PingTest/TraceRoute/RouteTracerMethodC.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.NetworkInformation; 7 | using System.Text; 8 | 9 | namespace PingTracer.TraceRoute 10 | { 11 | public static class RouteTracerMethodC 12 | { 13 | /// 14 | /// Performs an asynchronous, multi-threaded traceroute operation. 15 | /// 16 | /// A thread pool upon which to execute the pings. It should contain as many threads as the value. 17 | /// An object which should be passed through to the OnHostResult method. 18 | /// The target host to ping. 19 | /// The maximum number of hops to try. 20 | /// Callback method which will be called with the result of each individual ping. 21 | /// Timeout in milliseconds after which the ping should be considered unsuccessful. 22 | public static void TraceRoute(SimpleThreadPool threadPool, object token, IPAddress Target, byte MaxHops, Action OnHostResult, int PingTimeoutMs = 5000) 23 | { 24 | byte[] buffer = new byte[0]; 25 | for (byte ttl = 1; ttl <= MaxHops; ttl++) 26 | { 27 | object state = new 28 | { 29 | token, 30 | ttl, 31 | Target, 32 | MaxHops, 33 | OnHostResult, 34 | PingTimeoutMs, 35 | buffer 36 | }; 37 | threadPool.Enqueue(() => 38 | { 39 | PingSync(state); 40 | }); 41 | } 42 | } 43 | 44 | private static void PingSync(dynamic state) 45 | { 46 | object token = state.token; 47 | byte ttl = state.ttl; 48 | IPAddress Target = state.Target; 49 | byte MaxHops = state.MaxHops; 50 | Action OnHostResult = state.OnHostResult; 51 | int PingTimeoutMs = state.PingTimeoutMs; 52 | byte[] buffer = state.buffer; 53 | 54 | PingOptions opt = new PingOptions(ttl, true); 55 | Ping ping = PingInstancePool.Get(); 56 | Stopwatch sw = new Stopwatch(); 57 | sw.Start(); 58 | PingReply reply = ping.Send(Target, PingTimeoutMs, buffer, opt); 59 | sw.Stop(); 60 | 61 | bool Success = reply.Status == IPStatus.Success || reply.Status == IPStatus.TtlExpired; 62 | long RoundTripTime = reply.RoundtripTime * 10000 + sw.ElapsedMilliseconds; 63 | IPAddress ReplyFrom = Success ? reply.Address : IPAddress.Any; 64 | 65 | PingInstancePool.Recycle(ping); 66 | 67 | TraceRouteHostResult result = new TraceRouteHostResult(token, Success, RoundTripTime, ReplyFrom, ttl, Target, MaxHops, PingTimeoutMs); 68 | OnHostResult(result); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /PingTest/PingTest/TraceRoute/TraceRouteHostResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | 7 | namespace PingTracer.TraceRoute 8 | { 9 | public class TraceRouteHostResult 10 | { 11 | /// 12 | /// Token originally provided to RouteTracer.TraceRoute() 13 | /// 14 | public object token; 15 | /// 16 | /// If true, the ping recieved a response. 17 | /// 18 | public bool success; 19 | /// 20 | /// The round-trip-time of the ping, in milliseconds (only if ). 21 | /// 22 | public long roundTripTime; 23 | /// 24 | /// The address a reply was received from (only if ). 25 | /// 26 | public IPAddress replyFrom; 27 | /// 28 | /// The ttl value which was sent with this ping. 29 | /// 30 | public byte ttl; 31 | /// 32 | /// The destination host of the ping (may differ from the address). 33 | /// 34 | public IPAddress target; 35 | /// 36 | /// Maximum ttl value which was used for the traceroute operation. 37 | /// 38 | public byte maxHops; 39 | /// 40 | /// Number of milliseconds after which the ping was instructed to time out. 41 | /// 42 | public int pingTimeoutMs; 43 | 44 | public TraceRouteHostResult(object token, bool success, long roundTripTime, IPAddress replyFrom, byte ttl, IPAddress target, byte maxHops, int pingTimeoutMs) 45 | { 46 | this.token = token; 47 | this.success = success; 48 | this.roundTripTime = roundTripTime; 49 | this.replyFrom = replyFrom; 50 | this.ttl = ttl; 51 | this.target = target; 52 | this.maxHops = maxHops; 53 | this.pingTimeoutMs = pingTimeoutMs; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PingTest/PingTest/Tracer/HostSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Xml; 7 | using System.Xml.Serialization; 8 | 9 | namespace PingTracer.Tracer 10 | { 11 | /// 12 | /// Defines host(s) to ping, along with various related options. 13 | /// 14 | public class HostSettings 15 | { 16 | public string host; 17 | public string displayName = ""; 18 | public int rate = 1; 19 | public bool pingsPerSecond = true; 20 | public bool doTraceRoute = true; 21 | public bool reverseDnsLookup = true; 22 | public bool drawServerNames = true; 23 | public bool drawLastPing = true; 24 | public bool drawAverage = true; 25 | public bool drawJitter = false; 26 | public bool drawMinMax = false; 27 | public bool drawPacketLoss = true; 28 | public bool drawLimitText = false; 29 | public int badThreshold = 100; 30 | public int worseThreshold = 200; 31 | public int upperLimit = 300; 32 | public int lowerLimit = 0; 33 | public int ScalingMethodID = 0; 34 | public bool preferIpv4 = true; 35 | public bool logFailures = true; 36 | public bool logSuccesses = false; 37 | 38 | public override bool Equals(object other) 39 | { 40 | if (other is HostSettings) 41 | { 42 | HostSettings o = (HostSettings)other; 43 | return host == o.host 44 | && rate == o.rate 45 | && pingsPerSecond == o.pingsPerSecond 46 | && doTraceRoute == o.doTraceRoute 47 | && reverseDnsLookup == o.reverseDnsLookup 48 | && drawServerNames == o.drawServerNames 49 | && drawMinMax == o.drawMinMax 50 | && drawPacketLoss == o.drawPacketLoss 51 | && badThreshold == o.badThreshold 52 | && worseThreshold == o.worseThreshold 53 | && lowerLimit == o.lowerLimit 54 | && upperLimit == o.upperLimit 55 | && ScalingMethodID == o.ScalingMethodID 56 | && preferIpv4 == o.preferIpv4; 57 | } 58 | return false; 59 | } 60 | public override int GetHashCode() 61 | { 62 | return host.GetHashCode() ^ rate.GetHashCode() ^ badThreshold.GetHashCode() ^ worseThreshold.GetHashCode() ^ lowerLimit.GetHashCode() ^ upperLimit.GetHashCode(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /PingTest/PingTest/Tracer/PingController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PingTracer.Tracer 8 | { 9 | /// 10 | /// Handles pinging of the host(s) defined by a Profile. 11 | /// 12 | public class PingController 13 | { 14 | /// 15 | /// Gets the Profile bound to this PingController. 16 | /// 17 | public readonly HostSettings p; 18 | public PingController(HostSettings p) 19 | { 20 | this.p = p; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /PingTest/PingTest/Tracert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.NetworkInformation; 8 | using System.Net.Sockets; 9 | using System.Text; 10 | using System.Threading; 11 | 12 | namespace PingTracer 13 | { 14 | /// 15 | /// BASED ON http://www.fluxbytes.com/csharp/tracert-implementation-in-c/ 16 | /// 17 | public static class Tracert 18 | { 19 | /// 20 | /// Traces the route which data have to travel through in order to reach an IP address. 21 | /// 22 | /// The IP address of the destination. 23 | /// Max hops to be returned. 24 | public static IEnumerable Trace(IPAddress address, int maxHops, int timeout) 25 | { 26 | // Max hops should be at least one or else there won't be any data to return. 27 | if (maxHops < 1) 28 | throw new ArgumentException("Max hops can't be lower than 1."); 29 | 30 | // Ensure that the timeout is not set to 0 or a negative number. 31 | if (timeout < 1) 32 | throw new ArgumentException("Timeout value must be higher than 0."); 33 | 34 | 35 | Ping ping = new Ping(); 36 | PingOptions pingOptions = new PingOptions(1, true); 37 | Stopwatch pingReplyTime = new Stopwatch(); 38 | PingReply reply; 39 | byte[] buffer = new byte[0]; 40 | int consecutiveTimeouts = 0; 41 | do 42 | { 43 | pingReplyTime.Start(); 44 | reply = ping.Send(address, timeout, buffer, pingOptions); 45 | pingReplyTime.Stop(); 46 | IPAddress replyAddress = reply.Address; 47 | if (replyAddress != null && replyAddress.GetAddressBytes().All(b => b == 0)) 48 | replyAddress = null; 49 | if (reply.Status == IPStatus.TimedOut) 50 | consecutiveTimeouts++; 51 | else 52 | consecutiveTimeouts = 0; 53 | // Return out TracertEntry object with all the information about the hop. 54 | yield return new TracertEntry() 55 | { 56 | HopID = pingOptions.Ttl, 57 | Address = replyAddress, 58 | ReplyTime = pingReplyTime.ElapsedMilliseconds, 59 | ReplyStatus = reply.Status 60 | }; 61 | 62 | pingOptions.Ttl++; 63 | pingReplyTime.Reset(); 64 | } 65 | while (reply.Status != IPStatus.Success && pingOptions.Ttl <= maxHops && consecutiveTimeouts < 5); 66 | } 67 | 68 | ///// 69 | ///// Quickly traces the route which data have to travel through in order to reach an IP address. All hosts are pinged concurrently. 70 | ///// 71 | ///// The IP address of the destination. 72 | ///// Max hops to be returned. 73 | //public static IEnumerable FastTrace(IPAddress address, int maxHops, int timeout) 74 | //{ 75 | // // Max hops should be at least one or else there won't be any data to return. 76 | // if (maxHops < 1) 77 | // throw new ArgumentException("Max hops can't be lower than 1."); 78 | 79 | // // Ensure that the timeout is not set to 0 or a negative number. 80 | // if (timeout < 1) 81 | // throw new ArgumentException("Timeout value must be higher than 0."); 82 | 83 | // byte[] buffer = new byte[0]; 84 | // ConcurrentQueue traceRouteReplies = new ConcurrentQueue(); 85 | // for (int i = 1; i <= maxHops; i++) 86 | // { 87 | // Ping ping = PingInstancePool.Get(); 88 | // ping.PingCompleted += ping_PingCompleted; 89 | // PingOptions pingOptions = new PingOptions(i, true); 90 | // Stopwatch pingReplyTime = new Stopwatch(); 91 | // pingReplyTime.Start(); 92 | // ping.SendAsync(address, timeout, buffer, pingOptions, new object[] { pingOptions, pingReplyTime, traceRouteReplies, ping }); 93 | // } 94 | // while (traceRouteReplies.Count < maxHops) 95 | // Thread.Sleep(100); 96 | // SortedList sortedReplies = new SortedList(); 97 | // TracertEntry reply; 98 | // while (traceRouteReplies.TryDequeue(out reply)) 99 | // { 100 | // sortedReplies.Add(reply.HopID, reply); 101 | // } 102 | // foreach (int hopId in sortedReplies.Keys) 103 | // { 104 | // yield return sortedReplies[hopId]; 105 | // if (sortedReplies[hopId].ReplyStatus == IPStatus.Success) 106 | // break; 107 | // } 108 | //} 109 | 110 | //static void ping_PingCompleted(object sender, PingCompletedEventArgs e) 111 | //{ 112 | // Stopwatch pingReplyTime = (Stopwatch)((object[])e.UserState)[1]; 113 | // pingReplyTime.Stop(); 114 | // PingOptions pingOptions = (PingOptions)((object[])e.UserState)[0]; 115 | // ConcurrentQueue traceRouteReplies = (ConcurrentQueue)((object[])e.UserState)[2]; 116 | // Ping ping = (Ping)((object[])e.UserState)[3]; 117 | // ping.PingCompleted -= ping_PingCompleted; 118 | // PingInstancePool.Recycle(ping); 119 | 120 | // string hostname = string.Empty; 121 | // if (e.Reply.Address != null) 122 | // { 123 | // try 124 | // { 125 | // hostname = Dns.GetHostByAddress(e.Reply.Address).HostName; // Retrieve the hostname for the replied address. 126 | // } 127 | // catch (SocketException) { /* No host available for that address. */ } 128 | // } 129 | 130 | // Console.WriteLine(pingOptions.Ttl + " " + e.Reply.Address); 131 | // traceRouteReplies.Enqueue(new TracertEntry() 132 | // { 133 | // HopID = pingOptions.Ttl, 134 | // Address = e.Reply.Address, 135 | // Hostname = hostname, 136 | // ReplyTime = pingReplyTime.ElapsedMilliseconds, 137 | // ReplyStatus = e.Reply.Status 138 | // }); 139 | //} 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /PingTest/PingTest/TracertEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Net.NetworkInformation; 5 | using System.Text; 6 | 7 | namespace PingTracer 8 | { 9 | /// 10 | /// BASED ON http://www.fluxbytes.com/csharp/tracert-implementation-in-c/ 11 | /// 12 | public class TracertEntry 13 | { 14 | /// 15 | /// The hop id. Represents the number of the hop. 16 | /// 17 | public int HopID { get; set; } 18 | 19 | /// 20 | /// The IP address. 21 | /// 22 | public IPAddress Address { get; set; } 23 | 24 | /// 25 | /// The reply time it took for the host to receive and reply to the request in milliseconds. 26 | /// 27 | public long ReplyTime { get; set; } 28 | 29 | /// 30 | /// The reply status of the request. 31 | /// 32 | public IPStatus ReplyStatus { get; set; } 33 | 34 | public override string ToString() 35 | { 36 | string host; 37 | if (Address == null) 38 | host = "N/A"; 39 | else 40 | host = Address.ToString(); 41 | string status; 42 | if (ReplyStatus == IPStatus.TimedOut) 43 | status = "Request Timed Out"; 44 | else 45 | status = ReplyTime.ToString() + " ms"; 46 | return HopID + " | " + host + " | " + status; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /PingTest/PingTest/Util/PingInstancePool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net.NetworkInformation; 6 | using System.Text; 7 | 8 | namespace PingTracer 9 | { 10 | public static class PingInstancePool 11 | { 12 | static ConcurrentQueue pool = new ConcurrentQueue(); 13 | public static Ping Get() 14 | { 15 | Ping pinger; 16 | if (!pool.TryDequeue(out pinger)) 17 | pinger = new Ping(); 18 | return pinger; 19 | } 20 | public static void Recycle(Ping pinger) 21 | { 22 | pool.Enqueue(pinger); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PingTest/PingTest/Util/SimpleThreadPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | 8 | /// 9 | /// From https://github.com/bp2008/BPUtil 10 | /// 11 | namespace PingTracer 12 | { 13 | class PoolThread 14 | { 15 | public Thread thread; 16 | public EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset); 17 | public volatile bool thisThreadHasWork = false; 18 | public CancellationToken cancellationToken; 19 | private CancellationTokenSource cancellationTokenSource; 20 | 21 | public PoolThread(Thread thread) 22 | { 23 | this.thread = thread; 24 | this.cancellationTokenSource = new CancellationTokenSource(); 25 | this.cancellationToken = cancellationTokenSource.Token; 26 | } 27 | /// 28 | /// Sets the cancellationToken to the canceled state. 29 | /// 30 | public void Cancel() 31 | { 32 | if (!this.cancellationTokenSource.IsCancellationRequested) 33 | this.cancellationTokenSource.Cancel(); 34 | } 35 | } 36 | public class SimpleThreadPool 37 | { 38 | /// 39 | /// A stack of threads that are idle. 40 | /// 41 | List idleThreads = new List(); 42 | /// 43 | /// A queue of actions to be performed by threads. 44 | /// 45 | ConcurrentQueue actionQueue = new ConcurrentQueue(); 46 | int threadTimeoutMilliseconds; 47 | int _currentMinThreads; 48 | int _currentMaxThreads; 49 | int _currentLiveThreads = 0; 50 | int _currentBusyThreads = 0; 51 | int threadNamingCounter = -1; 52 | bool threadsAreBackgroundThreads; 53 | string poolName; 54 | object threadLock = new object(); 55 | volatile bool abort = false; 56 | /// 57 | /// Gets the number of threads that are currently available, including those which are busy and those which are idle. 58 | /// 59 | public int CurrentLiveThreads 60 | { 61 | get 62 | { 63 | return Thread.VolatileRead(ref _currentLiveThreads); 64 | } 65 | } 66 | /// 67 | /// Gets the number of threads that are currently busy processing actions. 68 | /// 69 | public int CurrentBusyThreads 70 | { 71 | get 72 | { 73 | return Thread.VolatileRead(ref _currentBusyThreads); 74 | } 75 | } 76 | /// 77 | /// Gets or sets the soft maximum number of threads this pool should have active at any given time. It is possible for there to be temporarily more threads than this if certain race conditions are met. If reducing the value, it may take some time for the number of threads to fall into line, as no special effort is taken to reduce the live thread count quickly. 78 | /// 79 | public int MaxThreads 80 | { 81 | get 82 | { 83 | return Thread.VolatileRead(ref _currentMaxThreads); 84 | } 85 | set 86 | { 87 | if (value < 1 || MinThreads > value) 88 | throw new Exception("MaxThreads must be >= 1 and >= MinThreads"); 89 | Interlocked.Exchange(ref _currentMaxThreads, value); 90 | } 91 | } 92 | /// 93 | /// Gets or sets the minimum number of threads this pool should have active at any given time. If increasing the value, it may take some time for the number of threads to rise, as no special effort is taken to reach this number. 94 | /// 95 | public int MinThreads 96 | { 97 | get 98 | { 99 | return Thread.VolatileRead(ref _currentMinThreads); 100 | } 101 | set 102 | { 103 | if (value < 0 || value > MaxThreads) 104 | throw new Exception("MinThreads must be >= 0 and <= MaxThreads"); 105 | Interlocked.Exchange(ref _currentMinThreads, value); 106 | } 107 | } 108 | /// 109 | /// 110 | /// 111 | /// 112 | /// The minimum number of threads that should be kept alive at all times. 113 | /// The largest number of threads this pool should attempt to have alive at any given time. It is possible for there to be temporarily more threads than this if certain race conditions are met. 114 | /// 115 | /// If true, the application will be able to exit without waiting for this thread pool. Background threads do not prevent a process from terminating. Once all foreground threads belonging to a process have terminated, the common language runtime ends the process. Any remaining background threads are stopped and do not complete. 116 | /// A method to use for logging exceptions. If null, SimpleHttpLogger.Log will be used. 117 | public SimpleThreadPool(string poolName, int minThreads = 6, int maxThreads = 32, int threadTimeoutMilliseconds = 60000, bool useBackgroundThreads = true) 118 | { 119 | this.poolName = poolName; 120 | this.threadTimeoutMilliseconds = threadTimeoutMilliseconds; 121 | if (minThreads < 0 || minThreads > maxThreads) 122 | throw new ArgumentException("minThreads must be >= 0 and <= maxThreads", "minThreads"); 123 | if (maxThreads < 1 || minThreads > maxThreads) 124 | throw new ArgumentException("maxThreads must be >= 1 and >= minThreads", "maxThreads"); 125 | this._currentMinThreads = minThreads; 126 | this._currentMaxThreads = maxThreads; 127 | this.threadsAreBackgroundThreads = useBackgroundThreads; 128 | SpawnNewIdleThreads(minThreads); 129 | } 130 | /// 131 | /// Creates new threads and signal them to begin working. 132 | /// 133 | /// 134 | private void SpawnNewActiveThreads(int count) 135 | { 136 | SpawnNewActiveOrIdleThreads(count, true); 137 | } 138 | /// 139 | /// Creates new thread but does not signal them. Instead, the threads are added to the pool of idle threads. 140 | /// 141 | /// 142 | private void SpawnNewIdleThreads(int count) 143 | { 144 | SpawnNewActiveOrIdleThreads(count, false); 145 | } 146 | private void SpawnNewActiveOrIdleThreads(int count, bool active) 147 | { 148 | if (abort) 149 | return; 150 | lock (threadLock) 151 | { 152 | for (int i = 0; i < count; i++) 153 | { 154 | if (CurrentLiveThreads < MaxThreads) 155 | { 156 | Interlocked.Increment(ref _currentLiveThreads); 157 | PoolThread pt = new PoolThread(new Thread(threadLoop)); 158 | if (active) 159 | pt.waitHandle.Set(); 160 | pt.thread.IsBackground = threadsAreBackgroundThreads; 161 | pt.thread.Name = poolName + " " + Interlocked.Increment(ref threadNamingCounter); 162 | pt.thread.Start(pt); 163 | if (!active) 164 | idleThreads.Add(pt); 165 | } 166 | } 167 | } 168 | } 169 | /// 170 | /// Aborts all idle threads, prevents the creation of new threads, and prevents new actions from being enqueued. This cannot be undone. 171 | /// 172 | public void Stop() 173 | { 174 | abort = true; 175 | lock (threadLock) 176 | { 177 | foreach (PoolThread pt in idleThreads) 178 | try 179 | { 180 | pt.Cancel(); 181 | } 182 | catch (ThreadAbortException) { throw; } 183 | catch (Exception) { } 184 | } 185 | } 186 | public void Enqueue(Action action) 187 | { 188 | if (abort) 189 | return; 190 | actionQueue.Enqueue(action); 191 | if (!SignalTopThread()) 192 | SpawnNewActiveThreads(1); 193 | } 194 | 195 | private bool SignalTopThread() 196 | { 197 | if (idleThreads.Count > 0) 198 | { 199 | lock (threadLock) 200 | { 201 | if (idleThreads.Count > 0) 202 | { 203 | PoolThread pt = idleThreads[idleThreads.Count - 1]; 204 | idleThreads.RemoveAt(idleThreads.Count - 1); 205 | pt.thisThreadHasWork = true; 206 | pt.waitHandle.Set(); 207 | return true; 208 | } 209 | } 210 | } 211 | return false; 212 | } 213 | private void threadLoop(object args) 214 | { 215 | try 216 | { 217 | PoolThread pt = (PoolThread)args; 218 | while (true) 219 | { 220 | // Wait for a signal 221 | if (WaitHandle.WaitAny(new WaitHandle[] { pt.waitHandle, pt.cancellationToken.WaitHandle }, threadTimeoutMilliseconds) != 0) 222 | { 223 | // Timeout has occurred. Make sure this thread has no work to perform before quitting. 224 | lock (threadLock) 225 | { 226 | bool isCancelled = pt.cancellationToken.IsCancellationRequested; 227 | if (!isCancelled && pt.thisThreadHasWork) 228 | { 229 | // This thread can't quit now because it has work to do. 230 | pt.thisThreadHasWork = false; 231 | } 232 | else if (!isCancelled && CurrentLiveThreads <= MinThreads) 233 | { 234 | // There is no work to do right now, but this thread is not allowed to quit. 235 | continue; 236 | } 237 | else 238 | { 239 | // This thread is allowed to quit 240 | Interlocked.Decrement(ref _currentLiveThreads); 241 | idleThreads.Remove(pt); 242 | return; 243 | } 244 | } 245 | } 246 | pt.thisThreadHasWork = false; 247 | // If we get here, this thread has been signaled or the timeout has expired but this thread was not allowed to quit. 248 | Interlocked.Increment(ref _currentBusyThreads); 249 | try 250 | { 251 | // Check for queued actions to perform. 252 | Action action; 253 | while (actionQueue.TryDequeue(out action)) 254 | { 255 | try 256 | { 257 | action(); 258 | } 259 | catch (ThreadAbortException) { throw; } 260 | catch (Exception) { } 261 | } 262 | } 263 | finally 264 | { 265 | Interlocked.Decrement(ref _currentBusyThreads); 266 | } 267 | // Return me to the pool 268 | lock (threadLock) 269 | { 270 | idleThreads.Add(pt); 271 | } 272 | } 273 | } 274 | catch (OperationCanceledException) { } 275 | catch (ThreadAbortException) { } 276 | catch (Exception) { } 277 | lock (threadLock) 278 | { 279 | Interlocked.Decrement(ref _currentLiveThreads); 280 | idleThreads.Remove((PoolThread)args); 281 | } 282 | } 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /PingTest/PingTest/WindowParams.cs: -------------------------------------------------------------------------------- 1 | namespace PingTracer 2 | { 3 | public class WindowParams 4 | { 5 | /// 6 | /// X coordinate 7 | /// 8 | public int X; 9 | /// 10 | /// Y coordinate 11 | /// 12 | public int Y; 13 | /// 14 | /// Width, ignore if less than 1. 15 | /// 16 | public int W; 17 | /// 18 | /// Height, ignore if less than 1. 19 | /// 20 | public int H; 21 | public WindowParams() { } 22 | 23 | public WindowParams(int x, int y, int w, int h) 24 | { 25 | X = x; 26 | Y = y; 27 | W = w; 28 | H = h; 29 | } 30 | public override string ToString() 31 | { 32 | return X + "," + Y + "," + W + "," + H; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /PingTest/PingTest/include/SmartPingF.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp2008/pingtracer/5b307f27d9214a2e290302318c5e94a9c175a9f2/PingTest/PingTest/include/SmartPingF.dll -------------------------------------------------------------------------------- /PingTest/PingTest/include/Tracer.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace PingTracer.include 2 | { 3 | partial class Tracer 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.SuspendLayout(); 32 | // 33 | // Tracer 34 | // 35 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 36 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 37 | this.ClientSize = new System.Drawing.Size(800, 450); 38 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; 39 | this.Name = "Tracer"; 40 | this.Text = "Tracer"; 41 | this.Load += new System.EventHandler(this.Tracer_Load); 42 | this.ResumeLayout(false); 43 | 44 | } 45 | 46 | #endregion 47 | } 48 | } -------------------------------------------------------------------------------- /PingTest/PingTest/include/Tracer.cs: -------------------------------------------------------------------------------- 1 | using PingTracer.Tracer; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Data; 6 | using System.Drawing; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Windows.Forms; 11 | 12 | namespace PingTracer.include 13 | { 14 | public partial class Tracer : Form 15 | { 16 | public Tracer(HostSettings p) 17 | { 18 | InitializeComponent(); 19 | } 20 | 21 | private void Tracer_Load(object sender, EventArgs e) 22 | { 23 | 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /PingTest/PingTest/include/Tracer.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /PingTest/PingTest/pingtracer.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp2008/pingtracer/5b307f27d9214a2e290302318c5e94a9c175a9f2/PingTest/PingTest/pingtracer.ico -------------------------------------------------------------------------------- /PingTest/PingTracer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28729.10 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PingTracer", "PingTest\PingTracer.csproj", "{854FD525-4946-412E-9B3F-69412F505C30}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{740E81B9-D7FB-43D7-A5F0-D28E538B0E36}" 9 | ProjectSection(SolutionItems) = preProject 10 | .gitignore = .gitignore 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {854FD525-4946-412E-9B3F-69412F505C30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {854FD525-4946-412E-9B3F-69412F505C30}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {854FD525-4946-412E-9B3F-69412F505C30}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {854FD525-4946-412E-9B3F-69412F505C30}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {CEEE6D15-D50F-4579-90BA-81E99E4BAF78} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /PingTest/PingTracer.vssscc: -------------------------------------------------------------------------------- 1 | "" 2 | { 3 | "FILE_VERSION" = "9237" 4 | "ENLISTMENT_CHOICE" = "NEVER" 5 | "PROJECT_FILE_RELATIVE_PATH" = "" 6 | "NUMBER_OF_EXCLUDED_FILES" = "0" 7 | "ORIGINAL_PROJECT_FILE_PATH" = "" 8 | "NUMBER_OF_NESTED_PROJECTS" = "0" 9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROJECT" 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ping Tracer 2 | 3 | Ping Tracer continuously pings each network host between your computer and a given destination, helping identify the source of connectivity problems. 4 | 5 | ![image](https://github.com/bp2008/pingtracer/assets/5639911/3eff5b0c-b025-49bb-a149-77b2cb4c55ae) 6 | 7 | This program helps to visually determine the origin of connection problems. The latency over time is shown on graphs, and each instance of packet loss is marked in red. 8 | 9 | A common use for such a tool is to monitor your connection to a multiplayer game server so you know who to blame when you experience lag. For example, if you experience a terrible moment of lag and you see that every node beyond your router is showing elevated latency or packet loss, then the lag was on your end. Typically, a poorly performing node will affect your connection to every node after it. 10 | 11 | I built this program for personal use, and decided to share it for free as an open source project. As such, it is light on features and polish. 12 | 13 | Notes about PingTracer's "Trace Route" implementation: 14 | 15 | * The trace route operation is not optimized for speed, and will take many seconds to complete if any hosts are unresponsive. 16 | * The trace route operation attempts to contact each host (a.k.a. network node) only once. Any host that fails to respond during the trace route operation will not be monitored. 17 | * The trace route operation is ended if 5 consecutive hosts fail to respond. This usually indicates that the destination host was already passed by and did not respond to the trace ping. 18 | * Some hosts respond to the traceroute but do not respond to direct pings. Such hosts are removed from monitoring after several seconds. 19 | * You can increase the ping rate as high as 10 pings per second (per host!) which can add up to dozens or even hundreds of pings per second. Please use responsibly. 20 | * Some routers implement [ICMP rate limiting](https://docs.paloaltonetworks.com/pan-os/10-0/pan-os-admin/networking/session-settings-and-timeouts/icmp/icmpv6-rate-limiting.html) in such a way that penalizes rapid pinging. Therefore, with higher ping rates you may see increased packet loss which is not representative of actual network performance. 21 | 22 | ## Installation 23 | 24 | Just download the latest release from [the releases tab](https://github.com/bp2008/pingtracer/releases) and extract it wherever you like. 25 | 26 | ## Keyboard shortcuts for the graphs 27 | Key | Alternate Key | action 28 | -:|-:|- 29 | Home | 9 | jump to beginning (first ping) 30 | End | 0 | jump to end (last ping) 31 | Page Up/Down | -/+ | move one page width to the left/right 32 | 33 | 34 | 35 | ## Command-Line arguments 36 | ``` 37 | Arguments: 38 | -h 39 | Load a saved configuration with Display Name or Host field matching . 40 | If a matching configuration is not found, one will be created. 41 | -4 42 | (Use with -h) This indicates the "Prefer IPv4" checkbox must be checked. 43 | -6 44 | (Use with -h) This indicates the "Prefer IPv4" checkbox must be unchecked. 45 | -t0 46 | (Use with -h) This indicates the "Trace Route" checkbox must be unchecked. 47 | -t1 48 | (Use with -h) This indicates the "Trace Route" checkbox must be checked. 49 | -l x,y,w,h 50 | The window will be moved to the specified location and size. 51 | "w" and "h" parameters are optional. 52 | -s 53 | Pinging will begin automatically. 54 | -m 55 | The ping graphs will be maximized. 56 | ``` 57 | 58 | See the latest command line arguments in-app with a usage example via Ping Tracer's `Tools` > `Command Line Arguments`. 59 | 60 | 61 | ## Building from source 62 | 63 | PingTracer (as of version 1.17) is a relatively simple C# and Windows Forms app with no third-party dependencies except .NET Framework 4.6.2. You should have no trouble building it in a standard installation of Visual Studio Community Edition. 64 | --------------------------------------------------------------------------------