using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Threading; namespace PostSharp.Samples.Profiling { internal class SampleCollector { // This is the maximal duration allowed to collect the metrics from all threads for a single method. static readonly long timestampTolerance = Stopwatch.Frequency / 100; private readonly ThreadLocal<ThreadLocalSampleCollector> _threadLocalCollectors; public IList<ThreadLocalSampleCollector> ThreadLocalCollectors => this._threadLocalCollectors.Values; private readonly object registrationLock = new object(); private MetricMetadata[] _metricsMetadata = new MetricMetadata[1024]; public int ProfiledMethodCount { get; private set; } internal MetricMetadata[] MetricsMetadata { get => this._metricsMetadata; } public SampleCollector() { this._threadLocalCollectors = new ThreadLocal<ThreadLocalSampleCollector>(() => new ThreadLocalSampleCollector(this), true); } public MetricMetadata RegisterMethod(MethodBase method) { lock (this.registrationLock) { var profiledMethod = new MetricMetadata(method, this.ProfiledMethodCount); if (this.MetricsMetadata.Length <= this.ProfiledMethodCount) { Array.Resize(ref this._metricsMetadata, this.MetricsMetadata.Length * 2); } this.MetricsMetadata[profiledMethod.Index] = profiledMethod; this.ProfiledMethodCount++; return profiledMethod; } } public ThreadLocalSampleCollector GetThreadLocalCollector() => this._threadLocalCollectors.Value; public MetricData[] GetMetrics() { MetricMetadata[] profiledMethodsCopy; MetricData[] metrics; lock (this.registrationLock) { profiledMethodsCopy = this.MetricsMetadata; metrics = new MetricData[this.ProfiledMethodCount]; } var treadLocalCollectorsCopy = this._threadLocalCollectors.Values; var timestamp = ProfilingServices.GetTimestamp(); for (var i = 0; i < metrics.Length; i++) { var method = profiledMethodsCopy[i]; var attempts = 0; while (true) { metrics[i].Timestamp = timestamp; foreach (var threadLocalCollector in treadLocalCollectorsCopy) { if (threadLocalCollector.GetSample(method, out var threadLocalData)) { metrics[i].AddData(threadLocalData); } } timestamp = ProfilingServices.GetTimestamp(); // Detect if our thread has been preempted, and retry if so. if (timestamp - metrics[i].Timestamp < timestampTolerance) { break; } else { metrics[i] = default; attempts++; if (attempts > 3) { Console.WriteLine("Too many non-voluntary preemptions in the publisher thread. "); metrics[i].SetInvalid(); break; } } } } return metrics; } } }