in src/CSVReader/ETLStackBrowse/Stack.cs [130:545]
public StackResult ComputeStacks(ETLTrace trace, IStackParameters parms)
{
this.parms = parms;
this.trace = trace;
t0 = trace.Parameters.T0;
t1 = trace.Parameters.T1;
if (t0 == t1)
{
t1 = t0 + 1; // just add 1ms to get a non-zero window
}
atomsRecords = trace.atomsRecords;
atomsFields = trace.atomsFields;
recordInfo = trace.CommonFieldIds;
listEventFields = trace.EventFields;
threads = trace.Threads;
stackIgnoreEvents = trace.StackIgnoreEvents;
fElideGenerics = trace.UIParameters.ElideGenerics;
fUnmangleBartok = trace.UIParameters.UnmangleBartokSymbols;
frameFilters = parms.FrameFilters;
butterflyPivot = parms.ButterflyPivot;
fUseExeFrame = parms.UseExeFrame;
fUsePid = parms.UsePid;
fUseTid = parms.UseTid;
fFoldModules = parms.FoldModules;
fUseRootAI = parms.UseRootAI;
fUseIODuration = parms.UseIODuration;
fReserved = parms.AnalyzeReservedMemory;
backpatchRecordType = new int[atomsRecords.Count];
MemProcessor memProcessor = new MemProcessor(trace);
MemEffect memEffect = new MemEffect();
ParseFrameFilters();
PrepareNewTreeRoot();
idDPC = atomsRecords.Lookup("DPC");
idStack = atomsRecords.Lookup("Stack");
idVAlloc = atomsRecords.Lookup("VirtualAlloc");
idVFree = atomsRecords.Lookup("VirtualFree");
idCSwitch = atomsRecords.Lookup("CSwitch");
idFileIoOpEnd = atomsRecords.Lookup("FileIoOpEnd");
idDiskRead = atomsRecords.Lookup("DiskRead");
idDiskReadInit = atomsRecords.Lookup("DiskReadInit");
idDiskWrite = atomsRecords.Lookup("DiskWrite");
idDiskWriteInit = atomsRecords.Lookup("DiskWriteInit");
idIStart = atomsRecords.Lookup("I-Start");
idIDCStart = atomsRecords.Lookup("I-DCStart");
idIEnd = atomsRecords.Lookup("I-End");
// we're tracking previous context switch events for each thread
// so we can use the delay as a cost metric
ThreadStat[] stats = trace.NewThreadStats();
byThreadDesc = new byte[threads.Count][];
for (int i = 0; i < threads.Count; i++)
{
ThreadInfo ti = threads[i];
byThreadDesc[i] = ByteWindow.MakeBytes(String.Format("tid ({0,5})", ti.threadid));
}
for (int i = 0; i < stats.Length; i++)
{
stats[i].time = t0;
}
// we keep previous events in the event stream so that
// we can associate a stack with one of those events
// the stacks usually come right after the event
// but there could me intervening events
// and those might have stacks as well
// the stack has to match the thread and the timestamp
// of the previous event for it to be associated with
// that event
const int maxEvent = 16;
PreviousEvent[] prev = new PreviousEvent[maxEvent];
int iNextEvent = 0;
// we keep a goodly number of previous stacks so that we can resolve pending I/O's from the past
// there is no great magic number here but we want enough for the number of CPUs and plenty
// for pending IOs. In the event that we have not figured out the number of CPU's
// the IO suggested size is probably enough anyway
int maxPendingStacks = Math.Max(trace.MaxCPU, 32); // should be enough for a large number of outstanding IOs
frameStates = new FrameState[maxPendingStacks];
int victim = 0;
for (int i = 0; i < maxPendingStacks; i++)
{
frameStates[i].threadId = -1;
}
bool[] threadFilters = trace.Parameters.GetThreadFilters();
bool[] filters = ComputeStackEventFilters(trace);
bool fIsMemAnalysis = ((idVAlloc >= 0 && filters[idVAlloc]) || (idVFree >= 0 && filters[idVFree]));
IdentifyRecordsToBackpatch(filters);
ETWLineReader l = trace.StandardLineReader();
foreach (ByteWindow b in l.Lines())
{
// if this is an event that we don't recognized then skip it
if (l.idType < 0)
{
continue;
}
// if it's not a stack event then this might be an event that is introducing a stack
// so record the event and the time in the circular buffer in that case
if (l.idType != idStack)
{
if (stackIgnoreEvents[l.idType])
{
continue;
}
if (l.idType == idIStart || l.idType == idIDCStart)
{
// TODO This is a hack. We try to determine the full path of a module name by
// looking what modules are loaded. Currently machine wide, could make process wide
// pretty easily.
var filePathBytes = new ByteWindow(b, fldImageFileName);
filePathBytes.Trim();
filePathBytes.ib++;
filePathBytes.len -= 2;
if (filePathBytes.len > 0)
{
var filePath = filePathBytes.GetString();
var index = filePath.LastIndexOf('\\');
if (index >= 0)
{
var fileName = filePath.Substring(index + 1);
fullModuleNames[fileName] = filePath;
}
}
}
// this is where we use DiskRead, DiskWrite, and FileIoOpEnd events to patch the past
if (TryChangeWeightOfPastEvent(l, b))
{
continue;
}
prev[iNextEvent].time = l.t;
if (!l.MatchingTextFilter() || !l.MatchingMemory())
{
prev[iNextEvent].eventId = -1;
prev[iNextEvent].tid = -1;
continue;
}
else
{
prev[iNextEvent].eventId = l.idType;
int iThreadField = recordInfo[l.idType].threadField;
if (iThreadField >= 0)
{
// we know a specific thread that generated this stack, require the match
prev[iNextEvent].tid = b.GetInt(iThreadField);
}
else
{
// -1 matches anything, unknown thread triggered the stack event
prev[iNextEvent].tid = -1;
}
prev[iNextEvent].weight = 0;
if (l.idType == idCSwitch)
{
int oldTid = b.GetInt(fldCSwitchOldTID);
int idxOld = trace.FindThreadInfoIndex(l.t, oldTid);
stats[idxOld].time = l.t;
int newTid = b.GetInt(fldCSwitchNewTID);
int idxNew = trace.FindThreadInfoIndex(l.t, newTid);
ulong waitTime = (ulong)(l.t - stats[idxNew].time);
prev[iNextEvent].weight = waitTime;
prev[iNextEvent].tid = newTid;
}
else if (fIsMemAnalysis && (l.idType == idVAlloc || l.idType == idVFree))
{
memProcessor.ProcessMemRecord(l, b, memEffect);
ulong size = 0;
if (l.idType == idVAlloc)
{
if (fReserved)
{
size = memEffect.reserved;
}
else
{
size = memEffect.committed;
}
}
else
{
if (fReserved)
{
size = memEffect.released;
}
else
{
size = memEffect.decommitted;
}
}
// this allocation didn't affect the statistic of interest... ignore it
if (size == 0)
{
prev[iNextEvent].eventId = -1;
prev[iNextEvent].tid = -1;
continue;
}
else
{
prev[iNextEvent].weight = size;
}
}
else if (l.idType == idIStart || l.idType == idIEnd)
{
ulong addrBase = b.GetHex(fldImageBaseAddr);
ulong addrEnd = b.GetHex(fldImageEndAddr);
ulong size = addrEnd - addrBase;
prev[iNextEvent].weight = size;
}
else if (recordInfo[l.idType].sizeField > 0)
{
// get IO Size if appropriate
if (!fUseIODuration)
{
prev[iNextEvent].weight = (ulong)b.GetLong(recordInfo[l.idType].sizeField);
}
}
// add the synthetic filename field if there is one in the leaf record type
if (filters[l.idType] && recordInfo[l.idType].goodNameField >= 0)
{
bsym.Assign(b, recordInfo[l.idType].goodNameField).Trim();
prev[iNextEvent].filenameId = atomsNodeNames.EnsureContains(bsym);
}
else
{
prev[iNextEvent].filenameId = -1;
}
}
iNextEvent = (iNextEvent + 1) % maxEvent;
continue;
}
// ok at this point we definitely have a stack event
// make sure this stack is for a thread we are measuring
int threadId = b.GetInt(fldStackThreadId);
int idx = trace.FindThreadInfoIndex(l.t, threadId);
if (!threadFilters[idx])
{
continue;
}
// we care about this thread, look up the state this thread is in
// do we have a pending stack already
int iStack = 0;
for (iStack = 0; iStack < maxPendingStacks; iStack++)
{
if (frameStates[iStack].threadId == threadId && frameStates[iStack].time == l.t)
{
break;
}
}
// this is a new stack, not a continuation, so we may have to flush
if (iStack == maxPendingStacks)
{
// check to see if this new stack is of the correct type before we flush one
// first we look backwards to see if we can find the event that introduced this stack
bool fFound = true;
int i = iNextEvent;
for (; ; )
{
if (--i < 0)
{
i = maxEvent - 1;
}
// if the time matches and this is a desired event type then many we can use it
if (prev[i].time == l.t && prev[i].eventId >= 0 && filters[prev[i].eventId])
{
// the previous event has to be non-thread-specific (like VirtualAlloc)
// or else the thread has to match (for context switches)
if (prev[i].tid == -1)
{
break;
}
if (prev[i].tid == threadId)
{
break;
}
}
if (i == iNextEvent)
{
fFound = false;
break;
}
}
// if we don't have a previous record that corresonds to this stack
// or if we have a record but it is not one that we are collecting stacks for
// then we skip this stack entirely
if (!fFound)
{
continue;
}
victim++;
if (victim == maxPendingStacks)
{
victim = 0;
}
// ok we're going with this stack, so we have to flush a stack we've been building up (it's done by now)
// because only one stack can be pending for any given CPU at any time index
if (frameStates[victim].root != null)
{
ProcessFrames(treeRoot, victim);
}
frameStates[victim].root = null;
frameStates[victim].threadId = threadId;
frameStates[victim].time = l.t;
frameStates[victim].weight = prev[i].weight;
frameStates[victim].filenameId = prev[i].filenameId;
frameStates[victim].eventId = prev[i].eventId;
// this event is consumed, we can't use it twice even if another stack otherwise might seem to match
prev[i].eventId = -1;
iStack = victim;
}
bsym.Assign(b, fldStackSymbol).Trim();
if (fElideGenerics || fUnmangleBartok)
{
PostProcessSymbol(bsym);
}
if (fFoldModules)
{
bsym.Truncate((byte)'!');
}
int id = atomsNodeNames.EnsureContains(bsym);
if (!fFoldModules || frameStates[iStack].root == null || frameStates[iStack].root.id != id)
{
Frame f = new Frame();
f.next = frameStates[iStack].root;
f.id = id;
frameStates[iStack].root = f;
}
}
// flush pending frames
for (int i = 0; i < maxPendingStacks; i++)
{
if (frameStates[i].root != null)
{
ProcessFrames(treeRoot, i);
}
}
frameStates = null;
StackResult result = new StackResult();
result.parms = parms;
result.t0 = t0;
result.t1 = t1;
result.cStitchedStacks = cStitchedStacks;
result.treeRoot = treeRoot;
result.atomsNodeNames = atomsNodeNames;
result.rollupStats = rollupStats;
result.idPivot = idPivot;
result.stackFilters = filters;
return result;
}