├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── azure-pipelines └── build.yml ├── media ├── vcperf-timetrace-overview.png └── vcperf-wpa-overview.gif ├── packages.config ├── set_version.ps1 ├── src ├── Commands.cpp ├── Commands.h ├── CppBuildInsightsEtw.xml ├── GenericFields.cpp ├── GenericFields.h ├── PayloadBuilder.h ├── TimeTrace │ ├── ExecutionHierarchy.cpp │ ├── ExecutionHierarchy.h │ ├── PackedProcessThreadRemapping.cpp │ ├── PackedProcessThreadRemapping.h │ ├── TimeTraceGenerator.cpp │ └── TimeTraceGenerator.h ├── Utility.h ├── VcperfBuildInsights.h ├── WPA │ ├── Analyzers │ │ ├── ContextBuilder.cpp │ │ ├── ContextBuilder.h │ │ ├── ExpensiveTemplateInstantiationCache.cpp │ │ ├── ExpensiveTemplateInstantiationCache.h │ │ └── MiscellaneousCache.h │ └── Views │ │ ├── BuildExplorerView.cpp │ │ ├── BuildExplorerView.h │ │ ├── FilesView.cpp │ │ ├── FilesView.h │ │ ├── FunctionsView.cpp │ │ ├── FunctionsView.h │ │ ├── TemplateInstantiationsView.cpp │ │ └── TemplateInstantiationsView.h └── main.cpp ├── tag_version.ps1 ├── vcperf.nuspec ├── vcperf.sln ├── vcperf.vcxproj └── vcperf.vcxproj.filters /.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 | out/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015/2017 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # Visual Studio 2017 auto generated files 34 | Generated\ Files/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # Benchmark Results 50 | BenchmarkDotNet.Artifacts/ 51 | 52 | # .NET Core 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | **/Properties/launchSettings.json 57 | 58 | # StyleCop 59 | StyleCopReport.xml 60 | 61 | # Files built by Visual Studio 62 | *_i.c 63 | *_p.c 64 | *_i.h 65 | *.ilk 66 | *.meta 67 | *.obj 68 | *.iobj 69 | *.pch 70 | *.pdb 71 | *.ipdb 72 | *.pgc 73 | *.pgd 74 | *.rsp 75 | *.sbr 76 | *.tlb 77 | *.tli 78 | *.tlh 79 | *.tmp 80 | *.tmp_proj 81 | *.log 82 | *.vspscc 83 | *.vssscc 84 | .builds 85 | *.pidb 86 | *.svclog 87 | *.scc 88 | 89 | # Chutzpah Test files 90 | _Chutzpah* 91 | 92 | # Visual C++ cache files 93 | ipch/ 94 | *.aps 95 | *.ncb 96 | *.opendb 97 | *.opensdf 98 | *.sdf 99 | *.cachefile 100 | *.VC.db 101 | *.VC.VC.opendb 102 | 103 | # Visual Studio profiler 104 | *.psess 105 | *.vsp 106 | *.vspx 107 | *.sap 108 | 109 | # Visual Studio Trace Files 110 | *.e2e 111 | 112 | # TFS 2012 Local Workspace 113 | $tf/ 114 | 115 | # Guidance Automation Toolkit 116 | *.gpState 117 | 118 | # ReSharper is a .NET coding add-in 119 | _ReSharper*/ 120 | *.[Rr]e[Ss]harper 121 | *.DotSettings.user 122 | 123 | # JustCode is a .NET coding add-in 124 | .JustCode 125 | 126 | # TeamCity is a build add-in 127 | _TeamCity* 128 | 129 | # DotCover is a Code Coverage Tool 130 | *.dotCover 131 | 132 | # AxoCover is a Code Coverage Tool 133 | .axoCover/* 134 | !.axoCover/settings.json 135 | 136 | # Visual Studio code coverage results 137 | *.coverage 138 | *.coveragexml 139 | 140 | # NCrunch 141 | _NCrunch_* 142 | .*crunch*.local.xml 143 | nCrunchTemp_* 144 | 145 | # MightyMoose 146 | *.mm.* 147 | AutoTest.Net/ 148 | 149 | # Web workbench (sass) 150 | .sass-cache/ 151 | 152 | # Installshield output folder 153 | [Ee]xpress/ 154 | 155 | # DocProject is a documentation generator add-in 156 | DocProject/buildhelp/ 157 | DocProject/Help/*.HxT 158 | DocProject/Help/*.HxC 159 | DocProject/Help/*.hhc 160 | DocProject/Help/*.hhk 161 | DocProject/Help/*.hhp 162 | DocProject/Help/Html2 163 | DocProject/Help/html 164 | 165 | # Click-Once directory 166 | publish/ 167 | 168 | # Publish Web Output 169 | *.[Pp]ublish.xml 170 | *.azurePubxml 171 | # Note: Comment the next line if you want to checkin your web deploy settings, 172 | # but database connection strings (with potential passwords) will be unencrypted 173 | *.pubxml 174 | *.publishproj 175 | 176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 177 | # checkin your Azure Web App publish settings, but sensitive information contained 178 | # in these scripts will be unencrypted 179 | PublishScripts/ 180 | 181 | # NuGet Packages 182 | *.nupkg 183 | # The packages folder can be ignored because of Package Restore 184 | **/[Pp]ackages/* 185 | # except build/, which is used as an MSBuild target. 186 | !**/[Pp]ackages/build/ 187 | # Uncomment if necessary however generally it will be regenerated when needed 188 | #!**/[Pp]ackages/repositories.config 189 | # NuGet v3's project.json files produces more ignorable files 190 | *.nuget.props 191 | *.nuget.targets 192 | 193 | # Microsoft Azure Build Output 194 | csx/ 195 | *.build.csdef 196 | 197 | # Microsoft Azure Emulator 198 | ecf/ 199 | rcf/ 200 | 201 | # Windows Store app package directories and files 202 | AppPackages/ 203 | BundleArtifacts/ 204 | Package.StoreAssociation.xml 205 | _pkginfo.txt 206 | *.appx 207 | 208 | # Visual Studio cache files 209 | # files ending in .cache can be ignored 210 | *.[Cc]ache 211 | # but keep track of directories ending in .cache 212 | !*.[Cc]ache/ 213 | 214 | # Others 215 | ClientBin/ 216 | ~$* 217 | *~ 218 | *.dbmdl 219 | *.dbproj.schemaview 220 | *.jfm 221 | *.pfx 222 | *.publishsettings 223 | orleans.codegen.cs 224 | 225 | # Including strong name files can present a security risk 226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 227 | #*.snk 228 | 229 | # Since there are multiple workflows, uncomment next line to ignore bower_components 230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 231 | #bower_components/ 232 | 233 | # RIA/Silverlight projects 234 | Generated_Code/ 235 | 236 | # Backup & report files from converting an old project file 237 | # to a newer Visual Studio version. Backup files are not needed, 238 | # because we have git ;-) 239 | _UpgradeReport_Files/ 240 | Backup*/ 241 | UpgradeLog*.XML 242 | UpgradeLog*.htm 243 | ServiceFabricBackup/ 244 | *.rptproj.bak 245 | 246 | # SQL Server files 247 | *.mdf 248 | *.ldf 249 | *.ndf 250 | 251 | # Business Intelligence projects 252 | *.rdl.data 253 | *.bim.layout 254 | *.bim_*.settings 255 | *.rptproj.rsuser 256 | 257 | # Microsoft Fakes 258 | FakesAssemblies/ 259 | 260 | # GhostDoc plugin setting file 261 | *.GhostDoc.xml 262 | 263 | # Node.js Tools for Visual Studio 264 | .ntvs_analysis.dat 265 | node_modules/ 266 | 267 | # Visual Studio 6 build log 268 | *.plg 269 | 270 | # Visual Studio 6 workspace options file 271 | *.opt 272 | 273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 274 | *.vbw 275 | 276 | # Visual Studio LightSwitch build output 277 | **/*.HTMLClient/GeneratedArtifacts 278 | **/*.DesktopClient/GeneratedArtifacts 279 | **/*.DesktopClient/ModelManifest.xml 280 | **/*.Server/GeneratedArtifacts 281 | **/*.Server/ModelManifest.xml 282 | _Pvt_Extensions 283 | 284 | # Paket dependency manager 285 | .paket/paket.exe 286 | paket-files/ 287 | 288 | # FAKE - F# Make 289 | .fake/ 290 | 291 | # JetBrains Rider 292 | .idea/ 293 | *.sln.iml 294 | 295 | # CodeRush 296 | .cr/ 297 | 298 | # Python Tools for Visual Studio (PTVS) 299 | __pycache__/ 300 | *.pyc 301 | 302 | # Cake - Uncomment if you are using it 303 | # tools/** 304 | # !tools/packages.config 305 | 306 | # Tabs Studio 307 | *.tss 308 | 309 | # Telerik's JustMock configuration file 310 | *.jmconfig 311 | 312 | # BizTalk build output 313 | *.btp.cs 314 | *.btm.cs 315 | *.odx.cs 316 | *.xsd.cs 317 | 318 | # OpenCover UI analysis results 319 | OpenCover/ 320 | 321 | # Azure Stream Analytics local run output 322 | ASALocalRun/ 323 | 324 | # MSBuild Binary and Structured Log 325 | *.binlog 326 | 327 | # NVidia Nsight GPU debugger configuration file 328 | *.nvuser 329 | 330 | # MFractors (Xamarin productivity tool) working folder 331 | .mfractor/ 332 | 333 | # vscode settings 334 | .vscode/ 335 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vcperf 2 | 3 | ## Overview 4 | 5 | vcperf is a C++ build analysis tool for the MSVC toolchain. It is built on top of C++ Build Insights, MSVC's data collection and analysis platform. Use vcperf to collect build traces that you can view in [Windows Performance Analyzer](https://docs.microsoft.com/windows-hardware/test/wpt/windows-performance-analyzer) (WPA) to understand your build times. An example of a vcperf trace viewed in WPA is shown below. 6 | 7 | ![Overview of what a vcperf trace looks like when viewed in WPA.](media/vcperf-wpa-overview.gif) 8 | 9 | vcperf can also generate flame graphs viewable in Microsoft Edge's trace viewer, as shown below: 10 | 11 | ![Overview of what a vcperf trace looks like when viewed in Microsoft Edge.](media/vcperf-timetrace-overview.png) 12 | 13 | ## How vcperf works 14 | 15 | vcperf makes use of the [Event Tracing for Windows](https://docs.microsoft.com/windows/win32/etw/about-event-tracing) (ETW) relogging interface available in the [C++ Build Insights SDK](https://docs.microsoft.com/cpp/build-insights/reference/sdk/overview?view=vs-2019). This interface allows vcperf to translate an MSVC build trace into a new, customized ETW event format that is suitable for viewing in WPA. The translation process involves determining the context of each event, and emiting new events that include this information. For example, when vcperf emits an event for the code generation time of a function, it also includes the compiler or linker invocation in which the code generation took place. Having this context available allows gaining more insight from the data, such as determining the functions that took longest to generate for one particular invocation. 16 | 17 | ## Customizing vcperf to your needs 18 | 19 | We made vcperf available as an open-source project to allow you to customize it for your own scenarios. Here are some ideas to consider when extending vcperf: 20 | 21 | - Writing the events in a format that works with a different viewer. 22 | - Modifying and filtering the events shown in WPA. 23 | 24 | An example vcperf extension is shown in the following Git commit: [ba2dd59fa1ec43542be3cca3641156cd18dc98df](https://github.com/microsoft/vcperf/commit/ba2dd59fa1ec43542be3cca3641156cd18dc98df). It detects linkers that were restarted during your build due to error conditions, and highlights them in the Build Explorer view. 25 | 26 | ## Contributing 27 | 28 | This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 29 | 30 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. 31 | 32 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 33 | 34 | Note that currently, all tests for vcperf are done internally by Microsoft. All requests for contributions will need to pass these tests prior to integrating the code in the vcperf repo. Please feel free to start a pull request and we will follow up to ask for more information. 35 | 36 | ## Build and run 37 | 38 | Following the instructions below to build and run the product. 39 | 40 | Requirements: 41 | 42 | - Visual Studio 2019 or later. 43 | - Windows 8 and above. 44 | 45 | Build steps: 46 | 47 | 1. Clone the repository on your machine. 48 | 1. Open the Visual Studio solution file. 49 | 1. vcperf relies on the C++ Build Insights SDK NuGet package. Restore NuGet packages and accept the license for the SDK. 50 | 1. Build the desired configuration. Available platforms are x86, x64, and ARM64 and available configurations are *Debug* and *Release*. 51 | 52 | Running vcperf: 53 | 54 | 1. vcperf requires *CppBuildInsights.dll* and *KernelTraceControl.dll* to run. These files are available in the C++ Build Insights NuGet package. When building vcperf, they are automatically copied next to it in the output directory. If you are going to move vcperf around on your machine, please be sure to move these DLL's along with it. 55 | 1. Launch a command prompt. (Must be elevated command prompt to collect CPU sampling) 56 | 1. Use vcperf according to the [Command-line reference](#command-line-reference) below. 57 | 1. Before viewing traces in WPA, follow the instructions in [Installing the C++ Build Insights WPA add-in](#installing-wpa-add-in). 58 | 59 | ## Installing the C++ Build Insights WPA add-in 60 | 61 | Viewing vcperf traces in WPA requires the C++ Build Insights WPA add-in. Install it by following these steps: 62 | 63 | 1. Have WPA installed on your machine, or install the latest version available here: [https://docs.microsoft.com/windows-hardware/get-started/adk-install](https://docs.microsoft.com/windows-hardware/get-started/adk-install). Make sure your WPA version has perf_msvcbuildinsights.dll located at `C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit`. 64 | 1. Make sure you have restored the NuGet packages for the vcperf solution. 65 | 66 | ## Command-line reference 67 | 68 | ### Commands to start and stop traces 69 | 70 | *IMPORTANT: Unless /noadmin is used, the following commands require administrative privileges.* 71 | 72 | | Option | Arguments and description | 73 | |------------------|---------------------------| 74 | | `/start` | `[/noadmin]` `[/nocpusampling]` `[/level1 \| /level2 \| /level3]` `` | 75 | | | Tells *vcperf.exe* to start a trace under the given session name. When running vcperf without admin privileges, there can be more than one active session on a given machine.

If the `/noadmin` option is specified, *vcperf.exe* doesn't require admin privileges. "If the `/noadmin` option is specified, vcperf.exe doesn't require admin privileges, and the `/nocpusampling` flag is ignored."

If the `/nocpusampling` option is specified, *vcperf.exe* doesn't collect CPU samples. It prevents the use of the CPU Usage (Sampled) view in Windows Performance Analyzer, but makes the collected traces smaller.

The `/level1`, `/level2`, or `/level3` option is used to specify which MSVC events to collect, in increasing level of information. Level 3 includes all events. Level 2 includes all events except template instantiation events. Level 1 includes all events except template instantiation, function, and file events. If unspecified, `/level2` is selected by default.

Once tracing is started, *vcperf.exe* returns immediately. Events are collected system-wide for all processes running on the machine. That means that you don't need to build your project from the same command prompt as the one you used to run *vcperf.exe*. For example, you can build your project from Visual Studio. | 76 | | `/stop` | (1) `[/templates]` `` ``
(2) `[/templates]` `` `/timetrace` `` | 77 | | | Stops the trace identified by the given session name. Runs a post-processing step on the trace to generate a file specified by the `` parameter.

If the `/templates` option is specified, also analyze template instantiation events.

(1) Generates a file viewable in Windows Performance Analyzer (WPA). The output file requires a `.etl` extension.
(2) Generates a file viewable in Microsoft Edge's trace viewer ([edge://tracing](edge://tracing)). The output file requires a `.json` extension. | 78 | | `/stopnoanalyze` | `` `` | 79 | | | Stops the trace identified by the given session name and writes the raw, unprocessed data in the specified output file. The resulting file isn't meant to be viewed in WPA.

The post-processing step involved in the `/stop` command can sometimes be lengthy. You can use the `/stopnoanalyze` command to delay this post-processing step. Use the `/analyze` command when you're ready to produce a file viewable in Windows Performance Analyzer. | 80 | 81 | ### Miscellaneous commands 82 | 83 | | Option | Arguments and description | 84 | |------------|---------------------------| 85 | | `/analyze` | (1) `[/templates]` `` ``
(2) `[/templates]` `` `/timetrace` `` | 86 | | | Accepts a raw trace file produced by the `/stopnoanalyze` command. Runs a post-processing step on this trace to generate a file specified by the `` parameter.

If the `/templates` option is specified, also analyze template instantiation events.

(1) Generates a file viewable in Windows Performance Analyzer (WPA). The output file requires a `.etl` extension.
(2) Generates a file viewable in Microsoft Edge's trace viewer ([edge://tracing](edge://tracing)). The output file requires a `.json` extension. | 87 | 88 | ## Overview of the code 89 | 90 | This section briefly describes the source files found in the `src` directory. 91 | 92 | |Item name|Description| 93 | |-|-| 94 | |WPA\Analyzers\ContextBuilder.cpp/.h|Analyzer that determines important information about every event, such as which *cl* or *link* invocation it comes from. This data is used by all *View* components when writing their events in the relogged trace.| 95 | |WPA\Analyzers\ExpensiveTemplateInstantiationCache.cpp/.h|Analyzer that pre-computes the templates with the longest instantiation times. This data is later consumed by *TemplateInstantiationsView*.| 96 | |WPA\Analyzers\MiscellaneousCache.h|Analyzer that can be used to cache miscellaneous data about a trace.| 97 | |WPA\Views\BuildExplorerView.cpp/.h|Component that builds the view responsible for showing overall build times in WPA.| 98 | |WPA\Views\FilesView.cpp/.h|Component that builds the view responsible for showing file parsing times in WPA.| 99 | |WPA\Views\FunctionsView.cpp/.h|Component that builds the view responsible for showing function code generation times in WPA.| 100 | |WPA\Views\TemplateInstantiationsView.cpp/.h|Component that builds the view responsible for showing template instantiation times in WPA.| 101 | |TimeTrace\ExecutionHierarchy.cpp/.h|Analyzer that creates a number of hierarchies out of a trace. Its data is later consumed by *TimeTraceGenerator*.| 102 | |TimeTrace\TimeTraceGenerator.cpp/.h|Component that creates and outputs a `.json` trace viewable in Microsoft Edge's trace viewer.| 103 | |TimeTrace\PackedProcessThreadRemapping.cpp/.h|Component that attempts to keep entries on each hierarchy as close as possible by giving a more *logical distribution* of processes and threads.| 104 | |Commands.cpp/.h|Implements all commands available in vcperf.| 105 | |GenericFields.cpp/.h|Implements the generic field support, used to add custom columns to the views.| 106 | |main.cpp|The program's starting point. This file parses the command line and redirects control to a command in the Commands.cpp/.h file.| 107 | |PayloadBuilder.h|A helper library used to build ETW event payloads prior to injecting them in the relogged trace.| 108 | |Utility.h|Contains common types used everywhere.| 109 | |VcperfBuildInsights.h|A wrapper around CppBuildInsights.hpp, used mainly to set up namespace aliases.| 110 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /azure-pipelines/build.yml: -------------------------------------------------------------------------------- 1 | trigger: none 2 | name: $(date:yyMMdd)$(rev:rr) 3 | resources: 4 | repositories: 5 | - repository: 1ESPipelineTemplates 6 | type: git 7 | name: 1ESPipelineTemplates/1ESPipelineTemplates 8 | ref: refs/tags/release 9 | variables: 10 | - name: ApiScanClientId 11 | value: d318cba7-db4d-4fb3-99e1-01879cb74e91 12 | extends: 13 | template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates 14 | parameters: 15 | sdl: 16 | policheck: 17 | enabled: true 18 | pool: 19 | name: VSEngSS-MicroBuild2022-1ES 20 | customBuildTags: 21 | - $(custom-build-tag) 22 | stages: 23 | - stage: stage 24 | jobs: 25 | - job: Job_1 26 | displayName: Release 27 | cancelTimeoutInMinutes: 1 28 | templateContext: 29 | outputs: 30 | - output: nuget 31 | displayName: Publish NuGet Package 32 | packageParentPath: $(Build.ArtifactStagingDirectory) 33 | packagesToPush: $(Build.ArtifactStagingDirectory)/*.nupkg 34 | publishVstsFeed: $(feed-publish) 35 | codeSignValidationEnabled: false 36 | steps: 37 | - checkout: self 38 | clean: true 39 | persistCredentials: True 40 | - task: PowerShell@2 41 | displayName: Set Version String 42 | inputs: 43 | filePath: $(Build.SourcesDirectory)\set_version.ps1 44 | arguments: -BuildNumber $(Build.BuildNumber) 45 | - task: NuGetCommand@2 46 | displayName: NuGet restore 47 | inputs: 48 | solution: vcperf.sln 49 | - task: VSBuild@1 50 | displayName: Build x64 vcperf 51 | inputs: 52 | solution: vcperf.sln 53 | msbuildArgs: /p:ExternalPreprocessorDefinitions="VCPERF_VERSION=$(VersionString)" 54 | platform: x64 55 | configuration: Release 56 | clean: true 57 | createLogFile: true 58 | logFileVerbosity: diagnostic 59 | - task: VSBuild@1 60 | displayName: Build x86 vcperf 61 | inputs: 62 | solution: vcperf.sln 63 | msbuildArgs: /p:ExternalPreprocessorDefinitions="VCPERF_VERSION=$(VersionString)" 64 | platform: x86 65 | configuration: Release 66 | clean: true 67 | createLogFile: true 68 | logFileVerbosity: diagnostic 69 | - task: VSBuild@1 70 | displayName: Build ARM64 vcperf 71 | inputs: 72 | solution: vcperf.sln 73 | msbuildArgs: /p:ExternalPreprocessorDefinitions="VCPERF_VERSION=$(VersionString)" 74 | platform: ARM64 75 | configuration: Release 76 | createLogFile: true 77 | logFileVerbosity: diagnostic 78 | - task: securedevelopmentteam.vss-secure-development-tools.build-task-prefast.SDLNativeRules@3 79 | displayName: 'Run the PREfast SDL Native Rules for MSBuild x86, x64 & arm64' 80 | env: 81 | SYSTEM_ACCESSTOKEN: $(System.AccessToken) 82 | inputs: 83 | publishXML: true 84 | userProvideBuildInfo: auto 85 | setupCommandlinePicker: vs2022 86 | - task: NuGetCommand@2 87 | displayName: Create NuGet Package 88 | inputs: 89 | command: custom 90 | arguments: pack $(Build.SourcesDirectory)\vcperf.nuspec -Tool -Version $(VersionString) -BasePath $(Build.SourcesDirectory) -NoPackageAnalysis -OutputDirectory $(Build.ArtifactStagingDirectory) 91 | - task: APIScan@2 92 | displayName: Run APIScan 93 | inputs: 94 | softwareFolder: '$(Build.SourcesDirectory)/out/Release/x64;$(Build.SourcesDirectory)/out/Release/x86' 95 | softwareName: 'VCPerf' 96 | softwareVersionNum: '$(VersionString)' 97 | symbolsFolder: '$(Build.SourcesDirectory)' 98 | isLargeApp: false 99 | preserveTempFiles: true 100 | toolVersion: LatestPreRelease 101 | env: 102 | AzureServicesAuthConnectionString: runAs=App;AppId=$(ApiScanClientId) 103 | -------------------------------------------------------------------------------- /media/vcperf-timetrace-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vcperf/572c5f2e40198e59ebe8d1737dc3bbbd28135bf0/media/vcperf-timetrace-overview.png -------------------------------------------------------------------------------- /media/vcperf-wpa-overview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vcperf/572c5f2e40198e59ebe8d1737dc3bbbd28135bf0/media/vcperf-wpa-overview.gif -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /set_version.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [String]$BuildNumber = "" 3 | ) 4 | 5 | $VersionMajor = "2" 6 | $VersionMinor = "4" 7 | 8 | Write-Host "##vso[task.setvariable variable=VersionString;]$VersionMajor.$VersionMinor.$BuildNumber" 9 | -------------------------------------------------------------------------------- /src/Commands.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Commands.h" 3 | 4 | #include 5 | 6 | #include "VcperfBuildInsights.h" 7 | 8 | #include "WPA\Analyzers\ExpensiveTemplateInstantiationCache.h" 9 | #include "WPA\Analyzers\ContextBuilder.h" 10 | #include "WPA\Analyzers\MiscellaneousCache.h" 11 | #include "WPA\Views\BuildExplorerView.h" 12 | #include "WPA\Views\FunctionsView.h" 13 | #include "WPA\Views\FilesView.h" 14 | #include "WPA\Views\TemplateInstantiationsView.h" 15 | #include "TimeTrace\ExecutionHierarchy.h" 16 | #include "TimeTrace\TimeTraceGenerator.h" 17 | 18 | using namespace Microsoft::Cpp::BuildInsights; 19 | 20 | namespace vcperf 21 | { 22 | 23 | const wchar_t* ResultCodeToString(RESULT_CODE rc) 24 | { 25 | switch (rc) 26 | { 27 | case RESULT_CODE_SUCCESS: 28 | return L"SUCCESS"; 29 | 30 | case RESULT_CODE_FAILURE_ANALYSIS_ERROR: 31 | return L"FAILURE_ANALYSIS_ERROR"; 32 | 33 | case RESULT_CODE_FAILURE_CANCELLED: 34 | return L"FAILURE_CANCELLED"; 35 | 36 | case RESULT_CODE_FAILURE_INVALID_INPUT_LOG_FILE: 37 | return L"FAILURE_INVALID_INPUT_LOG_FILE"; 38 | 39 | case RESULT_CODE_FAILURE_INVALID_OUTPUT_LOG_FILE: 40 | return L"FAILURE_INVALID_OUTPUT_LOG_FILE"; 41 | 42 | case RESULT_CODE_FAILURE_MISSING_ANALYSIS_CALLBACK: 43 | return L"FAILURE_MISSING_ANALYSIS_CALLBACK"; 44 | 45 | case RESULT_CODE_FAILURE_MISSING_RELOG_CALLBACK: 46 | return L"FAILURE_MISSING_RELOG_CALLBACK"; 47 | 48 | case RESULT_CODE_FAILURE_OPEN_INPUT_TRACE: 49 | return L"FAILURE_OPEN_INPUT_TRACE"; 50 | 51 | case RESULT_CODE_FAILURE_PROCESS_TRACE: 52 | return L"FAILURE_PROCESS_TRACE"; 53 | 54 | case RESULT_CODE_FAILURE_START_RELOGGER: 55 | return L"FAILURE_START_RELOGGER"; 56 | 57 | case RESULT_CODE_FAILURE_DROPPED_EVENTS: 58 | return L"FAILURE_DROPPED_EVENTS"; 59 | 60 | case RESULT_CODE_FAILURE_UNSUPPORTED_OS: 61 | return L"FAILURE_UNSUPPORTED_OS"; 62 | 63 | case RESULT_CODE_FAILURE_INVALID_TRACING_SESSION_NAME: 64 | return L"FAILURE_INVALID_TRACING_SESSION_NAME"; 65 | 66 | case RESULT_CODE_FAILURE_INSUFFICIENT_PRIVILEGES: 67 | return L"FAILURE_INSUFFICIENT_PRIVILEGES"; 68 | 69 | case RESULT_CODE_FAILURE_GENERATE_GUID: 70 | return L"FAILURE_GENERATE_GUID"; 71 | 72 | case RESULT_CODE_FAILURE_OBTAINING_TEMP_DIRECTORY: 73 | return L"FAILURE_OBTAINING_TEMP_DIRECTORY"; 74 | 75 | case RESULT_CODE_FAILURE_CREATE_TEMPORARY_DIRECTORY: 76 | return L"FAILURE_CREATE_TEMPORARY_DIRECTORY"; 77 | 78 | case RESULT_CODE_FAILURE_START_SYSTEM_TRACE: 79 | return L"FAILURE_START_SYSTEM_TRACE"; 80 | 81 | case RESULT_CODE_FAILURE_START_MSVC_TRACE: 82 | return L"FAILURE_START_MSVC_TRACE"; 83 | 84 | case RESULT_CODE_FAILURE_STOP_MSVC_TRACE: 85 | return L"FAILURE_STOP_MSVC_TRACE"; 86 | 87 | case RESULT_CODE_FAILURE_STOP_SYSTEM_TRACE: 88 | return L"FAILURE_STOP_SYSTEM_TRACE"; 89 | 90 | case RESULT_CODE_FAILURE_SESSION_DIRECTORY_RESOLUTION: 91 | return L"FAILURE_SESSION_DIRECTORY_RESOLUTION"; 92 | 93 | case RESULT_CODE_FAILURE_MSVC_TRACE_FILE_NOT_FOUND: 94 | return L"FAILURE_MSVC_TRACE_FILE_NOT_FOUND"; 95 | 96 | case RESULT_CODE_FAILURE_MERGE_TRACES: 97 | return L"FAILURE_MERGE_TRACES"; 98 | } 99 | 100 | return L"FAILURE_UNKNOWN_ERROR"; 101 | } 102 | 103 | void PrintTraceStatistics(const TRACING_SESSION_STATISTICS& stats) 104 | { 105 | std::wcout << L"Dropped MSVC events: " << stats.MSVCEventsLost << std::endl; 106 | std::wcout << L"Dropped MSVC buffers: " << stats.MSVCBuffersLost << std::endl; 107 | std::wcout << L"Dropped system events: " << stats.SystemEventsLost << std::endl; 108 | std::wcout << L"Dropped system buffers: " << stats.SystemBuffersLost << std::endl; 109 | } 110 | 111 | void PrintPrivacyNotice(const std::filesystem::path& outputFile) 112 | { 113 | std::error_code ec; 114 | auto absolutePath = std::filesystem::absolute(outputFile, ec); 115 | 116 | std::wcout << "The trace "; 117 | 118 | if (!ec) 119 | { 120 | std::wcout << "\"" << absolutePath.c_str() << "\" "; 121 | } 122 | else 123 | { 124 | std::wcout << "\"" << outputFile.c_str() << "\" "; 125 | } 126 | 127 | std::wcout << L"may contain personally identifiable information. This includes, but is not limited to, " 128 | L"paths of files that were accessed and names of processes that were running during the collection. " 129 | L"Please be aware of this when sharing this trace with others." << std::endl; 130 | } 131 | 132 | void PrintError(RESULT_CODE failureCode) 133 | { 134 | switch (failureCode) 135 | { 136 | case RESULT_CODE_FAILURE_INSUFFICIENT_PRIVILEGES: 137 | std::wcout << "This operation requires administrator privileges."; 138 | break; 139 | 140 | case RESULT_CODE_FAILURE_DROPPED_EVENTS: 141 | std::wcout << "Events were dropped during the trace. Please try recollecting the trace."; 142 | break; 143 | 144 | case RESULT_CODE_FAILURE_UNSUPPORTED_OS: 145 | std::wcout << "The version of Microsoft Visual C++ Build Insights that vcperf is using " 146 | "does not support the version of the operating system that the trace was collected on. " 147 | "Please try updating vcperf to the latest version."; 148 | break; 149 | case RESULT_CODE_FAILURE_NO_CONTEXT_INFO_AVAILABLE: 150 | std::wcout << "You are using a version of the MSVC toolset that does not support the `/noadmin` option. " 151 | "Please try updating your MSVC toolset to at least 16.11."; 152 | break; 153 | case RESULT_CODE_FAILURE_START_SYSTEM_TRACE: 154 | case RESULT_CODE_FAILURE_START_MSVC_TRACE: 155 | std::wcout << "A trace that is currently being collected on your system is preventing vcperf " 156 | "from starting a new one. This can occur if you forgot to stop a vcperf trace prior to " 157 | "running the start command, or if processes other than vcperf have started ETW traces of " 158 | "their own. Please try running the vcperf /stop or /stopnoanalyze commands on your previously " 159 | "started trace. If you do not remember the session name that was used for starting a previous " 160 | "vcperf trace, or if you don't recall starting one at all, you can use the 'tracelog -l' command " 161 | "from an elevated command prompt to list all ongoing tracing sessions on your system. Your currently " 162 | "ongoing vcperf trace will show up as MSVC_BUILD_INSIGHTS_SESSION_. " 163 | "You can then issue a vcperf /stop or /stopnoanalyze command with the identified session name (the " 164 | "part between the angle brackets). If no MSVC_BUILD_INSIGHTS_SESSION_ is found, it could mean a kernel " 165 | "ETW trace is currently being collected. This trace will show up as 'NT Kernel Logger' in your tracelog " 166 | "output, and will also prevent you from starting a new trace. You can stop the 'NT Kernel Logger' session " 167 | "by running 'xperf -stop' from an elevated command prompt."; 168 | break; 169 | 170 | default: 171 | std::wcout << L"ERROR CODE: " << ResultCodeToString(failureCode); 172 | } 173 | 174 | std::wcout << std::endl; 175 | } 176 | 177 | RESULT_CODE StopToWPA(const std::wstring& sessionName, const std::filesystem::path& outputFile, bool analyzeTemplates, 178 | TRACING_SESSION_STATISTICS& statistics) 179 | { 180 | ExpensiveTemplateInstantiationCache etic{ analyzeTemplates }; 181 | ContextBuilder cb; 182 | MiscellaneousCache mc; 183 | BuildExplorerView bev{ &cb, &mc }; 184 | FunctionsView funcv{ &cb, &mc }; 185 | FilesView fv{ &cb, &mc }; 186 | TemplateInstantiationsView tiv{ &cb, &etic, &mc, analyzeTemplates }; 187 | 188 | auto analyzerGroup = MakeStaticAnalyzerGroup(&cb, &etic, &mc); 189 | auto reloggerGroup = MakeStaticReloggerGroup(&etic, &mc, &cb, &bev, &funcv, &fv, &tiv); 190 | 191 | unsigned long long systemEventsRetentionFlags = RELOG_RETENTION_SYSTEM_EVENT_FLAGS_CPU_SAMPLES; 192 | 193 | int analysisPassCount = analyzeTemplates ? 2 : 1; 194 | 195 | return StopAndRelogTracingSession(sessionName.c_str(), outputFile.c_str(), 196 | &statistics, analysisPassCount, systemEventsRetentionFlags, analyzerGroup, reloggerGroup); 197 | } 198 | 199 | RESULT_CODE StopToTimeTrace(const std::wstring& sessionName, const std::filesystem::path& outputFile, bool analyzeTemplates, 200 | TRACING_SESSION_STATISTICS& statistics) 201 | { 202 | ExecutionHierarchy::Filter f{ analyzeTemplates, 203 | std::chrono::milliseconds(10), 204 | std::chrono::milliseconds(10) }; 205 | ExecutionHierarchy eh{ f }; 206 | TimeTraceGenerator ttg{ &eh, outputFile }; 207 | 208 | auto analyzerGroup = MakeStaticAnalyzerGroup(&eh, &ttg); 209 | int analysisPassCount = 1; 210 | 211 | return StopAndAnalyzeTracingSession(sessionName.c_str(), analysisPassCount, &statistics, analyzerGroup); 212 | } 213 | 214 | RESULT_CODE AnalyzeToWPA(const std::filesystem::path& inputFile, const std::filesystem::path& outputFile, bool analyzeTemplates) 215 | { 216 | ExpensiveTemplateInstantiationCache etic{ analyzeTemplates }; 217 | ContextBuilder cb; 218 | MiscellaneousCache mc; 219 | BuildExplorerView bev{ &cb, &mc }; 220 | FunctionsView funcv{ &cb, &mc }; 221 | FilesView fv{ &cb, &mc }; 222 | TemplateInstantiationsView tiv{ &cb, &etic, &mc, analyzeTemplates }; 223 | 224 | auto analyzerGroup = MakeStaticAnalyzerGroup(&cb, &etic, &mc); 225 | auto reloggerGroup = MakeStaticReloggerGroup(&etic, &mc, &cb, &bev, &funcv, &fv, &tiv); 226 | 227 | unsigned long long systemEventsRetentionFlags = RELOG_RETENTION_SYSTEM_EVENT_FLAGS_CPU_SAMPLES; 228 | 229 | int analysisPassCount = analyzeTemplates ? 2 : 1; 230 | 231 | return Relog(inputFile.c_str(), outputFile.c_str(), analysisPassCount, 232 | systemEventsRetentionFlags, analyzerGroup, reloggerGroup); 233 | } 234 | 235 | RESULT_CODE AnalyzeToTimeTrace(const std::filesystem::path& inputFile, const std::filesystem::path& outputFile, bool analyzeTemplates) 236 | { 237 | ExecutionHierarchy::Filter f{ analyzeTemplates, 238 | std::chrono::milliseconds(10), 239 | std::chrono::milliseconds(10) }; 240 | ExecutionHierarchy eh{ f }; 241 | TimeTraceGenerator ttg{ &eh, outputFile }; 242 | 243 | auto analyzerGroup = MakeStaticAnalyzerGroup(&eh, &ttg); 244 | int analysisPassCount = 1; 245 | 246 | return Analyze(inputFile.c_str(), analysisPassCount, analyzerGroup); 247 | } 248 | 249 | HRESULT DoStart(const std::wstring& sessionName, bool admin, bool cpuSampling, VerbosityLevel verbosityLevel) 250 | { 251 | TRACING_SESSION_OPTIONS options{}; 252 | 253 | switch (verbosityLevel) 254 | { 255 | case VerbosityLevel::VERBOSE: 256 | options.MsvcEventFlags |= TRACING_SESSION_MSVC_EVENT_FLAGS_FRONTEND_TEMPLATE_INSTANTIATIONS; 257 | 258 | case VerbosityLevel::MEDIUM: 259 | options.MsvcEventFlags |= TRACING_SESSION_MSVC_EVENT_FLAGS_FRONTEND_FILES; 260 | options.MsvcEventFlags |= TRACING_SESSION_MSVC_EVENT_FLAGS_BACKEND_FUNCTIONS; 261 | 262 | case VerbosityLevel::LIGHT: 263 | options.MsvcEventFlags |= TRACING_SESSION_MSVC_EVENT_FLAGS_BASIC; 264 | } 265 | 266 | if (cpuSampling) { 267 | options.SystemEventFlags |= TRACING_SESSION_SYSTEM_EVENT_FLAGS_CPU_SAMPLES; 268 | } 269 | 270 | if (!admin) { 271 | options.SystemEventFlags = 0; 272 | } 273 | 274 | std::wcout << L"Starting tracing session " << sessionName << L"..." << std::endl; 275 | 276 | auto rc = StartTracingSession(sessionName.c_str(), options); 277 | 278 | if (rc != RESULT_CODE_SUCCESS) 279 | { 280 | std::wcout << "Failed to start trace." << std::endl; 281 | PrintError(rc); 282 | 283 | return E_FAIL; 284 | } 285 | 286 | std::wcout << L"Tracing session started successfully!" << std::endl; 287 | 288 | return S_OK; 289 | } 290 | 291 | 292 | HRESULT DoStop(const std::wstring& sessionName, const std::filesystem::path& outputFile, bool analyzeTemplates, bool generateTimeTrace) 293 | { 294 | std::wcout << L"Stopping and analyzing tracing session " << sessionName << L"..." << std::endl; 295 | 296 | TRACING_SESSION_STATISTICS statistics{}; 297 | RESULT_CODE rc; 298 | if (!generateTimeTrace) { 299 | rc = StopToWPA(sessionName, outputFile, analyzeTemplates, statistics); 300 | } 301 | else { 302 | rc = StopToTimeTrace(sessionName, outputFile, analyzeTemplates, statistics); 303 | } 304 | 305 | PrintTraceStatistics(statistics); 306 | 307 | if (rc != RESULT_CODE_SUCCESS) 308 | { 309 | std::wcout << "Failed to stop trace." << std::endl; 310 | PrintError(rc); 311 | 312 | return E_FAIL; 313 | } 314 | 315 | PrintPrivacyNotice(outputFile); 316 | std::wcout << L"Tracing session stopped successfully!" << std::endl; 317 | 318 | return S_OK; 319 | } 320 | 321 | HRESULT DoStopNoAnalyze(const std::wstring& sessionName, const std::filesystem::path& outputFile) 322 | { 323 | TRACING_SESSION_STATISTICS statistics{}; 324 | 325 | std::wcout << L"Stopping tracing session " << sessionName << L"..." << std::endl; 326 | 327 | auto rc = StopTracingSession(sessionName.c_str(), outputFile.c_str(), &statistics); 328 | 329 | PrintTraceStatistics(statistics); 330 | 331 | if (rc != RESULT_CODE_SUCCESS) 332 | { 333 | std::wcout << "Failed to stop trace." << std::endl; 334 | PrintError(rc); 335 | 336 | return E_FAIL; 337 | } 338 | 339 | PrintPrivacyNotice(outputFile); 340 | std::wcout << L"Tracing session stopped successfully!" << std::endl; 341 | 342 | return S_OK; 343 | } 344 | 345 | HRESULT DoAnalyze(const std::filesystem::path& inputFile, const std::filesystem::path& outputFile, bool analyzeTemplates, bool generateTimeTrace) 346 | { 347 | std::wcout << L"Analyzing..." << std::endl; 348 | 349 | RESULT_CODE rc; 350 | if (!generateTimeTrace) { 351 | rc = AnalyzeToWPA(inputFile, outputFile, analyzeTemplates); 352 | } 353 | else { 354 | rc = AnalyzeToTimeTrace(inputFile, outputFile, analyzeTemplates); 355 | } 356 | 357 | if (rc != RESULT_CODE_SUCCESS) 358 | { 359 | std::wcout << "Failed to analyze trace." << std::endl; 360 | PrintError(rc); 361 | 362 | return E_FAIL; 363 | } 364 | 365 | PrintPrivacyNotice(outputFile); 366 | std::wcout << L"Analysis completed successfully!" << std::endl; 367 | 368 | return S_OK; 369 | } 370 | 371 | } // namespace vcperf 372 | -------------------------------------------------------------------------------- /src/Commands.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace vcperf 7 | { 8 | 9 | enum class VerbosityLevel 10 | { 11 | INVALID, 12 | LIGHT, 13 | MEDIUM, 14 | VERBOSE 15 | }; 16 | 17 | HRESULT DoStart(const std::wstring& sessionName, bool admin, bool cpuSampling, VerbosityLevel verbosityLevel); 18 | HRESULT DoStop(const std::wstring& sessionName, const std::filesystem::path& outputFile, bool analyzeTemplates = false, bool generateTimeTrace = false); 19 | HRESULT DoStopNoAnalyze(const std::wstring& sessionName, const std::filesystem::path& outputFile); 20 | HRESULT DoAnalyze(const std::filesystem::path& inputFile, const std::filesystem::path& outputFile, bool analyzeTemplates = false, bool generateTimeTrace = false); 21 | 22 | } // namespace vcperf -------------------------------------------------------------------------------- /src/CppBuildInsightsEtw.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 21 | 22 | 25 | 26 | 29 | 30 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 63 | 64 | 78 | 79 | 89 | 90 | 100 | 101 | 102 | 103 | 104 | 105 | 113 | 114 | 122 | 123 | 131 | 132 | 140 | 141 | 142 | 143 | 144 | 145 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 167 | 168 | 182 | 183 | 184 | 185 | 186 | 187 | 195 | 196 | 204 | 205 | 206 | 207 | 208 | 209 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 228 | 229 | 241 | 242 | 245 | 246 | 260 | 261 | 275 | 276 | 280 | 281 | 282 | 283 | 284 | 285 | 293 | 294 | 302 | 303 | 311 | 312 | 320 | 321 | 329 | 330 | 338 | 339 | 340 | 341 | 342 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 361 | 362 | 373 | 374 | 375 | 376 | 377 | 378 | 386 | 387 | 395 | 396 | 397 | 398 | 399 | 400 | 403 | 404 | 407 | 408 | 411 | 412 | 415 | 416 | 417 | 418 | 419 | 420 | 427 | 428 | 435 | 436 | 443 | 444 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | -------------------------------------------------------------------------------- /src/GenericFields.cpp: -------------------------------------------------------------------------------- 1 | #include "VcperfBuildInsights.h" 2 | #include "GenericFields.h" 3 | #include "PayloadBuilder.h" 4 | #include "CppBuildInsightsEtw.h" 5 | #include "Utility.h" 6 | 7 | using namespace Microsoft::Cpp::BuildInsights; 8 | 9 | namespace vcperf 10 | { 11 | 12 | template 13 | void LogGenericField(TField value, PCEVENT_DESCRIPTOR desc, const Event& e, const void* relogSession) 14 | { 15 | Payload p = PayloadBuilder::Build(value); 16 | 17 | InjectEvent(relogSession, &CppBuildInsightsGuid, desc, 18 | e.ProcessId(), e.ThreadId(), e.ProcessorIndex(), 19 | e.Timestamp(), p.GetData(), (unsigned long)p.Size()); 20 | } 21 | 22 | void LogGenericStringField(const char* value, const Event& e, const void* relogSession) 23 | { 24 | LogGenericField(value, &CppBuildInsightsAnsiStringGenericField, e, relogSession); 25 | } 26 | 27 | void LogGenericStringField(const wchar_t* value, const Event& e, const void* relogSession) 28 | { 29 | LogGenericField(value, &CppBuildInsightsUnicodeStringGenericField, e, relogSession); 30 | } 31 | 32 | void LogGenericUTF8StringField(const char* value, const Event& e, const void* relogSession) 33 | { 34 | LogGenericField(value, &CppBuildInsightsUTF8StringGenericField, e, relogSession); 35 | } 36 | 37 | void LogGenericIntegerField(int64_t value, const Event& e, const void* relogSession) 38 | { 39 | LogGenericField(value, &CppBuildInsightsIntegerGenericField, e, relogSession); 40 | } 41 | 42 | } // namespace vcperf -------------------------------------------------------------------------------- /src/GenericFields.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "VcperfBuildInsights.h" 6 | 7 | namespace vcperf 8 | { 9 | 10 | void LogGenericStringField(const char* value, const BI::Event& e, const void* relogSession); 11 | void LogGenericStringField(const wchar_t* value, const BI::Event& e, const void* relogSession); 12 | void LogGenericUTF8StringField(const char* value, const BI::Event& e, const void* relogSession); 13 | void LogGenericIntegerField(int64_t value, const BI::Event& e, const void* relogSession); 14 | 15 | } // namespace vcperf -------------------------------------------------------------------------------- /src/PayloadBuilder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace vcperf 8 | { 9 | 10 | class Payload 11 | { 12 | public: 13 | Payload(): 14 | payloadData{nullptr}, 15 | payloadByteSize{0} 16 | {} 17 | 18 | Payload(uint8_t* mem, size_t byteSize) : 19 | payloadData(mem), payloadByteSize(byteSize) { 20 | 21 | } 22 | 23 | ~Payload() { 24 | free(payloadData); 25 | } 26 | 27 | Payload(const Payload&) = delete; 28 | Payload& operator=(const Payload&) = delete; 29 | 30 | Payload& operator=(Payload&& rhs) noexcept 31 | { 32 | payloadData = rhs.payloadData; 33 | payloadByteSize = rhs.payloadByteSize; 34 | 35 | rhs.payloadByteSize = 0; 36 | rhs.payloadData = nullptr; 37 | 38 | return *this; 39 | } 40 | 41 | uint8_t* GetData() { return payloadData; } 42 | size_t Size() const { return payloadByteSize; } 43 | 44 | private: 45 | uint8_t* payloadData; 46 | size_t payloadByteSize; 47 | }; 48 | 49 | template 50 | class PayloadBuilder 51 | { 52 | typedef std::array FieldSizeArray; 53 | 54 | public: 55 | 56 | static Payload Build(Fields... fields) { 57 | 58 | FieldSizeArray fieldSizes; 59 | 60 | ComputeFieldSizes<0>(fieldSizes, fields...); 61 | 62 | size_t totalSize = std::accumulate(begin(fieldSizes), end(fieldSizes), size_t{}); 63 | 64 | if (totalSize == 0) { 65 | return { nullptr, 0 }; 66 | } 67 | 68 | uint8_t* mem = static_cast(malloc(totalSize)); 69 | 70 | CopyFields<0>(mem, fieldSizes, fields...); 71 | 72 | return { mem, totalSize }; 73 | } 74 | 75 | private: 76 | 77 | template 78 | static void ComputeFieldSizes(FieldSizeArray& sizes, Field field, OtherFields... otherFields) { 79 | 80 | static_assert(std::is_integral_v || std::is_floating_point_v, 81 | "Only integral, floating-point, and pointer types can be used for payload fields."); 82 | 83 | sizes[fieldIndex] = sizeof(Field); 84 | 85 | ComputeFieldSizes(sizes, otherFields...); 86 | } 87 | 88 | template 89 | static void ComputeFieldSizes(FieldSizeArray& sizes, Field* field, OtherFields... otherFields) { 90 | 91 | sizes[fieldIndex] = ComputePointerFieldSize(field); 92 | 93 | ComputeFieldSizes(sizes, otherFields...); 94 | } 95 | 96 | template 97 | static void ComputeFieldSizes(FieldSizeArray& sizes) { 98 | } 99 | 100 | static size_t ComputePointerFieldSize(const wchar_t* wideString) { 101 | return (wcslen(wideString) + 1) * sizeof(wchar_t); 102 | } 103 | 104 | static size_t ComputePointerFieldSize(const char* string) { 105 | return (strlen(string) + 1) * sizeof(char); 106 | } 107 | 108 | 109 | template 110 | static void CopyFields(uint8_t* dataPtr, FieldSizeArray& sizes, Field field, OtherFields... otherFields) { 111 | 112 | static_assert(std::is_integral_v || std::is_floating_point_v, 113 | "Only integral, floating-point, and pointer types can be used for payload fields."); 114 | 115 | memcpy(dataPtr, &field, sizes[fieldIndex]); 116 | 117 | CopyFields(dataPtr + sizes[fieldIndex], sizes, otherFields...); 118 | } 119 | 120 | template 121 | static void CopyFields(uint8_t* dataPtr, FieldSizeArray& sizes, Field* field, OtherFields... otherFields) { 122 | 123 | memcpy(dataPtr, field, sizes[fieldIndex]); 124 | 125 | CopyFields(dataPtr + sizes[fieldIndex], sizes, otherFields...); 126 | } 127 | 128 | template 129 | static void CopyFields(uint8_t* dataPtr, FieldSizeArray& sizes) { 130 | } 131 | }; 132 | 133 | } // namespace vcperf -------------------------------------------------------------------------------- /src/TimeTrace/ExecutionHierarchy.cpp: -------------------------------------------------------------------------------- 1 | #include "ExecutionHierarchy.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace Microsoft::Cpp::BuildInsights; 8 | using namespace Activities; 9 | using namespace SimpleEvents; 10 | 11 | using namespace vcperf; 12 | 13 | namespace 14 | { 15 | std::string ToString(const std::wstring& wstring) 16 | { 17 | assert(!wstring.empty()); 18 | 19 | const UINT codePage = CP_UTF8; 20 | int requiredSize = WideCharToMultiByte(codePage, 0, wstring.c_str(), static_cast(wstring.size()), 21 | NULL, 0, NULL, NULL); 22 | std::string convertedString = std::string(requiredSize, '\0'); 23 | WideCharToMultiByte(codePage, 0, wstring.c_str(), static_cast(wstring.size()), 24 | &convertedString[0], requiredSize, NULL, NULL); 25 | 26 | return convertedString; 27 | } 28 | 29 | long long ConvertTickPrecision(long long ticks, long long fromFreq, long long toFreq) 30 | { 31 | if (fromFreq <= 0) { 32 | return 0; 33 | } 34 | 35 | long long p1 = (ticks / fromFreq) * toFreq; 36 | long long p2 = (ticks % fromFreq) * toFreq / fromFreq; 37 | 38 | return p1 + p2; 39 | } 40 | 41 | std::chrono::nanoseconds ConvertTime(long long ticks, long long frequency) 42 | { 43 | return std::chrono::nanoseconds{ ConvertTickPrecision(ticks, frequency, std::chrono::nanoseconds::period::den) }; 44 | } 45 | 46 | unsigned int CountDigits(size_t number) 47 | { 48 | unsigned int digits; 49 | for (digits = 0; number > 0; ++digits) 50 | { 51 | number /= 10; 52 | } 53 | 54 | return digits; 55 | } 56 | 57 | std::string PrePadNumber(size_t number, char paddingCharacter, size_t totalExpectedLength) 58 | { 59 | std::string asPaddedString = std::to_string(number); 60 | 61 | if (asPaddedString.size() < totalExpectedLength) { 62 | asPaddedString.insert(0, totalExpectedLength - asPaddedString.size(), paddingCharacter); 63 | } 64 | 65 | return asPaddedString; 66 | } 67 | 68 | } // anonymous namespace 69 | 70 | bool ExecutionHierarchy::Entry::OverlapsWith(const Entry* other) const 71 | { 72 | return StartTimestamp < other->StopTimestamp && 73 | other->StartTimestamp < StopTimestamp; 74 | } 75 | 76 | ExecutionHierarchy::ExecutionHierarchy(const Filter& filter) : 77 | entries_{}, 78 | roots_{}, 79 | filter_{filter}, 80 | fileInputsOutputsPerInvocation_{}, 81 | symbolNames_{}, 82 | unresolvedTemplateInstantiationsPerSymbol_{} 83 | { 84 | } 85 | 86 | AnalysisControl ExecutionHierarchy::OnStartActivity(const EventStack& eventStack) 87 | { 88 | if ( MatchEventStackInMemberFunction(eventStack, this, &ExecutionHierarchy::OnNestedActivity) 89 | || MatchEventInMemberFunction(eventStack.Back(), this, &ExecutionHierarchy::OnRootActivity)) 90 | {} 91 | 92 | if ( MatchEventInMemberFunction(eventStack.Back(), this, &ExecutionHierarchy::OnInvocation) 93 | || MatchEventInMemberFunction(eventStack.Back(), this, &ExecutionHierarchy::OnFrontEndFile) 94 | || MatchEventStackInMemberFunction(eventStack, this, &ExecutionHierarchy::OnThread)) 95 | {} 96 | 97 | return AnalysisControl::CONTINUE; 98 | } 99 | 100 | AnalysisControl ExecutionHierarchy::OnStopActivity(const EventStack& eventStack) 101 | { 102 | MatchEventInMemberFunction(eventStack.Back(), this, &ExecutionHierarchy::OnFinishActivity); 103 | 104 | // apply filtering 105 | if ( MatchEventInMemberFunction(eventStack.Back(), this, &ExecutionHierarchy::OnFinishInvocation) 106 | || MatchEventStackInMemberFunction(eventStack, this, &ExecutionHierarchy::OnFinishFunction) 107 | || MatchEventStackInMemberFunction(eventStack, this, &ExecutionHierarchy::OnFinishTemplateInstantiation)) 108 | {} 109 | 110 | return AnalysisControl::CONTINUE; 111 | } 112 | 113 | AnalysisControl ExecutionHierarchy::OnSimpleEvent(const EventStack& eventStack) 114 | { 115 | if ( MatchEventInMemberFunction(eventStack.Back(), this, &ExecutionHierarchy::OnSymbolName) 116 | || MatchEventStackInMemberFunction(eventStack, this, &ExecutionHierarchy::OnCommandLine) 117 | || MatchEventStackInMemberFunction(eventStack, this, &ExecutionHierarchy::OnEnvironmentVariable) 118 | || MatchEventStackInMemberFunction(eventStack, this, &ExecutionHierarchy::OnFileInput) 119 | || MatchEventStackInMemberFunction(eventStack, this, &ExecutionHierarchy::OnFileOutput)) 120 | {} 121 | 122 | return AnalysisControl::CONTINUE; 123 | } 124 | 125 | const ExecutionHierarchy::Entry* ExecutionHierarchy::GetEntry(unsigned long long id) const 126 | { 127 | auto it = entries_.find(id); 128 | return it != entries_.end() ? &it->second : nullptr; 129 | } 130 | 131 | void ExecutionHierarchy::OnRootActivity(const Activity& root) 132 | { 133 | Entry* entry = CreateEntry(root); 134 | 135 | assert(std::find(roots_.begin(), roots_.end(), entry) == roots_.end()); 136 | roots_.push_back(entry); 137 | } 138 | 139 | void ExecutionHierarchy::OnNestedActivity(const Activity& parent, const Activity& child) 140 | { 141 | auto parentEntryIt = entries_.find(parent.EventInstanceId()); 142 | assert(parentEntryIt != entries_.end()); 143 | 144 | Entry* childEntry = CreateEntry(child); 145 | 146 | auto& children = parentEntryIt->second.Children; 147 | assert(std::find(children.begin(), children.end(), childEntry) == children.end()); 148 | children.push_back(childEntry); 149 | } 150 | 151 | void ExecutionHierarchy::OnFinishActivity(const Activity& activity) 152 | { 153 | auto it = entries_.find(activity.EventInstanceId()); 154 | assert(it != entries_.end()); 155 | 156 | it->second.StopTimestamp = ConvertTime(activity.StopTimestamp(), activity.TickFrequency()); 157 | } 158 | 159 | ExecutionHierarchy::Entry* ExecutionHierarchy::CreateEntry(const Activity& activity) 160 | { 161 | assert(entries_.find(activity.EventInstanceId()) == entries_.end()); 162 | 163 | Entry& entry = entries_[activity.EventInstanceId()]; 164 | 165 | entry.Id = activity.EventInstanceId(); 166 | entry.ProcessId = activity.ProcessId(); 167 | entry.ThreadId = activity.ThreadId(); 168 | entry.StartTimestamp = ConvertTime(activity.StartTimestamp(), activity.TickFrequency()); 169 | entry.StopTimestamp = ConvertTime(activity.StopTimestamp(), activity.TickFrequency()); 170 | entry.Name = activity.EventName(); 171 | 172 | return &entry; 173 | } 174 | 175 | void ExecutionHierarchy::OnInvocation(const Invocation& invocation) 176 | { 177 | auto it = entries_.find(invocation.EventInstanceId()); 178 | assert(it != entries_.end()); 179 | 180 | // may not be present, as it's not available in earlier versions of the toolset 181 | if (invocation.ToolPath()) { 182 | it->second.Properties.try_emplace("Tool Path", ToString(invocation.ToolPath())); 183 | } 184 | 185 | it->second.Properties.try_emplace("Working Directory", ToString(invocation.WorkingDirectory())); 186 | it->second.Properties.try_emplace("Tool Version", invocation.ToolVersionString()); 187 | 188 | if (invocation.EventId() == EVENT_ID_COMPILER) { 189 | it->second.Name = "CL Invocation " + std::to_string(invocation.InvocationId()); 190 | } 191 | else if (invocation.EventId() == EVENT_ID_LINKER) { 192 | it->second.Name = "Link Invocation " + std::to_string(invocation.InvocationId()); 193 | } 194 | } 195 | 196 | void ExecutionHierarchy::OnFrontEndFile(const FrontEndFile& frontEndFile) 197 | { 198 | auto it = entries_.find(frontEndFile.EventInstanceId()); 199 | assert(it != entries_.end()); 200 | it->second.Name = frontEndFile.Path(); 201 | } 202 | 203 | void ExecutionHierarchy::OnThread(const Activity& parent, const Thread& thread) 204 | { 205 | auto it = entries_.find(thread.EventInstanceId()); 206 | assert(it != entries_.end()); 207 | it->second.Name = std::string(parent.EventName()) + std::string(thread.EventName()); 208 | } 209 | 210 | void ExecutionHierarchy::OnFinishInvocation(const Invocation& invocation) 211 | { 212 | // store every FileInput and FileOutput as properties 213 | auto itFileInputsOutputs = fileInputsOutputsPerInvocation_.find(invocation.EventInstanceId()); 214 | if (itFileInputsOutputs != fileInputsOutputsPerInvocation_.end()) 215 | { 216 | auto itInvocation = entries_.find(invocation.EventInstanceId()); 217 | assert(itInvocation != entries_.end()); 218 | 219 | Entry& invocationEntry = itInvocation->second; 220 | const TFileInputsOutputs& data = itFileInputsOutputs->second; 221 | 222 | // FileInputs 223 | if (data.first.size() == 1) { 224 | invocationEntry.Properties.try_emplace("File Input", data.first[0]); 225 | } 226 | else 227 | { 228 | const size_t totalDigits = CountDigits(data.first.size()); 229 | for (size_t i = 0; i < data.first.size(); ++i) { 230 | invocationEntry.Properties.try_emplace("File Input #" + PrePadNumber(i, '0', totalDigits), data.first[i]); 231 | } 232 | } 233 | 234 | // FileOutputs 235 | if (data.second.size() == 1) { 236 | invocationEntry.Properties.try_emplace("File Output", data.second[0]); 237 | } 238 | else 239 | { 240 | const size_t totalDigits = CountDigits(data.second.size()); 241 | for (size_t i = 0; i < data.second.size(); ++i) { 242 | invocationEntry.Properties.try_emplace("File Output #" + PrePadNumber(i, '0', totalDigits), data.second[i]); 243 | } 244 | } 245 | 246 | fileInputsOutputsPerInvocation_.erase(invocation.EventInstanceId()); 247 | } 248 | } 249 | 250 | void ExecutionHierarchy::OnFinishFunction(const Activity& parent, const Function& function) 251 | { 252 | // filter by duration 253 | auto durationMs = std::chrono::duration_cast(function.Duration()); 254 | if (durationMs < filter_.IgnoreFunctionUnderMs) { 255 | IgnoreEntry(function.EventInstanceId(), parent.EventInstanceId()); 256 | } 257 | else 258 | { 259 | auto it = entries_.find(function.EventInstanceId()); 260 | assert(it != entries_.end()); 261 | it->second.Name = function.Name(); 262 | } 263 | } 264 | 265 | void ExecutionHierarchy::OnFinishTemplateInstantiation(const A::Activity& parent, const A::TemplateInstantiationGroup& templateInstantiationGroup) 266 | { 267 | const A::Activity& parentActivity = templateInstantiationGroup.Size() == 1 ? parent : templateInstantiationGroup[templateInstantiationGroup.Size() - 2]; 268 | 269 | if (!filter_.AnalyzeTemplates) { 270 | IgnoreEntry(templateInstantiationGroup.Back().EventInstanceId(), parentActivity.EventInstanceId()); 271 | } 272 | else 273 | { 274 | // keep full hierarchy when root passes filter, even if children wouldn't pass 275 | // we can only know root's Duration when it finishes: checking it when a child finishes will result in 0ns 276 | if (templateInstantiationGroup.Size() == 1 && 277 | std::chrono::duration_cast(templateInstantiationGroup.Front().Duration()) < filter_.IgnoreTemplateInstantiationUnderMs) 278 | { 279 | // ignores root TemplateInstantiation and its children (don't clear their symbol subscriptions, we'll deal with missing subscribers in OnSymbolName) 280 | IgnoreEntry(templateInstantiationGroup.Back().EventInstanceId(), parentActivity.EventInstanceId()); 281 | } 282 | else 283 | { 284 | // get us subscribed for name resolution (may already have some other activities following) 285 | auto result = unresolvedTemplateInstantiationsPerSymbol_.try_emplace(templateInstantiationGroup.Back().SpecializationSymbolKey(), 286 | TUnresolvedTemplateInstantiationNames()); 287 | result.first->second.push_back(templateInstantiationGroup.Back().EventInstanceId()); 288 | } 289 | } 290 | } 291 | 292 | void ExecutionHierarchy::OnSymbolName(const SymbolName& symbolName) 293 | { 294 | // SymbolName events get executed after all TemplateInstantiation in the same FrontEndPass take place 295 | // and we're sure to have exclusive keys for these symbols (they may have matching names between 296 | // FrontEndPass activities, but their key is unique for the trace) 297 | assert(symbolNames_.find(symbolName.Key()) == symbolNames_.end()); 298 | std::string& name = symbolNames_[symbolName.Key()]; 299 | name = symbolName.Name(); 300 | 301 | // now we've resolved the name, let subscribed activities know 302 | auto itSubscribedForSymbol = unresolvedTemplateInstantiationsPerSymbol_.find(symbolName.Key()); 303 | if (itSubscribedForSymbol != unresolvedTemplateInstantiationsPerSymbol_.end()) 304 | { 305 | for (unsigned long long id : itSubscribedForSymbol->second) 306 | { 307 | auto itEntry = entries_.find(id); 308 | 309 | // may've been filtered out (didn't clean up this subscription when filtering happened, as we're cleaning them all in a bit) 310 | if (itEntry != entries_.end()) { 311 | itEntry->second.Name = name; 312 | } 313 | } 314 | itSubscribedForSymbol->second.clear(); 315 | } 316 | } 317 | 318 | void ExecutionHierarchy::OnCommandLine(const Activity& parent, const CommandLine& commandLine) 319 | { 320 | auto it = entries_.find(parent.EventInstanceId()); 321 | assert(it != entries_.end()); 322 | 323 | it->second.Properties.try_emplace("Command Line", ToString(commandLine.Value())); 324 | } 325 | 326 | void ExecutionHierarchy::OnEnvironmentVariable(const Activity& parent, const EnvironmentVariable& environmentVariable) 327 | { 328 | // we're not interested in all of them, only the ones that impact the build process 329 | bool process = false; 330 | if (parent.EventId() == EVENT_ID::EVENT_ID_COMPILER) 331 | { 332 | process = _wcsicmp(environmentVariable.Name(), L"CL") == 0 333 | || _wcsicmp(environmentVariable.Name(), L"_CL_") == 0 334 | || _wcsicmp(environmentVariable.Name(), L"INCLUDE") == 0 335 | || _wcsicmp(environmentVariable.Name(), L"LIBPATH") == 0 336 | || _wcsicmp(environmentVariable.Name(), L"PATH") == 0; 337 | } 338 | else if (parent.EventId() == EVENT_ID::EVENT_ID_LINKER) 339 | { 340 | process = _wcsicmp(environmentVariable.Name(), L"LINK") == 0 341 | || _wcsicmp(environmentVariable.Name(), L"_LINK_") == 0 342 | || _wcsicmp(environmentVariable.Name(), L"LIB") == 0 343 | || _wcsicmp(environmentVariable.Name(), L"PATH") == 0 344 | || _wcsicmp(environmentVariable.Name(), L"TMP") == 0; 345 | } 346 | 347 | if (process) 348 | { 349 | auto it = entries_.find(parent.EventInstanceId()); 350 | assert(it != entries_.end()); 351 | 352 | it->second.Properties.try_emplace("Env Var: " + ToString(environmentVariable.Name()), ToString(environmentVariable.Value())); 353 | } 354 | } 355 | 356 | void ExecutionHierarchy::OnFileInput(const Invocation& parent, const FileInput& fileInput) 357 | { 358 | // an Invocation can have several FileInputs, keep track of them and add as properties later on 359 | auto result = fileInputsOutputsPerInvocation_.try_emplace(parent.EventInstanceId(), TFileInputs(), TFileOutputs()); 360 | auto &inputsOutputsPair = result.first->second; 361 | 362 | std::wstring path = fileInput.Path(); 363 | 364 | // A rare bug in the linker causes it to emit FileInput events 365 | // with an empty path. Ignore them. 366 | if (path.empty()) { 367 | return; 368 | } 369 | 370 | inputsOutputsPair.first.push_back(ToString(path)); 371 | } 372 | 373 | void ExecutionHierarchy::OnFileOutput(const Invocation& parent, const FileOutput& fileOutput) 374 | { 375 | // an Invocation can have several FileOutputs, keep track of them and add as properties later on 376 | auto result = fileInputsOutputsPerInvocation_.try_emplace(parent.EventInstanceId(), TFileInputs(), TFileOutputs()); 377 | auto& inputsOutputsPair = result.first->second; 378 | 379 | inputsOutputsPair.second.push_back(ToString(fileOutput.Path())); 380 | } 381 | 382 | void ExecutionHierarchy::IgnoreEntry(unsigned long long id, unsigned long long parentId) 383 | { 384 | // ensure parent no longer points to it 385 | auto itParent = entries_.find(parentId); 386 | assert(itParent != entries_.end()); 387 | 388 | std::vector& children = itParent->second.Children; 389 | auto itEntryAsChildren = std::find_if(children.begin(), children.end(), [&id](const Entry* child) { 390 | return child->Id == id; 391 | }); 392 | assert(itEntryAsChildren != children.end()); 393 | 394 | children.erase(itEntryAsChildren); 395 | 396 | // ignore it and its children (no need to let intermediate parents know, as they'll be erased as well) 397 | IgnoreEntry(id); 398 | } 399 | 400 | void ExecutionHierarchy::IgnoreEntry(unsigned long long id) 401 | { 402 | auto itEntry = entries_.find(id); 403 | if (itEntry != entries_.end()) 404 | { 405 | for (const Entry* child : itEntry->second.Children) { 406 | IgnoreEntry(child->Id); 407 | } 408 | 409 | entries_.erase(itEntry); 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /src/TimeTrace/ExecutionHierarchy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "VcperfBuildInsights.h" 8 | 9 | namespace vcperf 10 | { 11 | 12 | class ExecutionHierarchy : public BI::IAnalyzer 13 | { 14 | public: 15 | 16 | // controls which activities get ignored 17 | struct Filter 18 | { 19 | bool AnalyzeTemplates = false; 20 | std::chrono::milliseconds IgnoreTemplateInstantiationUnderMs = std::chrono::milliseconds(0); 21 | std::chrono::milliseconds IgnoreFunctionUnderMs = std::chrono::milliseconds(0); 22 | }; 23 | 24 | struct Entry 25 | { 26 | unsigned long long Id = 0L; 27 | unsigned long ProcessId = 0L; 28 | unsigned long ThreadId = 0L; 29 | std::chrono::nanoseconds StartTimestamp = std::chrono::nanoseconds(0); 30 | std::chrono::nanoseconds StopTimestamp = std::chrono::nanoseconds(0); 31 | std::string Name; 32 | 33 | std::vector Children; 34 | std::unordered_map Properties; 35 | 36 | bool OverlapsWith(const Entry* other) const; 37 | }; 38 | 39 | typedef std::vector TRoots; 40 | 41 | public: 42 | 43 | ExecutionHierarchy(const Filter& filter); 44 | 45 | BI::AnalysisControl OnStartActivity(const BI::EventStack& eventStack) override; 46 | BI::AnalysisControl OnStopActivity(const BI::EventStack& eventStack) override; 47 | BI::AnalysisControl OnSimpleEvent(const BI::EventStack& eventStack) override; 48 | 49 | const Entry* GetEntry(unsigned long long id) const; 50 | inline const TRoots& GetRoots() const { return roots_; } 51 | 52 | private: 53 | 54 | void OnRootActivity(const A::Activity& root); 55 | void OnNestedActivity(const A::Activity& parent, const A::Activity& child); 56 | void OnFinishActivity(const A::Activity& activity); 57 | 58 | Entry* CreateEntry(const A::Activity& activity); 59 | 60 | void OnInvocation(const A::Invocation& invocation); 61 | void OnFrontEndFile(const A::FrontEndFile& frontEndFile); 62 | void OnThread(const A::Activity& parent, const A::Thread& thread); 63 | 64 | void OnFinishInvocation(const A::Invocation& invocation); 65 | void OnFinishFunction(const A::Activity& parent, const A::Function& function); 66 | void OnFinishTemplateInstantiation(const A::Activity& parent, const A::TemplateInstantiationGroup& templateInstantiationGroup); 67 | 68 | void OnSymbolName(const SE::SymbolName& symbolName); 69 | void OnCommandLine(const A::Activity& parent, const SE::CommandLine& commandLine); 70 | void OnEnvironmentVariable(const A::Activity& parent, const SE::EnvironmentVariable& environmentVariable); 71 | void OnFileInput(const A::Invocation& parent, const SE::FileInput& fileInput); 72 | void OnFileOutput(const A::Invocation& parent, const SE::FileOutput& fileOutput); 73 | 74 | void IgnoreEntry(unsigned long long id, unsigned long long parentId); 75 | void IgnoreEntry(unsigned long long id); 76 | 77 | std::unordered_map entries_; 78 | TRoots roots_; 79 | Filter filter_; 80 | 81 | typedef std::vector TFileInputs; 82 | typedef std::vector TFileOutputs; 83 | typedef std::pair TFileInputsOutputs; 84 | std::unordered_map fileInputsOutputsPerInvocation_; 85 | 86 | typedef unsigned long long TSymbolKey; 87 | std::unordered_map symbolNames_; 88 | typedef std::vector TUnresolvedTemplateInstantiationNames; 89 | std::unordered_map unresolvedTemplateInstantiationsPerSymbol_; 90 | }; 91 | 92 | } // namespace vcperf 93 | -------------------------------------------------------------------------------- /src/TimeTrace/PackedProcessThreadRemapping.cpp: -------------------------------------------------------------------------------- 1 | #include "PackedProcessThreadRemapping.h" 2 | 3 | #include 4 | #include 5 | 6 | using namespace vcperf; 7 | 8 | PackedProcessThreadRemapping::PackedProcessThreadRemapping() : 9 | remappings_{}, 10 | localOffsetsData_{} 11 | { 12 | } 13 | 14 | void PackedProcessThreadRemapping::Calculate(const ExecutionHierarchy* hierarchy) 15 | { 16 | assert(hierarchy != nullptr); 17 | assert(remappings_.empty()); 18 | 19 | RemapRootsProcessId(hierarchy); 20 | RemapEntriesThreadId(hierarchy); 21 | } 22 | 23 | void PackedProcessThreadRemapping::CalculateChildrenLocalThreadData(const ExecutionHierarchy::Entry* entry) 24 | { 25 | assert(entry != nullptr); 26 | 27 | if (entry->Children.size() == 0) 28 | { 29 | // it's a leaf, so we're sure there's no data for it yet 30 | assert(localOffsetsData_.find(entry->Id) == localOffsetsData_.end()); 31 | 32 | LocalOffsetData& data = localOffsetsData_[entry->Id]; 33 | data.RawLocalThreadId = 0UL; // parent will update this in CalculateChildrenLocalThreadId 34 | data.RequiredThreadIdToFitHierarchy = 0UL; 35 | } 36 | else 37 | { 38 | CalculateChildrenLocalThreadId(entry); 39 | CalculateChildrenExtraThreadIdToFitHierarchy(entry); 40 | } 41 | } 42 | 43 | void PackedProcessThreadRemapping::CalculateChildrenLocalThreadId(const ExecutionHierarchy::Entry* entry) 44 | { 45 | assert(entry->Children.size() > 0); 46 | 47 | const std::vector& children = entry->Children; 48 | std::vector overlappingLocalThreadIds; 49 | 50 | for (auto itChild = children.begin(); itChild != children.end(); ++itChild) 51 | { 52 | const ExecutionHierarchy::Entry* child = *itChild; 53 | auto itLocalOffsetData = localOffsetsData_.find(child->Id); 54 | 55 | // not finding it means it's been ignored, so we don't need to perform any calculations for it 56 | if (itLocalOffsetData != localOffsetsData_.end()) 57 | { 58 | // children are sorted by start time, so we only have to check previous siblings for overlaps 59 | overlappingLocalThreadIds.clear(); 60 | for(auto itPrecedingSibling = std::make_reverse_iterator(itChild); itPrecedingSibling != children.rend(); ++itPrecedingSibling) 61 | { 62 | const ExecutionHierarchy::Entry* precedingSibling = *itPrecedingSibling; 63 | 64 | if (child->OverlapsWith(precedingSibling)) 65 | { 66 | // preceding sibling has been processed already, if its data is missing it means it's ignored 67 | auto it = localOffsetsData_.find(precedingSibling->Id); 68 | if (it != localOffsetsData_.end()) { 69 | overlappingLocalThreadIds.push_back(it->second.RawLocalThreadId); 70 | } 71 | } 72 | } 73 | 74 | // calculate first local ThreadId where we don't overlap any sibling 75 | unsigned long localThreadId = 0UL; 76 | while (std::find(overlappingLocalThreadIds.begin(), overlappingLocalThreadIds.end(), localThreadId) != overlappingLocalThreadIds.end()) 77 | { 78 | ++localThreadId; 79 | } 80 | 81 | // update local ThreadId 82 | assert(itLocalOffsetData != localOffsetsData_.end()); 83 | itLocalOffsetData->second.RawLocalThreadId = localThreadId; 84 | } 85 | } 86 | } 87 | 88 | void PackedProcessThreadRemapping::CalculateChildrenExtraThreadIdToFitHierarchy(const ExecutionHierarchy::Entry* entry) 89 | { 90 | assert(entry->Children.size() > 0); 91 | 92 | // group children with their partial LocalOffsetData 93 | typedef std::pair EntryWithOffsetData; 94 | std::vector sortedChildrenWithData; 95 | for (const ExecutionHierarchy::Entry* child : entry->Children) 96 | { 97 | auto it = localOffsetsData_.find(child->Id); 98 | 99 | // if data is missing, it means child is ignored 100 | if (it != localOffsetsData_.end()) { 101 | sortedChildrenWithData.emplace_back(child, &it->second); 102 | } 103 | } 104 | 105 | // we may end up having all children ignored 106 | unsigned long requiredThreadIdToFitHierarchy = 0UL; 107 | if (sortedChildrenWithData.size() > 0) 108 | { 109 | // sort them by their raw local ThreadId 110 | std::sort(sortedChildrenWithData.begin(), sortedChildrenWithData.end(), 111 | [](const EntryWithOffsetData& lhs, const EntryWithOffsetData& rhs) 112 | { 113 | return lhs.second->RawLocalThreadId < rhs.second->RawLocalThreadId; 114 | }); 115 | 116 | // consider all children within the same local ThreadId as "sequential" and those 117 | // with a different ThreadId as "parallel" 118 | unsigned long currentLocalThreadId = sortedChildrenWithData[0].second->RawLocalThreadId; 119 | unsigned long currentExtraThreadToFitHierarchy = 0UL; 120 | unsigned long requiredExtraThreadIdToFitHierarchy = 0UL; 121 | for (EntryWithOffsetData& data : sortedChildrenWithData) 122 | { 123 | // "sequential" entries live in the same ThreadId, so we only need to account for the one that 124 | // needs more room for its subhierarchy 125 | if (currentLocalThreadId == data.second->RawLocalThreadId) 126 | { 127 | if (data.second->RequiredThreadIdToFitHierarchy > currentExtraThreadToFitHierarchy) { 128 | currentExtraThreadToFitHierarchy = data.second->RequiredThreadIdToFitHierarchy; 129 | } 130 | } 131 | // when we have reached a different ThreadId (moved into a "parallel" entry), prepare for next iteration 132 | else 133 | { 134 | // accumulate calculated data for previous "sequential" entries 135 | requiredExtraThreadIdToFitHierarchy += currentExtraThreadToFitHierarchy; 136 | 137 | currentLocalThreadId = data.second->RawLocalThreadId; 138 | currentExtraThreadToFitHierarchy = data.second->RequiredThreadIdToFitHierarchy; 139 | } 140 | 141 | // calculate the real local ThreadId: the raw one taking previous siblings' requirements into account 142 | data.second->CalculatedLocalThreadId = data.second->RawLocalThreadId + requiredExtraThreadIdToFitHierarchy; 143 | } 144 | 145 | // last sequence of entries wasn't accumulated: we didn't iterate into a "next" ThreadId 146 | requiredExtraThreadIdToFitHierarchy += currentExtraThreadToFitHierarchy; 147 | 148 | requiredThreadIdToFitHierarchy = currentLocalThreadId + requiredExtraThreadIdToFitHierarchy; 149 | } 150 | 151 | // we're a parent, so we're sure we haven't been added to the map yet 152 | assert(localOffsetsData_.find(entry->Id) == localOffsetsData_.end()); 153 | auto& data = localOffsetsData_[entry->Id]; 154 | data.RawLocalThreadId = 0UL; // our own parent will update this value taking our siblings into account 155 | data.RequiredThreadIdToFitHierarchy = requiredThreadIdToFitHierarchy; 156 | } 157 | 158 | const PackedProcessThreadRemapping::Remap* PackedProcessThreadRemapping::GetRemapFor(unsigned long long id) const 159 | { 160 | auto it = remappings_.find(id); 161 | return it != remappings_.end() ? &it->second : nullptr; 162 | } 163 | 164 | void PackedProcessThreadRemapping::RemapRootsProcessId(const ExecutionHierarchy* hierarchy) 165 | { 166 | const ExecutionHierarchy::TRoots& roots = hierarchy->GetRoots(); 167 | std::vector overlappingProcessIds; 168 | for (auto itRoot = roots.begin(); itRoot != roots.end(); ++itRoot) 169 | { 170 | const ExecutionHierarchy::Entry* root = *itRoot; 171 | 172 | // entries are sorted by start time, so we only have to check previous siblings for overlaps 173 | overlappingProcessIds.clear(); 174 | for (auto itPrecedingSibling = std::make_reverse_iterator(itRoot); itPrecedingSibling != roots.rend(); ++itPrecedingSibling) 175 | { 176 | const ExecutionHierarchy::Entry* precedingSibling = *itPrecedingSibling; 177 | 178 | if (root->OverlapsWith(precedingSibling)) 179 | { 180 | // preceding sibling has been processed already, so we must find a remap for it 181 | auto it = remappings_.find(precedingSibling->Id); 182 | assert(it != remappings_.end()); 183 | 184 | overlappingProcessIds.push_back(it->second.ProcessId); 185 | } 186 | } 187 | 188 | // calculate first ProcessId where we don't overlap with any sibling 189 | unsigned long remappedProcessId = 0UL; 190 | while (std::find(overlappingProcessIds.begin(), overlappingProcessIds.end(), remappedProcessId) != overlappingProcessIds.end()) 191 | { 192 | ++remappedProcessId; 193 | } 194 | 195 | // roots always get assigned to the lowest ThreadId 196 | assert(remappings_.find(root->Id) == remappings_.end()); 197 | Remap& remap = remappings_[root->Id]; 198 | remap.ProcessId = remappedProcessId; 199 | remap.ThreadId = 0UL; 200 | } 201 | } 202 | 203 | void PackedProcessThreadRemapping::RemapEntriesThreadId(const ExecutionHierarchy* hierarchy) 204 | { 205 | for (const ExecutionHierarchy::Entry* root : hierarchy->GetRoots()) 206 | { 207 | // this data must exist because we've already calculated ProcessId remappings 208 | // remember: parallel entries at root level is represented via different ProcessId 209 | auto it = remappings_.find(root->Id); 210 | assert(it != remappings_.end()); 211 | 212 | RemapThreadIdFor(root, it->second.ProcessId, it->second.ThreadId); 213 | } 214 | } 215 | 216 | void PackedProcessThreadRemapping::RemapThreadIdFor(const ExecutionHierarchy::Entry* entry, unsigned long remappedProcessId, 217 | unsigned long parentAbsoluteThreadId) 218 | { 219 | for (const ExecutionHierarchy::Entry* child : entry->Children) 220 | { 221 | auto itLocalData = localOffsetsData_.find(child->Id); 222 | 223 | // if data is missing, we can ignore the hierarchy altogether 224 | if (itLocalData != localOffsetsData_.end()) 225 | { 226 | assert(remappings_.find(child->Id) == remappings_.end()); 227 | Remap& remap = remappings_[child->Id]; 228 | remap.ProcessId = remappedProcessId; 229 | remap.ThreadId = parentAbsoluteThreadId + itLocalData->second.CalculatedLocalThreadId; 230 | 231 | RemapThreadIdFor(child, remappedProcessId, remap.ThreadId); 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/TimeTrace/PackedProcessThreadRemapping.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "TimeTrace\ExecutionHierarchy.h" 6 | 7 | namespace vcperf 8 | { 9 | 10 | class PackedProcessThreadRemapping 11 | { 12 | public: 13 | 14 | struct Remap 15 | { 16 | unsigned long ProcessId = 0UL; 17 | unsigned long ThreadId = 0UL; 18 | }; 19 | 20 | public: 21 | 22 | PackedProcessThreadRemapping(); 23 | 24 | void Calculate(const ExecutionHierarchy* hierarchy); 25 | void CalculateChildrenLocalThreadData(const ExecutionHierarchy::Entry* entry); 26 | 27 | const Remap* GetRemapFor(unsigned long long id) const; 28 | 29 | private: 30 | 31 | struct LocalOffsetData 32 | { 33 | unsigned long RawLocalThreadId = 0UL; 34 | unsigned long RequiredThreadIdToFitHierarchy = 0UL; 35 | unsigned long CalculatedLocalThreadId = 0UL; 36 | }; 37 | 38 | void RemapRootsProcessId(const ExecutionHierarchy* hierarchy); 39 | void RemapEntriesThreadId(const ExecutionHierarchy* hierarchy); 40 | void RemapThreadIdFor(const ExecutionHierarchy::Entry* entry, unsigned long remappedProcessId, 41 | unsigned long parentAbsoluteThreadId); 42 | 43 | void CalculateChildrenLocalThreadId(const ExecutionHierarchy::Entry* entry); 44 | void CalculateChildrenExtraThreadIdToFitHierarchy(const ExecutionHierarchy::Entry* entry); 45 | 46 | std::unordered_map remappings_; 47 | std::unordered_map localOffsetsData_; 48 | }; 49 | 50 | } // namespace vcperf 51 | -------------------------------------------------------------------------------- /src/TimeTrace/TimeTraceGenerator.cpp: -------------------------------------------------------------------------------- 1 | #include "TimeTraceGenerator.h" 2 | 3 | #include 4 | #include 5 | 6 | using namespace Microsoft::Cpp::BuildInsights; 7 | using namespace Activities; 8 | using namespace vcperf; 9 | 10 | // anonymous namespace to hold auxiliary functions (instead of using it in the class and requiring #include 11 | namespace 12 | { 13 | 14 | void AddEntry(const ExecutionHierarchy::Entry* entry, nlohmann::json& traceEvents, const PackedProcessThreadRemapping& remappings) 15 | { 16 | const PackedProcessThreadRemapping::Remap* remap = remappings.GetRemapFor(entry->Id); 17 | unsigned long processId = remap != nullptr ? remap->ProcessId : entry->ProcessId; 18 | unsigned long threadId = remap != nullptr ? remap->ThreadId : entry->ThreadId; 19 | 20 | if (entry->Children.size() == 0) 21 | { 22 | auto startTimestamp = std::chrono::duration_cast(entry->StartTimestamp); 23 | auto duration = std::chrono::duration_cast(entry->StopTimestamp - entry->StartTimestamp); 24 | nlohmann::json completeEvent = 25 | { 26 | { "ph", "X" }, 27 | { "pid", processId }, 28 | { "tid", threadId }, 29 | { "name", entry->Name }, 30 | { "ts", startTimestamp.count() }, 31 | { "dur", duration.count() } 32 | }; 33 | 34 | // add extra properties, if any 35 | if (!entry->Properties.empty()) 36 | { 37 | nlohmann::json args = nlohmann::json::object(); 38 | for (auto& pair : entry->Properties) 39 | { 40 | args[pair.first] = pair.second; 41 | } 42 | 43 | completeEvent["args"] = args; 44 | } 45 | 46 | traceEvents.push_back(completeEvent); 47 | } 48 | else 49 | { 50 | auto startTimestamp = std::chrono::duration_cast(entry->StartTimestamp); 51 | nlohmann::json beginEvent = 52 | { 53 | { "ph", "B" }, 54 | { "pid", processId }, 55 | { "tid", threadId }, 56 | { "name", entry->Name }, 57 | { "ts", startTimestamp.count() } 58 | }; 59 | 60 | // add extra properties, if any 61 | if (!entry->Properties.empty()) 62 | { 63 | nlohmann::json args = nlohmann::json::object(); 64 | for (auto& pair : entry->Properties) 65 | { 66 | args[pair.first] = pair.second; 67 | } 68 | 69 | beginEvent["args"] = args; 70 | } 71 | 72 | traceEvents.push_back(beginEvent); 73 | 74 | for (const ExecutionHierarchy::Entry* child : entry->Children) 75 | { 76 | AddEntry(child, traceEvents, remappings); 77 | } 78 | 79 | auto stopTimestamp = std::chrono::duration_cast(entry->StopTimestamp); 80 | nlohmann::json endEvent = 81 | { 82 | { "ph", "E" }, 83 | { "pid", processId }, 84 | { "tid", threadId }, 85 | { "ts", stopTimestamp.count() } 86 | }; 87 | traceEvents.push_back(endEvent); 88 | } 89 | } 90 | 91 | } // anonymous namespace 92 | 93 | TimeTraceGenerator::TimeTraceGenerator(ExecutionHierarchy* hierarchy, const std::filesystem::path& outputFile) : 94 | hierarchy_{hierarchy}, 95 | outputFile_{outputFile}, 96 | remappings_{} 97 | { 98 | } 99 | 100 | BI::AnalysisControl TimeTraceGenerator::OnStopActivity(const BI::EventStack& eventStack) 101 | { 102 | MatchEventInMemberFunction(eventStack.Back(), this, &TimeTraceGenerator::ProcessActivity); 103 | 104 | return AnalysisControl::CONTINUE; 105 | } 106 | 107 | AnalysisControl TimeTraceGenerator::OnEndAnalysis() 108 | { 109 | remappings_.Calculate(hierarchy_); 110 | 111 | std::ofstream outputStream(outputFile_); 112 | if (!outputStream) { 113 | return AnalysisControl::FAILURE; 114 | } 115 | 116 | ExportTo(outputStream); 117 | outputStream.close(); 118 | 119 | return AnalysisControl::CONTINUE; 120 | } 121 | 122 | void TimeTraceGenerator::ProcessActivity(const Activity& activity) 123 | { 124 | CalculateChildrenOffsets(activity); 125 | } 126 | 127 | void TimeTraceGenerator::CalculateChildrenOffsets(const Activity& activity) 128 | { 129 | const ExecutionHierarchy::Entry* entry = hierarchy_->GetEntry(activity.EventInstanceId()); 130 | 131 | // may've been filtered out! 132 | if (entry != nullptr) 133 | { 134 | remappings_.CalculateChildrenLocalThreadData(entry); 135 | } 136 | } 137 | 138 | void TimeTraceGenerator::ExportTo(std::ostream& outputStream) const 139 | { 140 | nlohmann::json json = nlohmann::json::object(); 141 | 142 | // add hierarchy 143 | nlohmann::json traceEvents = nlohmann::json::array(); 144 | for (const ExecutionHierarchy::Entry* root : hierarchy_->GetRoots()) 145 | { 146 | AddEntry(root, traceEvents, remappings_); 147 | } 148 | json["traceEvents"] = traceEvents; 149 | 150 | // although "ms" is the default time unit, make it explicit ("ms" means "microseconds") 151 | json["displayTimeUnit"] = "ms"; 152 | 153 | outputStream << std::setw(2) << json << std::endl; 154 | } 155 | -------------------------------------------------------------------------------- /src/TimeTrace/TimeTraceGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "VcperfBuildInsights.h" 8 | #include "TimeTrace\ExecutionHierarchy.h" 9 | #include "TimeTrace\PackedProcessThreadRemapping.h" 10 | 11 | namespace vcperf 12 | { 13 | 14 | class TimeTraceGenerator : public BI::IAnalyzer 15 | { 16 | public: 17 | 18 | TimeTraceGenerator(ExecutionHierarchy* hierarchy, const std::filesystem::path& outputFile); 19 | 20 | BI::AnalysisControl OnStopActivity(const BI::EventStack& eventStack) override; 21 | BI::AnalysisControl OnEndAnalysis() override; 22 | 23 | private: 24 | 25 | void ProcessActivity(const A::Activity& activity); 26 | 27 | void CalculateChildrenOffsets(const A::Activity& activity); 28 | void ExportTo(std::ostream& outputStream) const; 29 | 30 | ExecutionHierarchy* hierarchy_; 31 | std::filesystem::path outputFile_; 32 | PackedProcessThreadRemapping remappings_; 33 | }; 34 | 35 | } // namespace vcperf 36 | -------------------------------------------------------------------------------- /src/Utility.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace vcperf 4 | { 5 | 6 | struct Guid : GUID 7 | { 8 | constexpr Guid( 9 | unsigned long d1, 10 | unsigned short d2, 11 | unsigned short d3, 12 | unsigned char b1, 13 | unsigned char b2, 14 | unsigned char b3, 15 | unsigned char b4, 16 | unsigned char b5, 17 | unsigned char b6, 18 | unsigned char b7, 19 | unsigned char b8 20 | ): 21 | GUID{d1, d2, d3, b1, b2, b3, b4, b5, b6, b7, b8} 22 | { 23 | } 24 | }; 25 | 26 | // f78a07b0-796a-5da4-5c20-61aa526e77af 27 | constexpr Guid CppBuildInsightsGuid = Guid{0xf78a07b0, 0x796a, 0x5da4, 28 | 0x5c, 0x20, 0x61, 0xaa, 0x52, 0x6e, 0x77, 0xaf}; 29 | 30 | } // namespace vcperf 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/VcperfBuildInsights.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace vcperf 6 | { 7 | namespace BI = Microsoft::Cpp::BuildInsights; 8 | namespace A = Microsoft::Cpp::BuildInsights::Activities; 9 | namespace SE = Microsoft::Cpp::BuildInsights::SimpleEvents; 10 | } 11 | -------------------------------------------------------------------------------- /src/WPA/Analyzers/ContextBuilder.cpp: -------------------------------------------------------------------------------- 1 | #include "ContextBuilder.h" 2 | 3 | using namespace Microsoft::Cpp::BuildInsights; 4 | using namespace Activities; 5 | using namespace SimpleEvents; 6 | 7 | using namespace vcperf; 8 | 9 | AnalysisControl ContextBuilder::OnStartActivity(const EventStack& eventStack) 10 | { 11 | if (!MustBuildContext()) { 12 | return AnalysisControl::CONTINUE; 13 | } 14 | 15 | currentContextData_ = nullptr; 16 | currentInstanceId_ = 0; 17 | 18 | if ( MatchEventStackInMemberFunction(eventStack, this, &ContextBuilder::OnCompilerPass) 19 | || MatchEventStackInMemberFunction(eventStack, this, &ContextBuilder::OnC2Thread)) 20 | { 21 | return AnalysisControl::CONTINUE; 22 | } 23 | 24 | if ( MatchEventStackInMemberFunction(eventStack, this, &ContextBuilder::OnNestedActivity) 25 | || MatchEventInMemberFunction(eventStack.Back(), this, &ContextBuilder::OnRootActivity)) 26 | { 27 | } 28 | 29 | MatchEventInMemberFunction(eventStack.Back(), this, &ContextBuilder::OnInvocation); 30 | 31 | return AnalysisControl::CONTINUE; 32 | } 33 | 34 | AnalysisControl ContextBuilder::OnStopActivity(const EventStack& eventStack) 35 | { 36 | if (!MustBuildContext()) { 37 | return AnalysisControl::CONTINUE; 38 | } 39 | 40 | currentContextData_ = nullptr; 41 | currentInstanceId_ = 0; 42 | 43 | if ( MatchEventStackInMemberFunction(eventStack, this, &ContextBuilder::OnStopNestedActivity) 44 | || MatchEventStackInMemberFunction(eventStack, this, &ContextBuilder::OnStopRootActivity)) 45 | {} 46 | 47 | if ( MatchEventStackInMemberFunction(eventStack, this, &ContextBuilder::OnStopCompilerPass) 48 | || MatchEventStackInMemberFunction(eventStack, this, &ContextBuilder::OnStopC2Thread) 49 | || MatchEventStackInMemberFunction(eventStack, this, &ContextBuilder::OnStopInvocation)) 50 | {} 51 | 52 | return AnalysisControl::CONTINUE; 53 | } 54 | 55 | AnalysisControl ContextBuilder::OnSimpleEvent(const EventStack& eventStack) 56 | { 57 | if (MustCacheMainComponents()) 58 | { 59 | if ( MatchEventStackInMemberFunction(eventStack, this, 60 | &ContextBuilder::OnLibOutput) 61 | 62 | || MatchEventStackInMemberFunction(eventStack, this, 63 | &ContextBuilder::OnExecutableImageOutput) 64 | 65 | || MatchEventStackInMemberFunction(eventStack, this, 66 | &ContextBuilder::OnImpLibOutput) 67 | 68 | || MatchEventStackInMemberFunction(eventStack, this, 69 | &ContextBuilder::OnCompilerInput) 70 | 71 | || MatchEventStackInMemberFunction(eventStack, this, 72 | &ContextBuilder::OnCompilerOutput)) 73 | {} 74 | 75 | return AnalysisControl::CONTINUE; 76 | } 77 | 78 | if (!MustBuildContext()) { 79 | return AnalysisControl::CONTINUE; 80 | } 81 | 82 | currentContextData_ = nullptr; 83 | currentInstanceId_ = eventStack.Size() == 1 ? 0 : 84 | eventStack[eventStack.Size() - 2].EventInstanceId(); 85 | 86 | return AnalysisControl::CONTINUE; 87 | } 88 | 89 | void ContextBuilder::ProcessLinkerOutput(const LinkerGroup& linkers, 90 | const FileOutput& output, bool overwrite) 91 | { 92 | // A linker invocation can respawn itself. If this occurs we assign 93 | // the linker's main component to all invocations. 94 | 95 | for (const Invocation& invocation : linkers) 96 | { 97 | auto it = mainComponentCache_.find(invocation.EventInstanceId()); 98 | 99 | if (!overwrite && it != mainComponentCache_.end()) { 100 | continue; 101 | } 102 | 103 | assert(overwrite || it == mainComponentCache_.end()); 104 | 105 | mainComponentCache_[invocation.EventInstanceId()].Path = output.Path(); 106 | } 107 | } 108 | 109 | void ContextBuilder::OnLibOutput(const LinkerGroup& linkers, const LibOutput& output) 110 | { 111 | ProcessLinkerOutput(linkers, output, true); 112 | } 113 | 114 | void ContextBuilder::OnExecutableImageOutput(const LinkerGroup& linkers, 115 | const ExecutableImageOutput& output) 116 | { 117 | ProcessLinkerOutput(linkers, output, true); 118 | } 119 | 120 | void ContextBuilder::OnImpLibOutput(const LinkerGroup& linkers, const ImpLibOutput& output) 121 | { 122 | ProcessLinkerOutput(linkers, output, false); 123 | } 124 | 125 | void ContextBuilder::OnCompilerInput(const Compiler& cl, const FileInput& input) 126 | { 127 | auto it = mainComponentCache_.find(cl.EventInstanceId()); 128 | 129 | if (it == mainComponentCache_.end() || !it->second.IsInput) 130 | { 131 | auto& component = mainComponentCache_[cl.EventInstanceId()]; 132 | 133 | component.Path = input.Path(); 134 | component.IsInput = true; 135 | 136 | return; 137 | } 138 | else // Has a component that is an input 139 | { 140 | // Compiler invocations may have more than one input. In these 141 | // cases the invocation is considered to have no main component. 142 | 143 | it->second.Path.clear(); 144 | } 145 | } 146 | 147 | void ContextBuilder::OnCompilerOutput(const Compiler& cl, const ObjOutput& output) 148 | { 149 | auto it = mainComponentCache_.find(cl.EventInstanceId()); 150 | 151 | if (it == mainComponentCache_.end()) 152 | { 153 | auto& component = mainComponentCache_[cl.EventInstanceId()]; 154 | 155 | component.Path = output.Path(); 156 | component.IsInput = false; 157 | 158 | return; 159 | } 160 | else if (!it->second.IsInput) 161 | { 162 | // Compiler invocations may have more than one output. In these 163 | // cases the invocation is considered to have no main component. 164 | 165 | it->second.Path.clear(); 166 | } 167 | } 168 | 169 | void ContextBuilder::OnRootActivity(const Activity& root) 170 | { 171 | unsigned long long id = root.EventInstanceId(); 172 | 173 | assert(activityContextLinks_.find(id) == activityContextLinks_.end()); 174 | assert(contextData_.find(id) == contextData_.end()); 175 | 176 | ContextData& newContext = contextData_[id]; 177 | newContext.TimelineId = GetNewTimelineId(); 178 | newContext.TimelineDescription = timelineDescriptions_[newContext.TimelineId].c_str(); 179 | newContext.InvocationId = 0; 180 | newContext.InvocationDescription = L""; 181 | newContext.Tool = ""; 182 | newContext.Component = L""; 183 | 184 | activityContextLinks_.try_emplace(id, &newContext, 0); 185 | 186 | currentContextData_ = &newContext; 187 | } 188 | 189 | void ContextBuilder::OnNestedActivity(const Activity& parent, const Activity& child) 190 | { 191 | unsigned long long childId = child.EventInstanceId(); 192 | 193 | auto itParent = activityContextLinks_.find(parent.EventInstanceId()); 194 | 195 | assert(itParent != activityContextLinks_.end()); 196 | assert(activityContextLinks_.find(childId) == activityContextLinks_.end()); 197 | 198 | ContextData* propagatedContext = itParent->second.LinkedContext; 199 | 200 | itParent->second.TimelineReuseCount++; 201 | 202 | activityContextLinks_.try_emplace(child.EventInstanceId(), propagatedContext, 0); 203 | 204 | currentContextData_ = propagatedContext; 205 | } 206 | 207 | void ContextBuilder::OnInvocation(const Invocation& invocation) 208 | { 209 | unsigned long long id = invocation.EventInstanceId(); 210 | 211 | auto itContextLink = activityContextLinks_.find(id); 212 | 213 | assert(itContextLink != activityContextLinks_.end()); 214 | 215 | ContextData* context = itContextLink->second.LinkedContext; 216 | 217 | ContextData& newContext = contextData_[id]; 218 | newContext.TimelineId = context->TimelineId; 219 | newContext.TimelineDescription = context->TimelineDescription; 220 | 221 | newContext.Tool = invocation.Type() == Invocation::Type::LINK ? 222 | "Link" : "CL"; 223 | 224 | newContext.InvocationId = invocation.InvocationId(); 225 | 226 | const wchar_t* wTool = invocation.Type() == Invocation::Type::LINK ? 227 | L"Link" : L"CL"; 228 | 229 | std::wstring invocationIdString = std::to_wstring(invocation.InvocationId()); 230 | 231 | unsigned long long instanceId = invocation.EventInstanceId(); 232 | 233 | auto it = mainComponentCache_.find(instanceId); 234 | 235 | if (it == mainComponentCache_.end() || it->second.Path.empty()) 236 | { 237 | std::wstring component = L"<" + std::wstring{wTool} + L" Invocation " + invocationIdString + L" Info>"; 238 | newContext.Component = CacheString(activeComponents_, instanceId, std::move(component)); 239 | 240 | std::wstring invocationDescription = std::wstring{wTool} + L" Invocation " + invocationIdString; 241 | newContext.InvocationDescription = CacheString(invocationDescriptions_, instanceId, std::move(invocationDescription)); 242 | } 243 | else 244 | { 245 | newContext.Component = it->second.Path.c_str(); 246 | 247 | std::wstring invocationDescription = std::wstring{wTool} + L" Invocation " + invocationIdString + 248 | L" (" + newContext.Component + L")"; 249 | newContext.InvocationDescription = CacheString(invocationDescriptions_, instanceId, std::move(invocationDescription)); 250 | } 251 | 252 | itContextLink->second.LinkedContext = &newContext; 253 | currentContextData_ = &newContext; 254 | } 255 | 256 | void ContextBuilder::OnCompilerPass(const Compiler& cl, const CompilerPass& pass) 257 | { 258 | ProcessParallelismForkPoint(cl, pass); 259 | 260 | auto* path = pass.InputSourcePath(); 261 | 262 | if (path == nullptr) { 263 | path = pass.OutputObjectPath(); 264 | } 265 | 266 | currentContextData_->Component = 267 | CacheString(activeComponents_, pass.EventInstanceId(), path); 268 | } 269 | 270 | void ContextBuilder::OnC2Thread(const C2DLL& c2, const Activity& threadOwner, 271 | const Thread& thread) 272 | { 273 | ProcessParallelismForkPoint(threadOwner, thread); 274 | } 275 | 276 | void ContextBuilder::ProcessParallelismForkPoint(const Activity& parent, 277 | const Activity& child) 278 | { 279 | unsigned long long parentId = parent.EventInstanceId(); 280 | 281 | auto itParentLink = activityContextLinks_.find(parentId); 282 | assert(itParentLink != activityContextLinks_.end()); 283 | 284 | ProcessParallelismForkPoint(itParentLink->second, child); 285 | } 286 | 287 | void ContextBuilder::ProcessParallelismForkPoint(ContextLink& parentContextLink, 288 | const Activity& child) 289 | { 290 | unsigned long long id = child.EventInstanceId(); 291 | 292 | assert(activityContextLinks_.find(id) == activityContextLinks_.end()); 293 | assert(contextData_.find(id) == contextData_.end()); 294 | 295 | ContextData* parentContext = parentContextLink.LinkedContext; 296 | 297 | ContextData& newContext = contextData_[id]; 298 | newContext.InvocationId = parentContext->InvocationId; 299 | newContext.InvocationDescription = parentContext->InvocationDescription; 300 | newContext.Tool = parentContext->Tool; 301 | newContext.Component = parentContext->Component; 302 | 303 | if (parentContextLink.TimelineReuseCount) 304 | { 305 | auto timelineId = GetNewTimelineId(); 306 | newContext.TimelineId = timelineId; 307 | newContext.TimelineDescription = timelineDescriptions_[timelineId].c_str(); 308 | } 309 | else 310 | { 311 | newContext.TimelineId = parentContext->TimelineId; 312 | newContext.TimelineDescription = parentContext->TimelineDescription; 313 | 314 | parentContextLink.TimelineReuseCount++; 315 | } 316 | 317 | activityContextLinks_.try_emplace(id, &newContext, 0); 318 | 319 | currentContextData_ = &newContext; 320 | } 321 | 322 | void ContextBuilder::OnStopRootActivity(const Activity& activity) 323 | { 324 | unsigned long long id = activity.EventInstanceId(); 325 | 326 | auto it = GetContextLink(id); 327 | 328 | assert(it->second.TimelineReuseCount == 0); 329 | 330 | availableTimelineIds_.push(it->second.LinkedContext->TimelineId); 331 | 332 | activityContextLinks_.erase(it); 333 | contextData_.erase(id); 334 | } 335 | 336 | void ContextBuilder::OnStopNestedActivity(const Activity& parent, 337 | const Activity& child) 338 | { 339 | auto itParent = GetContextLink(parent.EventInstanceId()); 340 | auto itChild = GetContextLink(child.EventInstanceId()); 341 | 342 | assert(itChild->second.TimelineReuseCount == 0); 343 | 344 | ContextData* parentContext = itParent->second.LinkedContext; 345 | ContextData* childContext = itChild->second.LinkedContext; 346 | 347 | assert(parentContext && childContext); 348 | 349 | if (parentContext->TimelineId == childContext->TimelineId) 350 | { 351 | assert(itParent->second.TimelineReuseCount); 352 | itParent->second.TimelineReuseCount--; 353 | } 354 | else { 355 | availableTimelineIds_.push(childContext->TimelineId); 356 | } 357 | 358 | activityContextLinks_.erase(itChild); 359 | } 360 | 361 | void ContextBuilder::OnStopCompilerPass(const CompilerPass& pass) 362 | { 363 | activeComponents_.erase(pass.EventInstanceId()); 364 | contextData_.erase(pass.EventInstanceId()); 365 | } 366 | 367 | void ContextBuilder::OnStopInvocation(const Invocation& invocation) 368 | { 369 | activeComponents_.erase(invocation.EventInstanceId()); 370 | invocationDescriptions_.erase(invocation.EventInstanceId()); 371 | contextData_.erase(invocation.EventInstanceId()); 372 | } 373 | 374 | void ContextBuilder::OnStopC2Thread(const C2DLL& c2, const Thread& thread) 375 | { 376 | contextData_.erase(thread.EventInstanceId()); 377 | } 378 | 379 | unsigned short ContextBuilder::GetNewTimelineId() 380 | { 381 | unsigned short timelineId; 382 | 383 | if (availableTimelineIds_.empty()) 384 | { 385 | timelineId = timelineCount_++; 386 | } 387 | else 388 | { 389 | timelineId = availableTimelineIds_.top(); 390 | availableTimelineIds_.pop(); 391 | } 392 | 393 | if (timelineDescriptions_.size() == timelineId) 394 | { 395 | timelineDescriptions_.emplace(timelineId, 396 | std::string{ "Timeline " } + std::to_string(timelineId)); 397 | } 398 | 399 | return timelineId; 400 | } -------------------------------------------------------------------------------- /src/WPA/Analyzers/ContextBuilder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "VcperfBuildInsights.h" 11 | #include "Utility.h" 12 | #include "PayloadBuilder.h" 13 | 14 | namespace vcperf 15 | { 16 | 17 | class ContextBuilder : public BI::IAnalyzer 18 | { 19 | struct Component 20 | { 21 | std::wstring Path; 22 | bool IsInput; 23 | }; 24 | 25 | public: 26 | struct ContextData 27 | { 28 | unsigned short TimelineId; 29 | const char* TimelineDescription; 30 | const char* Tool; 31 | unsigned int InvocationId; 32 | const wchar_t* InvocationDescription; 33 | const wchar_t* Component; 34 | }; 35 | 36 | private: 37 | struct ContextLink 38 | { 39 | ContextLink(ContextData* linkedContext, unsigned short timelineReusedCount): 40 | LinkedContext{linkedContext}, 41 | TimelineReuseCount{timelineReusedCount} 42 | {} 43 | 44 | ContextData* LinkedContext; 45 | unsigned short TimelineReuseCount; 46 | }; 47 | 48 | typedef std::unordered_map ContextDataMap; 49 | typedef std::unordered_map ContextLinkMap; 50 | 51 | public: 52 | ContextBuilder() : 53 | analysisCount_{0}, 54 | analysisPass_{0}, 55 | timelineCount_{0}, 56 | contextData_{}, 57 | activityContextLinks_{}, 58 | availableTimelineIds_{}, 59 | mainComponentCache_{}, 60 | activeComponents_{}, 61 | invocationDescriptions_{}, 62 | timelineDescriptions_{}, 63 | currentContextData_{nullptr}, 64 | currentInstanceId_{0} 65 | { 66 | } 67 | 68 | BI::AnalysisControl OnBeginAnalysis() override 69 | { 70 | analysisCount_++; 71 | return BI::AnalysisControl::CONTINUE; 72 | } 73 | 74 | BI::AnalysisControl OnEndAnalysis() override 75 | { 76 | analysisCount_--; 77 | return BI::AnalysisControl::CONTINUE; 78 | } 79 | 80 | BI::AnalysisControl OnBeginAnalysisPass() override 81 | { 82 | analysisPass_++; 83 | return BI::AnalysisControl::CONTINUE; 84 | } 85 | 86 | BI::AnalysisControl OnStartActivity(const BI::EventStack& eventStack) override; 87 | BI::AnalysisControl OnStopActivity(const BI::EventStack& eventStack) override; 88 | BI::AnalysisControl OnSimpleEvent(const BI::EventStack& eventStack) override; 89 | 90 | const ContextData* GetContextData() 91 | { 92 | if (currentContextData_) { 93 | return currentContextData_; 94 | } 95 | 96 | if (currentInstanceId_ == 0) { 97 | return nullptr; 98 | } 99 | 100 | return GetContextLink(currentInstanceId_)->second.LinkedContext; 101 | } 102 | 103 | private: 104 | 105 | bool MustCacheMainComponents() const { 106 | return analysisPass_ == 1; 107 | } 108 | 109 | bool MustBuildContext() const 110 | { 111 | if (IsRelogging()) 112 | { 113 | // At least one analysis pass is required to cache the main component paths. 114 | assert(analysisPass_ > 1); 115 | return true; 116 | } 117 | 118 | return false; 119 | } 120 | 121 | bool IsRelogging() const { 122 | return analysisCount_ == 1; 123 | } 124 | 125 | void OnLibOutput(const A::LinkerGroup& linkers, const SE::LibOutput& output); 126 | void OnExecutableImageOutput(const A::LinkerGroup& linkers, const SE::ExecutableImageOutput& output); 127 | void OnImpLibOutput(const A::LinkerGroup& linkers, const SE::ImpLibOutput& output); 128 | void OnCompilerInput(const A::Compiler& cl, const SE::FileInput& input); 129 | void OnCompilerOutput(const A::Compiler& cl, const SE::ObjOutput& output); 130 | 131 | void ProcessLinkerOutput(const A::LinkerGroup& linkers, const SE::FileOutput& output, bool overwrite); 132 | 133 | void OnRootActivity(const A::Activity& root); 134 | void OnNestedActivity(const A::Activity& parent, const A::Activity& child); 135 | void OnInvocation(const A::Invocation& invocation); 136 | void OnCompilerPass(const A::Compiler& cl, const A::CompilerPass& pass); 137 | void OnC2Thread(const A::C2DLL& c2, const A::Activity& threadOwner, const A::Thread& thread); 138 | 139 | void ProcessParallelismForkPoint(const A::Activity& parent, const A::Activity& child); 140 | void ProcessParallelismForkPoint(ContextLink& parentContextLink, const A::Activity& child); 141 | 142 | void OnStopRootActivity(const A::Activity& activity); 143 | void OnStopNestedActivity(const A::Activity& parent, const A::Activity& child); 144 | void OnStopCompilerPass(const A::CompilerPass& pass); 145 | void OnStopInvocation(const A::Invocation& invocation); 146 | void OnStopC2Thread(const A::C2DLL& c2, const A::Thread& thread); 147 | 148 | unsigned short GetNewTimelineId(); 149 | 150 | ContextLinkMap::iterator GetContextLink(unsigned long long instanceId) 151 | { 152 | auto it = activityContextLinks_.find(instanceId); 153 | 154 | assert(it != activityContextLinks_.end()); 155 | 156 | return it; 157 | } 158 | 159 | template 160 | const TChar* CacheString(std::unordered_map>& cache, 161 | unsigned long long instanceId, const TChar* value) 162 | { 163 | return CacheString(cache, instanceId, std::basic_string{value}); 164 | } 165 | 166 | template 167 | const TChar* CacheString(std::unordered_map>& cache, 168 | unsigned long long instanceId, std::basic_string&& value) 169 | { 170 | auto result = cache.emplace(instanceId, std::move(value)); 171 | 172 | assert(result.second); 173 | 174 | return result.first->second.c_str(); 175 | } 176 | 177 | int analysisCount_; 178 | int analysisPass_; 179 | 180 | unsigned int timelineCount_; 181 | 182 | ContextDataMap contextData_; 183 | ContextLinkMap activityContextLinks_; 184 | 185 | std::priority_queue< 186 | unsigned short, 187 | std::vector, 188 | std::greater> availableTimelineIds_; 189 | 190 | std::unordered_map mainComponentCache_; 191 | std::unordered_map activeComponents_; 192 | std::unordered_map invocationDescriptions_; 193 | std::unordered_map timelineDescriptions_; 194 | 195 | ContextData* currentContextData_; 196 | unsigned long long currentInstanceId_; 197 | }; 198 | 199 | } // namespace vcperf -------------------------------------------------------------------------------- /src/WPA/Analyzers/ExpensiveTemplateInstantiationCache.cpp: -------------------------------------------------------------------------------- 1 | #include "ExpensiveTemplateInstantiationCache.h" 2 | 3 | using namespace Microsoft::Cpp::BuildInsights; 4 | using namespace Activities; 5 | using namespace SimpleEvents; 6 | 7 | namespace vcperf 8 | { 9 | 10 | std::tuple ExpensiveTemplateInstantiationCache:: 11 | GetTemplateInstantiationInfo(const TemplateInstantiation& ti) const 12 | { 13 | auto itPrimary = keysToConsider_.find(ti.PrimaryTemplateSymbolKey()); 14 | 15 | if (itPrimary == keysToConsider_.end()) { 16 | return { false, nullptr, nullptr }; 17 | } 18 | 19 | auto itSpecialization = keysToConsider_.find(ti.SpecializationSymbolKey()); 20 | 21 | if (itSpecialization == keysToConsider_.end()) 22 | { 23 | return { true, itPrimary->second, "" }; 24 | } 25 | 26 | return { true, itPrimary->second, itSpecialization->second }; 27 | } 28 | 29 | AnalysisControl ExpensiveTemplateInstantiationCache:: 30 | OnStopActivity(const EventStack& eventStack) 31 | { 32 | if (!isEnabled_ || IsRelogging()) { 33 | return AnalysisControl::CONTINUE; 34 | } 35 | 36 | MatchEventStackInMemberFunction(eventStack, this, 37 | &ExpensiveTemplateInstantiationCache::OnTemplateInstantiation); 38 | 39 | return AnalysisControl::CONTINUE; 40 | } 41 | 42 | AnalysisControl ExpensiveTemplateInstantiationCache:: 43 | OnSimpleEvent(const EventStack& eventStack) 44 | { 45 | if (!isEnabled_ || IsRelogging()) { 46 | return AnalysisControl::CONTINUE; 47 | } 48 | 49 | MatchEventStackInMemberFunction(eventStack, this, 50 | &ExpensiveTemplateInstantiationCache::OnSymbolName); 51 | 52 | return AnalysisControl::CONTINUE; 53 | } 54 | 55 | AnalysisControl ExpensiveTemplateInstantiationCache::OnEndAnalysisPass() 56 | { 57 | if (!isEnabled_ || IsRelogging()) { 58 | return AnalysisControl::CONTINUE; 59 | } 60 | 61 | switch (analysisPass_) 62 | { 63 | case 1: 64 | DetermineTopPrimaryTemplates(); 65 | break; 66 | 67 | case 2: 68 | default: 69 | localSpecializationKeysToConsider_.clear(); 70 | break; 71 | } 72 | 73 | return AnalysisControl::CONTINUE; 74 | } 75 | 76 | void ExpensiveTemplateInstantiationCache:: 77 | OnTemplateInstantiation(const TemplateInstantiation& instantiation) 78 | { 79 | switch (analysisPass_) 80 | { 81 | case 1: 82 | Phase1RegisterPrimaryTemplateLocalTime(instantiation); 83 | break; 84 | 85 | case 2: 86 | Phase2RegisterSpecializationKey(instantiation); 87 | break; 88 | 89 | default: 90 | break; 91 | } 92 | } 93 | 94 | void ExpensiveTemplateInstantiationCache::OnSymbolName(const SymbolName& symbol) 95 | { 96 | switch (analysisPass_) 97 | { 98 | case 1: 99 | Phase1MergePrimaryTemplateDuration(symbol); 100 | break; 101 | 102 | case 2: 103 | Phase2MergeSpecializationKey(symbol); 104 | break; 105 | 106 | default: 107 | break; 108 | } 109 | } 110 | 111 | void ExpensiveTemplateInstantiationCache:: 112 | Phase1RegisterPrimaryTemplateLocalTime(const TemplateInstantiation& instantiation) 113 | { 114 | unsigned int microseconds = static_cast(std::chrono:: 115 | duration_cast(instantiation.Duration()).count()); 116 | 117 | localPrimaryTemplateTimes_[instantiation.PrimaryTemplateSymbolKey()] += microseconds; 118 | } 119 | 120 | void ExpensiveTemplateInstantiationCache:: 121 | Phase1MergePrimaryTemplateDuration(const SymbolName& symbol) 122 | { 123 | auto itPrimary = localPrimaryTemplateTimes_.find(symbol.Key()); 124 | 125 | if (itPrimary != localPrimaryTemplateTimes_.end()) 126 | { 127 | auto it = cachedSymbolNames_.insert(symbol.Name()).first; 128 | auto& stats = primaryTemplateStats_[it->c_str()]; 129 | stats.PrimaryKeys.push_back(symbol.Key()); 130 | stats.TotalMicroseconds += itPrimary->second; 131 | 132 | localPrimaryTemplateTimes_.erase(itPrimary); 133 | } 134 | } 135 | 136 | void ExpensiveTemplateInstantiationCache:: 137 | Phase2RegisterSpecializationKey(const TemplateInstantiation& instantiation) 138 | { 139 | if (keysToConsider_.find(instantiation.PrimaryTemplateSymbolKey()) != keysToConsider_.end()) { 140 | localSpecializationKeysToConsider_.insert(instantiation.SpecializationSymbolKey()); 141 | } 142 | } 143 | 144 | void ExpensiveTemplateInstantiationCache:: 145 | Phase2MergeSpecializationKey(const SymbolName& symbol) 146 | { 147 | auto it = localSpecializationKeysToConsider_.find(symbol.Key()); 148 | 149 | if (it != localSpecializationKeysToConsider_.end()) 150 | { 151 | auto itCached = cachedSymbolNames_.insert(symbol.Name()).first; 152 | keysToConsider_[symbol.Key()] = itCached->c_str(); 153 | 154 | localSpecializationKeysToConsider_.erase(it); 155 | } 156 | } 157 | 158 | void ExpensiveTemplateInstantiationCache::DetermineTopPrimaryTemplates() 159 | { 160 | unsigned int cutoff = 500000; 161 | 162 | unsigned long long durationBasedCutoff = 163 | static_cast(0.05 * traceDuration_.count()); 164 | 165 | if (durationBasedCutoff < cutoff) { 166 | cutoff = static_cast(durationBasedCutoff); 167 | } 168 | 169 | for (auto& p : primaryTemplateStats_) 170 | { 171 | if (p.second.TotalMicroseconds >= cutoff) 172 | { 173 | for (auto primKey : p.second.PrimaryKeys) { 174 | keysToConsider_[primKey] = p.first; 175 | } 176 | 177 | continue; 178 | } 179 | 180 | cachedSymbolNames_.erase(p.first); 181 | } 182 | 183 | primaryTemplateStats_.clear(); 184 | localPrimaryTemplateTimes_.clear(); 185 | } 186 | 187 | } // namespace vcperf -------------------------------------------------------------------------------- /src/WPA/Analyzers/ExpensiveTemplateInstantiationCache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "VcperfBuildInsights.h" 9 | 10 | namespace vcperf 11 | { 12 | 13 | class ExpensiveTemplateInstantiationCache : public BI::IAnalyzer 14 | { 15 | struct PrimaryTemplateStats 16 | { 17 | std::vector PrimaryKeys; 18 | unsigned int TotalMicroseconds; 19 | }; 20 | 21 | public: 22 | 23 | ExpensiveTemplateInstantiationCache(bool isEnabled): 24 | analysisCount_{0}, 25 | analysisPass_{0}, 26 | traceDuration_{0}, 27 | cachedSymbolNames_{}, 28 | primaryTemplateStats_{}, 29 | keysToConsider_{}, 30 | localPrimaryTemplateTimes_{}, 31 | isEnabled_{isEnabled} 32 | {} 33 | 34 | std::tuple 35 | GetTemplateInstantiationInfo(const A::TemplateInstantiation& ti) const; 36 | 37 | BI::AnalysisControl OnTraceInfo(const BI::TraceInfo& traceInfo) override 38 | { 39 | traceDuration_ = traceInfo.Duration(); 40 | return BI::AnalysisControl::CONTINUE; 41 | } 42 | 43 | BI::AnalysisControl OnBeginAnalysis() override 44 | { 45 | analysisCount_++; 46 | return BI::AnalysisControl::CONTINUE; 47 | } 48 | 49 | BI::AnalysisControl OnEndAnalysis() override 50 | { 51 | analysisCount_--; 52 | return BI::AnalysisControl::CONTINUE; 53 | } 54 | 55 | BI::AnalysisControl OnBeginAnalysisPass() override 56 | { 57 | analysisPass_++; 58 | return BI::AnalysisControl::CONTINUE; 59 | } 60 | 61 | BI::AnalysisControl OnStopActivity(const BI::EventStack& eventStack) override; 62 | BI::AnalysisControl OnSimpleEvent(const BI::EventStack& eventStack) override; 63 | BI::AnalysisControl OnEndAnalysisPass() override; 64 | 65 | private: 66 | bool IsRelogging() const { 67 | return analysisCount_ == 1; 68 | } 69 | 70 | void OnTemplateInstantiation(const A::TemplateInstantiation& instantiation); 71 | void OnSymbolName(const SE::SymbolName& symbol); 72 | void Phase1RegisterPrimaryTemplateLocalTime(const A::TemplateInstantiation& instantiation); 73 | void Phase1MergePrimaryTemplateDuration(const SE::SymbolName& symbol); 74 | void Phase2RegisterSpecializationKey(const A::TemplateInstantiation& instantiation); 75 | void Phase2MergeSpecializationKey(const SE::SymbolName& symbol); 76 | void DetermineTopPrimaryTemplates(); 77 | 78 | int analysisCount_; 79 | int analysisPass_; 80 | 81 | std::chrono::nanoseconds traceDuration_; 82 | 83 | // All phases 84 | std::unordered_set cachedSymbolNames_; 85 | std::unordered_map keysToConsider_; 86 | 87 | // Phase 1 88 | // The const char* keys in primaryTemplateStats_ point to the unique 89 | // cached values in cachedSymbolNames_. 90 | std::unordered_map primaryTemplateStats_; 91 | std::unordered_map localPrimaryTemplateTimes_; 92 | 93 | // Phase 2 94 | std::unordered_set localSpecializationKeysToConsider_; 95 | 96 | bool isEnabled_; 97 | 98 | }; 99 | 100 | } // namespace vcperf -------------------------------------------------------------------------------- /src/WPA/Analyzers/MiscellaneousCache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "VcperfBuildInsights.h" 9 | 10 | namespace vcperf 11 | { 12 | 13 | class MiscellaneousCache : public BI::IAnalyzer 14 | { 15 | public: 16 | MiscellaneousCache(): 17 | pass_{0}, 18 | exclusivityLeaves_{} 19 | {} 20 | 21 | struct TimingData 22 | { 23 | std::chrono::nanoseconds Duration; 24 | std::chrono::nanoseconds ExclusiveDuration; 25 | std::chrono::nanoseconds CPUTime; 26 | std::chrono::nanoseconds ExclusiveCPUTime; 27 | std::chrono::nanoseconds WallClockTimeResponsibility; 28 | }; 29 | 30 | const TimingData& GetTimingData(const A::Activity& a) 31 | { 32 | return timingData_[a.EventInstanceId()]; 33 | } 34 | 35 | BI::AnalysisControl OnEndAnalysisPass() override 36 | { 37 | ++pass_; 38 | 39 | return BI::AnalysisControl::CONTINUE; 40 | } 41 | 42 | BI::AnalysisControl OnStopActivity(const BI::EventStack& eventStack) override 43 | { 44 | if (pass_) { 45 | return BI::AnalysisControl::CONTINUE; 46 | } 47 | 48 | A::Activity a{eventStack.Back()}; 49 | 50 | assert(timingData_.find(a.EventInstanceId()) == timingData_.end()); 51 | 52 | auto& t = timingData_[a.EventInstanceId()]; 53 | 54 | t.Duration = a.Duration(); 55 | t.CPUTime = a.CPUTime(); 56 | t.WallClockTimeResponsibility = a.WallClockTimeResponsibility(); 57 | 58 | auto it = exclusivityLeaves_.find(eventStack.Back().EventInstanceId()); 59 | 60 | if (it == exclusivityLeaves_.end()) 61 | { 62 | t.ExclusiveDuration = a.ExclusiveDuration(); 63 | t.ExclusiveCPUTime = a.ExclusiveCPUTime(); 64 | } 65 | else 66 | { 67 | t.ExclusiveDuration = t.Duration; 68 | t.ExclusiveCPUTime = t.CPUTime; 69 | 70 | exclusivityLeaves_.erase(it); 71 | 72 | return BI::AnalysisControl::CONTINUE; 73 | } 74 | 75 | size_t stackSize = eventStack.Size(); 76 | 77 | if (stackSize <= 1) { 78 | return BI::AnalysisControl::CONTINUE; 79 | } 80 | 81 | auto child = eventStack.Back(); 82 | auto parent = eventStack[stackSize - 2]; 83 | 84 | unsigned long long childEventId = eventStack.Back().EventId(); 85 | 86 | if (childEventId == parent.EventId()) { 87 | return BI::AnalysisControl::CONTINUE; 88 | } 89 | 90 | if ( childEventId == BI::EVENT_ID_FUNCTION 91 | || childEventId == BI::EVENT_ID_FRONT_END_FILE) 92 | { 93 | exclusivityLeaves_.insert(eventStack[stackSize - 2].EventInstanceId()); 94 | } 95 | 96 | return BI::AnalysisControl::CONTINUE; 97 | } 98 | 99 | private: 100 | unsigned pass_; 101 | 102 | std::unordered_map timingData_; 103 | std::unordered_set exclusivityLeaves_; 104 | 105 | }; 106 | 107 | } // namespace vcperf -------------------------------------------------------------------------------- /src/WPA/Views/BuildExplorerView.cpp: -------------------------------------------------------------------------------- 1 | #include "BuildExplorerView.h" 2 | #include "Utility.h" 3 | #include "CppBuildInsightsEtw.h" 4 | #include "PayloadBuilder.h" 5 | 6 | using namespace Microsoft::Cpp::BuildInsights; 7 | using namespace Activities; 8 | using namespace SimpleEvents; 9 | 10 | namespace vcperf 11 | { 12 | 13 | AnalysisControl BuildExplorerView::OnActivity(const EventStack& eventStack, 14 | const void* relogSession) 15 | { 16 | if ( MatchEventInMemberFunction(eventStack.Back(), this, 17 | &BuildExplorerView::OnInvocation, relogSession) 18 | 19 | || MatchEventStackInMemberFunction(eventStack, this, 20 | &BuildExplorerView::OnCompilerPass, relogSession) 21 | 22 | || MatchEventStackInMemberFunction(eventStack, this, 23 | &BuildExplorerView::OnThread, relogSession)) 24 | { 25 | return AnalysisControl::CONTINUE; 26 | } 27 | 28 | switch (eventStack.Back().EventId()) 29 | { 30 | case EVENT_ID_PASS1: 31 | case EVENT_ID_PASS2: 32 | case EVENT_ID_PRE_LTCG_OPT_REF: 33 | case EVENT_ID_LTCG: 34 | case EVENT_ID_OPT_REF: 35 | case EVENT_ID_OPT_ICF: 36 | case EVENT_ID_OPT_LBR: 37 | case EVENT_ID_C1_DLL: 38 | case EVENT_ID_C2_DLL: 39 | case EVENT_ID_WHOLE_PROGRAM_ANALYSIS: 40 | case EVENT_ID_CODE_GENERATION: 41 | break; 42 | 43 | default: 44 | return AnalysisControl::CONTINUE; 45 | } 46 | 47 | LogActivity(relogSession, eventStack.Back()); 48 | 49 | return AnalysisControl::CONTINUE; 50 | } 51 | 52 | AnalysisControl BuildExplorerView::OnSimpleEvent(const EventStack& eventStack, 53 | const void* relogSession) 54 | { 55 | if ( MatchEventStackInMemberFunction(eventStack, this, 56 | &BuildExplorerView::OnCommandLine, relogSession) 57 | 58 | || MatchEventStackInMemberFunction(eventStack, this, 59 | &BuildExplorerView::OnCompilerEnvironmentVariable, relogSession) 60 | 61 | || MatchEventStackInMemberFunction(eventStack, this, 62 | &BuildExplorerView::OnLinkerEnvironmentVariable, relogSession)) 63 | { 64 | return AnalysisControl::CONTINUE; 65 | } 66 | 67 | return AnalysisControl::CONTINUE; 68 | } 69 | 70 | void BuildExplorerView::EmitInvocationEvents(const Invocation& invocation, 71 | const void* relogSession) 72 | { 73 | LogActivity(relogSession, invocation); 74 | 75 | // For start events we also log invocaton properties such as version, working directory, etc... 76 | ProcessStringProperty(relogSession, invocation, 77 | "Version", invocation.ToolVersionString()); 78 | 79 | // Tool path is not available for earlier versions of the toolset 80 | if (invocation.ToolPath()) 81 | { 82 | ProcessStringProperty(relogSession, invocation, 83 | "ToolPath", invocation.ToolPath()); 84 | } 85 | 86 | ProcessStringProperty(relogSession, invocation, 87 | "WorkingDirectory", invocation.WorkingDirectory()); 88 | } 89 | 90 | void BuildExplorerView::OnCompilerEnvironmentVariable(const Compiler& cl, 91 | const EnvironmentVariable& envVar, const void* relogSession) 92 | { 93 | auto* name = envVar.Name(); 94 | 95 | if (_wcsicmp(name, L"CL") == 0) 96 | { 97 | ProcessStringProperty(relogSession, envVar, "Env Var: CL", envVar.Value()); 98 | return; 99 | } 100 | 101 | if (_wcsicmp(name, L"_CL_") == 0) 102 | { 103 | ProcessStringProperty(relogSession, envVar, "Env Var: _CL_", envVar.Value()); 104 | return; 105 | } 106 | 107 | if (_wcsicmp(name, L"INCLUDE") == 0) 108 | { 109 | ProcessStringProperty(relogSession, envVar, "Env Var: INCLUDE", envVar.Value()); 110 | return; 111 | } 112 | 113 | if (_wcsicmp(name, L"LIBPATH") == 0) 114 | { 115 | ProcessStringProperty(relogSession, envVar, "Env Var: LIBPATH", envVar.Value()); 116 | return; 117 | } 118 | 119 | if (_wcsicmp(name, L"PATH") == 0) 120 | { 121 | ProcessStringProperty(relogSession, envVar, "Env Var: PATH", envVar.Value()); 122 | return; 123 | } 124 | 125 | // new env vars 126 | if (_wcsicmp(name, L"VSTEL_SolutionSessionID") == 0) 127 | { 128 | ProcessStringProperty(relogSession, envVar, "Env Var: VSTEL_SolutionSessionID", envVar.Value()); 129 | return; 130 | } 131 | 132 | if (_wcsicmp(name, L"VSTEL_CurrentSolutionBuildID") == 0) 133 | { 134 | ProcessStringProperty(relogSession, envVar, "Env Var: VSTEL_CurrentSolutionBuildID", envVar.Value()); 135 | return; 136 | } 137 | 138 | if (_wcsicmp(name, L"VSTEL_SolutionPath") == 0) 139 | { 140 | ProcessStringProperty(relogSession, envVar, "Env Var: VSTEL_SolutionPath", envVar.Value()); 141 | return; 142 | } 143 | 144 | if (_wcsicmp(name, L"VSTEL_MSBuildProjectFullPath") == 0) 145 | { 146 | ProcessStringProperty(relogSession, envVar, "Env Var: VSTEL_MSBuildProjectFullPath", envVar.Value()); 147 | return; 148 | } 149 | 150 | if (_wcsicmp(name, L"VSTEL_ProjectID") == 0) 151 | { 152 | ProcessStringProperty(relogSession, envVar, "Env Var: VSTEL_ProjectID", envVar.Value()); 153 | return; 154 | } 155 | } 156 | 157 | void BuildExplorerView::OnLinkerEnvironmentVariable(const Linker& link, 158 | const EnvironmentVariable& envVar, const void* relogSession) 159 | { 160 | auto* name = envVar.Name(); 161 | 162 | if (_wcsicmp(name, L"LINK") == 0) 163 | { 164 | ProcessStringProperty(relogSession, envVar, "Env Var: LINK", envVar.Value()); 165 | return; 166 | } 167 | 168 | if (_wcsicmp(name, L"_LINK_") == 0) 169 | { 170 | ProcessStringProperty(relogSession, envVar, "Env Var: _LINK_", envVar.Value()); 171 | return; 172 | } 173 | 174 | if (_wcsicmp(name, L"LIB") == 0) 175 | { 176 | ProcessStringProperty(relogSession, envVar, "Env Var: LIB", envVar.Value()); 177 | return; 178 | } 179 | 180 | if (_wcsicmp(name, L"PATH") == 0) 181 | { 182 | ProcessStringProperty(relogSession, envVar, "Env Var: PATH", envVar.Value()); 183 | return; 184 | } 185 | 186 | if (_wcsicmp(name, L"TMP") == 0) 187 | { 188 | ProcessStringProperty(relogSession, envVar, "Env Var: TMP", envVar.Value()); 189 | return; 190 | } 191 | 192 | if (_wcsicmp(name, L"VSTEL_SolutionSessionID") == 0) 193 | { 194 | ProcessStringProperty(relogSession, envVar, "Env Var: VSTEL_SolutionSessionID", envVar.Value()); 195 | return; 196 | } 197 | 198 | if (_wcsicmp(name, L"VSTEL_CurrentSolutionBuildID") == 0) 199 | { 200 | ProcessStringProperty(relogSession, envVar, "Env Var: VSTEL_CurrentSolutionBuildID", envVar.Value()); 201 | return; 202 | } 203 | 204 | if (_wcsicmp(name, L"VSTEL_SolutionPath") == 0) 205 | { 206 | ProcessStringProperty(relogSession, envVar, "Env Var: VSTEL_SolutionPath", envVar.Value()); 207 | return; 208 | } 209 | 210 | if (_wcsicmp(name, L"VSTEL_MSBuildProjectFullPath") == 0) 211 | { 212 | ProcessStringProperty(relogSession, envVar, "Env Var: VSTEL_MSBuildProjectFullPath", envVar.Value()); 213 | return; 214 | } 215 | 216 | if (_wcsicmp(name, L"VSTEL_ProjectID") == 0) 217 | { 218 | ProcessStringProperty(relogSession, envVar, "Env Var: VSTEL_ProjectID", envVar.Value()); 219 | return; 220 | } 221 | } 222 | 223 | void BuildExplorerView::LogActivity(const void* relogSession, const Activity& a, 224 | const char* activityName) 225 | { 226 | using std::chrono::duration_cast; 227 | using std::chrono::milliseconds; 228 | 229 | PCEVENT_DESCRIPTOR desc = &CppBuildInsightsBuildExplorerActivity_V1; 230 | 231 | auto* context = contextBuilder_->GetContextData(); 232 | auto& td = miscellaneousCache_->GetTimingData(a); 233 | 234 | Payload p = PayloadBuilder< 235 | uint16_t, const char*, const char*, uint32_t, const wchar_t*, const wchar_t*, 236 | const char*, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t>::Build( 237 | context->TimelineId, 238 | context->TimelineDescription, 239 | context->Tool, 240 | context->InvocationId, 241 | context->InvocationDescription, 242 | context->Component, 243 | activityName, 244 | (uint32_t)duration_cast(td.ExclusiveDuration).count(), 245 | (uint32_t)duration_cast(td.Duration).count(), 246 | (uint32_t)duration_cast(td.ExclusiveCPUTime).count(), 247 | (uint32_t)duration_cast(td.CPUTime).count(), 248 | (uint32_t)duration_cast(td.WallClockTimeResponsibility).count()); 249 | 250 | InjectEvent(relogSession, &CppBuildInsightsGuid, desc, a.ProcessId(), a.ThreadId(), 251 | a.ProcessorIndex(), a.StartTimestamp(), p.GetData(), (unsigned long)p.Size()); 252 | } 253 | 254 | 255 | template 256 | void BuildExplorerView::ProcessStringProperty(const void* relogSession, 257 | const Event& e, const char* name, const TChar* value) 258 | { 259 | size_t len = std::char_traits::length(value); 260 | 261 | constexpr size_t COMMAND_LINE_SEGMENT_LEN = 1000; 262 | 263 | while (len > COMMAND_LINE_SEGMENT_LEN) 264 | { 265 | TChar segment[COMMAND_LINE_SEGMENT_LEN + 1]; 266 | 267 | memcpy(segment, value, COMMAND_LINE_SEGMENT_LEN * sizeof(TChar)); 268 | segment[COMMAND_LINE_SEGMENT_LEN] = 0; 269 | 270 | LogStringPropertySegment(relogSession, e, name, segment); 271 | 272 | len -= COMMAND_LINE_SEGMENT_LEN; 273 | value += COMMAND_LINE_SEGMENT_LEN; 274 | } 275 | 276 | LogStringPropertySegment(relogSession, e, name, value); 277 | } 278 | 279 | 280 | void BuildExplorerView::LogStringPropertySegment(const void* relogSession, 281 | const Event& e, const char* name, const char* value) 282 | { 283 | LogStringPropertySegment(relogSession, e, name, value, 284 | &CppBuildInsightsBuildExplorerAnsiStringProperty); 285 | } 286 | 287 | void BuildExplorerView::LogStringPropertySegment(const void* relogSession, 288 | const Event& e, const char* name, const wchar_t* value) 289 | { 290 | LogStringPropertySegment(relogSession, e, name, value, 291 | &CppBuildInsightsBuildExplorerUnicodeStringProperty); 292 | } 293 | 294 | template 295 | void BuildExplorerView::LogStringPropertySegment(const void* relogSession, 296 | const Event& e, const char* name, const TChar* value, 297 | PCEVENT_DESCRIPTOR desc) 298 | { 299 | auto* context = contextBuilder_->GetContextData(); 300 | 301 | Payload p = PayloadBuilder< 302 | uint16_t, const char*, const char*, uint32_t, const wchar_t*, 303 | const wchar_t*, const char*, const TChar*>::Build( 304 | context->TimelineId, 305 | context->TimelineDescription, 306 | context->Tool, 307 | context->InvocationId, 308 | context->InvocationDescription, 309 | context->Component, 310 | name, 311 | value 312 | ); 313 | 314 | InjectEvent(relogSession, &CppBuildInsightsGuid, desc, 315 | e.ProcessId(), e.ThreadId(), e.ProcessorIndex(), 316 | e.Timestamp(), p.GetData(), (unsigned long)p.Size()); 317 | } 318 | 319 | } // namespace vcperf -------------------------------------------------------------------------------- /src/WPA/Views/BuildExplorerView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "VcperfBuildInsights.h" 4 | #include "WPA\Analyzers\ContextBuilder.h" 5 | #include "WPA\Analyzers\MiscellaneousCache.h" 6 | 7 | namespace vcperf 8 | { 9 | 10 | class BuildExplorerView : public BI::IRelogger 11 | { 12 | public: 13 | BuildExplorerView(ContextBuilder* contextBuilder, 14 | MiscellaneousCache* miscellaneousCache) : 15 | contextBuilder_{contextBuilder}, 16 | miscellaneousCache_{miscellaneousCache} 17 | {} 18 | 19 | BI::AnalysisControl OnStartActivity(const BI::EventStack& eventStack, 20 | const void* relogSession) override 21 | { 22 | return OnActivity(eventStack, relogSession); 23 | } 24 | 25 | BI::AnalysisControl OnSimpleEvent(const BI::EventStack& eventStack, 26 | const void* relogSession) override; 27 | 28 | void OnInvocation(const A::Invocation& invocation, const void* relogSession) 29 | { 30 | EmitInvocationEvents(invocation, relogSession); 31 | } 32 | 33 | void OnCompilerPass(const A::CompilerPass& pass, const void* relogSession) 34 | { 35 | LogActivity(relogSession, pass); 36 | } 37 | 38 | void OnThread(const A::Activity& a, const A::Thread& t, const void* relogSession) 39 | { 40 | OnThreadActivity(a, t, relogSession); 41 | } 42 | 43 | void OnCommandLine(const A::Invocation& invocation, const SE::CommandLine& commandLine, 44 | const void* relogSession) 45 | { 46 | ProcessStringProperty(relogSession, commandLine, "CommandLine", commandLine.Value()); 47 | } 48 | 49 | void OnCompilerEnvironmentVariable(const A::Compiler& cl, const SE::EnvironmentVariable& envVar, 50 | const void* relogSession); 51 | 52 | void OnLinkerEnvironmentVariable(const A::Linker& link, const SE::EnvironmentVariable& envVar, 53 | const void* relogSession); 54 | 55 | BI::AnalysisControl OnEndRelogging() override 56 | { 57 | return BI::AnalysisControl::CONTINUE; 58 | } 59 | 60 | private: 61 | 62 | BI::AnalysisControl OnActivity(const BI::EventStack& eventStack, const void* relogSession); 63 | 64 | void OnThreadActivity(const A::Activity& a, const A::Thread& t, const void* relogSession) 65 | { 66 | std::string activityName = a.EventName(); 67 | activityName += "Thread"; 68 | 69 | LogActivity(relogSession, t, activityName.c_str()); 70 | } 71 | 72 | void EmitInvocationEvents(const A::Invocation& invocation, const void* relogSession); 73 | 74 | void LogActivity(const void* relogSession, const A::Activity& a, const char* activityName); 75 | 76 | void LogActivity(const void* relogSession, const A::Activity& a) 77 | { 78 | LogActivity(relogSession, a, a.EventName()); 79 | } 80 | 81 | template 82 | void ProcessStringProperty(const void* relogSession, 83 | const BI::Event& e, const char* name, const TChar* value); 84 | 85 | void LogStringPropertySegment(const void* relogSession, 86 | const BI::Event& e, const char* name, const char* value); 87 | 88 | void LogStringPropertySegment(const void* relogSession, 89 | const BI::Event& e, const char* name, const wchar_t* value); 90 | 91 | template 92 | void LogStringPropertySegment(const void* relogSession, const BI::Event& e, 93 | const char* name, const TChar* value, PCEVENT_DESCRIPTOR desc); 94 | 95 | std::wstring invocationInfoString_; 96 | 97 | ContextBuilder* contextBuilder_; 98 | MiscellaneousCache* miscellaneousCache_; 99 | }; 100 | 101 | } // namespace vcperf -------------------------------------------------------------------------------- /src/WPA/Views/FilesView.cpp: -------------------------------------------------------------------------------- 1 | #include "FilesView.h" 2 | #include "CppBuildInsightsEtw.h" 3 | #include "PayloadBuilder.h" 4 | 5 | using namespace Microsoft::Cpp::BuildInsights; 6 | using namespace Activities; 7 | 8 | namespace vcperf 9 | { 10 | 11 | AnalysisControl FilesView::OnStartActivity(const EventStack& eventStack, 12 | const void* relogSession) 13 | { 14 | MatchEventStackInMemberFunction(eventStack, this, &FilesView::OnFileParse, relogSession); 15 | 16 | return AnalysisControl::CONTINUE; 17 | } 18 | 19 | void FilesView::OnFileParse(const FrontEndFileGroup& files, const void* relogSession) 20 | { 21 | using std::chrono::duration_cast; 22 | using std::chrono::milliseconds; 23 | 24 | PCEVENT_DESCRIPTOR desc = &CppBuildInsightsFileActivity_V1; 25 | 26 | auto* context = contextBuilder_->GetContextData(); 27 | 28 | const FrontEndFile& currentFile = files.Back(); 29 | 30 | const char* parentPath = ""; 31 | 32 | if (files.Size() > 1) 33 | { 34 | parentPath = files[files.Size() - 2].Path(); 35 | } 36 | 37 | auto& td = miscellaneousCache_->GetTimingData(currentFile); 38 | 39 | Payload p = PayloadBuilder::Build( 41 | context->TimelineId, 42 | context->TimelineDescription, 43 | context->Tool, 44 | context->InvocationId, 45 | context->Component, 46 | currentFile.Path(), 47 | parentPath, 48 | (uint16_t)files.Size() - 1, 49 | "Parsing", 50 | (uint32_t)duration_cast(td.ExclusiveDuration).count(), 51 | (uint32_t)duration_cast(td.Duration).count(), 52 | (uint32_t)duration_cast(td.WallClockTimeResponsibility).count() 53 | ); 54 | 55 | InjectEvent(relogSession, &CppBuildInsightsGuid, desc, 56 | currentFile.ProcessId(), currentFile.ThreadId(), currentFile.ProcessorIndex(), 57 | currentFile.StartTimestamp(), p.GetData(), (unsigned long)p.Size()); 58 | } 59 | 60 | } // namespace vcperf -------------------------------------------------------------------------------- /src/WPA/Views/FilesView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "VcperfBuildInsights.h" 4 | #include "WPA\Analyzers\ContextBuilder.h" 5 | #include "WPA\Analyzers\MiscellaneousCache.h" 6 | 7 | namespace vcperf 8 | { 9 | 10 | class FilesView : public BI::IRelogger 11 | { 12 | public: 13 | FilesView(ContextBuilder* contextBuilder, 14 | MiscellaneousCache* miscellaneousCache) : 15 | contextBuilder_{contextBuilder}, 16 | miscellaneousCache_{miscellaneousCache} 17 | {} 18 | 19 | BI::AnalysisControl OnStartActivity(const BI::EventStack& eventStack, 20 | const void* relogSession) override; 21 | 22 | void OnFileParse(const A::FrontEndFileGroup& files, const void* relogSession); 23 | 24 | private: 25 | ContextBuilder* contextBuilder_; 26 | MiscellaneousCache* miscellaneousCache_; 27 | }; 28 | 29 | } // namespace vcperf -------------------------------------------------------------------------------- /src/WPA/Views/FunctionsView.cpp: -------------------------------------------------------------------------------- 1 | #include "FunctionsView.h" 2 | 3 | #include "CppBuildInsightsEtw.h" 4 | 5 | #include "PayloadBuilder.h" 6 | 7 | using namespace Microsoft::Cpp::BuildInsights; 8 | using namespace Activities; 9 | using namespace SimpleEvents; 10 | 11 | namespace vcperf 12 | { 13 | 14 | enum class EventId 15 | { 16 | FORCE_INLINEE = 1 17 | }; 18 | 19 | AnalysisControl FunctionsView::OnActivity(const EventStack& eventStack, const void* relogSession) 20 | { 21 | // We avoid emitting functions that take less than 100 milliseconds to optimize 22 | // in order to limit the size of the dataset that WPA has to deal with. 23 | if (miscellaneousCache_->GetTimingData(eventStack.Back()).Duration < std::chrono::milliseconds(100)) { 24 | return AnalysisControl::CONTINUE; 25 | } 26 | 27 | MatchEventStackInMemberFunction(eventStack, this, &FunctionsView::EmitFunctionActivity, relogSession); 28 | 29 | return AnalysisControl::CONTINUE; 30 | } 31 | 32 | AnalysisControl FunctionsView::OnSimpleEvent(const EventStack& eventStack, const void* relogSession) 33 | { 34 | // We avoid emitting functions that take less than 100 milliseconds to optimize 35 | // in order to limit the size of the dataset that WPA has to deal with. 36 | if ( eventStack.Size() >= 2 37 | && miscellaneousCache_->GetTimingData(eventStack[eventStack.Size()-2]).Duration < std::chrono::milliseconds(100)) 38 | { 39 | return AnalysisControl::CONTINUE; 40 | } 41 | 42 | MatchEventStackInMemberFunction(eventStack, this, &FunctionsView::EmitFunctionForceInlinee, relogSession); 43 | 44 | return AnalysisControl::CONTINUE; 45 | } 46 | 47 | void FunctionsView::EmitFunctionActivity(Function func, const void* relogSession) 48 | { 49 | using namespace std::chrono; 50 | 51 | PCEVENT_DESCRIPTOR desc = &CppBuildInsightsFunctionActivity_V1; 52 | 53 | auto* context = contextBuilder_->GetContextData(); 54 | 55 | auto& td = miscellaneousCache_->GetTimingData(func); 56 | 57 | Payload p = PayloadBuilder::Build( 59 | context->TimelineId, 60 | context->TimelineDescription, 61 | context->Tool, 62 | context->InvocationId, 63 | context->Component, 64 | func.EventInstanceId(), 65 | func.Name(), 66 | "CodeGeneration", 67 | (uint32_t)duration_cast(td.Duration).count(), 68 | (uint32_t)duration_cast(td.WallClockTimeResponsibility).count() 69 | ); 70 | 71 | InjectEvent(relogSession, &CppBuildInsightsGuid, desc, 72 | func.ProcessId(), func.ThreadId(), func.ProcessorIndex(), 73 | func.StartTimestamp(), p.GetData(), (unsigned long)p.Size()); 74 | } 75 | 76 | void FunctionsView::EmitFunctionForceInlinee(const Function& func, 77 | const ForceInlinee& forceInlinee, const void* relogSession) 78 | { 79 | PCEVENT_DESCRIPTOR desc = &CppBuildInsightsFunctionSimpleEvent_V1; 80 | 81 | auto* context = contextBuilder_->GetContextData(); 82 | 83 | Payload p = PayloadBuilder::Build( 86 | context->TimelineId, 87 | context->TimelineDescription, 88 | context->Tool, 89 | context->InvocationId, 90 | context->Component, 91 | func.EventInstanceId(), 92 | func.Name(), 93 | "CodeGeneration", 94 | static_cast(EventId::FORCE_INLINEE), 95 | "ForceInlinee", 96 | forceInlinee.Name(), 97 | forceInlinee.Size() 98 | ); 99 | 100 | InjectEvent(relogSession, &CppBuildInsightsGuid, desc, 101 | forceInlinee.ProcessId(), forceInlinee.ThreadId(), forceInlinee.ProcessorIndex(), 102 | forceInlinee.Timestamp(), p.GetData(), (unsigned long)p.Size()); 103 | } 104 | 105 | } // namespace vcperf -------------------------------------------------------------------------------- /src/WPA/Views/FunctionsView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "VcperfBuildInsights.h" 4 | #include "WPA\Analyzers\ContextBuilder.h" 5 | #include "WPA\Analyzers\MiscellaneousCache.h" 6 | 7 | namespace vcperf 8 | { 9 | 10 | class FunctionsView : public BI::IRelogger 11 | { 12 | public: 13 | FunctionsView(ContextBuilder* contextBuilder, 14 | MiscellaneousCache* miscellaneousCache): 15 | contextBuilder_{contextBuilder}, 16 | miscellaneousCache_{miscellaneousCache} 17 | {} 18 | 19 | BI::AnalysisControl OnStartActivity(const BI::EventStack& eventStack, 20 | const void* relogSession) override 21 | { 22 | return OnActivity(eventStack, relogSession); 23 | } 24 | 25 | BI::AnalysisControl OnSimpleEvent(const BI::EventStack& eventStack, 26 | const void* relogSession) override; 27 | 28 | private: 29 | BI::AnalysisControl OnActivity(const BI::EventStack& eventStack, const void* relogSession); 30 | 31 | void EmitFunctionActivity(A::Function func, const void* relogSession); 32 | 33 | void EmitFunctionForceInlinee(const A::Function& func, const SE::ForceInlinee& forceInlinee, 34 | const void* relogSession); 35 | 36 | ContextBuilder* contextBuilder_; 37 | MiscellaneousCache* miscellaneousCache_; 38 | 39 | }; 40 | 41 | } // namespace vcperf -------------------------------------------------------------------------------- /src/WPA/Views/TemplateInstantiationsView.cpp: -------------------------------------------------------------------------------- 1 | #include "TemplateInstantiationsView.h" 2 | #include "CppBuildInsightsEtw.h" 3 | #include "PayloadBuilder.h" 4 | 5 | using namespace Microsoft::Cpp::BuildInsights; 6 | using namespace Activities; 7 | using namespace SimpleEvents; 8 | 9 | namespace vcperf 10 | { 11 | 12 | void TemplateInstantiationsView::OnTemplateInstantiationStart( 13 | const TemplateInstantiation& ti, const void* relogSession) 14 | { 15 | using std::chrono::duration_cast; 16 | using std::chrono::microseconds; 17 | 18 | PCEVENT_DESCRIPTOR desc = &CppBuildInsightsTemplateInstantiationActivity_V1; 19 | 20 | auto tiInfo = tiCache_->GetTemplateInstantiationInfo(ti); 21 | 22 | bool isInfoAvailable = std::get<0>(tiInfo); 23 | 24 | if (!isInfoAvailable) { 25 | return; 26 | } 27 | 28 | auto* context = contextBuilder_->GetContextData(); 29 | 30 | auto& td = miscellaneousCache_->GetTimingData(ti); 31 | 32 | const char* primaryTemplateName = std::get<1>(tiInfo); 33 | const char* specializationName = std::get<2>(tiInfo); 34 | 35 | Payload p = PayloadBuilder ::Build( 37 | context->TimelineId, 38 | context->TimelineDescription, 39 | context->Tool, 40 | context->InvocationId, 41 | context->Component, 42 | primaryTemplateName, 43 | specializationName, 44 | (uint32_t)duration_cast(td.Duration).count(), 45 | (uint32_t)duration_cast(td.WallClockTimeResponsibility).count() 46 | ); 47 | 48 | InjectEvent(relogSession, &CppBuildInsightsGuid, desc, 49 | ti.ProcessId(), ti.ThreadId(), ti.ProcessorIndex(), 50 | ti.StartTimestamp(), p.GetData(), (unsigned long)p.Size()); 51 | } 52 | 53 | } // namespace vcperf -------------------------------------------------------------------------------- /src/WPA/Views/TemplateInstantiationsView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "VcperfBuildInsights.h" 4 | #include "Utility.h" 5 | #include "PayloadBuilder.h" 6 | #include "WPA\Analyzers\ContextBuilder.h" 7 | #include "WPA\Analyzers\ExpensiveTemplateInstantiationCache.h" 8 | #include "WPA\Analyzers\MiscellaneousCache.h" 9 | 10 | namespace vcperf 11 | { 12 | 13 | class TemplateInstantiationsView : public BI::IRelogger 14 | { 15 | public: 16 | TemplateInstantiationsView( 17 | ContextBuilder* contextBuilder, 18 | const ExpensiveTemplateInstantiationCache* tiCache, 19 | MiscellaneousCache* miscellaneousCache, 20 | bool isEnabled) : 21 | contextBuilder_{contextBuilder}, 22 | tiCache_{tiCache}, 23 | miscellaneousCache_{miscellaneousCache}, 24 | isEnabled_{isEnabled} 25 | {} 26 | 27 | BI::AnalysisControl OnStartActivity(const BI::EventStack& eventStack, const void* relogSession) override 28 | { 29 | if (!isEnabled_) { 30 | return BI::AnalysisControl::CONTINUE; 31 | } 32 | 33 | MatchEventStackInMemberFunction(eventStack, this, 34 | &TemplateInstantiationsView::OnTemplateInstantiationStart, relogSession); 35 | 36 | return BI::AnalysisControl::CONTINUE; 37 | } 38 | 39 | void OnTemplateInstantiationStart(const A::TemplateInstantiation& ti, const void* relogSession); 40 | 41 | private: 42 | ContextBuilder* contextBuilder_; 43 | const ExpensiveTemplateInstantiationCache* tiCache_; 44 | MiscellaneousCache* miscellaneousCache_; 45 | 46 | bool isEnabled_; 47 | }; 48 | 49 | } // namespace vcperf -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "Commands.h" 7 | 8 | #include "VcperfBuildInsights.h" 9 | 10 | #ifndef VCPERF_VERSION 11 | #define VCPERF_VERSION DEVELOPER VERSION 12 | #endif 13 | 14 | #define STRINGIFY(x) #x 15 | #define GET_VERSION_STRING(ver) STRINGIFY(ver) 16 | 17 | #define VCPERF_VERSION_STRING GET_VERSION_STRING(VCPERF_VERSION) 18 | 19 | // TODO: Start using some kind of command line parsing library 20 | 21 | enum class StartSubCommand 22 | { 23 | NONE, 24 | NO_ADMIN, 25 | NO_CPU_SAMPLING, 26 | LEVEL1, 27 | LEVEL2, 28 | LEVEL3 29 | }; 30 | 31 | using namespace Microsoft::Cpp::BuildInsights; 32 | 33 | using namespace vcperf; 34 | 35 | bool CheckCommand(std::wstring arg, const wchar_t* value) 36 | { 37 | auto ciCompare = [](wchar_t c1, wchar_t c2) { return std::towupper(c1) == std::towupper(c2); }; 38 | 39 | std::wstring slash = std::wstring{ L"/" } + value; 40 | std::wstring hyphen = std::wstring{ L"-" } + value; 41 | 42 | return std::equal(begin(arg), end(arg), begin(slash), end(slash), ciCompare) 43 | || std::equal(begin(arg), end(arg), begin(hyphen), end(hyphen), ciCompare); 44 | } 45 | 46 | bool ValidateFile(const std::filesystem::path& file, bool isInput, const std::wstring& extension) 47 | { 48 | if (file.extension() != extension) { 49 | std::wcout << L"ERROR: Your " << (isInput ? L"input" : L"output") << L" file must have the " << extension << L" extension." << std::endl; 50 | return false; 51 | } 52 | 53 | if (isInput && !std::filesystem::exists(file)) { 54 | std::wcout << L"ERROR: File not found: " << file << std::endl; 55 | return false; 56 | } 57 | 58 | return true; 59 | } 60 | 61 | StartSubCommand CheckStartSubCommands(const wchar_t* arg) 62 | { 63 | if (CheckCommand(arg, L"noadmin")) { 64 | return StartSubCommand::NO_ADMIN; 65 | } 66 | 67 | if (CheckCommand(arg, L"nocpusampling")) { 68 | return StartSubCommand::NO_CPU_SAMPLING; 69 | } 70 | 71 | if (CheckCommand(arg, L"level1")) { 72 | return StartSubCommand::LEVEL1; 73 | } 74 | 75 | if (CheckCommand(arg, L"level2")) { 76 | return StartSubCommand::LEVEL2; 77 | } 78 | 79 | if (CheckCommand(arg, L"level3")) { 80 | return StartSubCommand::LEVEL3; 81 | } 82 | 83 | return StartSubCommand::NONE; 84 | } 85 | 86 | void PrintStopOrAnalyzeCommandLineHint(const wchar_t* command, const wchar_t* sessionOrInputHelp) 87 | { 88 | std::wcout << L"vcperf.exe " << command << " [/templates] " << sessionOrInputHelp << " outputFile.etl" << std::endl; 89 | std::wcout << L"vcperf.exe " << command << " [/templates] " << sessionOrInputHelp << " /timetrace outputFile.json" << std::endl; 90 | } 91 | 92 | int ParseStopOrAnalyze(int argc, wchar_t* argv[], const wchar_t* command, const wchar_t* sessionOrInputHelp, 93 | std::wstring& firstArg, std::wstring& outputFile, bool& analyzeTemplates, bool& generateTimeTrace) 94 | { 95 | if (argc < 4) 96 | { 97 | PrintStopOrAnalyzeCommandLineHint(command, sessionOrInputHelp); 98 | return E_FAIL; 99 | } 100 | 101 | int curArgc = 2; 102 | 103 | analyzeTemplates = false; 104 | generateTimeTrace = false; 105 | 106 | // options prior to input file 107 | 108 | std::wstring arg = argv[curArgc++]; 109 | if (CheckCommand(arg, L"templates")) 110 | { 111 | analyzeTemplates = true; 112 | arg = argv[curArgc++]; 113 | 114 | if (argc < 5) 115 | { 116 | PrintStopOrAnalyzeCommandLineHint(command, sessionOrInputHelp); 117 | return E_FAIL; 118 | } 119 | } 120 | 121 | // session name or input file 122 | firstArg = arg; 123 | arg = argv[curArgc++]; 124 | 125 | // options prior to output file 126 | 127 | if (CheckCommand(arg, L"timetrace")) 128 | { 129 | if (argc < 5) 130 | { 131 | PrintStopOrAnalyzeCommandLineHint(command, sessionOrInputHelp); 132 | return E_FAIL; 133 | } 134 | 135 | generateTimeTrace = true; 136 | arg = argv[curArgc++]; 137 | } 138 | 139 | if (analyzeTemplates && generateTimeTrace && argc < 6) 140 | { 141 | PrintStopOrAnalyzeCommandLineHint(command, sessionOrInputHelp); 142 | return E_FAIL; 143 | } 144 | 145 | // output file 146 | outputFile = arg; 147 | 148 | if (!ValidateFile(outputFile, false, generateTimeTrace ? L".json" : L".etl")) { 149 | PrintStopOrAnalyzeCommandLineHint(command, sessionOrInputHelp); 150 | return E_FAIL; 151 | } 152 | 153 | return S_OK; 154 | } 155 | 156 | int wmain(int argc, wchar_t* argv[]) 157 | { 158 | std::wcout << L"Microsoft (R) Visual C++ (R) Performance Analyzer " << 159 | VCPERF_VERSION_STRING << std::endl; 160 | 161 | HRESULT hr = CoInitialize(nullptr); 162 | 163 | if (hr != S_OK) { 164 | std::wcout << L"ERROR: failed to initialize COM." << std::endl; 165 | } 166 | 167 | if (argc < 2) 168 | { 169 | std::wcout << std::endl; 170 | std::wcout << L"USAGE:" << std::endl; 171 | std::wcout << L"vcperf.exe /start [/noadmin] [/nocpusampling] [/level1 | /level2 | /level3] sessionName" << std::endl; 172 | std::wcout << L"vcperf.exe /stop [/templates] sessionName outputFile.etl" << std::endl; 173 | std::wcout << L"vcperf.exe /stop [/templates] sessionName /timetrace outputFile.json" << std::endl; 174 | std::wcout << L"vcperf.exe /stopnoanalyze sessionName outputRawFile.etl" << std::endl; 175 | std::wcout << L"vcperf.exe /analyze [/templates] inputRawFile.etl output.etl" << std::endl; 176 | std::wcout << L"vcperf.exe /analyze [/templates] inputRawFile.etl /timetrace output.json" << std::endl; 177 | 178 | std::wcout << std::endl; 179 | 180 | return E_FAIL; 181 | } 182 | 183 | if (CheckCommand(argv[1], L"start")) 184 | { 185 | if (argc < 3) 186 | { 187 | std::wcout << L"vcperf.exe /start [/noadmin] [/nocpusampling] [/level1 | /level2 | /level3] sessionName" << std::endl; 188 | return E_FAIL; 189 | } 190 | 191 | bool noAdminSpecified = false; 192 | bool noCpuSamplingSpecified = false; 193 | VerbosityLevel verbosityLevel = VerbosityLevel::INVALID; 194 | 195 | int currentArg = 2; 196 | StartSubCommand subCommand = CheckStartSubCommands(argv[currentArg]); 197 | 198 | while (subCommand != StartSubCommand::NONE) 199 | { 200 | if (subCommand == StartSubCommand::NO_ADMIN) 201 | { 202 | if (noAdminSpecified) 203 | { 204 | std::wcout << L"ERROR: you can only specify /noadmin once." << std::endl; 205 | std::wcout << L"vcperf.exe /start [/noadmin] [/nocpusampling] [/level1 | /level2 | /level3] sessionName" << std::endl; 206 | return E_FAIL; 207 | } 208 | noAdminSpecified = true; 209 | } 210 | else if (subCommand == StartSubCommand::NO_CPU_SAMPLING) 211 | { 212 | if (noCpuSamplingSpecified) 213 | { 214 | std::wcout << L"ERROR: you can only specify /nocpusampling once." << std::endl; 215 | std::wcout << L"vcperf.exe /start [/noadmin] [/nocpusampling] [/level1 | /level2 | /level3] sessionName" << std::endl; 216 | return E_FAIL; 217 | } 218 | noCpuSamplingSpecified = true; 219 | } 220 | else if (verbosityLevel != VerbosityLevel::INVALID) 221 | { 222 | std::wcout << L"ERROR: you can only specify one verbosity level: /level1, /level2, or /level3." << std::endl; 223 | std::wcout << L"vcperf.exe /start [/noadmin] [/nocpusampling] [/level1 | /level2 | /level3] sessionName" << std::endl; 224 | return E_FAIL; 225 | } 226 | else 227 | { 228 | switch (subCommand) 229 | { 230 | case StartSubCommand::LEVEL1: 231 | verbosityLevel = VerbosityLevel::LIGHT; 232 | break; 233 | 234 | case StartSubCommand::LEVEL2: 235 | verbosityLevel = VerbosityLevel::MEDIUM; 236 | break; 237 | 238 | case StartSubCommand::LEVEL3: 239 | verbosityLevel = VerbosityLevel::VERBOSE; 240 | break; 241 | } 242 | } 243 | 244 | currentArg++; 245 | 246 | if (currentArg >= argc) { 247 | break; 248 | } 249 | 250 | subCommand = CheckStartSubCommands(argv[currentArg]); 251 | } 252 | 253 | if (currentArg >= argc) 254 | { 255 | std::wcout << L"ERROR: a session name must be specified." << std::endl; 256 | std::wcout << L"vcperf.exe /start [/noadmin] [/nocpusampling] [/level1 | /level2 | /level3] sessionName" << std::endl; 257 | return E_FAIL; 258 | } 259 | 260 | if (verbosityLevel == VerbosityLevel::INVALID) 261 | { 262 | verbosityLevel = VerbosityLevel::MEDIUM; 263 | } 264 | 265 | std::wstring sessionName = argv[currentArg]; 266 | 267 | return DoStart(sessionName, !noAdminSpecified, !noCpuSamplingSpecified, verbosityLevel); 268 | } 269 | else if (CheckCommand(argv[1], L"stop")) 270 | { 271 | std::wstring sessionName, outputFile; 272 | bool analyzeTemplates, generateTimeTrace; 273 | 274 | if (S_OK != ParseStopOrAnalyze(argc, argv, L"/stop", L"sessionName", sessionName, outputFile, analyzeTemplates, generateTimeTrace)) { 275 | return E_FAIL; 276 | } 277 | 278 | return DoStop(sessionName, outputFile, analyzeTemplates, generateTimeTrace); 279 | } 280 | else if (CheckCommand(argv[1], L"stopnoanalyze")) 281 | { 282 | if (argc < 4) 283 | { 284 | std::wcout << L"vcperf.exe /stopnoanalyze sessionName outputRawFile.etl" << std::endl; 285 | return E_FAIL; 286 | } 287 | 288 | std::wstring sessionName = argv[2]; 289 | std::filesystem::path outputFile = argv[3]; 290 | 291 | if (!ValidateFile(outputFile, false, L".etl")) { 292 | return E_FAIL; 293 | } 294 | 295 | return DoStopNoAnalyze(sessionName, outputFile); 296 | } 297 | else if (CheckCommand(argv[1], L"analyze")) 298 | { 299 | std::wstring inputFile, outputFile; 300 | bool analyzeTemplates, generateTimeTrace; 301 | 302 | if (S_OK != ParseStopOrAnalyze(argc, argv, L"/analyze", L"input.etl", inputFile, outputFile, analyzeTemplates, generateTimeTrace)) { 303 | return E_FAIL; 304 | } 305 | 306 | if (!ValidateFile(inputFile, true, L".etl")) { 307 | return E_FAIL; 308 | } 309 | 310 | return DoAnalyze(inputFile, outputFile, analyzeTemplates, generateTimeTrace); 311 | } 312 | else 313 | { 314 | std::wcout << L"ERROR: Unknown command " << argv[1] << std::endl; 315 | 316 | return E_FAIL; 317 | } 318 | 319 | return S_OK; 320 | } -------------------------------------------------------------------------------- /tag_version.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [String]$VersionString = "", 3 | [String]$SHA1= "" 4 | ) 5 | 6 | #clean local tags 7 | git fetch -q --prune origin "+refs/tags/*:refs/tags/*" 8 | git tag $VersionString $sha1 9 | git push -q origin --tags -------------------------------------------------------------------------------- /vcperf.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MSVC.Redist.vcperf 5 | $version$ 6 | vcperf 7 | Microsoft 8 | false 9 | Microsoft (R) Visual C++ (R) Performance Analyzer 10 | © Microsoft Corporation. All rights reserved. 11 | msvc,vcperf,native 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /vcperf.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32626.61 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vcperf", "vcperf.vcxproj", "{E0135A9D-8AEA-40C4-8500-C793D210C271}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|ARM64 = Debug|ARM64 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|ARM64 = Release|ARM64 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {E0135A9D-8AEA-40C4-8500-C793D210C271}.Debug|ARM64.ActiveCfg = Debug|ARM64 19 | {E0135A9D-8AEA-40C4-8500-C793D210C271}.Debug|ARM64.Build.0 = Debug|ARM64 20 | {E0135A9D-8AEA-40C4-8500-C793D210C271}.Debug|x64.ActiveCfg = Debug|x64 21 | {E0135A9D-8AEA-40C4-8500-C793D210C271}.Debug|x64.Build.0 = Debug|x64 22 | {E0135A9D-8AEA-40C4-8500-C793D210C271}.Debug|x86.ActiveCfg = Debug|Win32 23 | {E0135A9D-8AEA-40C4-8500-C793D210C271}.Debug|x86.Build.0 = Debug|Win32 24 | {E0135A9D-8AEA-40C4-8500-C793D210C271}.Release|ARM64.ActiveCfg = Release|ARM64 25 | {E0135A9D-8AEA-40C4-8500-C793D210C271}.Release|ARM64.Build.0 = Release|ARM64 26 | {E0135A9D-8AEA-40C4-8500-C793D210C271}.Release|x64.ActiveCfg = Release|x64 27 | {E0135A9D-8AEA-40C4-8500-C793D210C271}.Release|x64.Build.0 = Release|x64 28 | {E0135A9D-8AEA-40C4-8500-C793D210C271}.Release|x86.ActiveCfg = Release|Win32 29 | {E0135A9D-8AEA-40C4-8500-C793D210C271}.Release|x86.Build.0 = Release|Win32 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {4A0B4595-54D1-4F39-95B1-87E7D00549FB} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /vcperf.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | ARM64 7 | 8 | 9 | Debug 10 | Win32 11 | 12 | 13 | Release 14 | ARM64 15 | 16 | 17 | Release 18 | Win32 19 | 20 | 21 | Debug 22 | x64 23 | 24 | 25 | Release 26 | x64 27 | 28 | 29 | 30 | 15.0 31 | {E0135A9D-8AEA-40C4-8500-C793D210C271} 32 | vcperf 33 | 10.0 34 | 35 | 36 | 37 | Application 38 | true 39 | v143 40 | MultiByte 41 | 42 | 43 | Application 44 | false 45 | v143 46 | true 47 | MultiByte 48 | Spectre 49 | 50 | 51 | Application 52 | true 53 | v143 54 | MultiByte 55 | 56 | 57 | Application 58 | true 59 | v143 60 | MultiByte 61 | 62 | 63 | Application 64 | false 65 | v143 66 | true 67 | MultiByte 68 | Spectre 69 | 70 | 71 | Application 72 | false 73 | v143 74 | true 75 | MultiByte 76 | Spectre 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 | $(SolutionDir)out\$(Configuration)\x86\ 106 | $(SolutionDir)src\x86\$(Configuration)\ 107 | 108 | 109 | $(SolutionDir)out\$(Configuration)\x86\ 110 | $(SolutionDir)src\x86\$(Configuration)\ 111 | 112 | 113 | $(SolutionDir)out\$(Configuration)\x64\ 114 | $(SolutionDir)src\x64\$(Configuration)\ 115 | 116 | 117 | $(SolutionDir)out\$(Configuration)\x64\ 118 | $(SolutionDir)src\x64\$(Configuration)\ 119 | 120 | 121 | $(SolutionDir)out\$(Configuration)\ARM64\ 122 | $(SolutionDir)src\ARM64\$(Configuration)\ 123 | $(CommonExcludePath) 124 | true 125 | 126 | 127 | $(SolutionDir)out\$(Configuration)\ARM64\ 128 | $(SolutionDir)src\ARM64\$(Configuration)\ 129 | 130 | 131 | 132 | Level3 133 | Disabled 134 | true 135 | true 136 | $(IntDir);src;%(AdditionalIncludeDirectories) 137 | stdcpp17 138 | $(ExternalPreprocessorDefinitions);_MBCS;CPP_BUILD_INSIGHTS_RELOG;%(PreprocessorDefinitions) 139 | 140 | 141 | %(AdditionalLibraryDirectories) 142 | %(AdditionalDependencies) 143 | 144 | 145 | mc $(SolutionDir)\src\CppBuildInsightsEtw.xml -h $(IntDir) -r $(IntDir) 146 | 147 | 148 | copy $(SolutionDir)\src\CppBuildInsightsEtw.xml $(OutDir) 149 | 150 | 151 | 152 | 153 | Level3 154 | Disabled 155 | true 156 | true 157 | $(IntDir);src;%(AdditionalIncludeDirectories) 158 | stdcpp17 159 | $(ExternalPreprocessorDefinitions);_MBCS;CPP_BUILD_INSIGHTS_RELOG;%(PreprocessorDefinitions) 160 | 161 | 162 | %(AdditionalLibraryDirectories) 163 | %(AdditionalDependencies) 164 | 165 | 166 | mc $(SolutionDir)\src\CppBuildInsightsEtw.xml -h $(IntDir) -r $(IntDir) 167 | 168 | 169 | copy $(SolutionDir)\src\CppBuildInsightsEtw.xml $(OutDir) 170 | 171 | 172 | 173 | 174 | Level3 175 | Disabled 176 | true 177 | true 178 | $(IntDir);src;%(AdditionalIncludeDirectories) 179 | stdcpp17 180 | $(ExternalPreprocessorDefinitions);_MBCS;CPP_BUILD_INSIGHTS_RELOG;%(PreprocessorDefinitions) 181 | 182 | 183 | %(AdditionalLibraryDirectories) 184 | %(AdditionalDependencies) 185 | 186 | 187 | mc $(SolutionDir)\src\CppBuildInsightsEtw.xml -h $(IntDir) -r $(IntDir) 188 | 189 | 190 | copy $(SolutionDir)\src\CppBuildInsightsEtw.xml $(OutDir) 191 | 192 | 193 | 194 | 195 | Level3 196 | MaxSpeed 197 | true 198 | true 199 | true 200 | true 201 | $(IntDir);src;%(AdditionalIncludeDirectories) 202 | stdcpp17 203 | $(ExternalPreprocessorDefinitions);_MBCS;CPP_BUILD_INSIGHTS_RELOG;NDEBUG;%(PreprocessorDefinitions) 204 | Guard 205 | /Qspectre %(AdditionalOptions) 206 | 207 | 208 | true 209 | true 210 | %(AdditionalLibraryDirectories) 211 | %(AdditionalDependencies) 212 | /debugtype:cv,fixup /DYNAMICBASE /CETCOMPAT %(AdditionalOptions) 213 | 214 | 215 | mc $(SolutionDir)\src\CppBuildInsightsEtw.xml -h $(IntDir) -r $(IntDir) 216 | 217 | 218 | copy $(SolutionDir)\src\CppBuildInsightsEtw.xml $(OutDir) 219 | 220 | 221 | 222 | 223 | Level3 224 | MaxSpeed 225 | true 226 | true 227 | true 228 | true 229 | $(IntDir);src;%(AdditionalIncludeDirectories) 230 | stdcpp17 231 | $(ExternalPreprocessorDefinitions);_MBCS;CPP_BUILD_INSIGHTS_RELOG;NDEBUG;%(PreprocessorDefinitions) 232 | Guard 233 | /Qspectre %(AdditionalOptions) 234 | 235 | 236 | true 237 | true 238 | %(AdditionalLibraryDirectories) 239 | %(AdditionalDependencies) 240 | /debugtype:cv,fixup /DYNAMICBASE /CETCOMPAT %(AdditionalOptions) 241 | 242 | 243 | mc $(SolutionDir)\src\CppBuildInsightsEtw.xml -h $(IntDir) -r $(IntDir) 244 | 245 | 246 | copy $(SolutionDir)\src\CppBuildInsightsEtw.xml $(OutDir) 247 | 248 | 249 | 250 | 251 | Level3 252 | MaxSpeed 253 | true 254 | true 255 | true 256 | true 257 | $(IntDir);src;%(AdditionalIncludeDirectories) 258 | stdcpp17 259 | $(ExternalPreprocessorDefinitions);_MBCS;CPP_BUILD_INSIGHTS_RELOG;NDEBUG;%(PreprocessorDefinitions) 260 | Guard 261 | /Qspectre %(AdditionalOptions) 262 | 263 | 264 | true 265 | true 266 | %(AdditionalLibraryDirectories) 267 | %(AdditionalDependencies) 268 | /DYNAMICBASE %(AdditionalOptions) 269 | 270 | 271 | mc $(SolutionDir)\src\CppBuildInsightsEtw.xml -h $(IntDir) -r $(IntDir) 272 | 273 | 274 | copy $(SolutionDir)\src\CppBuildInsightsEtw.xml $(OutDir) 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | Designer 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 323 | 324 | 325 | 326 | 327 | 328 | -------------------------------------------------------------------------------- /vcperf.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {6662cbbc-0b0f-4663-b55e-832b4ba907c9} 18 | 19 | 20 | {bf80ee78-b96b-47c6-88c5-6449fd14a6f2} 21 | 22 | 23 | {e8d7ee64-3202-4eb3-9409-a5b48b479489} 24 | 25 | 26 | {5bfef654-b52c-4c77-a35d-95b8c98bfe70} 27 | 28 | 29 | {f6f5a0ac-cf4e-4ca4-b07a-7ca118492af7} 30 | 31 | 32 | {f0294dc9-9c99-49e2-8bcd-b5f6eaf956db} 33 | 34 | 35 | {c72eeb06-e722-4c16-b021-4fef2d5bebf4} 36 | 37 | 38 | {f61a812e-1836-4138-94d5-51a333458674} 39 | 40 | 41 | 42 | 43 | Source Files 44 | 45 | 46 | Source Files 47 | 48 | 49 | Source Files 50 | 51 | 52 | Source Files\WPA\Views 53 | 54 | 55 | Source Files\WPA\Views 56 | 57 | 58 | Source Files\WPA\Views 59 | 60 | 61 | Source Files\WPA\Views 62 | 63 | 64 | Source Files\WPA\Analyzers 65 | 66 | 67 | Source Files\WPA\Analyzers 68 | 69 | 70 | Source Files\TimeTrace 71 | 72 | 73 | Source Files\TimeTrace 74 | 75 | 76 | Source Files\TimeTrace 77 | 78 | 79 | 80 | 81 | Header Files 82 | 83 | 84 | Header Files 85 | 86 | 87 | Header Files 88 | 89 | 90 | Header Files 91 | 92 | 93 | Header Files\WPA\Views 94 | 95 | 96 | Header Files\WPA\Views 97 | 98 | 99 | Header Files\WPA\Views 100 | 101 | 102 | Header Files\WPA\Views 103 | 104 | 105 | Header Files\WPA\Analyzers 106 | 107 | 108 | Header Files\WPA\Analyzers 109 | 110 | 111 | Header Files\WPA\Analyzers 112 | 113 | 114 | Header Files 115 | 116 | 117 | Header Files\TimeTrace 118 | 119 | 120 | Header Files\TimeTrace 121 | 122 | 123 | Header Files\TimeTrace 124 | 125 | 126 | 127 | 128 | Resource Files 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | --------------------------------------------------------------------------------