Skip to content

Commit a66bff0

Browse files
paulusakademiuscaaavik-msftbrianrob
authored
Add multi line viewer for Event Window (#2175)
* Add multi line viewer for Event Window This will add a multi line viewer to the Event Viewer, which can be turned on and off with F2 * Address review comments. -Change m_payloads from Array to List -Add other fields from conditional calls to AddField -Add "Press f2 to hide/unhide" text -Set default size of multi line viewer to 25% -Fix scroll behaviour * Update src/PerfView/EventViewer/EventWindow.xaml Co-authored-by: Cameron Aavik <[email protected]> * Update src/PerfView/EventViewer/EventWindow.xaml.cs Removing unnecessary INullOrEmpty() call. Co-authored-by: Brian Robbins <[email protected]> --------- Co-authored-by: Cameron Aavik <[email protected]> Co-authored-by: Brian Robbins <[email protected]>
1 parent b0c027a commit a66bff0

File tree

5 files changed

+124
-29
lines changed

5 files changed

+124
-29
lines changed

src/CSVReader/CsvReader.cs

+1
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ internal class CsvEventRecord : EventRecord
525525
public override double TimeStampRelatveMSec { get { return m_TimeStampRelativeMSec; } }
526526
public override string ProcessName { get { return m_ProcessName; } }
527527
public override string Rest { get { return m_Data; } set { } }
528+
public override List<Payload> Payloads => throw new NotImplementedException();
528529
#region private
529530
internal CsvEventRecord(ByteWindow window, CsvEventSource source, string[] colNames, Dictionary<string, int> columnOrder, double[] columnSums) : base(4)
530531
{

src/CSVReader/EventSource.cs

+21-2
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,24 @@ public static List<string> ParseColumns(string columnSpec, ICollection<string> c
144144
}
145145
}
146146

147+
/// <summary>
148+
/// A Payload object represents the Rest string from EventRecord as Object to make working
149+
/// with the values inside easier
150+
/// </summary>
151+
public class Payload
152+
{
153+
public Payload(string payloadName, string payload)
154+
{
155+
m_payloadName = payloadName;
156+
m_payload = payload;
157+
}
158+
159+
public string PayloadName { get { return m_payloadName; } set { } }
160+
public string PayloadValue { get { return m_payload; } set { } }
161+
162+
private string m_payloadName;
163+
private string m_payload;
164+
}
147165
/// <summary>
148166
/// An EventRecord is a abstraction that is returned by the EventSource.Events API. It represents
149167
/// a single event and is everything the GUI needs to display the event in the GUI.
@@ -153,7 +171,8 @@ public abstract class EventRecord
153171
public abstract string EventName { get; }
154172
public abstract string ProcessName { get; }
155173
public abstract double TimeStampRelatveMSec { get; }
156-
174+
public abstract List<Payload> Payloads { get; }
175+
157176
// TODO FIX NOW should be abstract, get CSV and ETW subclasses to implement
158177
/// <summary>
159178
/// The names of the fields in this record
@@ -186,7 +205,7 @@ public abstract class EventRecord
186205
public string DisplayField8 { get { return m_displayFields[7]; } set { } }
187206
public string DisplayField9 { get { return m_displayFields[8]; } set { } }
188207
public string DisplayField10 { get { return m_displayFields[9]; } set { } }
189-
208+
190209
// returns true of 'pattern' matches the display fields.
191210
public virtual bool Matches(Regex pattern)
192211
{

src/PerfView/EtwEventSource.cs

+28-20
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ public override void ForEach(Func<EventRecord, bool> callback)
319319
}
320320
}
321321
}
322-
322+
323323
ETWEventRecord eventRecord = null;
324324
if (textFilter != null)
325325
{
@@ -346,7 +346,8 @@ public override void ForEach(Func<EventRecord, bool> callback)
346346
eventRecord = emptyEventRecord;
347347
eventRecord.m_timeStampRelativeMSec = data.TimeStampRelativeMSec;
348348
}
349-
349+
350+
350351
if (eventRecord == null)
351352
{
352353
eventRecord = new ETWEventRecord(this, data, columnOrder, NonRestFields, durationMSec);
@@ -534,6 +535,7 @@ private Dictionary<TraceEventCounts, TraceEventCounts> GetEventCounts(List<strin
534535
private Dictionary<string, bool> m_selectedEvents; // set to true if the event is only present because it is a start for a stop.
535536
private bool m_selectedAllEvents; // This ensures that when a user selects all events he gets everything
536537

538+
537539
internal class ETWEventRecord : EventRecord
538540
{
539541
// Used as the null record (after MaxRet happens).
@@ -552,29 +554,30 @@ internal ETWEventRecord(ETWEventSource source, TraceEvent data, Dictionary<strin
552554

553555
m_timeStampRelativeMSec = data.TimeStampRelativeMSec;
554556
m_idx = data.EventIndex;
555-
557+
m_payloads = new List<Payload>();
558+
556559
// Compute the data column
557560
var restString = new StringBuilder();
558561

559562
// Deal with the special HasStack, ThreadID and ActivityID, DataLength fields;
560563
var hasStack = data.CallStackIndex() != CallStackIndex.Invalid;
561564
if (hasStack)
562565
{
563-
AddField("HasStack", hasStack.ToString(), columnOrder, restString);
566+
AddField("HasStack", hasStack.ToString(), columnOrder, restString, m_payloads);
564567
}
565568

566569
var asCSwitch = data as CSwitchTraceData;
567570
if (asCSwitch != null)
568571
{
569-
AddField("HasBlockingStack", (asCSwitch.BlockingStack() != CallStackIndex.Invalid).ToString(), columnOrder, restString);
572+
AddField("HasBlockingStack", (asCSwitch.BlockingStack() != CallStackIndex.Invalid).ToString(), columnOrder, restString, m_payloads);
570573
}
571574

572-
AddField("ThreadID", data.ThreadID.ToString("n0"), columnOrder, restString);
573-
AddField("ProcessorNumber", data.ProcessorNumber.ToString(), columnOrder, restString);
575+
AddField("ThreadID", data.ThreadID.ToString("n0"), columnOrder, restString, m_payloads);
576+
AddField("ProcessorNumber", data.ProcessorNumber.ToString(), columnOrder, restString, m_payloads);
574577

575578
if (0 < durationMSec)
576579
{
577-
AddField("DURATION_MSEC", durationMSec.ToString("n3"), columnOrder, restString);
580+
AddField("DURATION_MSEC", durationMSec.ToString("n3"), columnOrder, restString, m_payloads);
578581
}
579582

580583
var payloadNames = data.PayloadNames;
@@ -583,28 +586,28 @@ internal ETWEventRecord(ETWEventSource source, TraceEvent data, Dictionary<strin
583586
// WPP events look classic and use the EventID as their discriminator
584587
if (data.IsClassicProvider && data.ID != 0)
585588
{
586-
AddField("EventID", ((int)data.ID).ToString(), columnOrder, restString);
589+
AddField("EventID", ((int)data.ID).ToString(), columnOrder, restString, m_payloads);
587590
}
588591

589-
AddField("DataLength", data.EventDataLength.ToString(), columnOrder, restString);
592+
AddField("DataLength", data.EventDataLength.ToString(), columnOrder, restString, m_payloads);
590593
}
591594

592595
try
593596
{
594597
for (int i = 0; i < payloadNames.Length; i++)
595598
{
596-
AddField(payloadNames[i], data.PayloadString(i), columnOrder, restString);
599+
AddField(payloadNames[i], data.PayloadString(i), columnOrder, restString, m_payloads);
597600
}
598601
}
599602
catch (Exception e)
600603
{
601-
AddField("ErrorParsingFields", e.Message, columnOrder, restString);
604+
AddField("ErrorParsingFields", e.Message, columnOrder, restString, m_payloads);
602605
}
603606

604607
var message = data.FormattedMessage;
605608
if (message != null)
606609
{
607-
AddField("FormattedMessage", message, columnOrder, restString);
610+
AddField("FormattedMessage", message, columnOrder, restString, m_payloads);
608611
}
609612

610613
if (source.m_needsComputers)
@@ -621,7 +624,7 @@ internal ETWEventRecord(ETWEventSource source, TraceEvent data, Dictionary<strin
621624
id = "^" + id; // Indicates it is at the start of the task.
622625
}
623626

624-
AddField("ActivityInfo", id, columnOrder, restString);
627+
AddField("ActivityInfo", id, columnOrder, restString, m_payloads );
625628
}
626629

627630
var startStopActivity = source.m_startStopActivityComputer.GetCurrentStartStopActivity(thread, data);
@@ -634,26 +637,26 @@ internal ETWEventRecord(ETWEventSource source, TraceEvent data, Dictionary<strin
634637
parentName = startStopActivity.Creator.Name;
635638
}
636639

637-
AddField("StartStopActivity", name + "/P=" + parentName, columnOrder, restString);
640+
AddField("StartStopActivity", name + "/P=" + parentName, columnOrder, restString, m_payloads);
638641
}
639642
}
640643
}
641644

642645
// We pass 0 as the process ID for creating the activityID because we want uniform syntax.
643646
if (data.ActivityID != Guid.Empty)
644647
{
645-
AddField("ActivityID", StartStopActivityComputer.ActivityPathString(data.ActivityID), columnOrder, restString);
648+
AddField("ActivityID", StartStopActivityComputer.ActivityPathString(data.ActivityID), columnOrder, restString, m_payloads);
646649
}
647650

648651
Guid relatedActivityID = data.RelatedActivityID;
649652
if (relatedActivityID != Guid.Empty)
650653
{
651-
AddField("RelatedActivityID", StartStopActivityComputer.ActivityPathString(data.RelatedActivityID), columnOrder, restString);
654+
AddField("RelatedActivityID", StartStopActivityComputer.ActivityPathString(data.RelatedActivityID), columnOrder, restString, m_payloads);
652655
}
653656

654657
if(data.ContainerID != null)
655658
{
656-
AddField("ContainerID", data.ContainerID, columnOrder, restString);
659+
AddField("ContainerID", data.ContainerID, columnOrder, restString, m_payloads);
657660
}
658661

659662
m_asText = restString.ToString();
@@ -666,7 +669,8 @@ internal ETWEventRecord(ETWEventSource source, TraceEvent data, Dictionary<strin
666669
public DateTime OriginTimeStamp { get { return TimeZoneInfo.ConvertTime(LocalTimeStamp, this.m_source.OriginTimeZone); } }
667670
public override string Rest { get { return m_asText; } set { } }
668671
public EventIndex Index { get { return m_idx; } }
669-
672+
public override List<Payload> Payloads { get { return m_payloads; } }
673+
670674
#region private
671675

672676
private static readonly Regex specialCharRemover = new Regex(" *[\r\n\t]+ *", RegexOptions.Compiled);
@@ -675,12 +679,15 @@ internal ETWEventRecord(ETWEventSource source, TraceEvent data, Dictionary<strin
675679
/// Adds 'fieldName' with value 'fieldValue' to the output. It either goes into a column (based on columnOrder) or it goes into
676680
/// 'rest' as a fieldName="fieldValue" string. It also updates 'columnSums' for the fieldValue for any in a true column
677681
/// </summary>
678-
private void AddField(string fieldName, string fieldValue, Dictionary<string, int> columnOrder, StringBuilder restString)
682+
private void AddField(string fieldName, string fieldValue, Dictionary<string, int> columnOrder, StringBuilder restString, List<Payload> payloadsList)
679683
{
680684
if (fieldValue == null)
681685
{
682686
fieldValue = "";
683687
}
688+
689+
payloadsList.Add(new Payload(fieldName, fieldValue));
690+
684691
// If the field value has to many newlines in it, the GUI gets confused because the text block is larger than
685692
// the vertical size. WPF may fix this at some point, but in the mean time this is a work around.
686693
fieldValue = specialCharRemover.Replace(fieldValue, " ");
@@ -787,6 +794,7 @@ public override bool Matches(Regex textRegex)
787794
private string m_asText;
788795
private EventIndex m_idx;
789796
private ETWEventSource m_source; // Lets you get at source information
797+
private List<Payload> m_payloads;
790798
#endregion
791799
}
792800

src/PerfView/EventViewer/EventWindow.xaml

+21-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
<Style x:Key="CenterAlign">
1515
<Setter Property="TextBlock.TextAlignment" Value="Center" />
1616
</Style>
17+
<WPFToolKit:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
1718
</Window.Resources>
1819
<DockPanel Background="{DynamicResource BackgroundColour}">
1920
<DockPanel.CommandBindings>
21+
<CommandBinding Command="{x:Static src:EventWindow.ToggleMultiLineViewPaneCommand}" Executed="DoToggleMultiLineViewPane" />
2022
<CommandBinding Command="{x:Static src:EventWindow.UsersGuideCommand}" Executed="DoHyperlinkHelp"/>
2123
<CommandBinding Command="Help" Executed="DoHyperlinkHelp" />
2224
<CommandBinding Command="{x:Static src:EventWindow.UpdateCommand}" Executed="DoUpdate"/>
@@ -85,7 +87,7 @@
8587
<MenuItem Header="_Show Local Time" IsCheckable="True" IsChecked="False" Checked="DoUseLocalTime" Unchecked="DoUseOriginTime"/>
8688
</MenuItem>
8789
<MenuItem Header="_Help">
88-
<MenuItem Header="_Help on Event Viewer" Command="Help" CommandParameter="EventViewerQuickStart" />
90+
<MenuItem Header="_Help on Event Viewer" Command="Help" CommandParameter="EventViewerQuickStart" />
8991
<MenuItem Header="_Users Guide" Command="{x:Static src:EventWindow.UsersGuideCommand}" CommandParameter="UsersGuide"/>
9092
</MenuItem>
9193
</Menu>
@@ -219,6 +221,8 @@
219221
<Grid.RowDefinitions>
220222
<RowDefinition Height="auto"/>
221223
<RowDefinition Height="*"/>
224+
<RowDefinition Height="auto"/>
225+
<RowDefinition Name="MultiLineViewPaneRowDef" Height="0.25*"/>
222226
</Grid.RowDefinitions>
223227
<Grid Grid.Row="0">
224228
<Grid.ColumnDefinitions>
@@ -333,6 +337,22 @@
333337
Width="Auto" />
334338
</WPFToolKit:DataGrid.Columns>
335339
</WPFToolKit:DataGrid>
340+
<GridSplitter Grid.Row="2" Height="3" HorizontalAlignment="Stretch" VerticalAlignment="Center" />
341+
<WPFToolKit:DataGrid x:Name="MultiLineView" Grid.Row="3" AutoGenerateColumns="False" WPFToolKit:ScrollViewer.CanContentScroll="False" AlternatingRowBackground="{DynamicResource AlternateRowBackground}" AutomationProperties.Name="Multi-Line View" >
342+
<DataGrid.Columns>
343+
<DataGridTextColumn Header="Payload Name" Binding="{Binding PayloadName, Mode=OneWay}" Width="*"/>
344+
<DataGridTextColumn Header="Payload" Binding="{Binding PayloadValue, Mode=OneWay}" Width="3*" >
345+
<DataGridTextColumn.ElementStyle>
346+
<Style TargetType="TextBlock">
347+
<Setter Property="TextWrapping" Value="Wrap"/>
348+
<Setter Property="FontFamily" Value="Consolas" />
349+
</Style>
350+
</DataGridTextColumn.ElementStyle>
351+
</DataGridTextColumn>
352+
</DataGrid.Columns>
353+
</WPFToolKit:DataGrid>
354+
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="3" Text="Press F2 to hide/unhide"
355+
Visibility="{Binding Items.IsEmpty, Converter={StaticResource BooleanToVisibilityConverter}, ElementName=MultiLineView}"/>
336356
</Grid>
337357
</Grid>
338358
</DockPanel>

0 commit comments

Comments
 (0)