Skip to content

Conversation

@iSazonov
Copy link
Collaborator

@iSazonov iSazonov commented Sep 22, 2020

PR Summary

Simple demo:

$sb = { $a = 3; if ($a -eq 3) { Write-Host ">$a" } else { Sleep 5 } Sleep 2 }
Measure-Script -ScriptBlock $sb
Measure-Script { function f { param($p = @{ a=$(Sleep 2;1)}) $p }; f }

PR Context

Based on Jason's approach lzybkr@d620bb4

  • Perf event (with minimal data) issues for every sequence point while measure runs

    • the event is marked as start and recursive event
  • Perf events are implemented in ProfilerEventSource class

  • Perf event contains

    • Timestamp
    • script block Id
    • parent script block Id (allow to keep track of the call stack)
    • runspace Id (allow to support jobs, ForEach -Parallel and many runspaces)
  • Now perf events are issued for default parameter evaluations (and more).

  • Limitations:

    • no Stop events
    • we do not explicitly see parameter binding operations, pipeline/cmdlet phases (Begin, Process, End)
  • Rundown event (with full meta data) issues for every sequence point after measure stops

  • Rundown event issues for every script block (not used in Measure-Script now)

  • Rundown event issues are implemented in ProfilerRundownEventSource class

  • Scriptblock cache was simplifed and now it caches CompiledScriptBlock-s

    • previously ScriptBlock object was cached but never used directly - before use we always cloned them and a comment says it is because we should avoid a reference to SessionStateInternal and memory leak.
    • the ScriptBlock cloning was really new ScriptBlock(CompiledScriptBlock)
    • so it makes sense to cache ScriptBlock objects, now we cache CompiledScriptBlock-s and create script blocks with new ScriptBlock(CompiledScriptBlock) without extra cloning.
    • removed a lock if script block is already compiled
  • To collect meta data we use ConcurrentDictionary CompiledScriptBlock.s_cachedScripts cache

  • To initialize the cache at measure start we use CompiledScriptBlock cache and then register every script block at creation time.

  • This allow us to have meta data for every script block.

  • Exception (an edge case) - long running script blocks already removed from CompiledScriptBlock cache.

  • Measure-Script cmdlet is implemented for demo use

  • ProfileEventRecord is used for typed output

  • Duration is broken in common

    • now duration is calculated as substruction two Start event timestamps
    • we haven't Stop events
    • so it is not work for last event

For discussion

Since we have only start timestamps we haven't stop timestamp for last sequence point. Perhaps we need a script block stop event and maybe start event.

Here a question is should we think about measuring pipelines, parameter bindings.

Also can we be compatible with PerfView tool?

PR Checklist

@nohwnd
Copy link
Contributor

nohwnd commented Sep 23, 2020

Looks good. I am not able to build it on Windows. Cleaned up and ran bootstrap script, but I still see
"C:\Projects\powershell\src\System.Management.Automation\engine\Subsystem\SubsystemInfo.cs(304,29): error CS0103: The name 'SubsystemStrings' does not exist in the current context [C:\Projects\powershell\src\System.Management.Automation\System.Management.Automation.csproj]" any advice?

@iSazonov
Copy link
Collaborator Author

iSazonov commented Sep 23, 2020

@lzybkr I added a scriptblock rundown event per #13589 (comment)
For the example $sb = { Get-Process pwsh* | % -Begin { Sleep 2} -Process { $_.Name } } we get 3 such events:

  • for the full scriptblock
  • for { Sleep 2}
  • for { $_.Name }
    We get events for nested scriptblocks. Do we need its?

Also I found how use ConditionalWeakTable. With it I get strange scriptblocks without sequence points.

@nohwnd It is very strange. It seems your local fork is broken. At least you could do ipmo ./Build.psm1 -Force and Start-PSBuild -Clean. Then I could remove local directory tree and load from GitHub again.

Copy link
Contributor

@lzybkr lzybkr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks pretty good, I especially like relying on the rundown event internally which means it should work well out of process as well.

@lzybkr
Copy link
Contributor

lzybkr commented Sep 23, 2020

@iSazonov - I'm not sure what it would take to get support in perfview. I believe it support languages like JavaScript so I'd imagine PowerShell could do something similar. Maybe @brianrob (current maintainer of perfview) can offer some suggestions.

@lzybkr
Copy link
Contributor

lzybkr commented Sep 23, 2020

@IISResetMe (who adapted my SDK sample into the module PSProfiler) might also want to take a look.

@brianrob
Copy link

What kind of support in PerfView are you looking for?

@IISResetMe
Copy link
Collaborator

The output format is slightly awkward, but beyond that I could definitely work with this:

$sb = { Get-Process pwsh* | % -Begin { Sleep 2} -Process { $_.Name } }
Measure-Script -ScriptBlock $sb |
  Where ExtentText -NotLike '[{}]' |
  Group {$_.Extent.StartOffset},{$_.Extent.EndOffset} |
  Select Count,
         @{N='TotalTime';E={$t = [timespan]::Zero;$_.Group.ForEach({$t=$t+$_.Duration});$t}},
         @{N='Code';E={"{0,$($_.Group[0].Extent.StartColumnNumber - $sb.Ast.Extent.StartColumnNumber)}{1}"-f$null,$_.Group[0].ExtentText}}
Count TotalTime        Code
----- ---------        ----
    1 00:00:00.0025372   Get-Process pwsh* | % -Begin { Sleep 2} -Process { $_.Name }
    1 00:00:02.0061880                                  Sleep 2
    3 00:00:00.0001681                                                      $_.Name

Will take a closer look at the code tomorrow

@lzybkr
Copy link
Contributor

lzybkr commented Sep 24, 2020

@brianrob - I think PowerShell maintainers and some module authors would appreciate hybrid PowerShell/C# call stacks from CPU sampling.

This PR is generating minimal events (analogous to an IP but no stack) while profiling via an EventSource (using instrumentation in the generated code which is interpreted and lazily jit compiled). After turning off profiling, a rundown event provider is generating metadata to recover the source line and function details from the minimal events.

@iSazonov may have other ideas how PowerShell users might use perfview or wpa.

@iSazonov
Copy link
Collaborator Author

What kind of support in PerfView are you looking for?

My thoughts is very simple. Since users will can able to collect PowerShell performance events out-of-process they need tools to analyze the data.

  • Developer scenarios. Visual Studio and VS Code PowerShell extension/PES could expose a profiler somewhat.
  • Hosting scenarios. Azure, server scripts, PowerShell hosting applications - users will collect PowerShell performance events and they need a standalone tool to analyze them.
    I would like to avoid creating a separate new tool as it is a lot of work. It would be great to use a well known and reliable PerfView (and maybe WPA).
    As end user I'd expect PerfView will show me a callstack tree like for .Net.
    I guess we need to issue events like .Net does. I guess we should emulate such start/stop events. I hope it is possible and simplify PerfView adoption.

@lzybkr Could you please point me where we could issue a stop event for scriptblock/sequence point?

@brianrob What do you think? Is it possible? Could you tell me how we should create events correctly for PerfView?

@nohwnd
Copy link
Contributor

nohwnd commented Sep 24, 2020

Tried running this over a Pester test and it failed. Here is a mini repro: _scriptBlock is null when you hit the @() token in Measure-Script { function f { param($p = @()) $p }; f }

PS C:\Projects\powershell> $error[0] | fl -Force *

PSMessageDetails      :
Exception             : System.NullReferenceException: Object reference not set to an instance of an object.
                           at System.Management.Automation.Language.FunctionContext.UpdatePositionNoBreak(Int32 pos) in C:\Projects\powershell\src\System.Management.Automation\engine\parser\Compiler.cs:line 815
                           at System.Management.Automation.Language.FunctionContext.UpdatePosition(Int32 pos) in C:\Projects\powershell\src\System.Management.Automation\engine\parser\Compiler.cs:line 826
                           at lambda_method39(Closure , FunctionContext )
                           at System.Management.Automation.Language.Compiler.GetExpressionValue(ExpressionAst expressionAst, Boolean isTrustedInput, ExecutionContext context, SessionStateInternal sessionStateInternal,
                        IDictionary usingValues, Func`2& lambda, IScriptExtent[]& sequencePoints, Type& localsTupleType) in C:\Projects\powershell\src\System.Management.Automation\engine\parser\Compiler.cs:line 2179
                           at System.Management.Automation.Language.Compiler.DefaultValueExpressionWrapper.GetValue(ExecutionContext context, SessionStateInternal sessionStateInternal, IDictionary usingValues) in
                        C:\Projects\powershell\src\System.Management.Automation\engine\parser\Compiler.cs:line 1958
                           at System.Management.Automation.ScriptParameterBinder.GetDefaultScriptParameterValue(RuntimeDefinedParameter parameter, IDictionary implicitUsingParameters) in
                        C:\Projects\powershell\src\System.Management.Automation\engine\scriptparameterbinder.cs:line 149
                           at System.Management.Automation.ParameterBinderController.BindUnboundScriptParameterWithDefaultValue(MergedCompiledCommandParameter parameter) in
                        C:\Projects\powershell\src\System.Management.Automation\engine\ParameterBinderController.cs:line 1279
                           at System.Management.Automation.ParameterBinderController.BindUnboundScriptParameters() in C:\Projects\powershell\src\System.Management.Automation\engine\ParameterBinderController.cs:line 1242
                           at System.Management.Automation.ScriptParameterBinderController.BindCommandLineParameters(Collection`1 arguments) in
                        C:\Projects\powershell\src\System.Management.Automation\engine\scriptparameterbindercontroller.cs:line 99
                           at System.Management.Automation.DlrScriptCommandProcessor.EnterScope() in C:\Projects\powershell\src\System.Management.Automation\engine\ScriptCommandProcessor.cs:line 609
                           at System.Management.Automation.DlrScriptCommandProcessor.RunClause(Action`1 clause, Object dollarUnderbar, Object inputToProcess) in
                        C:\Projects\powershell\src\System.Management.Automation\engine\ScriptCommandProcessor.cs:line 501
TargetObject          :
CategoryInfo          : NotSpecified: (:) [f], NullReferenceException
FullyQualifiedErrorId : System.NullReferenceException,f
ErrorDetails          :
InvocationInfo        : System.Management.Automation.InvocationInfo
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
                        at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}

@rjmholt
Copy link
Collaborator

rjmholt commented Sep 24, 2020

@nohwnd for reference your original error was because the resource strings needed to be rebuilt. Start-PSBuild -Clean usually fixes that

@brianrob
Copy link

I just skimmed the PR to try and understand how things work. I would like to make sure I understand how things work, and then have a few questions.

My understanding is that there is a desire to enable profiling in two ways:

  1. Via Measure-ScriptBlock, which would result in the use of EventSource and EventListener to generate results inside of the pwsh process, and display them to the console.
  2. Via ETW or other platform loggers by enabling the events via an out-of-process mechanism. This profiling would be fully controlled via the platform logger and would occur regardless of whether or not Measure-ScriptBlock is invoked.

There are a couple of pieces of functionality that I can see being implemented in PerfView. Of course, I'm open to feedback and other ideas as well:

  1. The ability to determine how long a particular scriptblock/cmdlet takes to execute. This can be done via the Events view as long as there are start/stop events that get emitted on either side of the execution. I'm not sure if these exist today, but I don't see them in this PR.
  2. Views where we attribute computing resources (CPU, memory allocations, etc.) to their script/cmdlet-based stack.

For the computing resource based views, we'd need to understand a couple of things:

  1. How to construct the stack.
  2. How to resolve symbols (if necessary).

Right now, PerfView has functionality that does this for jitted code that is compiled on-the-fly by the .NET runtime. This is done by using rundown events to provide function memory ranges and symbols. There's mention of both interpreted and jitted code here. For interpreted code, we'd need events that would allow us to build the appropriate stack. You could imagine doing this with start/stop events that wrap the "scope" of each frame. This would work as long as collection started before the invocation. If it is started in the middle of an invocation, we'd see broken stacks, but these may still be valuable depending on how much movement up and down the stack there is. For jitted code, are we talking about using the .NET runtime's JIT, or something else?

@iSazonov
Copy link
Collaborator Author

@brianrob

My understanding is that there is a desire to enable profiling in two ways:

Yes.

There are a couple of pieces of functionality that I can see being implemented in PerfView. Of course, I'm open to feedback and other ideas as well:

  1. The ability to determine how long a particular scriptblock/cmdlet takes to execute. This can be done via the Events view as long as there are start/stop events that get emitted on either side of the execution. I'm not sure if these exist today, but I don't see them in this PR.
  2. Views where we attribute computing resources (CPU, memory allocations, etc.) to their script/cmdlet-based stack.

Yes for 1. Currently we issue an event with number 1 as start event but without the "start" label.
From your comment I see that we need (1) both start and stop events, (2) a way to track a script stack.
I hope @lzybkr give the cue where we could issue stop events and how we could track script stack.

For 2. I am not sure we can utilize resource tracking because scripts are executed in runspaces (you can think that it is special thread) and there could be some parallel runspaces so we cannot directly know which runspace is consuming resources- we should track a long correlation sequence scriptblock-runspace-.Net thread. It looks too complex for me. Although it is possible... It is my answer on "For jitted code, are we talking about using the .NET runtime's JIT, or something else?" too - it would interesting to have the correlated views (script - JIT) but looks too complex for the first step PR.

@iSazonov
Copy link
Collaborator Author

iSazonov commented Sep 24, 2020

Tried running this over a Pester test and it failed. Here is a mini repro: _scriptBlock is null when you hit the @() token in Measure-Script { function f { param($p = @()) $p }; f }

NRE is in "ProfilerEventSource.LogInstance.SequencePoint(_scriptBlock.Id, pos);" (in UpdatePositionNoBreak()) because "_scriptBlock" is null. I wonder why... It seems a "bug" mentioned above.

@lzybkr
Copy link
Contributor

lzybkr commented Sep 25, 2020

@brianrob PowerShell does use the .NET jit via LINQ expression trees. PowerShell forked the DLR to use the interpreter which will jit compile code if it executes enough times.

@iSazonov
Copy link
Collaborator Author

Measure-Script { function f { param($p = @()) $p }; f }

We catch NRE on "@()". It is parameter binding. This is evaluated out of current scripblock. There is created pseudo functioncontext with sequence points but without a reference to current scriptblock.

  • If we skip issuing the perf event in the case we never see parameter bindings in the profiler. If we issue the perf event there will be no link to a scriptblock and we will not know the context when analyzing.
  • In the example we call GetDefaultScriptParameterValue(). So it is called everywhere where parameter binding exists (funcrions/scriptblocks).
  • another code path - GetExpressionValue(). This is used in many places and we may not see it in the profiler too.

Since these code paths is complex, with static methods without scriptblock parameter it is a huge work to fix all them.
One workaround I can think is to track functioncontext in interpreter. Is it possible/make sense?

@ghost ghost added the Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept label Oct 4, 2020
@nohwnd
Copy link
Contributor

nohwnd commented Oct 6, 2020

I just tried it out with some real scripts and my module that summarizes the results and it works nicely. I am preparing a presentation about this for PWSH24. Is it reasonable to expect that this will be available in a month or two? (even if behind an experimental flag)

@iSazonov
Copy link
Collaborator Author

iSazonov commented Oct 7, 2020

@nohwnd Thanks!

Do you ask about 7.1 or 7.2? I am sure we will get this in 7.2 Preview1 as experimental.
As for 7.1 we need to ask MSFT team. I think it is low risk because we don't change functional code. But the code will be changed without a doubt because we need to add stop events. Currently Measure-Script cmdlet is demo only. I could make it more useful, move to Utility module and mark as experimental feature.

@ghost ghost removed the Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept label Oct 7, 2020
@nohwnd
Copy link
Contributor

nohwnd commented Oct 7, 2020

7.2 is okay. I just wanted to know when roughly this will be ready to try, because I have only a version that works with PowerShell 5, by doing some "reflection" magic, but nothing for the PowerShell core versions.

@iSazonov
Copy link
Collaborator Author

iSazonov commented Oct 7, 2020

@nohwnd .Net 5.0 will be released in November. PowerShell 7.1 will be released in the time too. Then the holidays come. I'd expect 7.2 Preview1 in January unless MSFT team forked earlier.

@SteveL-MSFT
Copy link
Member

I agree with @nohwnd that the cmdlet should be Trace-Script and a separate cmdlet (in a separate PR) would use the trace objects to give a view to the user on hot spots in their script.

/// <summary>
/// Measure-ScriptBlock output type.
/// </summary>
internal struct ProfileEventRecord
Copy link
Member

@SteveL-MSFT SteveL-MSFT May 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be public as it's a type returned to the user as output

@SteveL-MSFT
Copy link
Member

SteveL-MSFT commented May 23, 2022

Was playing with this branch and made a function to consolidate and sort the results https://gist.github.com/SteveL-MSFT/f602dd7e3faac36cc8c781b3c07d036e.

Gist
GitHub Gist: instantly share code, notes, and snippets.

@iSazonov
Copy link
Collaborator Author

The goal here should be to determine if the PR is good enough to be an experimental feature so we can get user feedback. @iSazonov can you turn this into an ExperimentalFeature?

First of all, we need answers to the basic questions that have been formulated. I saw this PR as more of a problem study than a final product.

@ghost ghost added the Review - Needed The PR is being reviewed label Jun 1, 2022
@ghost
Copy link

ghost commented Jun 1, 2022

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Maintainer, please provide feedback and/or mark it as Waiting on Author

@SteveL-MSFT
Copy link
Member

SteveL-MSFT commented Jun 13, 2022

The goal here should be to determine if the PR is good enough to be an experimental feature so we can get user feedback. @iSazonov can you turn this into an ExperimentalFeature?

First of all, we need answers to the basic questions that have been formulated. I saw this PR as more of a problem study than a final product.

An experimental feature isn't a final product. It's getting it into the hands of users to get real usage feedback. Because it's experimental, it means changes can happen without them being breaking changes. Can you summarize what questions you want answered?

@ghost ghost removed the Review - Needed The PR is being reviewed label Jun 13, 2022
{ "Import-Module", new SessionStateCmdletEntry("Import-Module", typeof(ImportModuleCommand), helpFile) },
{ "Invoke-Command", new SessionStateCmdletEntry("Invoke-Command", typeof(InvokeCommandCommand), helpFile) },
{ "Invoke-History", new SessionStateCmdletEntry("Invoke-History", typeof(InvokeHistoryCommand), helpFile) },
{ "Measure-Script", new SessionStateCmdletEntry("Measure-Script", typeof(MeasureScriptCommand), helpFile) },
Copy link
Member

@SteveL-MSFT SteveL-MSFT Jun 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trace-Script seems like a better name for what it does currently. A separate Measure-Script may be needed later that leverages Trace-Script, but produces summarized output to the user. I don't think Measure-Script needs to be part of this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree.

Measure-Script is the name of the main cmdlet in PSProfiler. Trace-Script is the name of the main cmdlet in my Profiler module, but only because Measure-Script is already taken. Both these cmdlets provide a summary.

So using Trace-Script here to get the "raw" data, is imho the best choice. And I am okay with renaming mine.

@ghost ghost added the Review - Needed The PR is being reviewed label Jun 21, 2022
@ghost
Copy link

ghost commented Jun 21, 2022

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Maintainer, please provide feedback and/or mark it as Waiting on Author

Copy link
Member

@daxian-dbw daxian-dbw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't review code in Profiler.cs yet, but want to share my feedback so far.

Can you summarize what questions you want answered?

I want to echo on this. @iSazonov can you please summarize those questions again?
I think we need to scope the functionalities we want in this PR. Based on @nohwnd's comments above, it looks the Pester coverage functionality is mostly working with the changes. So, can we set that to be the goal of this PR? (That means we only accept the features/changes needed for Pester coverage and nothing else.)

}

internal static ScriptBlock TryGetCachedScriptBlock(string fileName, string fileContents)
internal static CompiledScriptBlockData TryGetCachedCompiledScriptBlock(string fileName, string fileContents)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about keeping the method signature unchanged, but only changing the cache to hold CompiledScriptBlockData instead? In that way, the call sites of the TryGetCacheXXX and CacheXXX methods are unchanged. It's really just an implementation detail of those 2 methods.

Comment on lines +273 to +277
CompiledScriptBlockData compiledScriptBlockData = ScriptBlock.TryGetCachedCompiledScriptBlock(_path, scriptContents);
if (compiledScriptBlockData != null)
{
this.ScriptBlock = new ScriptBlock(compiledScriptBlockData);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid making changes to the call sites of TryGetCachedScriptBlock. Whether it caches ScriptBlock or CompiledScriptBlockData should be an implementation detail to TryGetCachedScriptBlock.

{
this.ScriptBlock = new ScriptBlock(_scriptBlockAst, isFilter: false);
ScriptBlock.CacheScriptBlock(_scriptBlock.Clone(), _path, scriptContents);
ScriptBlock.CacheCompiledScriptBlock(_scriptBlock.Clone(), _path, scriptContents);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

// Sequence point on 0 position is an entry point in a scriptblock.
// In the time the callstack has still point to parent scriptblock
// so we take last element from the callstack,
// for rest sequence points we take an element before last as parent.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please reword the comments here? The algorithm is not clear to me by reading the existing comments. An example would be helpful too.

internal void UpdatePositionNoBreak(int pos)
{
_currentSequencePointIndex = pos;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, we only emit events when _checkBreakpoints == true? Why not when it's false, and in what cases _checkBreakpoints would be false?

Also, then this method doesn't really do anything and should be removed.

Comment on lines +7103 to +7106
MethodInfo method = _checkBreakpoints
? CachedReflectionInfo.FunctionContext_UpdatePosition
: CachedReflectionInfo.FunctionContext_UpdatePositionNoBreak;
exprs.Add(Expression.Call(Compiler.s_functionContext, method, ExpressionCache.Constant(_sequencePoint)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be changed to:

            exprs.Add(
                Expression.Assign(
                    Expression.Field(Compiler.s_functionContext, CachedReflectionInfo.FunctionContext__currentSequencePointIndex),
                    ExpressionCache.Constant(_sequencePoint)));

            if (_checkBreakpoints)
            {
                exprs.Add(Expression.Call(Compiler.s_functionContext, CachedReflectionInfo.FunctionContext_UpdatePosition);
            }

            exprs.Add(ExpressionCache.Empty);

internal class CompiledScriptBlockData
{
private static readonly ConcurrentDictionary<CompiledScriptBlockData, CompiledScriptBlockData> s_compiledScriptBlockTable
= new ConcurrentDictionary<CompiledScriptBlockData, CompiledScriptBlockData>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do the key and value are the same object? Comments are needed here to explain the intent.

=> ast is UsingStatementAst cmdAst && cmdAst.IsUsingModuleOrAssembly();

internal static void CacheScriptBlock(ScriptBlock scriptBlock, string fileName, string fileContents)
internal static void CacheCompiledScriptBlock(ScriptBlock scriptBlock, string fileName, string fileContents)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, better make the signature unchanged. What to actually cache is an implementation detail.

[EventSource(Name = "Microsoft-PowerShell-Profiler")]
internal class ProfilerEventSource : EventSource
{
internal static ProfilerEventSource LogInstance = new ProfilerEventSource();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other event source classes use the name Log for this field: https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/parser/Parser.cs#L8260
So, better to keep consistent.

// We can safely do this because lambda and sequencePoints
// parameters of the method come always from callsites as null.
// (We could remove them at all.)
scriptBlock.Compile(optimized: false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for getting the script call stack when generating events? It looks quite hacky.

I think we need to define the goal for this PR. I guess the goal is to make the Pester's coverage functionality able to work with the ETW events generated from the changes here. So, is the script stack necessary for the Pester's coverage functionality to work?

If we set that as the goal, we should only include features/changes that are necessary for that to work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both Pester code coverage, and Profiler would depend on this. Pester code coverage is a subset of Profiler functionality. Both use this interface to provide their services:

https://github.com/nohwnd/Profiler/blob/main/csharp/Profiler/ITracer.cs

Which collects:

// The currently executing scriptblock and extent
IScriptExtent extent,
ScriptBlock scriptBlock,

// how many items are on callstack
int level,

// the current function and module name
string functionName,
string moduleName

The data is collected here, by reflecting on debugger, functionContext and session state:

https://github.com/nohwnd/Profiler/blob/main/csharp/Profiler/Tracer.cs#L157-L171

As long as this info is provided in some form, the current version of Profiler can work with this.

"Level" I should be able to get from scriptblock ids as pointed out here: #13673 (comment)

Module I should be able to get from the scriptblock and its associated session state. I think.

But what about the function name? Any suggestions where I can grab that?

@ghost ghost added Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept and removed Review - Needed The PR is being reviewed labels Jun 28, 2022
@nohwnd
Copy link
Contributor

nohwnd commented Jun 28, 2022

Commented above, if the current implementation adds function name (or someone tells me where to get its😇 ), I should be good to integrate this with Profiler.


Since we last talked a year ago my Profiler module evolved a bit and now collects also module and function name (from session state and current function context). Which makes me think that exposing this as a list of events with predefined data might be limiting in the future.

Could there be also an additional extension point similar to $ExecutionContext.InvokeCommand.CommandNotFoundAction, where a callback could be registered?

There are few general objects that might potentially have more interesting info, and it would be cool to have a built-in extension point that I could use in the future, to look at the current script extent, scriptblock, session state, function context, and callstack.

@iSazonov
Copy link
Collaborator Author

@daxian-dbw This project has always been a hobby of mine, but waiting more than a year and a half is no longer a hobby, but a frustrating disappointment. I hope you understand that I don't have any motivation to remember this old story, especially since I found several other projects that I'm happy to spend my time on. I have already closed most of my unanswered PRs. Since no one was upset, it means no one needs them. The ones I left may be useful to someone or you can close them. I always saw in PowerShell a huge potential to change the ecosystem. Unfortunately this is in a deep sleep and I don't expect it to change in the foreseeable future unfortunately.

@ghost ghost removed the Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept label Jun 28, 2022
@daxian-dbw
Copy link
Member

@iSazonov I totally understand your frustration and I'm truly sorry seeing this happening.

@iSazonov
Copy link
Collaborator Author

@daxian-dbw The sole purpose of this PR was to implement the idea of Json Shirk so that we could explore it.
The cmdlet from the PR is only for exploring and testing. I think it should be moved into our HelpersCommon module. I know MSFT team prefer to have new cmdlets as community modules so new profiler cmdlets/tools can be released in this way.
Only @nohwnd showed interest so it's worth realizing what he wants.
Apart from this I see another important scenario for developers, administrators and support teams - the possibility of remote collection of events and study them in tools like PerfView and speedscope. I assume they need a couple of Start/Stop events while we now only release the Start event.

@nohwnd
Copy link
Contributor

nohwnd commented Jun 29, 2022

My current solution works okay, except that it can't profile additional runspaces (not sure if that is a limitation I will be able to lift easily). Unless @JustinGrote wants to push this forward, I am okay with this PR being closed.

@JustinGrote
Copy link
Contributor

Thanks for the explanation @iSazonov, I would agree to drop it in favor of a module-based implementation unless there's something engine-intrinsic that needs to occur in order to assist with a profiling scenario (e.g. runspaces as @nohwnd mentioned)

@iSazonov
Copy link
Collaborator Author

Thanks for the explanation @iSazonov, I would agree to drop it in favor of a module-based implementation unless there's something engine-intrinsic that needs to occur in order to assist with a profiling scenario (e.g. runspaces as @nohwnd mentioned)

Events should be generated in Engine. Rest of processing can be external.

@SteveL-MSFT
Copy link
Member

@iSazonov I appreciate all the work you've done and we've been trying to have Mondays focused on community contributions since the start of this month so that at least we have a more regular cadence when folks can expect an ack or response.

For this PR, I think the maintainers should take it over to get the events into 7.3 and the processing of the events will be an external module.

@JustinGrote
Copy link
Contributor

@iSazonov I appreciate all the work you've done and we've been trying to have Mondays focused on community contributions since the start of this month so that at least we have a more regular cadence when folks can expect an ack or response.

For this PR, I think the maintainers should take it over to get the events into 7.3 and the processing of the events will be an external module.

I agree, add the API to be able to get the profiling events and leave the implementation up to the community.

@ghost ghost added the Review - Needed The PR is being reviewed label Jul 7, 2022
@ghost
Copy link

ghost commented Jul 7, 2022

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Maintainer, please provide feedback and/or mark it as Waiting on Author

@daxian-dbw
Copy link
Member

The Engine Working Group discussed this PR in the last meeting (7/7), and we agreed to close it with the following reasons:

  1. According to this comment from @iSazonov, only @nohwnd showed interests, but @nohwnd already has a working solution and is OK to close this PR. It indicates that this is not a high demand feature.

  2. Based on the PR description and @iSazonov's comments, the change in this PR is not feature-complete. The biggest concern is that the STOP event is missing because PowerShell currently doesn't emit sequence point on finishing script execution. It may be non-trivial work to add it, so the cost of resource to take over this PR is likely high.

  3. We think our efforts should be put in more recent PRs instead, so hopefully new contributors won't face the same frustration again and again.

Thank you @iSazonov for putting up this PR and led the discussion around the idea of emitting events during script execution. We can always reference this PR when a similar need comes up in future.

@daxian-dbw daxian-dbw closed this Jul 18, 2022
@ghost ghost removed the Review - Needed The PR is being reviewed label Jul 18, 2022
@iSazonov
Copy link
Collaborator Author

@daxian-dbw @SeeminglyScience In the PR was made some helper changes in Engine. Could you please review them? Perhaps this is of some value for other scenarios. If not, I will delete this branch altogether.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.