private void AddProfilerEvent(Event @event)
            {
                string eventType = (@event.EventType == EventType.GarbageCollectionFinished) ? "GC" : "JIT";

                _parent.DebugWrite(String.Format("#{0} {1} (start={2}ms; end={3}ms)",
                                                 ++_dbgIdx, eventType, _startMilliseconds, @event.TimeMilliseconds));

                double startSeconds = _startMilliseconds / 1000.0;
                double endSeconds   = @event.TimeMilliseconds / 1000.0;

                lock (_pendingJobItemsGuard)
                {
                    if (_pendingJobItems == null)
                    {
                        _pendingJobItems = new List <ClrJobItem>();
                    }
                    else
                    {
                        int        count   = _pendingJobItems.Count; // > 0 if pendingJobItems is not null
                        ClrJobItem lastEnd = _pendingJobItems[count - 1];
                        if (endSeconds <= lastEnd.TimeSeconds)
                        {
                            _parent.DebugWrite(String.Format("#{0} Ignored (1)", _dbgIdx));

                            return;                              // ignore duplicate or obsolete events
                        }
                        if (startSeconds <= lastEnd.TimeSeconds) // intersecting events
                        {
                            if (endSeconds <= lastEnd.TimeSeconds)
                            {
                                _parent.DebugWrite(String.Format("#{0} Ignored (2)", _dbgIdx));

                                return;
                            }
                            // special: merge events
                            _pendingJobItems.RemoveAt(count - 1);
                            _pendingJobItems.Add(new ClrJobItem(endSeconds, false));

                            _parent.DebugWrite(String.Format(
                                                   "#{0} Replaced last {1} from (start={2:F3}s; end={3:F3}s) to (start={4:F3}s; end={5:F3}s)",
                                                   _dbgIdx, eventType,
                                                   _pendingJobItems[count - 2].TimeSeconds, lastEnd.TimeSeconds,
                                                   _pendingJobItems[count - 2].TimeSeconds, endSeconds));

                            return;
                        }
                    }
                    _pendingJobItems.Add(new ClrJobItem(startSeconds, true));
                    if (startSeconds == endSeconds)
                    {
                        const double MinTimeDelta = 0.001; // 1 msec
                        endSeconds += MinTimeDelta;
                    }
                    _pendingJobItems.Add(new ClrJobItem(endSeconds, false));
                }

                _parent.DebugWrite(String.Format("#{0} Added {1} (start={2:F3}s; end={3:F3}s)",
                                                 _dbgIdx, eventType, startSeconds, endSeconds));
            }
        private static void WriteClrJobItems(StreamWriter writer, IList <ClrJobItem> items)
        {
            ClrJobItem jobStart = null;
            int        n        = 0;

            for (int i = 0, count = items.Count; i < count; ++i)
            {
                ClrJobItem job = items[i];
                if (i % 2 == 0)
                {
                    if (!job.IsStart)
                    {
                        writer.WriteLine($"WARNING: unexpected end item ({job.TimeSeconds:F3})");
                        continue;
                    }
                    jobStart = job;
                }
                else
                {
                    if (job.IsStart)
                    {
                        writer.WriteLine($"WARNING: unexpected start item ({job.TimeSeconds:F3})");
                        continue;
                    }
                    if (jobStart == null)
                    {
                        writer.WriteLine($"WARNING: end item without start item ({job.TimeSeconds:F3})");
                        continue;
                    }
                    writer.WriteLine($"#{++n:D4} {jobStart.TimeSeconds:F3} .. {job.TimeSeconds:F3}");
                    jobStart = null;
                }
            }
            if (jobStart != null)
            {
                writer.WriteLine($"WARNING: start item without end item ({jobStart.TimeSeconds:F3})");
            }
        }
        // adds new items to chart values; may need to combine the end of the existing chart values and
        // the beginning of the new items list
        private void AddClrJobItems(ChartValues <ClrJobItem> chartValues, List <ClrJobItem> jobItems,
                                    string eventTypeName)
        {
            Debug.Assert((jobItems.Count >= 2) && (jobItems.Count % 2 == 0));

            int chartValuesCount = chartValues.Count;

            if (chartValuesCount > 0)
            {
                Debug.Assert((chartValuesCount >= 2) &&
                             chartValues[chartValuesCount - 2].IsStart &&
                             !chartValues[chartValuesCount - 1].IsStart);

                double lastOldItemStartTime = chartValues[chartValuesCount - 2].TimeSeconds;
                double lastOldItemEndTime   = chartValues[chartValuesCount - 1].TimeSeconds;

                Debug.Assert(lastOldItemStartTime <= lastOldItemEndTime);
                Debug.Assert(jobItems[0].IsStart && !jobItems[1].IsStart);

                double firstNewItemStartTime = jobItems[0].TimeSeconds;
                double firstNewItemEndTime   = jobItems[1].TimeSeconds;

                Debug.Assert(firstNewItemStartTime <= firstNewItemEndTime);

                if (firstNewItemStartTime <= lastOldItemEndTime)
                {
                    // intersecting with previous event
                    if (firstNewItemEndTime <= lastOldItemEndTime)
                    {
                        // ignore duplicate events
                        if (firstNewItemStartTime >= lastOldItemStartTime)
                        {
                            jobItems.RemoveRange(0, 2);

                            DebugWrite($"Ignored duplicate {eventTypeName} event {firstNewItemStartTime:F3} .. {firstNewItemEndTime:F3}");
                        }
                        // else: (unexpected) the next event start is before the previous event start
                    }
                    else // firstNewItemEndTime > lastOldItemEndTime
                    {
                        // combine events
                        if (firstNewItemStartTime >= lastOldItemStartTime)
                        {
                            ClrJobItem saved = chartValues[chartValuesCount - 1];
                            saved.TimeSeconds = firstNewItemEndTime;
                            chartValues[chartValuesCount - 1] = saved; // allow 'chartValues' to notify it has been changed

                            jobItems.RemoveRange(0, 2);

                            DebugWrite($"Changed last {eventTypeName} event end time to {firstNewItemEndTime:F3}");
                        }
                        // else: unsupported case
                    }
                }
            }

            if (jobItems.Count > 0) // jobItems might be changed (first two items removed)
            {
                chartValues.AddRange(jobItems);

                DebugWrite($"{jobItems.Count} {eventTypeName} events added to chart");
            }
        }