Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PythonProfilderCommandService to support new profiler #8150

Merged
merged 7 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Python/Product/Profiling/Profiling.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,16 @@
<Compile Include="Profiling\AutomationProfiling.cs" />
<Compile Include="Profiling\AutomationReport.cs" />
<Compile Include="Profiling\AutomationSession.cs" />
<Compile Include="Profiling\CommandArgumentBuilder.cs" />
<Compile Include="Profiling\CompareReportsView.cs" />
<Compile Include="Profiling\CompareReportsWindow.xaml.cs">
<DependentUpon>CompareReportsWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Profiling\IPythonPerformanceReport.cs" />
<Compile Include="Profiling\IPythonProfileSession.cs" />
<Compile Include="Profiling\IPythonProfiling.cs" />
<Compile Include="Profiling\IPythonProfilingCommandArgs.cs" />
<Compile Include="Profiling\IPythonProfilerCommandService.cs" />
<Compile Include="Profiling\ProfilingSessionEditorFactory.cs" />
<Compile Include="Profiling\CustomPythonInterpreterView.cs" />
<Compile Include="Profiling\PythonInterpreterView.cs" />
Expand All @@ -80,7 +83,10 @@
<Compile Include="Profiling\ProfilingTargetView.cs" />
<Compile Include="Profiling\SessionNode.cs" />
<Compile Include="Profiling\StandaloneTargetView.cs" />
<Compile Include="Profiling\PythonProfilingCommandArgs.cs" />
<Compile Include="Profiling\TreeViewIconIndex.cs" />
<Compile Include="Profiling\UserInputDialog.cs" />
<Compile Include="Profiling\PythonProfilerCommandService.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProvideFileFilterAttribute.cs" />
<Compile Include="GlobalSuppressions.cs" />
Expand Down
175 changes: 175 additions & 0 deletions Python/Product/Profiling/Profiling/CommandArgumentBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Python Tools for Visual Studio
// Copyright(c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the License); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABILITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.



namespace Microsoft.PythonTools.Profiling {
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using Microsoft.PythonTools.Infrastructure;
using Microsoft.PythonTools.Interpreter;

internal class CommandArgumentBuilder {

/// <summary>
/// Constructs a <see cref="PythonProfilingCommandArgs"/> based on the provided profiling target.
/// </summary>
public PythonProfilingCommandArgs BuildCommandArgsFromTarget(ProfilingTarget target) {
if (target == null) {
return null;
}

try {
var pythonProfilingPackage = PythonProfilingPackage.Instance;
var joinableTaskFactory = pythonProfilingPackage.JoinableTaskFactory;

PythonProfilingCommandArgs command = null;

joinableTaskFactory.Run(async () => {
await joinableTaskFactory.SwitchToMainThreadAsync();

var name = target.GetProfilingName(pythonProfilingPackage, out var save);
var explorer = await pythonProfilingPackage.ShowPerformanceExplorerAsync();
var session = explorer.Sessions.AddTarget(target, name, save);

command = SelectBuilder(target, session);

});

return command;
} catch (Exception ex) {
Debug.Fail($"Error building command: {ex.Message}");
throw;
}
}

/// <summary>
/// Select the appropriate builder based on the provided profiling target.
/// </summary>
private PythonProfilingCommandArgs SelectBuilder(ProfilingTarget target, SessionNode session) {
var projectTarget = target.ProjectTarget;
var standaloneTarget = target.StandaloneTarget;

if (projectTarget != null) {
return BuildProjectCommandArgs(projectTarget, session);
} else if (standaloneTarget != null) {
return BuildStandaloneCommandArgs(standaloneTarget, session);
}
return null;
}

private PythonProfilingCommandArgs BuildProjectCommandArgs(ProjectTarget projectTarget, SessionNode session) {
var solution = PythonProfilingPackage.Instance.Solution;
var project = solution.EnumerateLoadedPythonProjects()
.SingleOrDefault(p => p.GetProjectIDGuidProperty() == projectTarget.TargetProject);

if (project == null) {
return null;
}

LaunchConfiguration config = null;
try {
config = project?.GetLaunchConfigurationOrThrow();
} catch (NoInterpretersException ex) {
PythonToolsPackage.OpenNoInterpretersHelpPage(session._serviceProvider, ex.HelpPage);
return null;
} catch (MissingInterpreterException ex) {
MessageBox.Show(ex.Message, Strings.ProductTitle);
return null;
} catch (IOException ex) {
MessageBox.Show(ex.Message, Strings.ProductTitle);
return null;
}
if (config == null) {
MessageBox.Show(Strings.ProjectInterpreterNotFound.FormatUI(project.GetNameProperty()), Strings.ProductTitle);
return null;
}

if (string.IsNullOrEmpty(config.ScriptName)) {
MessageBox.Show(Strings.NoProjectStartupFile, Strings.ProductTitle);
return null;
}

if (string.IsNullOrEmpty(config.WorkingDirectory) || config.WorkingDirectory == ".") {
config.WorkingDirectory = project.ProjectHome;
if (string.IsNullOrEmpty(config.WorkingDirectory)) {
config.WorkingDirectory = Path.GetDirectoryName(config.ScriptName);
}
}

var pythonExePath = config.GetInterpreterPath();
var scriptPath = string.Join(" ", ProcessOutput.QuoteSingleArgument(config.ScriptName), config.ScriptArguments);
var workingDir = config.WorkingDirectory;
var envVars = session._serviceProvider.GetPythonToolsService().GetFullEnvironment(config);

var command = new PythonProfilingCommandArgs {
PythonExePath = pythonExePath,
ScriptPath = scriptPath,
WorkingDir = workingDir,
Args = Array.Empty<string>(),
EnvVars = envVars
};
return command;
}

private PythonProfilingCommandArgs BuildStandaloneCommandArgs(StandaloneTarget standaloneTarget, SessionNode session) {
if (standaloneTarget == null) {
return null;
}

LaunchConfiguration config = null;

if (standaloneTarget.InterpreterPath != null) {
config = new LaunchConfiguration(null);
}

if (standaloneTarget.PythonInterpreter != null) {
var registry = session._serviceProvider.GetComponentModel().GetService<IInterpreterRegistryService>();
var interpreter = registry.FindConfiguration(standaloneTarget.PythonInterpreter.Id);
if (interpreter == null) {
return null;
}

config = new LaunchConfiguration(interpreter);
}

config.InterpreterPath = standaloneTarget.InterpreterPath;
config.ScriptName = standaloneTarget.Script;
config.ScriptArguments = standaloneTarget.Arguments;
config.WorkingDirectory = standaloneTarget.WorkingDirectory;

var argsInput = standaloneTarget.Arguments;
var parsedArgs = string.IsNullOrWhiteSpace(argsInput)
? Array.Empty<string>()
: argsInput.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

var envVars = session._serviceProvider.GetPythonToolsService().GetFullEnvironment(config);

return new PythonProfilingCommandArgs {
PythonExePath = config.GetInterpreterPath(),
WorkingDir = standaloneTarget.WorkingDirectory,
ScriptPath = standaloneTarget.Script,
Args = parsedArgs,
EnvVars = envVars
};
}
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Python Tools for Visual Studio
// Copyright(c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the License); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABILITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

namespace Microsoft.PythonTools.Profiling {

/// <summary>
/// Defines a service interface for collecting user input and converting to Python profiling command arguments.
/// </summary>
public interface IPythonProfilerCommandService {
/// <summary>
/// Collects user input via a dialog and converts it into a <see cref="IPythonProfilingCommandArgs"/>.
/// </summary>
IPythonProfilingCommandArgs GetCommandArgsFromUserInput();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Python Tools for Visual Studio
// Copyright(c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the License); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABILITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

using System.Collections.Generic;

namespace Microsoft.PythonTools.Profiling
{
/// <summary>
/// Contains the arguments for a Python profiling command.
/// </summary>
public interface IPythonProfilingCommandArgs {
string PythonExePath { get; set; }
string WorkingDir { get; set; }
string ScriptPath { get; set; }
string[] Args { get; set; }
Dictionary<string, string> EnvVars { get; set; }
}
}
2 changes: 1 addition & 1 deletion Python/Product/Profiling/Profiling/LaunchProfiling.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
AutomationProperties.AutomationId="ProfileScript"
GroupName="ProjectOrStandalone"
IsChecked="{Binding IsStandaloneSelected}" />
<ScrollViewer Grid.Row="4" Height="100">
<ScrollViewer Grid.Row="4" Height="200">
<GroupBox Header="{x:Static ui:Strings.LaunchProfiling_ScriptOptions}"
IsEnabled="{Binding IsStandaloneSelected}">
<Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public ProfilingTargetView(IServiceProvider serviceProvider) {
IsProjectSelected = false;
IsStandaloneSelected = true;
}
_startText = Strings.LaunchProfiling_Start;
_startText = Strings.LaunchProfiling_OK;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Python Tools for Visual Studio
// Copyright(c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the License); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABILITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

namespace Microsoft.PythonTools.Profiling {
using System;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Windows;

/// <summary>
/// Implements a service to collect user input for profiling and convert to a <see cref="PythonProfilingCommandArgs"/>.
/// </summary>
[Export(typeof(IPythonProfilerCommandService))]
class PythonProfilerCommandService : IPythonProfilerCommandService {
private readonly CommandArgumentBuilder _commandArgumentBuilder;
private readonly UserInputDialog _userInputDialog;

public PythonProfilerCommandService() {
_commandArgumentBuilder = new CommandArgumentBuilder();
_userInputDialog = new UserInputDialog();
}

/// <summary>
/// Collects user input and constructs a <see cref="PythonProfilingCommandArgs"/> object.
/// </summary>
/// <returns>
/// A <see cref="PythonProfilingCommandArgs"/> object based on user input, or <c>null</c> if canceled.
/// </returns>
public IPythonProfilingCommandArgs GetCommandArgsFromUserInput() {
try {
var pythonProfilingPackage = PythonProfilingPackage.Instance;
var targetView = new ProfilingTargetView(pythonProfilingPackage);

if (_userInputDialog.ShowDialog(targetView)) {
var target = targetView.GetTarget();
return _commandArgumentBuilder.BuildCommandArgsFromTarget(target);
}
} catch (Exception ex) {
Debug.Fail($"Error displaying user input dialog: {ex.Message}");
MessageBox.Show($"An unexpected error occurred: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}

return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Python Tools for Visual Studio
// Copyright(c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the License); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABILITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

using System.Collections.Generic;
using System.ComponentModel.Composition;

namespace Microsoft.PythonTools.Profiling {
/// <summary>
/// Represents the arguments for a Python profiling command.
/// </summary>
[Export(typeof(IPythonProfilingCommandArgs))]
public class PythonProfilingCommandArgs : IPythonProfilingCommandArgs {
public string PythonExePath { get; set; }
public string WorkingDir { get; set; }
public string ScriptPath { get; set; }
public string[] Args { get; set; }
public Dictionary<string, string> EnvVars { get; set; }
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public string InterpreterPath {
}

/// <summary>
/// True if InterpreterPath is valid; false if it will be ignored.
/// True if PythonExePath is valid; false if it will be ignored.
/// </summary>
public bool CanSpecifyInterpreterPath {
get {
Expand Down
Loading