-
Notifications
You must be signed in to change notification settings - Fork 359
/
Copy pathDotNetHeapDumpGraphReader.cs
1007 lines (876 loc) · 38.4 KB
/
DotNetHeapDumpGraphReader.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
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
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using Graphs;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
using Microsoft.Diagnostics.Tracing.Parsers.Symbol;
using Address = System.UInt64;
/// <summary>
/// Reads a .NET Heap dump generated from ETW
/// </summary>
public class DotNetHeapDumpGraphReader
{
/// <summary>
/// A class for reading ETW events from the .NET runtime and creating a MemoryGraph from it. This only works on V4.5.1 of the runtime or later.
/// </summary>
/// <param name="log">A place to put diagnostic messages.</param>
public DotNetHeapDumpGraphReader(TextWriter log)
{
m_log = log;
}
/// <summary>
/// Read in the memory dump from javaScriptEtlName. Since there can be more than one, choose the first one
/// after double startTimeRelativeMSec. If processId is non-zero only that process is considered, otherwise it considered
/// the first heap dump regardless of process.
/// </summary>
public MemoryGraph Read(string etlFilePath, string processNameOrId = null, double startTimeRelativeMSec = 0)
{
m_etlFilePath = etlFilePath;
MemoryGraph ret = new(10000);
Append(ret, etlFilePath, processNameOrId, startTimeRelativeMSec);
ret.AllowReading();
return ret;
}
public MemoryGraph Read(TraceEventDispatcher source, string processNameOrId = null, double startTimeRelativeMSec = 0)
{
MemoryGraph ret = new(10000);
Append(ret, source, processNameOrId, startTimeRelativeMSec);
ret.AllowReading();
return ret;
}
public void Append(MemoryGraph memoryGraph, string etlName, string processNameOrId = null, double startTimeRelativeMSec = 0)
{
using (TraceEventDispatcher source = TraceEventDispatcher.GetDispatcherFromFileName(etlName))
{
Append(memoryGraph, source, processNameOrId, startTimeRelativeMSec);
}
}
public void Append(MemoryGraph memoryGraph, TraceEventDispatcher source, string processNameOrId = null, double startTimeRelativeMSec = 0)
{
SetupCallbacks(memoryGraph, source, processNameOrId, startTimeRelativeMSec);
source.Process();
ConvertHeapDataToGraph();
}
/// <summary>
/// If set before Read or Append is called, keep track of the additional information about GC generations associated with .NET Heaps.
/// </summary>
public DotNetHeapInfo DotNetHeapInfo
{
get { return m_dotNetHeapInfo; }
set { m_dotNetHeapInfo = value; }
}
#region private
/// <summary>
/// Sets up the callbacks needed to do a heap dump (work need before processing the events()
/// </summary>
internal void SetupCallbacks(MemoryGraph memoryGraph, TraceEventDispatcher source, string processNameOrId = null, double startTimeRelativeMSec = 0)
{
m_graph = memoryGraph;
m_typeID2TypeIndex = new Dictionary<ulong, NodeTypeIndex>(1000);
m_moduleID2Name = new Dictionary<ulong, string>(16);
m_arrayNametoIndex = new Dictionary<string, NodeTypeIndex>(32);
m_objectToRCW = new Dictionary<ulong, RCWInfo>(100);
m_nodeBlocks = new Queue<GCBulkNodeTraceData>();
m_edgeBlocks = new Queue<GCBulkEdgeTraceData>();
m_typeBlocks = new Queue<GCBulkTypeTraceData>();
m_staticVarBlocks = new Queue<GCBulkRootStaticVarTraceData>();
m_ccwBlocks = new Queue<GCBulkRootCCWTraceData>();
m_typeIntern = new Dictionary<string, NodeTypeIndex>();
m_root = new MemoryNodeBuilder(m_graph, "[.NET Roots]");
m_typeStorage = m_graph.AllocTypeNodeStorage();
// We also keep track of the loaded modules in the target process just in case it is a project N scenario.
// (Not play for play but it is small).
m_modules = new Dictionary<ulong, Module>(32);
m_ignoreEvents = true;
m_ignoreUntilMSec = startTimeRelativeMSec;
m_processId = 0; // defaults to a wildcard.
if (processNameOrId != null)
{
if (!int.TryParse(processNameOrId, out m_processId))
{
m_processId = -1; // an illegal value.
m_processName = processNameOrId;
}
}
// Remember the module IDs too.
Action<ModuleLoadUnloadTraceData> moduleCallback = delegate (ModuleLoadUnloadTraceData data)
{
if (data.ProcessID != m_processId)
{
return;
}
ulong moduleID = unchecked((ulong)data.ModuleID);
if (!m_moduleID2Name.ContainsKey(moduleID))
{
m_moduleID2Name[moduleID] = data.ModuleILPath;
}
m_log.WriteLine("Found Module {0} ID 0x{1:x}", data.ModuleILFileName, moduleID);
};
source.Clr.AddCallbackForEvents(moduleCallback); // Get module events for clr provider
// TODO should not be needed if we use CAPTURE_STATE when collecting.
ClrRundownTraceEventParser clrRundown = new(source);
clrRundown.AddCallbackForEvents(moduleCallback); // and its rundown provider.
DbgIDRSDSTraceData lastDbgData = null;
SymbolTraceEventParser symbolParser = new(source);
symbolParser.ImageIDDbgID_RSDS += delegate (DbgIDRSDSTraceData data)
{
if (data.ProcessID != m_processId)
{
return;
}
lastDbgData = (DbgIDRSDSTraceData)data.Clone();
};
source.Kernel.ImageGroup += delegate (ImageLoadTraceData data)
{
if (m_processId == 0)
{
return;
}
if (data.ProcessID != m_processId)
{
return;
}
Module module = new(data.ImageBase);
module.Path = data.FileName;
module.Size = data.ImageSize;
module.BuildTime = data.BuildTime;
if (lastDbgData != null && data.TimeStampRelativeMSec == lastDbgData.TimeStampRelativeMSec)
{
module.PdbGuid = lastDbgData.GuidSig;
module.PdbAge = lastDbgData.Age;
module.PdbName = lastDbgData.PdbFileName;
}
m_modules[module.ImageBase] = module;
};
// TODO this does not work in the circular case
source.Kernel.ProcessGroup += delegate (ProcessTraceData data)
{
if (0 <= m_processId || m_processName == null)
{
return;
}
if (string.Equals(data.ProcessName, processNameOrId, StringComparison.OrdinalIgnoreCase))
{
m_log.WriteLine("Found process id {0} for process Name {1}", processNameOrId, data.ProcessName);
m_processId = data.ProcessID;
}
else
{
m_log.WriteLine("Found process {0} but does not match {1}", data.ProcessName, processNameOrId);
}
};
source.Clr.GCGenAwareStart += delegate (GenAwareBeginTraceData data)
{
m_seenStart = true;
m_ignoreEvents = false;
};
source.Clr.GCStart += delegate (GCStartTraceData data)
{
// If this GC is not part of a heap dump, ignore it.
// TODO FIX NOW if (data.ClientSequenceNumber == 0)
// return;
if (data.TimeStampRelativeMSec < m_ignoreUntilMSec)
{
return;
}
if (m_processId == 0)
{
m_processId = data.ProcessID;
m_log.WriteLine("Process wildcard selects process id {0}", m_processId);
}
if (data.ProcessID != m_processId)
{
m_log.WriteLine("GC Start found but Process ID {0} != {1} desired ID", data.ProcessID, m_processId);
return;
}
if (!IsProjectN && data.ProviderGuid == ClrTraceEventParser.NativeProviderGuid)
{
IsProjectN = true;
}
if (data.Depth < 2 || data.Type != GCType.NonConcurrentGC)
{
m_log.WriteLine("GC Start found but not a Foreground Gen 2 GC");
return;
}
if (data.Reason != GCReason.Induced)
{
m_log.WriteLine("GC Start not induced. Skipping.");
return;
}
if (!m_seenStart)
{
m_gcID = data.Count;
m_log.WriteLine("Found a Gen2 Induced non-background GC Start at {0:n3} msec GC Count {1}", data.TimeStampRelativeMSec, m_gcID);
m_ignoreEvents = false;
m_seenStart = true;
memoryGraph.Is64Bit = (data.PointerSize == 8);
}
};
source.Clr.GCStop += delegate (GCEndTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
if (data.Count == m_gcID)
{
m_log.WriteLine("Found a GC Stop at {0:n3} for GC {1}, ignoring events from now on.", data.TimeStampRelativeMSec, m_gcID);
m_ignoreEvents = true;
if (m_nodeBlocks.Count == 0 && m_typeBlocks.Count == 0 && m_edgeBlocks.Count == 0)
{
m_log.WriteLine("Found no node events, looking for another GC");
m_seenStart = false;
return;
}
// TODO we have to continue processing to get the module rundown events.
// If we could be sure to get these early, we could optimized this.
// source.StopProcessing();
}
else
{
m_log.WriteLine("Found a GC Stop at {0:n3} but id {1} != {2} Target ID", data.TimeStampRelativeMSec, data.Count, m_gcID);
}
};
source.Clr.GCGenAwareEnd += delegate (GenAwareEndTraceData data)
{
m_ignoreEvents = true;
if (m_nodeBlocks.Count == 0 && m_typeBlocks.Count == 0 && m_edgeBlocks.Count == 0)
{
m_log.WriteLine("Found no node events, looking for another GC");
m_seenStart = false;
return;
}
};
source.Clr.TypeBulkType += delegate (GCBulkTypeTraceData data)
{
// Don't check m_ignoreEvents here, as BulkType events can be emitted by other events...such as the GC allocation event.
// This means that when setting m_processId to 0 in the command line may still lose type events.
if (data.ProcessID != m_processId)
{
return;
}
m_typeBlocks.Enqueue((GCBulkTypeTraceData)data.Clone());
};
source.Clr.GCBulkNode += delegate (GCBulkNodeTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
m_nodeBlocks.Enqueue((GCBulkNodeTraceData)data.Clone());
};
source.Clr.GCBulkEdge += delegate (GCBulkEdgeTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
m_edgeBlocks.Enqueue((GCBulkEdgeTraceData)data.Clone());
};
source.Clr.GCBulkRootEdge += delegate (GCBulkRootEdgeTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
MemoryNodeBuilder staticRoot = m_root.FindOrCreateChild("[static vars]");
for (int i = 0; i < data.Count; i++)
{
GCBulkRootEdgeValues value = data.Values(i);
GCRootFlags flags = value.GCRootFlag;
if ((flags & GCRootFlags.WeakRef) == 0) // ignore weak references. they are not roots.
{
GCRootKind kind = value.GCRootKind;
MemoryNodeBuilder root = m_root;
string name;
if (kind == GCRootKind.Stack)
{
name = "[local vars]";
}
else
{
root = m_root.FindOrCreateChild("[other roots]");
if ((flags & GCRootFlags.RefCounted) != 0)
{
name = "[COM/WinRT Objects]";
}
else if (kind == GCRootKind.Finalizer)
{
name = "[finalizer Handles]";
}
else if (kind == GCRootKind.Handle)
{
if (flags == GCRootFlags.Pinning)
{
name = "[pinning Handles]";
}
else
{
name = "[strong Handles]";
}
}
else
{
name = "[other Handles]";
}
// Remember the root for later processing.
if (value.RootedNodeAddress != 0)
{
ulong gcRootId = value.GCRootID;
if (gcRootId != 0 && IsProjectN)
{
Module gcRootModule = GetModuleForAddress(gcRootId);
if (gcRootModule != null)
{
int staticRva = (int)(gcRootId - gcRootModule.ImageBase);
NodeTypeIndex staticTypeIdx = m_graph.CreateType(staticRva, gcRootModule, 0, " (static var)");
NodeIndex staticNodeIdx = m_graph.CreateNode();
m_children.Clear();
m_children.Add(m_graph.GetNodeIndex(value.RootedNodeAddress));
m_graph.SetNode(staticNodeIdx, staticTypeIdx, 0, m_children);
staticRoot.AddChild(staticNodeIdx);
Trace.WriteLine("Got Static 0x" + gcRootId.ToString("x") + " pointing at 0x" + value.RootedNodeAddress.ToString("x") + " kind " + value.GCRootKind + " flags " + value.GCRootFlag);
continue;
}
}
Trace.WriteLine("Got GC Root 0x" + gcRootId.ToString("x") + " pointing at 0x" + value.RootedNodeAddress.ToString("x") + " kind " + value.GCRootKind + " flags " + value.GCRootFlag);
}
}
root = root.FindOrCreateChild(name);
ulong objId = value.RootedNodeAddress;
root.AddChild(m_graph.GetNodeIndex(objId));
}
}
};
source.Clr.GCBulkRCW += delegate (GCBulkRCWTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
for (int i = 0; i < data.Count; i++)
{
GCBulkRCWValues comInfo = data.Values(i);
m_objectToRCW[comInfo.ObjectID] = new RCWInfo(comInfo);
}
};
source.Clr.GCBulkRootCCW += delegate (GCBulkRootCCWTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
m_ccwBlocks.Enqueue((GCBulkRootCCWTraceData)data.Clone());
};
source.Clr.GCBulkRootStaticVar += delegate (GCBulkRootStaticVarTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
m_staticVarBlocks.Enqueue((GCBulkRootStaticVarTraceData)data.Clone());
};
source.Clr.GCBulkRootConditionalWeakTableElementEdge += delegate (GCBulkRootConditionalWeakTableElementEdgeTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
MemoryNodeBuilder otherRoots = m_root.FindOrCreateChild("[other roots]");
MemoryNodeBuilder dependentHandles = otherRoots.FindOrCreateChild("[Dependent Handles]");
for (int i = 0; i < data.Count; i++)
{
GCBulkRootConditionalWeakTableElementEdgeValues value = data.Values(i);
// TODO fix this so that they you see this as an arc from source to target.
// The target is alive only if the source ID (which is a weak handle) is alive (non-zero)
if (value.GCKeyNodeID != 0)
{
dependentHandles.AddChild(m_graph.GetNodeIndex(value.GCValueNodeID));
}
}
};
source.Clr.GCGenerationRange += delegate (GCGenerationRangeTraceData data)
{
if (m_ignoreEvents || data.ProcessID != m_processId)
{
return;
}
if (m_dotNetHeapInfo == null)
{
return;
}
// We want the 'after' ranges so we wait
if (m_nodeBlocks.Count == 0)
{
return;
}
ulong start = data.RangeStart;
ulong end = start + data.RangeUsedLength;
m_dotNetHeapInfo.Segments ??= new List<GCHeapDumpSegment>();
GCHeapDumpSegment segment = new();
segment.Start = start;
segment.End = end;
switch (data.Generation)
{
case 0:
segment.Gen0End = end;
break;
case 1:
segment.Gen1End = end;
break;
case 2:
segment.Gen2End = end;
break;
case 3:
segment.Gen3End = end;
break;
case 4:
segment.Gen4End = end;
break;
default:
throw new Exception("Invalid generation in GCGenerationRangeTraceData");
}
m_dotNetHeapInfo.Segments.Add(segment);
};
}
/// <summary>
/// After reading the events the graph is not actually created, you need to post process the information we gathered
/// from the events. This is where that happens. Thus 'SetupCallbacks, Process(), ConvertHeapDataToGraph()' is how
/// you dump a heap.
/// </summary>
internal unsafe void ConvertHeapDataToGraph()
{
if (m_converted)
{
return;
}
m_converted = true;
const int MaxNodeCount = 10_000_000;
if (!m_seenStart)
{
if (m_processName != null)
{
throw new ApplicationException("ETL file did not include a Heap Dump for process " + m_processName);
}
throw new ApplicationException("ETL file did not include a Heap Dump for process ID " + m_processId);
}
if (!m_ignoreEvents)
{
throw new ApplicationException("ETL file shows the start of a heap dump but not its completion.");
}
m_log.WriteLine("Processing Heap Data, BulkTypeEventCount:{0} BulkNodeEventCount:{1} BulkEdgeEventCount:{2}",
m_typeBlocks.Count, m_nodeBlocks.Count, m_edgeBlocks.Count);
// Process the type information (we can't do it on the fly because we need the module information, which may be
// at the end of the trace.
while (m_typeBlocks.Count > 0)
{
GCBulkTypeTraceData data = m_typeBlocks.Dequeue();
for (int i = 0; i < data.Count; i++)
{
GCBulkTypeValues typeData = data.Values(i);
string typeName = typeData.TypeName;
if (IsProjectN)
{
// For project N we only log the type ID and module base address.
Debug.Assert(typeName.Length == 0);
Debug.Assert((typeData.Flags & TypeFlags.ModuleBaseAddress) != 0);
ulong moduleBaseAddress = typeData.TypeID - (ulong)typeData.TypeNameID; // Tricky way of getting the image base.
Debug.Assert((moduleBaseAddress & 0xFFFF) == 0); // Image loads should be on 64K boundaries.
Module module = GetModuleForImageBase(moduleBaseAddress);
if (module.Path == null)
{
m_log.WriteLine("Error: Could not find DLL name for imageBase 0x{0:x} looking up typeID 0x{1:x} with TypeNameID {2:x}",
moduleBaseAddress, typeData.TypeID, typeData.TypeNameID);
}
m_typeID2TypeIndex[typeData.TypeID] = m_graph.CreateType(typeData.TypeNameID, module);
}
else
{
if (typeName.Length == 0)
{
if ((typeData.Flags & TypeFlags.Array) != 0)
{
typeName = "ArrayType(0x" + typeData.TypeNameID.ToString("x") + ")";
}
else
{
typeName = "Type(0x" + typeData.TypeNameID.ToString("x") + ")";
}
}
// TODO FIX NOW these are kind of hacks
typeName = Regex.Replace(typeName, @"`\d+", "");
typeName = typeName.Replace("[", "<");
typeName = typeName.Replace("]", ">");
typeName = typeName.Replace("<>", "[]");
if (!m_moduleID2Name.TryGetValue(typeData.ModuleID, out string moduleName))
{
moduleName = "Module(0x" + typeData.ModuleID.ToString("x") + ")";
m_moduleID2Name[typeData.ModuleID] = moduleName;
}
// Is this type a an RCW? If so mark the type name that way.
if ((typeData.Flags & TypeFlags.ExternallyImplementedCOMObject) != 0)
{
typeName = "[RCW " + typeName + "]";
}
m_typeID2TypeIndex[typeData.TypeID] = CreateType(typeName, moduleName);
// Trace.WriteLine(string.Format("Type 0x{0:x} = {1}", typeData.TypeID, typeName));
}
}
}
// Process all the ccw root information (which also need the type information complete)
MemoryNodeBuilder ccwRoot = m_root.FindOrCreateChild("[COM/WinRT Objects]");
while (m_ccwBlocks.Count > 0)
{
GCBulkRootCCWTraceData data = m_ccwBlocks.Dequeue();
GrowableArray<NodeIndex> ccwChildren = new(1);
for (int i = 0; i < data.Count; i++)
{
unsafe
{
GCBulkRootCCWValues ccwInfo = data.Values(i);
// TODO Debug.Assert(ccwInfo.IUnknown != 0);
if (ccwInfo.IUnknown == 0)
{
// TODO currently there are times when a CCWs IUnknown pointer is not set (it is set lazily).
// m_log.WriteLine("Warning seen a CCW with IUnknown == 0");
continue;
}
// Create a CCW node that represents the COM object that has one child that points at the managed object.
NodeIndex ccwNode = m_graph.GetNodeIndex(ccwInfo.IUnknown);
NodeTypeIndex ccwTypeIndex = GetTypeIndex(ccwInfo.TypeID, 200);
NodeType ccwType = m_graph.GetType(ccwTypeIndex, m_typeStorage);
string typeName = "[CCW 0x" + ccwInfo.IUnknown.ToString("x") + " for type " + ccwType.Name + "]";
ccwTypeIndex = CreateType(typeName);
ccwChildren.Clear();
ccwChildren.Add(m_graph.GetNodeIndex(ccwInfo.ObjectID));
m_graph.SetNode(ccwNode, ccwTypeIndex, 200, ccwChildren);
ccwRoot.AddChild(ccwNode);
}
}
}
// Process all the static variable root information (which also need the module information complete
MemoryNodeBuilder staticVarsRoot = m_root.FindOrCreateChild("[static vars]");
while (m_staticVarBlocks.Count > 0)
{
GCBulkRootStaticVarTraceData data = m_staticVarBlocks.Dequeue();
for (int i = 0; i < data.Count; i++)
{
GCBulkRootStaticVarValues staticVarData = data.Values(i);
MemoryNodeBuilder rootToAddTo = staticVarsRoot;
if ((staticVarData.Flags & GCRootStaticVarFlags.ThreadLocal) != 0)
{
rootToAddTo = m_root.FindOrCreateChild("[thread static vars]");
}
// Get the type name.
string typeName;
if (m_typeID2TypeIndex.TryGetValue(staticVarData.TypeID, out NodeTypeIndex typeIdx))
{
NodeType type = m_graph.GetType(typeIdx, m_typeStorage);
typeName = type.Name;
}
else
{
typeName = "Type(0x" + staticVarData.TypeID.ToString("x") + ")";
}
string fullFieldName = typeName + "." + staticVarData.FieldName;
rootToAddTo = rootToAddTo.FindOrCreateChild("[static var " + fullFieldName + "]");
NodeIndex nodeIdx = m_graph.GetNodeIndex(staticVarData.ObjectID);
rootToAddTo.AddChild(nodeIdx);
}
}
// var typeStorage = m_graph.AllocTypeNodeStorage();
GCBulkNodeUnsafeNodes nodeStorage = default(GCBulkNodeUnsafeNodes);
// Process all the node and edge nodes we have collected.
bool doCompletionCheck = true;
for (; ; )
{
GCBulkNodeUnsafeNodes* node = GetNextNode(&nodeStorage);
if (node == null)
{
break;
}
// Get the node index
NodeIndex nodeIdx = m_graph.GetNodeIndex((ulong)node->Address);
int objSize = (int)node->Size;
Debug.Assert(node->Size < 0x1000000000);
NodeTypeIndex typeIdx = GetTypeIndex(node->TypeID, objSize);
// TODO FIX NOW REMOVE
// var type = m_graph.GetType(typeIdx, typeStorage);
// Trace.WriteLine(string.Format("Got Object 0x{0:x} Type {1} Size {2} #children {3} nodeIdx {4}", (Address)node->Address, type.Name, objSize, node->EdgeCount, nodeIdx));
// Process the edges (which can add children)
m_children.Clear();
for (int i = 0; i < node->EdgeCount; i++)
{
ulong edge = GetNextEdge();
NodeIndex childIdx = m_graph.GetNodeIndex(edge);
m_children.Add(childIdx);
// Trace.WriteLine(string.Format(" Child 0x{0:x}", edge));
}
// TODO we can use the nodes type to see if this is an RCW before doing this lookup which may be a bit more efficient.
if (m_objectToRCW.TryGetValue((ulong)node->Address, out RCWInfo info))
{
// Add the COM object this RCW points at as a child of this node.
m_children.Add(m_graph.GetNodeIndex(info.IUnknown));
// We add 1000 to account for the overhead of the RCW that is NOT on the GC heap.
objSize += 1000;
}
Debug.Assert(!m_graph.IsDefined(nodeIdx));
m_graph.SetNode(nodeIdx, typeIdx, objSize, m_children);
if (m_graph.NodeCount >= MaxNodeCount)
{
doCompletionCheck = false;
m_log.WriteLine("[WARNING]: Exceeded max node count {0}. Processed {1}/{2} nodes with {3} node bulk events to go.",
MaxNodeCount, m_curNodeIdx, m_curNodeBlock.Count, m_nodeBlocks.Count);
break;
}
}
if (m_curEdgeBlock != null && m_curEdgeBlock.Count != m_curEdgeIdx)
{
m_log.WriteLine("[WARNING]: Extra edge data found. Processing edge {0}/{1} with {2} edge bulk events to go.",
m_curEdgeIdx, m_curEdgeBlock.Count, m_edgeBlocks.Count);
if (doCompletionCheck)
{
throw new ApplicationException("Error: Giving up on heap dump.");
}
}
m_root.Build();
m_graph.RootIndex = m_root.Index;
}
/// <summary>
/// Given a module image base, return a Module instance that has all the information we have on it.
/// </summary>
private Module GetModuleForImageBase(ulong moduleBaseAddress)
{
if (!m_modules.TryGetValue(moduleBaseAddress, out Module module))
{
module = new Module(moduleBaseAddress);
m_modules.Add(moduleBaseAddress, module);
}
if (module.PdbName == null && module.Path != null)
{
m_log.WriteLine("No PDB information for {0} in ETL file, looking for it directly", module.Path);
if (File.Exists(module.Path))
{
using (PEFile.PEFile modulePEFile = new(module.Path))
{
if (!modulePEFile.GetPdbSignature(out module.PdbName, out module.PdbGuid, out module.PdbAge))
{
m_log.WriteLine("Could not get PDB information for {0}", module.Path);
}
}
}
}
return module;
}
/// <summary>
/// if 'addressInModule' points inside any loaded module return that module. Otherwise return null
/// </summary>
private Module GetModuleForAddress(ulong addressInModule)
{
if (m_lastModule != null && m_lastModule.ImageBase <= addressInModule && addressInModule < m_lastModule.ImageBase + (uint)m_lastModule.Size)
{
return m_lastModule;
}
foreach (Module module in m_modules.Values)
{
if (module.ImageBase <= addressInModule && addressInModule < module.ImageBase + (uint)module.Size)
{
m_lastModule = module;
return module;
}
}
return null;
}
private Module m_lastModule; // one element cache
private unsafe GCBulkNodeUnsafeNodes* GetNextNode(GCBulkNodeUnsafeNodes* buffer)
{
if (m_curNodeBlock == null || m_curNodeBlock.Count <= m_curNodeIdx)
{
m_curNodeBlock = null;
if (m_nodeBlocks.Count == 0)
{
return null;
}
GCBulkNodeTraceData nextBlock = m_nodeBlocks.Dequeue();
if (m_curNodeBlock != null && nextBlock.Index != m_curNodeBlock.Index + 1)
{
throw new ApplicationException("Error expected Node Index " + (m_curNodeBlock.Index + 1) + " Got " + nextBlock.Index + " Giving up on heap dump.");
}
m_curNodeBlock = nextBlock;
m_curNodeIdx = 0;
}
return m_curNodeBlock.UnsafeNodes(m_curNodeIdx++, buffer);
}
private ulong GetNextEdge()
{
if (m_curEdgeBlock == null || m_curEdgeBlock.Count <= m_curEdgeIdx)
{
m_curEdgeBlock = null;
if (m_edgeBlocks.Count == 0)
{
throw new ApplicationException("Error not enough edge data. Giving up on heap dump.");
}
GCBulkEdgeTraceData nextEdgeBlock = m_edgeBlocks.Dequeue();
if (m_curEdgeBlock != null && nextEdgeBlock.Index != m_curEdgeBlock.Index + 1)
{
throw new ApplicationException("Error expected Node Index " + (m_curEdgeBlock.Index + 1) + " Got " + nextEdgeBlock.Index + " Giving up on heap dump.");
}
m_curEdgeBlock = nextEdgeBlock;
m_curEdgeIdx = 0;
}
return m_curEdgeBlock.Values(m_curEdgeIdx++).Target;
}
private NodeTypeIndex GetTypeIndex(ulong typeID, int objSize)
{
if (!m_typeID2TypeIndex.TryGetValue(typeID, out NodeTypeIndex ret))
{
m_log.WriteLine("Error: Did not have a type definition for typeID 0x{0:x}", typeID);
Trace.WriteLine(string.Format("Error: Did not have a type definition for typeID 0x{0:x}", typeID));
string typeName = "UNKNOWN 0x" + typeID.ToString("x");
ret = CreateType(typeName);
m_typeID2TypeIndex[typeID] = ret;
}
if (objSize > 1000)
{
NodeType type = m_graph.GetType(ret, m_typeStorage);
string suffix = GetObjectSizeSuffix(objSize); // indicates the size range
string typeName = type.Name + suffix;
// TODO FIX NOW worry about module collision
if (!m_arrayNametoIndex.TryGetValue(typeName, out ret))
{
if (IsProjectN)
{
ret = m_graph.CreateType(type.RawTypeID, type.Module, objSize, suffix);
}
else
{
ret = CreateType(typeName, type.ModuleName);
}
m_arrayNametoIndex.Add(typeName, ret);
}
}
return ret;
}
// Returns a string suffix that discriminates interesting size ranges.
private static string GetObjectSizeSuffix(int objSize)
{
if (objSize < 1000)
{
return "";
}
string size;
if (objSize < 10000)
{
size = "1K";
}
else if (objSize < 100000)
{
size = "10K";
}
else if (objSize < 1000000)
{
size = "100K";
}
else if (objSize < 10000000)
{
size = "1M";
}
else if (objSize < 100000000)
{
size = "10M";
}
else
{
size = "100M";
}
return " (Bytes > " + size + ")";
}
private NodeTypeIndex CreateType(string typeName, string moduleName = null)
{
string fullTypeName = typeName;
if (moduleName != null)
{
fullTypeName = moduleName + "!" + typeName;
}
if (!m_typeIntern.TryGetValue(fullTypeName, out NodeTypeIndex ret))
{
ret = m_graph.CreateType(typeName, moduleName);
m_typeIntern.Add(fullTypeName, ret);
}
return ret;
}
/// <summary>
/// Converts a raw TypeID (From the ETW data), to the graph type index)
/// </summary>
private Dictionary<ulong, NodeTypeIndex> m_typeID2TypeIndex;
private Dictionary<ulong, string> m_moduleID2Name;
private Dictionary<string, NodeTypeIndex> m_arrayNametoIndex;
/// <summary>
/// Remembers addition information about RCWs.
/// </summary>
private sealed class RCWInfo
{
public RCWInfo(GCBulkRCWValues data) { IUnknown = data.IUnknown; }
public ulong IUnknown;
};
private Dictionary<ulong, RCWInfo> m_objectToRCW;
/// <summary>
/// We gather all the BulkTypeTraceData into a list m_typeBlocks which we then process as a second pass (because we need module info which may be after the type info).
/// </summary>
private Queue<GCBulkTypeTraceData> m_typeBlocks;
/// <summary>
/// We gather all the BulkTypeTraceData into a list m_typeBlocks which we then process as a second pass (because we need module info which may be after the type info).
/// </summary>
private Queue<GCBulkRootStaticVarTraceData> m_staticVarBlocks;
/// <summary>
/// We gather all the GCBulkRootCCWTraceData into a list m_ccwBlocks which we then process as a second pass (because we need type info which may be after the ccw info).
/// </summary>
private Queue<GCBulkRootCCWTraceData> m_ccwBlocks;
/// <summary>
/// We gather all the GCBulkNodeTraceData events into a list m_nodeBlocks. m_curNodeBlock is the current block we are processing and 'm_curNodeIdx' is the node within the event
/// </summary>
private Queue<GCBulkNodeTraceData> m_nodeBlocks;
private GCBulkNodeTraceData m_curNodeBlock;
private int m_curNodeIdx;
/// <summary>
/// We gather all the GCBulkEdgeTraceData events into a list m_edgeBlocks. m_curEdgeBlock is the current block we are processing and 'm_curEdgeIdx' is the edge within the event
/// </summary>
private Queue<GCBulkEdgeTraceData> m_edgeBlocks;
private int m_curEdgeIdx;
private GCBulkEdgeTraceData m_curEdgeBlock;
/// <summary>
/// We want type indexes to be shared as much as possible, so this table remembers the ones we have already created.
/// </summary>
private Dictionary<string, NodeTypeIndex> m_typeIntern;
// scratch location for creating nodes.
private GrowableArray<NodeIndex> m_children;
// This is a 'scratch location' we use to fetch type information.
private NodeType m_typeStorage;
// m_modules is populated as types are defined, and then we look up all the necessary module info later.
private Dictionary<ulong, Module> m_modules; // Currently only non-null if it is a project N heap dump
private bool IsProjectN; // only set after we see the GCStart
// Information from the constructor
private string m_etlFilePath;
private double m_ignoreUntilMSec; // ignore until we see this
private int m_processId;
private string m_processName;
private TextWriter m_log;
// State that lets up pick the particular heap dump int the ETL file and ignore the rest.
private bool m_converted;
private bool m_seenStart;
private bool m_ignoreEvents;
private int m_gcID;
// The graph we generating.