/Python/Product/PythonTools/PythonTools/Commands/DiagnosticsCommand.cs
C# | 319 lines | 263 code | 32 blank | 24 comment | 13 complexity | bae710100bb580d2934730b9d9369e3e MD5 | raw file
1// Python Tools for Visual Studio
2// Copyright(c) Microsoft Corporation
3// All rights reserved.
4//
5// Licensed under the Apache License, Version 2.0 (the License); you may not use
6// this file except in compliance with the License. You may obtain a copy of the
7// License at http://www.apache.org/licenses/LICENSE-2.0
8//
9// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
10// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
11// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
12// MERCHANTABLITY OR NON-INFRINGEMENT.
13//
14// See the Apache Version 2.0 License for specific language governing
15// permissions and limitations under the License.
16
17using System;
18using System.Collections.Generic;
19using System.Diagnostics;
20using System.Globalization;
21using System.IO;
22using System.Linq;
23using System.Reflection;
24using System.Text;
25using System.Text.RegularExpressions;
26using System.Threading;
27using System.Threading.Tasks;
28using Microsoft.PythonTools.Infrastructure;
29using Microsoft.PythonTools.Interpreter;
30using Microsoft.PythonTools.Logging;
31using Microsoft.PythonTools.Project;
32using Microsoft.PythonTools.Project.Web;
33using Microsoft.VisualStudioTools;
34using Microsoft.VisualStudioTools.Project;
35
36namespace Microsoft.PythonTools.Commands {
37 /// <summary>
38 /// Provides the command for starting a file or the start item of a project in the REPL window.
39 /// </summary>
40 internal sealed class DiagnosticsCommand : Command {
41 private readonly IServiceProvider _serviceProvider;
42
43 private static readonly IEnumerable<string> InterestingDteProperties = new[] {
44 "InterpreterId",
45 "InterpreterVersion",
46 "StartupFile",
47 "WorkingDirectory",
48 "PublishUrl",
49 "SearchPath",
50 "CommandLineArguments",
51 "InterpreterPath"
52 };
53
54 private static readonly IEnumerable<string> InterestingProjectProperties = new[] {
55 "ClusterRunEnvironment",
56 "ClusterPublishBeforeRun",
57 "ClusterWorkingDir",
58 "ClusterMpiExecCommand",
59 "ClusterAppCommand",
60 "ClusterAppArguments",
61 "ClusterDeploymentDir",
62 "ClusterTargetPlatform",
63 PythonWebLauncher.DebugWebServerTargetProperty,
64 PythonWebLauncher.DebugWebServerTargetTypeProperty,
65 PythonWebLauncher.DebugWebServerArgumentsProperty,
66 PythonWebLauncher.DebugWebServerEnvironmentProperty,
67 PythonWebLauncher.RunWebServerTargetProperty,
68 PythonWebLauncher.RunWebServerTargetTypeProperty,
69 PythonWebLauncher.RunWebServerArgumentsProperty,
70 PythonWebLauncher.RunWebServerEnvironmentProperty,
71 PythonWebPropertyPage.StaticUriPatternSetting,
72 PythonWebPropertyPage.StaticUriRewriteSetting,
73 PythonWebPropertyPage.WsgiHandlerSetting
74 };
75
76 private static readonly Regex InterestingApplicationLogEntries = new Regex(
77 @"^Application: (devenv\.exe|.+?Python.+?\.exe|ipy(64)?\.exe)",
78 RegexOptions.IgnoreCase | RegexOptions.CultureInvariant
79 );
80
81 public DiagnosticsCommand(IServiceProvider serviceProvider) {
82 _serviceProvider = serviceProvider;
83 }
84
85 public override void DoCommand(object sender, EventArgs args) {
86 var dlg = new DiagnosticsForm(_serviceProvider, "Gathering data...");
87
88 ThreadPool.QueueUserWorkItem(x => {
89 string data;
90 try {
91 data = GetData(dlg);
92 } catch (Exception ex) when (!ex.IsCriticalException()) {
93 data = ex.ToUnhandledExceptionMessage(GetType());
94 }
95 try {
96 dlg.BeginInvoke((Action)(() => {
97 dlg.Ready(data);
98 }));
99 } catch (InvalidOperationException) {
100 // Window has been closed already
101 }
102 });
103 dlg.ShowDialog();
104 }
105
106 private string GetData(System.Windows.Forms.Control ui) {
107 StringBuilder res = new StringBuilder();
108
109 if (PythonToolsPackage.IsIpyToolsInstalled()) {
110 res.AppendLine("WARNING: IpyTools is installed on this machine. Having both IpyTools and Python Tools for Visual Studio installed will break Python editing.");
111 }
112
113 string pythonPathIsMasked = "";
114 EnvDTE.DTE dte = null;
115 IPythonInterpreterFactoryProvider[] knownProviders = null;
116 IPythonLauncherProvider[] launchProviders = null;
117 InMemoryLogger inMemLogger = null;
118 ui.Invoke((Action)(() => {
119 pythonPathIsMasked = _serviceProvider.GetPythonToolsService().GeneralOptions.ClearGlobalPythonPath
120 ? " (masked)"
121 : "";
122 dte = (EnvDTE.DTE)_serviceProvider.GetService(typeof(EnvDTE.DTE));
123 var model = _serviceProvider.GetComponentModel();
124 knownProviders = model.GetExtensions<IPythonInterpreterFactoryProvider>().ToArray();
125 launchProviders = model.GetExtensions<IPythonLauncherProvider>().ToArray();
126 inMemLogger = model.GetService<InMemoryLogger>();
127 }));
128
129 res.AppendLine("Projects: ");
130
131 var projects = dte.Solution.Projects;
132
133 foreach (EnvDTE.Project project in projects) {
134 string name;
135 try {
136 // Some projects will throw rather than give us a unique
137 // name. They are not ours, so we will ignore them.
138 name = project.UniqueName;
139 } catch (Exception ex) when (!ex.IsCriticalException()) {
140 bool isPythonProject = false;
141 try {
142 isPythonProject = Utilities.GuidEquals(PythonConstants.ProjectFactoryGuid, project.Kind);
143 } catch (Exception ex2) when (!ex2.IsCriticalException()) {
144 }
145
146 if (isPythonProject) {
147 // Actually, it was one of our projects, so we do care
148 // about the exception. We'll add it to the output,
149 // rather than crashing.
150 res.AppendLine(" Project: " + ex.Message);
151 res.AppendLine(" Kind: Python");
152 }
153 continue;
154 }
155 res.AppendLine(" Project: " + name);
156
157 if (Utilities.GuidEquals(PythonConstants.ProjectFactoryGuid, project.Kind)) {
158 res.AppendLine(" Kind: Python");
159
160 foreach (var prop in InterestingDteProperties) {
161 res.AppendLine(" " + prop + ": " + GetProjectProperty(project, prop));
162 }
163
164 var pyProj = project.GetPythonProject();
165 if (pyProj != null) {
166 ui.Invoke((Action)(() => {
167 foreach (var prop in InterestingProjectProperties) {
168 var propValue = pyProj.GetProjectProperty(prop);
169 if (propValue != null) {
170 res.AppendLine(" " + prop + ": " + propValue);
171 }
172 }
173 }));
174
175 foreach (var factory in pyProj.InterpreterFactories) {
176 res.AppendLine();
177 res.AppendLine(" Interpreter: " + factory.Configuration.FullDescription);
178 res.AppendLine(" Id: " + factory.Configuration.Id);
179 res.AppendLine(" Version: " + factory.Configuration.Version);
180 res.AppendLine(" Arch: " + factory.Configuration.Architecture);
181 res.AppendLine(" Prefix Path: " + factory.Configuration.PrefixPath ?? "(null)");
182 res.AppendLine(" Path: " + factory.Configuration.InterpreterPath ?? "(null)");
183 res.AppendLine(" Windows Path: " + factory.Configuration.WindowsInterpreterPath ?? "(null)");
184 res.AppendLine(" Lib Path: " + factory.Configuration.LibraryPath ?? "(null)");
185 res.AppendLine(string.Format(" Path Env: {0}={1}{2}",
186 factory.Configuration.PathEnvironmentVariable ?? "(null)",
187 Environment.GetEnvironmentVariable(factory.Configuration.PathEnvironmentVariable ?? ""),
188 pythonPathIsMasked
189 ));
190 }
191 }
192 } else {
193 res.AppendLine(" Kind: " + project.Kind);
194 }
195
196 res.AppendLine();
197 }
198
199 res.AppendLine("Environments: ");
200 foreach (var provider in knownProviders.MaybeEnumerate()) {
201 res.AppendLine(" " + provider.GetType().FullName);
202 foreach (var config in provider.GetInterpreterConfigurations()) {
203 res.AppendLine(" Id: " + config.Id);
204 res.AppendLine(" Factory: " + config.FullDescription);
205 res.AppendLine(" Version: " + config.Version);
206 res.AppendLine(" Arch: " + config.Architecture);
207 res.AppendLine(" Prefix Path: " + config.PrefixPath ?? "(null)");
208 res.AppendLine(" Path: " + config.InterpreterPath ?? "(null)");
209 res.AppendLine(" Windows Path: " + config.WindowsInterpreterPath ?? "(null)");
210 res.AppendLine(" Lib Path: " + config.LibraryPath ?? "(null)");
211 res.AppendLine(" Path Env: " + config.PathEnvironmentVariable ?? "(null)");
212 res.AppendLine();
213 }
214 }
215
216 res.AppendLine("Launchers:");
217 foreach (var launcher in launchProviders.MaybeEnumerate()) {
218 res.AppendLine(" Launcher: " + launcher.GetType().FullName);
219 res.AppendLine(" " + launcher.Description);
220 res.AppendLine(" " + launcher.Name);
221 res.AppendLine();
222 }
223
224 try {
225 res.AppendLine("Logged events/stats:");
226 res.AppendLine(inMemLogger.ToString());
227 res.AppendLine();
228 } catch (Exception ex) when (!ex.IsCriticalException()) {
229 res.AppendLine(" Failed to access event log.");
230 res.AppendLine(ex.ToString());
231 res.AppendLine();
232 }
233
234 try {
235 res.AppendLine("System events:");
236
237 var application = new EventLog("Application");
238 var lastWeek = DateTime.Now.Subtract(TimeSpan.FromDays(7));
239 foreach (var entry in application.Entries.Cast<EventLogEntry>()
240 .Where(e => e.InstanceId == 1026L) // .NET Runtime
241 .Where(e => e.TimeGenerated >= lastWeek)
242 .Where(e => InterestingApplicationLogEntries.IsMatch(e.Message))
243 .OrderByDescending(e => e.TimeGenerated)
244 ) {
245 res.AppendLine(string.Format("Time: {0:s}", entry.TimeGenerated));
246 using (var reader = new StringReader(entry.Message.TrimEnd())) {
247 for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) {
248 res.AppendLine(line);
249 }
250 }
251 res.AppendLine();
252 }
253
254 } catch (Exception ex) when (!ex.IsCriticalException()) {
255 res.AppendLine(" Failed to access event log.");
256 res.AppendLine(ex.ToString());
257 res.AppendLine();
258 }
259
260 res.AppendLine("Loaded assemblies:");
261 foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().OrderBy(assem => assem.FullName)) {
262 AssemblyFileVersionAttribute assemFileVersion;
263 var error = "(null)";
264 try {
265 assemFileVersion = assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false)
266 .OfType<AssemblyFileVersionAttribute>()
267 .FirstOrDefault();
268 } catch (Exception e) when (!e.IsCriticalException()) {
269 assemFileVersion = null;
270 error = string.Format("{0}: {1}", e.GetType().Name, e.Message);
271 }
272
273 res.AppendLine(string.Format(" {0}, FileVersion={1}",
274 assembly.FullName,
275 assemFileVersion?.Version ?? error
276 ));
277 }
278 res.AppendLine();
279
280 string globalAnalysisLog = PythonTypeDatabase.GlobalLogFilename;
281 if (File.Exists(globalAnalysisLog)) {
282 res.AppendLine("Global Analysis:");
283 try {
284 res.AppendLine(File.ReadAllText(globalAnalysisLog));
285 } catch (Exception ex) when (!ex.IsCriticalException()) {
286 res.AppendLine("Error reading the global analysis log.");
287 res.AppendLine("Please wait for analysis to complete and try again.");
288 res.AppendLine(ex.ToString());
289 }
290 }
291 res.AppendLine();
292
293 res.AppendLine("Environment Analysis Logs: ");
294 foreach (var provider in knownProviders) {
295 foreach (var factory in provider.GetInterpreterFactories().OfType<IPythonInterpreterFactoryWithDatabase>()) {
296 res.AppendLine(factory.Configuration.FullDescription);
297 string analysisLog = factory.GetAnalysisLogContent(CultureInfo.InvariantCulture);
298 if (!string.IsNullOrEmpty(analysisLog)) {
299 res.AppendLine(analysisLog);
300 }
301 res.AppendLine();
302 }
303 }
304 return res.ToString();
305 }
306
307 private static string GetProjectProperty(EnvDTE.Project project, string name) {
308 try {
309 return project.Properties.Item(name).Value.ToString();
310 } catch {
311 return "<undefined>";
312 }
313 }
314
315 public override int CommandId {
316 get { return (int)PkgCmdIDList.cmdidDiagnostics; }
317 }
318 }
319}